提交 884dd54b 编写于 作者: J jonathan pickett

since redis now has built-in service support, and Sentinel should be used for...

since redis now has built-in service support, and Sentinel should be used for monitoring, this project is no longer needed
上级 3cd77a5c
*.log
*.user
*.exe
*.sdf
*.suo
*.bin
*.tlog
*.filters
*.opensdf
Install/obj/*
Install/bin/*
Install/Debug/*
Install/Release/*
RedisWatcher/Debug/*
RedisWatcher/Release/*
Debug/*
Release/*
RedisWatcher/RedisWatcher.h
RedisWatcher/RedisWatcher.rc
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">x86</Platform>
<ProductVersion>3.5</ProductVersion>
<ProjectGuid>{f152845b-db83-4862-909c-0476930233c1}</ProjectGuid>
<SchemaVersion>2.0</SchemaVersion>
<OutputName>InstallWatcher</OutputName>
<OutputType>Package</OutputType>
<WixTargetsPath Condition=" '$(WixTargetsPath)' == '' AND '$(MSBuildExtensionsPath32)' != '' ">$(MSBuildExtensionsPath32)\Microsoft\WiX\v3.x\Wix.targets</WixTargetsPath>
<WixTargetsPath Condition=" '$(WixTargetsPath)' == '' ">$(MSBuildExtensionsPath)\Microsoft\WiX\v3.x\Wix.targets</WixTargetsPath>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
<OutputPath>bin\$(Configuration)\</OutputPath>
<IntermediateOutputPath>obj\$(Configuration)\</IntermediateOutputPath>
<DefineConstants>Debug</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
<OutputPath>bin\$(Configuration)\</OutputPath>
<IntermediateOutputPath>obj\$(Configuration)\</IntermediateOutputPath>
</PropertyGroup>
<ItemGroup>
<Compile Include="Product.wxs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\RedisWatcher\RedisWatcher.vcxproj">
<Name>RedisWatcher</Name>
<Project>{a249484f-526d-4725-9434-430f74837ffe}</Project>
<Private>True</Private>
<DoNotHarvest>True</DoNotHarvest>
<RefProjectOutputGroups>Binaries;Content;Satellites</RefProjectOutputGroups>
<RefTargetDir>INSTALLLOCATION</RefTargetDir>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<WixExtension Include="WixUtilExtension">
<HintPath>$(WixExtDir)\WixUtilExtension.dll</HintPath>
<Name>WixUtilExtension</Name>
</WixExtension>
</ItemGroup>
<Import Project="$(WixTargetsPath)" />
<!--
To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Wix.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi" xmlns:util='http://schemas.microsoft.com/wix/UtilExtension'>
<Product Id="3404a743-6802-41dd-a0bd-9229827fd2af" Name="RedisWatcher" Language="1033" Version="1.0.0.0" Manufacturer="MsOpenTech"
UpgradeCode="dcf0348c-eb55-4f12-b639-15310d3a9b5d">
<Package InstallerVersion="200" Compressed="yes" />
<Media Id="1" Cabinet="media1.cab" EmbedCab="yes" />
<PropertyRef Id="WIX_ACCOUNT_USERS" />
<PropertyRef Id="WIX_ACCOUNT_ADMINISTRATORS"/>
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="ProgramFilesFolder">
<Directory Id="INSTALLLOCATION" Name="RedisWatcher">
<Component Id="ProductComponent" Guid="1b743227-f33f-426c-a0ea-20bd159b21d6">
<File Id="RedisWatcher.exe" Name="$(var.RedisWatcher.TargetFileName)" Source="$(var.RedisWatcher.TargetPath)"
DiskId="1" KeyPath="yes" />
<File Id="RedisWatcher.man" Name="RedisWatcher.man" Source="$(var.RedisWatcher.ProjectDir)" DiskId="1" >
<util:EventManifest MessageFile="[INSTALLLOCATION]RedisWatcher.exe" ResourceFile="[INSTALLLOCATION]RedisWatcher.exe"/>
</File>
<File Id="watcher.conf" Name="watcher.conf" Source="$(var.RedisWatcher.ProjectDir)" DiskId="1" >
<Permission GenericAll="yes" GenericWrite="yes" User="[WIX_ACCOUNT_USERS]" />
<Permission GenericAll="yes" User="[WIX_ACCOUNT_ADMINISTRATORS]" />
</File>
<ServiceInstall Id="RedisWatcherSvc" Name="RedisWatcherSvc" DisplayName ="Redis watcher" ErrorControl="normal"
Start="auto" Type="ownProcess" Description="Redis process starter and watchdog">
</ServiceInstall>
<ServiceControl Id="SvcControl" Name="RedisWatcherSvc" Remove="uninstall" Stop="both" Wait="no" />
</Component>
</Directory>
</Directory>
</Directory>
<Feature Id="ProductFeature" Title="RedisWatcher" Level="1">
<ComponentRef Id="ProductComponent" />
<ComponentGroupRef Id="Product.Generated" />
</Feature>
</Product>
</Wix>
redis-watcher
=============
RedisWatcher is an application that will run one or more instances of redis-server.
If the redis-server process terminates, then RedisWatcher will restart it.
RedisWatcher is installed as a Windows service.
It can also run as an application by passing 'console' as a command line argument.
Building
========
Visual Studio 2010 was used to create the solution and project files.
The solution includes an installer project that uses WiX Toolset v3.5.
You can download WiX from http://wix.codeplex.com
The RedisWatcher project uses mc.exe to process the RedisWatcher.man to create .h and .rc files
Configuring the watcher
=======================
The watcher.conf file configures RedisWatcher.
The service will load the watcher.conf from the same folder as RedisWatcher.exe.
A sample watcher.conf is provided by the installer.
Specify the location of the redis-server executable to run, as well as the executable file name.
Configure each instance of redis-server to be run.
- the working directory must be unique per instance
- the cmdparms is used to specify the configuration file to load if any.
If running multiple instances, use a Redis configuration file to specify the listening port per instance
If watcher.conf is modified while the service is running, it is reloaded.
- Any running redis-server processes are not terminated by RedisWatcher.
- If new redis-server instances are configured, new ones will be started.
Tracing
=======
ETW event messages are defined in RedisWatcher.man.
This is compiled during the build to create .h and .rc files.
The installer modifies the 'messageFileName' and 'resourceFileName' attributes.
The event provider is registered with Windows by the installer.
To manually register the event provider
wevtutil im RedisWatcher.man
To manually uninstall the event provider
wevtutil um RedisWatcher.man
To start tracing
logman start mytrace -p MsOpenTech-RedisWatcher -o mytrace.etl -ets
To stop tracing
logman stop mytrace -ets
To convert trace output to xml file
tracerpt mytrace.etl
Other tools are available for formatting the trace.

Microsoft Visual Studio Solution File, Format Version 11.00
# Visual Studio 2010
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "RedisWatcher", "RedisWatcher\RedisWatcher.vcxproj", "{A249484F-526D-4725-9434-430F74837FFE}"
EndProject
Project("{930C7802-8A8C-48F9-8165-68863BCCD9DD}") = "Install", "Install\Install.wixproj", "{F152845B-DB83-4862-909C-0476930233C1}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Mixed Platforms = Debug|Mixed Platforms
Debug|Win32 = Debug|Win32
Debug|x86 = Debug|x86
Release|Mixed Platforms = Release|Mixed Platforms
Release|Win32 = Release|Win32
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{A249484F-526D-4725-9434-430F74837FFE}.Debug|Mixed Platforms.ActiveCfg = Debug|Win32
{A249484F-526D-4725-9434-430F74837FFE}.Debug|Mixed Platforms.Build.0 = Debug|Win32
{A249484F-526D-4725-9434-430F74837FFE}.Debug|Win32.ActiveCfg = Debug|Win32
{A249484F-526D-4725-9434-430F74837FFE}.Debug|Win32.Build.0 = Debug|Win32
{A249484F-526D-4725-9434-430F74837FFE}.Debug|x86.ActiveCfg = Debug|Win32
{A249484F-526D-4725-9434-430F74837FFE}.Release|Mixed Platforms.ActiveCfg = Release|Win32
{A249484F-526D-4725-9434-430F74837FFE}.Release|Mixed Platforms.Build.0 = Release|Win32
{A249484F-526D-4725-9434-430F74837FFE}.Release|Win32.ActiveCfg = Release|Win32
{A249484F-526D-4725-9434-430F74837FFE}.Release|Win32.Build.0 = Release|Win32
{A249484F-526D-4725-9434-430F74837FFE}.Release|x86.ActiveCfg = Release|Win32
{F152845B-DB83-4862-909C-0476930233C1}.Debug|Mixed Platforms.ActiveCfg = Debug|x86
{F152845B-DB83-4862-909C-0476930233C1}.Debug|Mixed Platforms.Build.0 = Debug|x86
{F152845B-DB83-4862-909C-0476930233C1}.Debug|Win32.ActiveCfg = Debug|x86
{F152845B-DB83-4862-909C-0476930233C1}.Debug|x86.ActiveCfg = Debug|x86
{F152845B-DB83-4862-909C-0476930233C1}.Debug|x86.Build.0 = Debug|x86
{F152845B-DB83-4862-909C-0476930233C1}.Release|Mixed Platforms.ActiveCfg = Release|x86
{F152845B-DB83-4862-909C-0476930233C1}.Release|Mixed Platforms.Build.0 = Release|x86
{F152845B-DB83-4862-909C-0476930233C1}.Release|Win32.ActiveCfg = Release|x86
{F152845B-DB83-4862-909C-0476930233C1}.Release|x86.ActiveCfg = Release|x86
{F152845B-DB83-4862-909C-0476930233C1}.Release|x86.Build.0 = Release|x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal
/***********************************************************************
* Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT
* LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR
* A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT.
*
* See the Apache License, Version 2.0 for specific language governing
* permissions and limitations under the License.
*
**********************************************************************/
#include "stdafx.h"
#include "watcher.h"
// Globals
// Name of configuration file
wchar_t * ConfigFile = L"watcher.conf";
wchar_t * configPath = NULL;
// Lock used to prevent threads from corrupting data
CRITICAL_SECTION ConfigLock;
// Locking functions
void Lock()
{
EnterCriticalSection(&ConfigLock);
}
void Unlock()
{
LeaveCriticalSection(&ConfigLock);
}
void InitLock()
{
InitializeCriticalSection(&ConfigLock);
}
void TermLock()
{
DeleteCriticalSection(&ConfigLock);
}
//
// Purpose:
// Initialize the watcher
//
// Parameters:
// configuration path
//
// Return value:
// TRUE or FALSE
//
BOOL WatcherStart(wchar_t * path)
{
WatcherConfig * config;
EventRegisterMsOpenTech_RedisWatcher();
InitLock();
EventWriteWatcher_Start();
if (CombineFilePath(path, ConfigFile, &configPath))
{
config = parseConfig(configPath);
if (config != NULL)
{
initialize(config);
}
startMonitorConfigFile(configPath);
return TRUE;
}
return FALSE;
}
//
// Purpose:
// Terminate the watcher
//
// Parameters:
// none
//
// Return value:
// none
//
void WatcherStop()
{
stopMonitorConfigFile();
cleanup();
EventWriteWatcher_Stop();
EventUnregisterMsOpenTech_RedisWatcher();
TermLock();
}
//
// Purpose:
// Main entry point
// Start as either a service or a console application
//
// Parameters:
// argc, argv
// no arguments means start as service
// "console" means start as console.
//
// Return value:
// exit code
//
int wmain(int argc, wchar_t* argv[])
{
wchar_t * path = NULL;
// If command-line parameter is "console", run as console app.
// Otherwise, the service is probably being started by the SCM.
if (argc > 1)
{
if (_wcsicmp(argv[1], L"console") == 0)
{
if (GetCurrentDir(&path))
{
wchar_t buff[100];
// Start redis watcher
if (!WatcherStart(path))
{
printf("Failed to start watcher\n");
return 1;
}
// run until user enters x
while (1)
{
_getws_s(buff, 100);
if (buff[0] == L'x') break;
}
WatcherStop();
return 0;
}
else
{
return 1;
}
}
else
{
printf("Parameter not valid\n");
return 1;
}
}
else
{
SvcStart();
return 0;
}
return 1;
}
Binary files a/msvs/RedisWatcher/RedisWatcher/RedisWatcher.man and /dev/null differ
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{A249484F-526D-4725-9434-430F74837FFE}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>RedisWatcher</RootNamespace>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<LinkIncremental>true</LinkIncremental>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<LinkIncremental>false</LinkIncremental>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<PrecompiledHeader>
</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
<PreBuildEvent>
<Command>mc -um $(ProjectName).man -h "$(ProjectDir)\" -z $(ProjectName)</Command>
</PreBuildEvent>
<PreBuildEvent>
<Message>build event manifest</Message>
</PreBuildEvent>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<PrecompiledHeader>
</PrecompiledHeader>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
</Link>
<PreBuildEvent>
<Command>mc -um $(ProjectName).man -h "$(ProjectDir)\" -z $(ProjectName)</Command>
<Message>build event manifest</Message>
</PreBuildEvent>
</ItemDefinitionGroup>
<ItemGroup>
<None Include="RedisWatcher.man">
<SubType>Designer</SubType>
</None>
<None Include="watcher.conf" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="RedisWatcher.h" />
<ClInclude Include="resource.h" />
<ClInclude Include="stdafx.h" />
<ClInclude Include="targetver.h" />
<ClInclude Include="watcher.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="config.c" />
<ClCompile Include="configchange.c" />
<ClCompile Include="RedisWatcher.c" />
<ClCompile Include="service.c" />
<ClCompile Include="stdafx.c" />
<ClCompile Include="util.c" />
<ClCompile Include="watcher.c" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="RedisWatcher.rc">
<AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">./</AdditionalIncludeDirectories>
</ResourceCompile>
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>
\ No newline at end of file
/***********************************************************************
* Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT
* LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR
* A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT.
*
* See the Apache License, Version 2.0 for specific language governing
* permissions and limitations under the License.
*
**********************************************************************/
#include "stdafx.h"
#include "watcher.h"
// strings used for parsing the config file
const wchar_t commentChar = L'#';
const wchar_t exepathToken[] = L"exepath";
const wchar_t exenameToken[] = L"exename";
const wchar_t fastfailmsToken[] = L"fastfailms";
const wchar_t fastfailretriesToken[] = L"fastfailretries";
const wchar_t startInstanceToken[] = L"{";
const wchar_t stopInstanceToken[] = L"}";
const wchar_t runmodeToken[] = L"runmode";
const wchar_t workingdirToken[] = L"workingdir";
const wchar_t cmdparmsToken[] = L"cmdparms";
const wchar_t saveoutToken[] = L"saveout";
const wchar_t runmodeHidden[] = L"hidden";
const wchar_t runmodeConsole[] = L"console";
#define CONFIGLINE_MAX 1024
//
// Purpose:
// Release config structure memory
//
// Parameters:
// WatcherConfig
//
// Return value:
// none
//
void freeConfig(WatcherConfig * config)
{
if (config->ExecutablePath != NULL)
{
free(config->ExecutablePath);
config->ExecutablePath = NULL;
}
if (config->ExecutableName != NULL)
{
free(config->ExecutableName);
config->ExecutableName = NULL;
}
if (config->ConfiguredInstances.Instances != NULL)
{
free(config->ConfiguredInstances.Instances);
config->ConfiguredInstances.Instances = NULL;
}
free(config);
}
//
// Purpose:
// Read and parse a configuration file
//
// Parameters:
// FILE reference
//
// Return value:
// WatcherConfig or NULL
//
WatcherConfig * parseConfigFile(FILE * fp)
{
ProcInstance * proc;
WatcherConfig * config;
wchar_t buf[CONFIGLINE_MAX+1];
wchar_t * line;
wchar_t * toksep;
int instanceCount = 0;
int numInstances = 0;
BOOL inInstance = FALSE;
int linenum = 0;
wchar_t * key;
wchar_t * value;
DWORD dwAttrib;
// scan for instances
while (fgetws(buf, CONFIGLINE_MAX+1, fp) != NULL)
{
line = Trim(buf);
if (_wcsnicmp(line, startInstanceToken, 1) == 0)
{
if (inInstance)
{
EventWriteConfig_File_MismatchBraces();
return NULL;
}
numInstances++;
inInstance = TRUE;
}
else if (_wcsnicmp(line, stopInstanceToken, 1) == 0)
{
if (!inInstance)
{
EventWriteConfig_File_MismatchBraces();
return NULL;
}
inInstance = FALSE;
}
if (feof(fp) != 0)
break;
}
if (inInstance)
{
EventWriteConfig_File_MismatchBraces();
return NULL;
}
config = (WatcherConfig *)malloc(sizeof(WatcherConfig));
if (config == NULL)
{
_set_errno(ERROR_NOT_ENOUGH_MEMORY);
return NULL;
}
config->ExecutableName = NULL;
config->ExecutablePath = NULL;
config->Policy.FastFailRetries = 0;
config->Policy.FastFailMs = 1000;
config->ConfiguredInstances.NumInstances = 0;
config->ConfiguredInstances.Instances = (ProcInstance *)malloc(sizeof(ProcInstance) * numInstances);
// start from beginning
fseek(fp, 0, SEEK_SET);
instanceCount = 0;
proc = NULL;
while (fgetws(buf, CONFIGLINE_MAX+1, fp) != NULL)
{
line = Trim(buf);
if (line[0] == commentChar || line[0] == L'\0')
{
continue;
}
if (_wcsnicmp(line, startInstanceToken, 1) == 0)
{
proc = config->ConfiguredInstances.Instances + instanceCount;
instanceCount++;
proc->CmdParam = NULL;
proc->CmdLine = NULL;
proc->WorkingDir = NULL;
proc->State = PROC_UNKNOWN;
proc->ProcessHandle = NULL;
proc->ProcessId = -1;
proc->SaveOutput = FALSE;
proc->History.StartTime = 0;
proc->History.StopTime = 0;
proc->History.FastFailCount = 0;
}
else if (_wcsnicmp(line, stopInstanceToken, 1) == 0)
{
if (proc->WorkingDir == NULL)
{
EventWriteConfig_File_Invalid_WorkingDir();
freeConfig(config);
return NULL;
}
config->ConfiguredInstances.NumInstances = instanceCount;
proc = NULL;
}
else
{
// each line except '{' and '}' has key and value
// find first space or tab
toksep = wcschr(line, L' ');
if (toksep == NULL)
{
toksep = wcschr(line, L'\t');
if (toksep == NULL)
{
continue;
}
}
// first token is key
key = line;
*toksep = L'\0';
// trim remainder to get value
value = Trim(toksep + 1);
if (_wcsicmp(key, exepathToken) == 0)
{
if (!CopyString(value, &config->ExecutablePath))
{
freeConfig(config);
return NULL;
}
}
else if (_wcsicmp(key, exenameToken) == 0)
{
if (!CopyString(value, &config->ExecutableName))
{
freeConfig(config);
return NULL;
}
}
else if (_wcsicmp(key, fastfailmsToken) == 0)
{
unsigned long ms = wcstoul(value, NULL, 10);
if (ms > 0)
{
config->Policy.FastFailMs = ms;
}
}
else if (_wcsicmp(key, fastfailretriesToken) == 0)
{
unsigned long retries = wcstoul(value, NULL, 10);
if (retries > 0)
{
config->Policy.FastFailRetries = retries;
}
}
else if (_wcsicmp(key, runmodeToken) == 0)
{
if (proc != NULL)
{
if (_wcsicmp(value, runmodeHidden) == 0)
{
proc->RunMode = CREATE_NO_WINDOW;
}
if (_wcsicmp(value, runmodeConsole) == 0)
{
proc->RunMode = CREATE_NEW_CONSOLE;
}
}
}
else if (_wcsicmp(key, workingdirToken) == 0)
{
if (proc != NULL)
{
if (!CopyString(value, &proc->WorkingDir))
{
freeConfig(config);
return NULL;
}
dwAttrib = GetFileAttributes(proc->WorkingDir);
if (dwAttrib == INVALID_FILE_ATTRIBUTES ||
(dwAttrib & FILE_ATTRIBUTE_DIRECTORY) != FILE_ATTRIBUTE_DIRECTORY)
{
EventWriteConfig_File_Invalid_WorkingDir();
freeConfig(config);
return NULL;
}
}
}
else if (_wcsicmp(key, cmdparmsToken) == 0)
{
if (proc != NULL)
{
if (!CopyString(value, &proc->CmdParam))
{
freeConfig(config);
return NULL;
}
}
}
else if (_wcsicmp(key, saveoutToken) == 0)
{
if (proc != NULL)
{
if (_wcsicmp(value, L"1") == 0)
{
proc->SaveOutput = TRUE;
}
}
}
}
}
if (config->ExecutablePath == NULL)
{
EventWriteConfig_File_Invalid_ExePath();
freeConfig(config);
return NULL;
}
if (config->ExecutableName == NULL)
{
EventWriteConfig_File_Invalid_ExeName();
freeConfig(config);
return NULL;
}
CombineFilePath(config->ExecutablePath, config->ExecutableName, &config->ExecutablePath);
dwAttrib = GetFileAttributes(config->ExecutablePath);
if (dwAttrib == INVALID_FILE_ATTRIBUTES ||
(dwAttrib & FILE_ATTRIBUTE_DIRECTORY))
{
EventWriteConfig_File_Invalid_ExePath();
freeConfig(config);
return NULL;
}
return config;
}
//
// Purpose:
// Read and parse a configuration file
//
// Parameters:
// file path
//
// Return value:
// WatcherConfig or NULL
//
WatcherConfig * parseConfig(wchar_t * configPath)
{
FILE * fp;
WatcherConfig * config;
if ((_wfopen_s(&fp, configPath, L"r")) != 0)
{
EventWriteConfig_File_Not_Found();
return NULL;
}
config = parseConfigFile(fp);
fclose(fp);
return config;
}
/***********************************************************************
* Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT
* LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR
* A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT.
*
* See the Apache License, Version 2.0 for specific language governing
* permissions and limitations under the License.
*
**********************************************************************/
#include "stdafx.h"
#include "watcher.h"
// Configuration file and directory to watch for changes
wchar_t * ConfigFile;
wchar_t * ConfigDir;
// Last checked file time
FILETIME LastUpdate;
// Handles used to monitor for config file changes
HANDLE ConfigNotify = INVALID_HANDLE_VALUE;
HANDLE WaitHandle = INVALID_HANDLE_VALUE;
// trying to load file durng notify fails. Delay before reading
const ULONG ConfigLoadDelay = 2000;
// forward declaration
void monitorConfigfile(ULONG ms);
//
// Purpose:
// Test if file update time has changed since last check
//
// Parameters:
// path to file
//
// Return value:
// TRUE or FALSE
//
BOOL TestFileChange(wchar_t * configPath)
{
HANDLE hFile;
FILETIME ftCreate, ftAccess, ftWrite;
BOOL changed = FALSE;
hFile = CreateFile(configPath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
if (hFile != INVALID_HANDLE_VALUE)
{
BOOL rc = GetFileTime(hFile, &ftCreate, &ftAccess, &ftWrite);
if (rc)
{
if (LastUpdate.dwHighDateTime != ftWrite.dwHighDateTime ||
LastUpdate.dwLowDateTime != ftWrite.dwLowDateTime)
{
changed = TRUE;
LastUpdate = ftWrite;
}
}
CloseHandle(hFile);
}
return changed;
}
//
// Purpose:
// Notification callback for configuration file change or timeout
// File may not be accessible during the change notification.
// Continue monitoring until there are no changes for some time.
// After the time delay, check if file time has changed, and if so,
// reload configuration and start new instances
//
// Parameters:
// context (not used) and timeout flag
//
// Return value:
// none
//
void CALLBACK FileChangeCallback(void * context, BOOLEAN timeout)
{
WatcherConfig * config;
if (ConfigNotify == INVALID_HANDLE_VALUE)
return;
UnregisterWait(WaitHandle);
WaitHandle = INVALID_HANDLE_VALUE;
if (!timeout)
{
// something changed, delay before reloading
FindNextChangeNotification(ConfigNotify);
monitorConfigfile(ConfigLoadDelay);
}
else
{
if (TestFileChange(ConfigFile))
{
config = parseConfig(ConfigFile);
if (config != NULL)
{
updateConfig(config);
}
}
FindNextChangeNotification(ConfigNotify);
monitorConfigfile(INFINITE);
}
}
//
// Purpose:
// Start monitoring the configuration file for updates
//
// Parameters:
// path to file
//
// Return value:
// none
//
void startMonitorConfigFile(wchar_t * configPath)
{
ConfigFile = NULL;
ConfigDir = NULL;
// keep copies of path as file and directory
if (CopyString(configPath, &ConfigFile) &&
CopyString(configPath, &ConfigDir))
{
wchar_t * lastSlash = NULL;
wchar_t * pos = ConfigDir;
// replace last '\' with null for directory
while (*pos != L'\0')
{
if (*pos == L'\\')
{
lastSlash = pos;
}
pos++;
}
if (lastSlash != NULL)
{
*lastSlash = L'\0';
}
// get current update time for file
TestFileChange(ConfigFile);
ConfigNotify = FindFirstChangeNotification(ConfigDir, FALSE, FILE_NOTIFY_CHANGE_LAST_WRITE);
if (ConfigNotify != INVALID_HANDLE_VALUE)
{
monitorConfigfile(INFINITE);
}
else
{
EventWriteConfig_Monitor_Fail();
}
}
}
//
// Purpose:
// Stop monitoring the configuration file for updates
//
// Parameters:
// none
//
// Return value:
// none
//
void stopMonitorConfigFile()
{
if (WaitHandle != INVALID_HANDLE_VALUE)
{
UnregisterWait(WaitHandle);
WaitHandle = INVALID_HANDLE_VALUE;
}
if (ConfigNotify != INVALID_HANDLE_VALUE)
{
FindCloseChangeNotification(ConfigNotify);
ConfigNotify = INVALID_HANDLE_VALUE;
}
}
//
// Purpose:
// Monitor the configuration file for updates or timeout
//
// Parameters:
// timeout in ms or INFINITE
//
// Return value:
// none
//
void monitorConfigfile(ULONG ms)
{
if (ConfigNotify != INVALID_HANDLE_VALUE)
{
BOOL rc = RegisterWaitForSingleObject(&WaitHandle,
ConfigNotify,
FileChangeCallback,
(PVOID)ConfigNotify,
ms,
WT_EXECUTEONLYONCE);
if (rc == FALSE)
{
EventWriteConfig_Monitor_Fail();
}
}
}
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by RedisWatcher.rc
//
#define IDR_WEVT_TEMPLATE1 101
// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 103
#define _APS_NEXT_COMMAND_VALUE 40001
#define _APS_NEXT_CONTROL_VALUE 1000
#define _APS_NEXT_SYMED_VALUE 101
#endif
#endif
/******************************************************
*
* Service code adapted from MSDN article
*
*******************************************************/
#include "stdafx.h"
#include "watcher.h"
// globals for the service
#define SVCNAME L"RedisWatchSvc"
const wchar_t * SvcName = SVCNAME;
SERVICE_STATUS gSvcStatus;
SERVICE_STATUS_HANDLE gSvcStatusHandle;
HANDLE ghSvcStopEvent = NULL;
// local methods for running the service
VOID WINAPI SvcCtrlHandler( DWORD );
VOID WINAPI SvcMain( DWORD, LPTSTR * );
VOID ReportSvcStatus( DWORD, DWORD, DWORD );
VOID SvcInit( DWORD, LPTSTR * );
// The set of services
SERVICE_TABLE_ENTRY DispatchTable[] =
{
{ SVCNAME, (LPSERVICE_MAIN_FUNCTION) SvcMain },
{ NULL, NULL }
};
//
// Purpose:
// Start running the service
//
// Parameters:
// None.
//
// Return value:
// None.
//
void SvcStart()
{
// This call returns when the service has stopped.
// The process should simply terminate when the call returns.
StartServiceCtrlDispatcher(DispatchTable);
}
//
// Purpose:
// Entry point for the service
//
// Parameters:
// dwArgc - Number of arguments in the lpszArgv array
// lpszArgv - Array of strings. The first string is the name of
// the service and subsequent strings are passed by the process
// that called the StartService function to start the service.
//
// Return value:
// None.
//
VOID WINAPI SvcMain(DWORD dwArgc, LPTSTR *lpszArgv)
{
// Register the handler function for the service
gSvcStatusHandle = RegisterServiceCtrlHandler(
SvcName,
SvcCtrlHandler);
if (!gSvcStatusHandle)
{
return;
}
// These SERVICE_STATUS members remain as set here
gSvcStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
gSvcStatus.dwServiceSpecificExitCode = 0;
// Report initial status to the SCM
ReportSvcStatus(SERVICE_START_PENDING, NO_ERROR, 3000);
// Perform service-specific initialization and work.
SvcInit(dwArgc, lpszArgv);
}
//
// Purpose:
// The service code
//
// Parameters:
// dwArgc - Number of arguments in the lpszArgv array
// lpszArgv - Array of strings. The first string is the name of
// the service and subsequent strings are passed by the process
// that called the StartService function to start the service.
//
// Return value:
// None
//
VOID SvcInit(DWORD dwArgc, LPTSTR *lpszArgv)
{
wchar_t * path = NULL;
// Create an event. The control handler function, SvcCtrlHandler,
// signals this event when it receives the stop control code.
ghSvcStopEvent = CreateEvent(
NULL, // default security attributes
TRUE, // manual reset event
FALSE, // not signaled
NULL); // no name
if (ghSvcStopEvent == NULL)
{
ReportSvcStatus(SERVICE_STOPPED, NO_ERROR, 0);
return;
}
// Report running status when initialization is complete.
ReportSvcStatus(SERVICE_RUNNING, NO_ERROR, 0);
// Start redis watcher
if (GetModulePath(&path))
{
if (WatcherStart(path))
{
// Wait for signal to stop the service.
WaitForSingleObject(ghSvcStopEvent, INFINITE);
}
WatcherStop();
}
ReportSvcStatus(SERVICE_STOPPED, NO_ERROR, 0);
return;
}
//
// Purpose:
// Sets the current service status and reports it to the SCM.
//
// Parameters:
// dwCurrentState - The current state (see SERVICE_STATUS)
// dwWin32ExitCode - The system error code
// dwWaitHint - Estimated time for pending operation,
// in milliseconds
//
// Return value:
// None
//
VOID ReportSvcStatus(DWORD dwCurrentState,
DWORD dwWin32ExitCode,
DWORD dwWaitHint)
{
static DWORD dwCheckPoint = 1;
// Fill in the SERVICE_STATUS structure.
gSvcStatus.dwCurrentState = dwCurrentState;
gSvcStatus.dwWin32ExitCode = dwWin32ExitCode;
gSvcStatus.dwWaitHint = dwWaitHint;
if (dwCurrentState == SERVICE_START_PENDING)
gSvcStatus.dwControlsAccepted = 0;
else
gSvcStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;
if ( (dwCurrentState == SERVICE_RUNNING) ||
(dwCurrentState == SERVICE_STOPPED) )
gSvcStatus.dwCheckPoint = 0;
else
gSvcStatus.dwCheckPoint = dwCheckPoint++;
// Report the status of the service to the SCM.
SetServiceStatus(gSvcStatusHandle, &gSvcStatus);
}
//
// Purpose:
// Called by SCM whenever a control code is sent to the service
// using the ControlService function.
//
// Parameters:
// dwCtrl - control code
//
// Return value:
// None
//
VOID WINAPI SvcCtrlHandler(DWORD dwCtrl)
{
// Handle the requested control code.
switch(dwCtrl)
{
case SERVICE_CONTROL_STOP:
ReportSvcStatus(SERVICE_STOP_PENDING, NO_ERROR, 0);
// Signal the service to stop.
SetEvent(ghSvcStopEvent);
ReportSvcStatus(gSvcStatus.dwCurrentState, NO_ERROR, 0);
return;
case SERVICE_CONTROL_INTERROGATE:
break;
default:
break;
}
}
// stdafx.cpp : source file that includes just the standard includes
// RedisWatcher.pch will be the pre-compiled header
// stdafx.obj will contain the pre-compiled type information
#include "stdafx.h"
// stdafx.h : include file for standard system include files,
// or project specific include files that are used frequently, but
// are changed infrequently
//
#pragma once
#include "targetver.h"
#include <windows.h>
#include <stdio.h>
#include <tchar.h>
#include <strsafe.h>
#include <Evntprov.h>
#include <TlHelp32.h>
#pragma once
// Including SDKDDKVer.h defines the highest available Windows platform.
// If you wish to build your application for a previous Windows platform, include WinSDKVer.h and
// set the _WIN32_WINNT macro to the platform you wish to support before including SDKDDKVer.h.
#include <SDKDDKVer.h>
/***********************************************************************
* Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT
* LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR
* A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT.
*
* See the Apache License, Version 2.0 for specific language governing
* permissions and limitations under the License.
*
**********************************************************************/
#include "stdafx.h"
#include "watcher.h"
//
// Purpose:
// Allocate memory and copy string
// If destination is not NULL, free the existing pointer
//
// Parameters:
// value is source string, dest is destination reference
//
// Return value:
// TRUE or FALSE
//
BOOL CopyString(wchar_t * value, wchar_t ** dest)
{
size_t len = wcslen(value) + 1;
if (*dest != NULL)
free (*dest);
*dest = (wchar_t *)malloc(len * sizeof(wchar_t));
if (*dest == NULL)
{
_set_errno(ERROR_NOT_ENOUGH_MEMORY);
return FALSE;
}
if (wcscpy_s(*dest, len, value) == 0)
{
return TRUE;
}
return FALSE;
}
//
// Purpose:
// Combine path and file name into a fqn
// If fullpath is not NULL, free the existing pointer
//
// Parameters:
// path, filename, fullpath is reference for FQN
//
// Return value:
// TRUE or FALSE
//
BOOL CombineFilePath(wchar_t * path, wchar_t * filename, wchar_t ** fullpath)
{
wchar_t * combpath;
size_t pathlen = wcslen(path);
size_t len = pathlen + wcslen(filename) + 2;
combpath = (wchar_t *)malloc(len * sizeof(wchar_t));
if (combpath == NULL)
{
_set_errno(ERROR_NOT_ENOUGH_MEMORY);
return FALSE;
}
if (wcscpy_s(combpath, len, path) != 0)
{
free (combpath);
return FALSE;
}
if (pathlen > 0 && path[pathlen - 1] != L'\\')
{
if (wcscat_s(combpath, len, L"\\") != 0)
{
free (combpath);
return FALSE;
}
}
if (wcscat_s(combpath, len, filename) != 0)
{
free (combpath);
return FALSE;
}
if (*fullpath != NULL)
free (*fullpath);
*fullpath = combpath;
return TRUE;
}
//
// Purpose:
// Get the current working directory
// If fullpath is not NULL, free the existing pointer
//
// Parameters:
// fullpath is reference for path
//
// Return value:
// TRUE or FALSE
//
BOOL GetCurrentDir(wchar_t ** fullpath)
{
wchar_t * combpath;
size_t pathlen = 0;
pathlen = GetCurrentDirectory(0, NULL);
if (pathlen > 0)
{
combpath = (wchar_t *)malloc(pathlen * sizeof(wchar_t));
if (combpath == NULL)
{
_set_errno(ERROR_NOT_ENOUGH_MEMORY);
return FALSE;
}
GetCurrentDirectory(pathlen, combpath);
if (*fullpath != NULL)
free (*fullpath);
*fullpath = combpath;
return TRUE;
}
return FALSE;
}
//
// Purpose:
// Get the current module path
// If fullpath is not NULL, free the existing pointer
// Used for running as service instead of CurrentDir
//
// Parameters:
// fullpath is reference for path
//
// Return value:
// TRUE or FALSE
//
BOOL GetModulePath(wchar_t ** fullpath)
{
wchar_t * combpath;
size_t pathlen = MAX_PATH;
wchar_t * pos;
wchar_t * lastSep;
combpath = (wchar_t *)malloc(pathlen * sizeof(wchar_t));
while (combpath != NULL && (pathlen == GetModuleFileName(NULL, combpath, pathlen)))
{
free(combpath);
pathlen = pathlen * 2;
combpath = (wchar_t *)malloc(pathlen * sizeof(wchar_t));
}
if (combpath == NULL)
{
_set_errno(ERROR_NOT_ENOUGH_MEMORY);
return FALSE;
}
// terminate at last slash
pos = combpath;
lastSep = NULL;
while (*pos != L'\0')
{
if (*pos == L'\\')
lastSep = pos;
pos++;
}
if (lastSep != NULL)
{
*lastSep = L'\0';
if (*fullpath != NULL)
free (*fullpath);
*fullpath = combpath;
return TRUE;
}
return FALSE;
}
//
// Purpose:
// Remove extra whitespace around a configuration token
//
// Parameters:
// string
//
// Return value:
// string
//
const wchar_t wspace[] = L" \t\r\n";
wchar_t * Trim(wchar_t * buf)
{
wchar_t * sp = buf;
wchar_t * ep = buf + wcslen(buf) - 1;
while (sp < ep && wcschr(wspace, *sp) != NULL)
sp++;
while (sp < ep && wcschr(wspace, *ep) != NULL)
ep--;
ep++;
*ep = L'\0';
return sp;
}
/***********************************************************************
* Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT
* LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR
* A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT.
*
* See the Apache License, Version 2.0 for specific language governing
* permissions and limitations under the License.
*
**********************************************************************/
#include "stdafx.h"
#include "watcher.h"
// Global state for the process
static WatcherConfig * Config;
static ProcList * Discovered;
// Local methods
ProcList * findRunningProcesses(wchar_t * executableName);
void startIdleConfigured();
void startMonitoring(ProcInstance * instance);
void startRunning(WatcherConfig * config, ProcInstance * instance);
void ReleaseInstanceHandles(ProcInstance * instance);
void ReleaseInstanceAllocations(ProcInstance * instance);
void CopyMonitoringInstance(ProcInstance * newInstance, ProcInstance * oldInstance);
//
// Purpose:
// Loads configuration, finds running processes and starts configured instances
//
// Parameters:
// Configuration
//
// Return value:
// None
//
void initialize(WatcherConfig * watchConfig)
{
Lock();
Config = watchConfig;
Discovered = findRunningProcesses(Config->ExecutableName);
// try to start configured process and start monitoring
startIdleConfigured();
Unlock();
}
//
// Purpose:
// Releases resources used and stops monitoring processes
//
// Parameters:
// none
//
// Return value:
// None
//
void cleanup()
{
int i;
ProcInstance * instance;
Lock();
if (Config != NULL)
{
instance = Config->ConfiguredInstances.Instances;
if (instance != NULL)
{
for (i = 0; i < Config->ConfiguredInstances.NumInstances; i++, instance++)
{
if (instance->ProcessId != -1)
ReleaseInstanceHandles(instance);
ReleaseInstanceAllocations(instance);
}
}
freeConfig(Config);
Config = NULL;
}
if (Discovered != NULL)
{
instance = Discovered->Instances;
if (instance != NULL)
{
for (i = 0; i < Discovered->NumInstances; i++, instance++)
{
if (instance->ProcessId != -1)
ReleaseInstanceHandles(instance);
}
}
free(Discovered);
Discovered = NULL;
}
Unlock();
}
//
// Purpose:
// Uses new configuration data. Starts new processes if any.
//
// Parameters:
// none
//
// Return value:
// None
//
void updateConfig(WatcherConfig * watchConfig)
{
int iold;
int inew;
ProcInstance * oldInstance;
ProcInstance * newInstance;
EventWriteConfig_File_Modified();
Lock();
// if old config has matching process, copy process info
if (Config != NULL)
{
oldInstance = Config->ConfiguredInstances.Instances;
for (iold = 0; iold < Config->ConfiguredInstances.NumInstances; iold++, oldInstance++)
{
if (oldInstance->ProcessId == -1)
continue;
newInstance = watchConfig->ConfiguredInstances.Instances;
for (inew = 0; inew < watchConfig->ConfiguredInstances.NumInstances; inew++, newInstance++)
{
if (newInstance->ProcessId == -1 &&
((oldInstance->WorkingDir != NULL && newInstance->WorkingDir != NULL &&
_wcsicmp(oldInstance->WorkingDir, newInstance->WorkingDir) == 0) ||
(oldInstance->WorkingDir == NULL && newInstance->WorkingDir == NULL)) &&
((oldInstance->CmdParam != NULL && newInstance->CmdParam != NULL &&
_wcsicmp(oldInstance->CmdParam, newInstance->CmdParam) == 0) ||
(oldInstance->CmdParam == NULL && newInstance->CmdParam == NULL)))
{
// same instance. Copy properties
CopyMonitoringInstance(newInstance, oldInstance);
}
}
}
}
// stop all non copied waits
cleanup();
// find running processes and monitor everything
initialize(watchConfig);
Unlock();
}
//
// Purpose:
// Find configured instance by pid
//
// Parameters:
// pid
//
// Return value:
// ProcInstance or NULL
//
ProcInstance * FindConfiguredMonitoring(int pid)
{
int i;
ProcInstance * instance;
if (Config == NULL)
return NULL;
instance = Config->ConfiguredInstances.Instances;
for (i = 0; i < Config->ConfiguredInstances.NumInstances; i++, instance++)
{
if (instance->ProcessId == pid)
{
return instance;
}
}
return NULL;
}
//
// Purpose:
// Find discovered instance by pid
//
// Parameters:
// pid
//
// Return value:
// ProcInstance or NULL
//
ProcInstance * FindDiscoveredMonitoring(int pid)
{
int i;
ProcInstance * instance;
if (Discovered != NULL)
{
instance = Discovered->Instances;
for (i = 0; i < Discovered->NumInstances; i++, instance++)
{
if (instance->ProcessId == pid)
{
return instance;
}
}
}
return NULL;
}
//
// Purpose:
// Copy instance information to new instance
//
// Parameters:
// newInstance
// oldInstance
//
// Return value:
// none
//
void CopyMonitoringInstance(ProcInstance * newInstance, ProcInstance * oldInstance)
{
// we are already monitoring this instance. Transfer state
newInstance->State = oldInstance->State;
newInstance->ProcessHandle = oldInstance->ProcessHandle;
newInstance->ProcessWaitHandle = oldInstance->ProcessWaitHandle;
newInstance->ProcessId = oldInstance->ProcessId;
newInstance->History = oldInstance->History;
oldInstance->ProcessHandle = NULL;
oldInstance->ProcessWaitHandle = NULL;
oldInstance->ProcessId = -1;
}
//
// Purpose:
// Find running processes by executable name, and return instances
//
// Parameters:
// executableName
//
// Return value:
// ProcList
//
ProcList * findRunningProcesses(wchar_t * executableName)
{
int i;
ProcInstance * instance;
ProcInstance * oldInstance;
PROCESSENTRY32 procentry;
HANDLE snapshot;
ProcList * list = (ProcList *)malloc(sizeof(ProcList));
if (list == NULL || Config == NULL)
return NULL;
procentry.dwSize = sizeof(PROCESSENTRY32);
list->NumInstances = 0;
list->Instances = NULL;
Lock();
// find running processes for exe
snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (snapshot != INVALID_HANDLE_VALUE)
{
i = 0;
if (Process32First(snapshot, &procentry) == TRUE)
{
// intentionally ignore first process
while (Process32Next(snapshot, &procentry) == TRUE)
{
if (_wcsicmp(procentry.szExeFile, Config->ExecutableName) == 0)
{
// found exe with right name.
i++;
}
}
}
// found a few. Allocate structures for monitoring and start monitoring
if (i > 0)
{
list->Instances = (ProcInstance *)malloc(sizeof(ProcInstance) * i);
ZeroMemory(list->Instances, sizeof(ProcInstance) * i);
list->NumInstances = i;
instance = list->Instances;
if (Process32First(snapshot, &procentry) == TRUE)
{
// intentionally ignore first process
while (Process32Next(snapshot, &procentry) == TRUE)
{
if (_wcsicmp(procentry.szExeFile, Config->ExecutableName) == 0)
{
// found exe with right name.
if (FindConfiguredMonitoring(procentry.th32ProcessID) != NULL)
{
// already monitoring a configured instance
instance->State = PROC_UNKNOWN;
instance->ProcessHandle = NULL;
instance->ProcessWaitHandle = NULL;
instance->ProcessId = -1;
}
else
{
oldInstance = FindDiscoveredMonitoring(procentry.th32ProcessID);
if (oldInstance == NULL)
{
HANDLE hProcess = OpenProcess(SYNCHRONIZE, FALSE, procentry.th32ProcessID);
if (hProcess != NULL)
{
instance->WorkingDir = NULL;
instance->CmdLine = NULL;
instance->State = PROC_RUNNING;
instance->ProcessHandle = hProcess;
instance->ProcessId = procentry.th32ProcessID;
startMonitoring(instance);
}
else
{
instance->State = PROC_UNKNOWN;
instance->ProcessHandle = NULL;
instance->ProcessWaitHandle = NULL;
instance->ProcessId = -1;
}
}
else
{
// we are already monitoring this instance. Transfer state
instance->WorkingDir = oldInstance->WorkingDir;
instance->CmdParam = oldInstance->CmdParam;
instance->CmdLine = oldInstance->CmdLine;
CopyMonitoringInstance(instance, oldInstance);
}
}
instance++;
}
}
}
}
CloseHandle(snapshot);
}
Unlock();
return list;
}
//
// Purpose:
// Release handles for an instance
//
// Parameters:
// instance
//
// Return value:
// none
//
void ReleaseInstanceHandles(ProcInstance * instance)
{
if (instance->ProcessHandle != NULL)
{
CloseHandle(instance->ProcessHandle);
instance->ProcessHandle = NULL;
}
if (instance->ProcessWaitHandle != NULL)
{
UnregisterWait(instance->ProcessWaitHandle);
instance->ProcessWaitHandle = NULL;
}
instance->ProcessId = -1;
instance->State = PROC_FAILED;
}
//
// Purpose:
// Release memory for an instance
//
// Parameters:
// instance
//
// Return value:
// none
//
void ReleaseInstanceAllocations(ProcInstance * instance)
{
if (instance->WorkingDir != NULL)
{
free(instance->WorkingDir);
instance->WorkingDir = NULL;
}
if (instance->CmdParam != NULL)
{
free(instance->CmdParam);
instance->CmdParam = NULL;
}
if (instance->CmdLine != NULL)
{
free(instance->CmdLine);
instance->CmdLine = NULL;
}
}
//
// Purpose:
// Handle notification of a proces exit
// If it is a configured instance, try to restart
// If it is a discovered instance,try to start non-running
// configured instances in case the port is now available
//
// Parameters:
// pid as notification context
//
// Return value:
// none
//
void CALLBACK ProcessExitCallback(void * context, BOOLEAN timeout)
{
ProcInstance * instance;
Lock();
if (context != NULL && Config != NULL)
{
int pid = (int)context;
instance = FindConfiguredMonitoring(pid);
if (instance != NULL)
{
ReleaseInstanceHandles(instance);
// we started this instance. Check if we should restart
instance->History.StopTime = GetTickCount();
if (instance->History.StopTime - instance->History.StartTime >
Config->Policy.FastFailMs)
{
// ran for a while. reset count
instance->History.FastFailCount = 0;
}
else
{
instance->History.FastFailCount++;
}
if (instance->History.FastFailCount > Config->Policy.FastFailRetries)
{
EventWriteWatcher_RestartInstance_Giveup();
}
else
{
// restart instance
EventWriteWatcher_RestartInstance();
startRunning(Config, instance);
}
}
else
{
instance = FindDiscoveredMonitoring(pid);
if (instance != NULL)
{
EventWriteWatcher_DiscoveredInstance_Exit();
ReleaseInstanceHandles(instance);
// we discovered this instance. Try to start a configured instance
startIdleConfigured();
}
}
}
Unlock();
}
//
// Purpose:
// Try to start non-running
// configured instances in case the port is now available
//
// Parameters:
// none
//
// Return value:
// none
//
void startIdleConfigured()
{
ProcInstance * instance;
int i;
if (Config == NULL)
return;
Lock();
instance = Config->ConfiguredInstances.Instances;
for (i = 0; i < Config->ConfiguredInstances.NumInstances; i++, instance++)
{
if (instance->State == PROC_UNKNOWN || instance->State == PROC_FAILED)
{
EventWriteWatcher_StartInstance();
startRunning(Config, instance);
}
}
Unlock();
}
//
// Purpose:
// Start monitoring an instance for termination
//
// Parameters:
// instance
//
// Return value:
// none
//
void startMonitoring(ProcInstance * instance)
{
BOOL rc = RegisterWaitForSingleObject(&instance->ProcessWaitHandle,
instance->ProcessHandle,
ProcessExitCallback,
(PVOID)instance->ProcessId,
INFINITE,
WT_EXECUTEONLYONCE);
if (rc == FALSE)
{
EventWriteWatcher_Monitor_Fail();
}
}
//
// Purpose:
// Create a command line to start a new instance
//
// Parameters:
// instance, configuration
//
// Return value:
// command line or NULL
//
wchar_t * makeCmdLine(WatcherConfig * config, ProcInstance * instance)
{
size_t cmdlineLen = 1;
wchar_t * cmdLine;
BOOL failed = FALSE;
if (config == NULL)
return NULL;
cmdlineLen += wcslen(config->ExecutablePath) + 3;
if (instance->CmdParam != NULL)
{
cmdlineLen += wcslen(instance->CmdParam) + 1;
}
cmdLine = (wchar_t *)malloc(cmdlineLen * sizeof(wchar_t));
if (cmdLine == NULL)
{
return NULL;
}
cmdLine[0] = L'\0';
// add quotes in case of spaces in path
if (FAILED(StringCchCat(cmdLine, cmdlineLen, L"\"")))
{
failed = TRUE;
}
if (FAILED(StringCchCat(cmdLine, cmdlineLen, config->ExecutablePath)))
{
failed = TRUE;
}
if (FAILED(StringCchCat(cmdLine, cmdlineLen, L"\"")))
{
failed = TRUE;
}
if (instance->CmdParam != NULL)
{
if (FAILED(StringCchCat(cmdLine, cmdlineLen, L" ")))
{
failed = TRUE;
}
if (FAILED(StringCchCat(cmdLine, cmdlineLen, instance->CmdParam)))
{
failed = TRUE;
}
}
if (failed)
{
free(cmdLine);
return NULL;
}
return cmdLine;
}
//
// Purpose:
// Start running a process for a configured instance
//
// Parameters:
// instance, configuration
//
// Return value:
// none
//
void startRunning(WatcherConfig * config, ProcInstance * instance)
{
PROCESS_INFORMATION ProcInfo;
STARTUPINFO StartInfo;
BOOL rc;
wchar_t *stdoutpath = NULL;
wchar_t *stderrpath = NULL;
HANDLE lStdOutHandle = INVALID_HANDLE_VALUE;
HANDLE lStdErrHandle = INVALID_HANDLE_VALUE;
ZeroMemory(&ProcInfo, sizeof(ProcInfo));
ZeroMemory(&StartInfo, sizeof(StartInfo));
StartInfo.cb = sizeof(StartInfo);
instance->CmdLine = makeCmdLine(config, instance);
if (instance->CmdLine == NULL)
{
// log error
return;
}
if (instance->SaveOutput)
{
SECURITY_ATTRIBUTES sec;
sec.bInheritHandle = TRUE;
sec.nLength = sizeof(SECURITY_ATTRIBUTES);
sec.lpSecurityDescriptor = NULL;
if (CombineFilePath(instance->WorkingDir, L"stdout.log",&stdoutpath))
{
lStdOutHandle = CreateFile(stdoutpath, GENERIC_WRITE |GENERIC_READ,
FILE_SHARE_READ, &sec, CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL, NULL);
}
if (CombineFilePath(instance->WorkingDir, L"stderr.log",&stderrpath))
{
lStdErrHandle = CreateFile(stderrpath, GENERIC_WRITE |GENERIC_READ,
FILE_SHARE_READ, &sec, CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL, NULL);
}
if (lStdOutHandle != INVALID_HANDLE_VALUE && lStdErrHandle != INVALID_HANDLE_VALUE)
{
StartInfo.hStdError = lStdErrHandle;
StartInfo.hStdOutput = lStdOutHandle;
StartInfo.hStdInput = INVALID_HANDLE_VALUE;
StartInfo.dwFlags = STARTF_USESTDHANDLES;
}
}
rc = CreateProcess(NULL,
instance->CmdLine,
NULL,
NULL,
TRUE,
instance->RunMode,
NULL,
instance->WorkingDir,
&StartInfo,
&ProcInfo);
if (rc == TRUE)
{
CloseHandle(ProcInfo.hThread);
instance->ProcessHandle = ProcInfo.hProcess;
instance->ProcessId = ProcInfo.dwProcessId;
instance->ProcessWaitHandle = NULL;
instance->History.StartTime = GetTickCount();
instance->State = PROC_RUNNING;
startMonitoring(instance);
}
else
{
EventWriteWatcher_StartInstance_Failure();
instance->ProcessHandle = NULL;
instance->ProcessId = -1;
instance->State = PROC_FAILED;
}
// close file handles & free paths
if (lStdOutHandle != INVALID_HANDLE_VALUE)
{
CloseHandle(lStdOutHandle);
}
if (lStdErrHandle != INVALID_HANDLE_VALUE)
{
CloseHandle(lStdErrHandle);
}
if (stderrpath != NULL)
free(stderrpath);
if (stdoutpath != NULL)
free(stdoutpath);
}
# required exepath and exename are combined to form full path
exepath c:\redis\bin
exename redis-server.exe
# optional fastfailMS is milliseconds after which failure is not failure to start (default 1000)
#fastfailMS 1000
# optional fastfailretries is number of times to retry if failure before fastfailMS (default 0)
#fastfailretries 0
# for each instance to run, put properties between '{' and '}' lines
# required workingdir is working directory for process - must be unique
# optional runmode may be 'console' or 'hidden'
# optional cmdparms is command line after exename (ex: cmdparms redis.conf)
# optional saveout is '1' or '0'. To save stdout to file use '1'.
{
workingdir c:\redis\inst1
runmode hidden
saveout 1
}
#{
# workingdir c:\redis\inst2
#runmode hidden
#saveout 1
#cmdparms redis.conf
#}
/***********************************************************************
* Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT
* LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR
* A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT.
*
* See the Apache License, Version 2.0 for specific language governing
* permissions and limitations under the License.
*
**********************************************************************/
#include "RedisWatcher.h"
// states for processes
#define PROC_RUNNING 1
#define PROC_UNKNOWN 2
#define PROC_FAILED 3
// failure state for process
typedef struct _ProcHistory
{
unsigned long StartTime;
unsigned long StopTime;
unsigned long FastFailCount;
} ProcHistory;
// restart policy from configuration
typedef struct _RestartPolicy
{
unsigned long FastFailRetries;
unsigned long FastFailMs; // if process fails within this time, it failed during start
} RestartPolicy;
// Process state and configuration
typedef struct _ProcInstance
{
wchar_t * WorkingDir;
wchar_t * CmdParam;
BOOL SaveOutput;
DWORD RunMode;
wchar_t * CmdLine;
int ProcessId;
HANDLE ProcessHandle;
HANDLE ProcessWaitHandle;
int State;
ProcHistory History;
} ProcInstance;
// Set of processes
typedef struct _ProcList
{
int NumInstances;
ProcInstance * Instances;
} ProcList;
// Configuration and processes
typedef struct _WatcherConfig
{
wchar_t * ExecutableName;
wchar_t * ExecutablePath;
RestartPolicy Policy;
ProcList ConfiguredInstances;
} WatcherConfig;
// method declarations
// High level start and stop
VOID SvcInstall();
void SvcStart();
BOOL WatcherStart(wchar_t * path);
void WatcherStop();
// initialize after loading or reloading configuration
void initialize(WatcherConfig * watchConfig);
void updateConfig(WatcherConfig * watchConfig);
void cleanup();
// configuration methods
WatcherConfig * parseConfig(wchar_t * configPath);
void freeConfig(WatcherConfig * config);
void startMonitorConfigFile(wchar_t * configPath);
void stopMonitorConfigFile();
// utility methods
void Lock();
void Unlock();
BOOL CopyString(wchar_t * value, wchar_t ** dest);
BOOL CombineFilePath(wchar_t * path, wchar_t * filename, wchar_t ** fullpath);
BOOL MakeAbsolute(wchar_t * filename, wchar_t ** fullpath);
BOOL GetCurrentDir(wchar_t ** fullpath);
BOOL GetModulePath(wchar_t ** fullpath);
wchar_t * Trim(wchar_t * buf);
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册