0525b更新
parent
6c5f0539e6
commit
feb5beabe4
@ -0,0 +1,4 @@
|
||||
engine-path: "C:\\Users\\zcw\\Desktop\\DCS\\Static\\influxDB\\engine"
|
||||
http-bind-address: ":6324"
|
||||
storage-cache-max-memory-size: 268435456
|
||||
bolt-path: "C:\\Users\\zcw\\Desktop\\DCS\\Static\\influxDB\\engine\\influxd.bolt"
|
||||
Binary file not shown.
@ -0,0 +1,241 @@
|
||||
Changes since 2.23
|
||||
------------------
|
||||
* NSSM once again calls TerminateProcess() correctly.
|
||||
|
||||
Changes since 2.22
|
||||
------------------
|
||||
* NSSM no longer clutters the event log with "The specified
|
||||
procedure could not be found" on legacy Windows releases.
|
||||
|
||||
* Fixed failure to set a local username to run the service.
|
||||
|
||||
Changes since 2.21
|
||||
------------------
|
||||
* Existing services can now be managed using the GUI
|
||||
or on the command line.
|
||||
|
||||
* NSSM can now set the priority class and processor
|
||||
affinity of the managed application.
|
||||
|
||||
* NSSM can now apply an unconditional delay before
|
||||
restarting the application.
|
||||
|
||||
* NSSM can now optionally rotate existing files when
|
||||
redirecting I/O.
|
||||
|
||||
* Unqualified path names are now relative to the
|
||||
application startup directory when redirecting I/O.
|
||||
|
||||
* NSSM can now set the service display name, description,
|
||||
startup type and log on details.
|
||||
|
||||
* All services now receive a standard console window,
|
||||
allowing them to read input correctly (if running in
|
||||
interactive mode).
|
||||
|
||||
Changes since 2.20
|
||||
------------------
|
||||
* Services installed from the GUI no longer have incorrect
|
||||
AppParameters set in the registry.
|
||||
|
||||
Changes since 2.19
|
||||
------------------
|
||||
* Services installed from the commandline without using the
|
||||
GUI no longer have incorrect AppStopMethod* registry
|
||||
entries set.
|
||||
|
||||
Changes since 2.18
|
||||
------------------
|
||||
* Support AppEnvironmentExtra to append to the environment
|
||||
instead of replacing it.
|
||||
|
||||
* The GUI is significantly less sucky.
|
||||
|
||||
Changes since 2.17
|
||||
------------------
|
||||
* Timeouts for each shutdown method can be configured in
|
||||
the registry.
|
||||
|
||||
* The GUI is slightly less sucky.
|
||||
|
||||
Changes since 2.16
|
||||
------------------
|
||||
* NSSM can now redirect the service's I/O streams to any path
|
||||
capable of being opened by CreateFile().
|
||||
|
||||
* Allow building on Visual Studio Express.
|
||||
|
||||
* Silently ignore INTERROGATE control.
|
||||
|
||||
* Try to send Control-C events to console applications when
|
||||
shutting them down.
|
||||
|
||||
Changes since 2.15
|
||||
------------------
|
||||
* Fixed case where NSSM could kill unrelated processes when
|
||||
shutting down.
|
||||
|
||||
Changes since 2.14
|
||||
------------------
|
||||
* NSSM is now translated into Italian.
|
||||
|
||||
* Fixed GUI not allowing paths longer than 256 characters.
|
||||
|
||||
Changes since 2.13
|
||||
------------------
|
||||
* Fixed default GUI language being French not English.
|
||||
|
||||
Changes since 2.12
|
||||
------------------
|
||||
* Fixed failure to run on Windows 2000.
|
||||
|
||||
Changes since 2.11
|
||||
------------------
|
||||
* NSSM is now translated into French.
|
||||
|
||||
* Really ensure systems recovery actions can happen.
|
||||
|
||||
The change supposedly introduced in v2.4 to allow service recovery
|
||||
actions to be activated when the application exits gracefully with
|
||||
a non-zero error code didn't actually work.
|
||||
|
||||
Changes since 2.10
|
||||
------------------
|
||||
* Support AppEnvironment for compatibility with srvany.
|
||||
|
||||
Changes since 2.9
|
||||
-----------------
|
||||
* Fixed failure to compile messages.mc in paths containing spaces.
|
||||
|
||||
* Fixed edge case with CreateProcess().
|
||||
|
||||
Correctly handle the case where the application executable is under
|
||||
a path which contains space and an executable sharing the initial
|
||||
part of that path (up to a space) exists.
|
||||
|
||||
Changes since 2.8
|
||||
-----------------
|
||||
* Fixed failure to run on Windows versions prior to Vista.
|
||||
|
||||
Changes since 2.7
|
||||
-----------------
|
||||
* Read Application, AppDirectory and AppParameters before each restart so
|
||||
a change to any one doesn't require restarting NSSM itself.
|
||||
|
||||
* Fixed messages not being sent to the event log correctly in some
|
||||
cases.
|
||||
|
||||
* Try to handle (strictly incorrect) quotes in AppDirectory.
|
||||
|
||||
Windows directories aren't allowed to contain quotes so CreateProcess()
|
||||
will fail if the AppDirectory is quoted. Note that it succeeds even if
|
||||
Application itself is quoted as the application plus parameters are
|
||||
interpreted as a command line.
|
||||
|
||||
* Fixed failed to write full arguments to AppParameters when
|
||||
installing a service.
|
||||
|
||||
* Throttle restarts.
|
||||
|
||||
Back off from restarting the application immediately if it starts
|
||||
successfully but exits too soon. The default value of "too soon" is
|
||||
1500 milliseconds. This can be configured by adding a DWORD value
|
||||
AppThrottle to the registry.
|
||||
|
||||
Handle resume messages from the service console to restart the
|
||||
application immediately even if it is throttled.
|
||||
|
||||
* Try to kill the process tree gracefully.
|
||||
|
||||
Before calling TerminateProcess() on all processes assocatiated with
|
||||
the monitored application, enumerate all windows and threads and
|
||||
post appropriate messages to them. If the application bothers to
|
||||
listen for such messages it has a chance to shut itself down gracefully.
|
||||
|
||||
Changes since 2.6
|
||||
-----------------
|
||||
* Handle missing registry values.
|
||||
|
||||
Warn if AppParameters is missing. Warn if AppDirectory is missing or
|
||||
unset and choose a fallback directory.
|
||||
First try to find the parent directory of the application. If that
|
||||
fails, eg because the application path is just "notepad" or something,
|
||||
start in the Windows directory.
|
||||
|
||||
* Kill process tree when stopping service.
|
||||
|
||||
Ensure that all child processes of the monitored application are
|
||||
killed when the service stops by recursing through all running
|
||||
processes and terminating those whose parent is the application
|
||||
or one of its descendents.
|
||||
|
||||
Changes since 2.5
|
||||
-----------------
|
||||
* Removed incorrect ExpandEnvironmentStrings() error.
|
||||
|
||||
A log_event() call was inadvertently left in the code causing an error
|
||||
to be set to the eventlog saying that ExpandEnvironmentStrings() had
|
||||
failed when it had actually succeeded.
|
||||
|
||||
Changes since 2.4
|
||||
-----------------
|
||||
* Allow use of REG_EXPAND_SZ values in the registry.
|
||||
|
||||
* Don't suicide on exit status 0 by default.
|
||||
|
||||
Suiciding when the application exits 0 will cause recovery actions to be
|
||||
taken. Usually this is inappropriate. Only suicide if there is an
|
||||
explicit AppExit value for 0 in the registry.
|
||||
|
||||
Technically such behaviour could be abused to do something like run a
|
||||
script after successful completion of a service but in most cases a
|
||||
suicide is undesirable when no actual failure occurred.
|
||||
|
||||
* Don't hang if startup parameters couldn't be determined.
|
||||
Instead, signal that the service entered the STOPPED state.
|
||||
Set START_PENDING state prior to actual startup.
|
||||
|
||||
Changes since 2.3
|
||||
-----------------
|
||||
* Ensure systems recovery actions can happen.
|
||||
|
||||
In Windows versions earlier than Vista the service manager would only
|
||||
consider a service failed (and hence eligible for recovery action) if
|
||||
the service exited without setting its state to SERVICE_STOPPED, even if
|
||||
it signalled an error exit code.
|
||||
In Vista and later the service manager can be configured to treat a
|
||||
graceful shutdown with error code as a failure but this is not the
|
||||
default behaviour.
|
||||
|
||||
Try to configure the service manager to use the new behaviour when
|
||||
starting the service so users who set AppExit to Exit can use recovery
|
||||
actions as expected.
|
||||
|
||||
Also recognise the new AppExit option Suicide for use on pre-Vista
|
||||
systems. When AppExit is Suicide don't stop the service but exit
|
||||
inelegantly, which should be seen as a failure.
|
||||
|
||||
Changes since 2.2
|
||||
-----------------
|
||||
* Send properly formatted messages to the event log.
|
||||
|
||||
* Fixed truncation of very long path lengths in the registry.
|
||||
|
||||
Changes since 2.1
|
||||
-----------------
|
||||
* Decide how to handle application exit.
|
||||
|
||||
When the service exits with exit code n look in
|
||||
HKLM\SYSTEM\CurrentControlSet\Services\<service>\Parameters\AppExit\<n>,
|
||||
falling back to the unnamed value if no such code is listed. Parse the
|
||||
(string) value of this entry as follows:
|
||||
|
||||
Restart: Start the application again (NSSM default).
|
||||
Ignore: Do nothing (srvany default).
|
||||
Exit: Stop the service.
|
||||
|
||||
Changes since 2.0
|
||||
-----------------
|
||||
* Added support for building a 64-bit executable.
|
||||
|
||||
* Added project files for newer versions of Visual Studio.
|
||||
@ -0,0 +1,692 @@
|
||||
NSSM: The Non-Sucking Service Manager
|
||||
Version 2.24, 2014-08-31
|
||||
|
||||
NSSM is a service helper program similar to srvany and cygrunsrv. It can
|
||||
start any application as an NT service and will restart the service if it
|
||||
fails for any reason.
|
||||
|
||||
NSSM also has a graphical service installer and remover.
|
||||
|
||||
Full documentation can be found online at
|
||||
|
||||
http://nssm.cc/
|
||||
|
||||
Since version 2.0, the GUI can be bypassed by entering all appropriate
|
||||
options on the command line.
|
||||
|
||||
Since version 2.1, NSSM can be compiled for x64 platforms.
|
||||
Thanks Benjamin Mayrargue.
|
||||
|
||||
Since version 2.2, NSSM can be configured to take different actions
|
||||
based on the exit code of the managed application.
|
||||
|
||||
Since version 2.3, NSSM logs to the Windows event log more elegantly.
|
||||
|
||||
Since version 2.5, NSSM respects environment variables in its parameters.
|
||||
|
||||
Since version 2.8, NSSM tries harder to shut down the managed application
|
||||
gracefully and throttles restart attempts if the application doesn't run
|
||||
for a minimum amount of time.
|
||||
|
||||
Since version 2.11, NSSM respects srvany's AppEnvironment parameter.
|
||||
|
||||
Since version 2.13, NSSM is translated into French.
|
||||
Thanks François-Régis Tardy.
|
||||
|
||||
Since version 2.15, NSSM is translated into Italian.
|
||||
Thanks Riccardo Gusmeroli.
|
||||
|
||||
Since version 2.17, NSSM can try to shut down console applications by
|
||||
simulating a Control-C keypress. If they have installed a handler routine
|
||||
they can clean up and shut down gracefully on receipt of the event.
|
||||
|
||||
Since version 2.17, NSSM can redirect the managed application's I/O streams
|
||||
to an arbitrary path.
|
||||
|
||||
Since version 2.18, NSSM can be configured to wait a user-specified amount
|
||||
of time for the application to exit when shutting down.
|
||||
|
||||
Since version 2.19, many more service options can be configured with the
|
||||
GUI installer as well as via the registry.
|
||||
|
||||
Since version 2.19, NSSM can add to the service's environment by setting
|
||||
AppEnvironmentExtra in place of or in addition to the srvany-compatible
|
||||
AppEnvironment.
|
||||
|
||||
Since version 2.22, NSSM can set the managed application's process priority
|
||||
and CPU affinity.
|
||||
|
||||
Since version 2.22, NSSM can apply an unconditional delay before restarting
|
||||
an application which has exited.
|
||||
|
||||
Since version 2.22, NSSM can rotate existing output files when redirecting I/O.
|
||||
|
||||
Since version 2.22, NSSM can set service display name, description, startup
|
||||
type, log on details and dependencies.
|
||||
|
||||
Since version 2.22, NSSM can manage existing services.
|
||||
|
||||
|
||||
Usage
|
||||
-----
|
||||
In the usage notes below, arguments to the program may be written in angle
|
||||
brackets and/or square brackets. <string> means you must insert the
|
||||
appropriate string and [<string>] means the string is optional. See the
|
||||
examples below...
|
||||
|
||||
Note that everywhere <servicename> appears you may substitute the
|
||||
service's display name.
|
||||
|
||||
|
||||
Installation using the GUI
|
||||
--------------------------
|
||||
To install a service, run
|
||||
|
||||
nssm install <servicename>
|
||||
|
||||
You will be prompted to enter the full path to the application you wish
|
||||
to run and any command line options to pass to that application.
|
||||
|
||||
Use the system service manager (services.msc) to control advanced service
|
||||
properties such as startup method and desktop interaction. NSSM may
|
||||
support these options at a later time...
|
||||
|
||||
|
||||
Installation using the command line
|
||||
-----------------------------------
|
||||
To install a service, run
|
||||
|
||||
nssm install <servicename> <application> [<options>]
|
||||
|
||||
NSSM will then attempt to install a service which runs the named application
|
||||
with the given options (if you specified any).
|
||||
|
||||
Don't forget to enclose paths in "quotes" if they contain spaces!
|
||||
|
||||
If you want to include quotes in the options you will need to """quote""" the
|
||||
quotes.
|
||||
|
||||
|
||||
Managing the service
|
||||
--------------------
|
||||
NSSM will launch the application listed in the registry when you send it a
|
||||
start signal and will terminate it when you send a stop signal. So far, so
|
||||
much like srvany. But NSSM is the Non-Sucking service manager and can take
|
||||
action if/when the application dies.
|
||||
|
||||
With no configuration from you, NSSM will try to restart itself if it notices
|
||||
that the application died but you didn't send it a stop signal. NSSM will
|
||||
keep trying, pausing between each attempt, until the service is successfully
|
||||
started or you send it a stop signal.
|
||||
|
||||
NSSM will pause an increasingly longer time between subsequent restart attempts
|
||||
if the service fails to start in a timely manner, up to a maximum of four
|
||||
minutes. This is so it does not consume an excessive amount of CPU time trying
|
||||
to start a failed application over and over again. If you identify the cause
|
||||
of the failure and don't want to wait you can use the Windows service console
|
||||
(where the service will be shown in Paused state) to send a continue signal to
|
||||
NSSM and it will retry within a few seconds.
|
||||
|
||||
By default, NSSM defines "a timely manner" to be within 1500 milliseconds.
|
||||
You can change the threshold for the service by setting the number of
|
||||
milliseconds as a REG_DWORD value in the registry at
|
||||
HKLM\SYSTEM\CurrentControlSet\Services\<service>\Parameters\AppThrottle.
|
||||
|
||||
Alternatively, NSSM can pause for a configurable amount of time before
|
||||
attempting to restart the application even if it successfully ran for the
|
||||
amount of time specified by AppThrottle. NSSM will consult the REG_DWORD value
|
||||
at HKLM\SYSTEM\CurrentControlSet\Services\<service>\Parameters\AppRestartDelay
|
||||
for the number of milliseconds to wait before attempting a restart. If
|
||||
AppRestartDelay is set and the application is determined to be subject to
|
||||
throttling, NSSM will pause the service for whichever is longer of the
|
||||
configured restart delay and the calculated throttle period.
|
||||
|
||||
If AppRestartDelay is missing or invalid, only throttling will be applied.
|
||||
|
||||
NSSM will look in the registry under
|
||||
HKLM\SYSTEM\CurrentControlSet\Services\<service>\Parameters\AppExit for
|
||||
string (REG_EXPAND_SZ) values corresponding to the exit code of the application.
|
||||
If the application exited with code 1, for instance, NSSM will look for a
|
||||
string value under AppExit called "1" or, if it does not find it, will
|
||||
fall back to the AppExit (Default) value. You can find out the exit code
|
||||
for the application by consulting the system event log. NSSM will log the
|
||||
exit code when the application exits.
|
||||
|
||||
Based on the data found in the registry, NSSM will take one of three actions:
|
||||
|
||||
If the value data is "Restart" NSSM will try to restart the application as
|
||||
described above. This is its default behaviour.
|
||||
|
||||
If the value data is "Ignore" NSSM will not try to restart the application
|
||||
but will continue running itself. This emulates the (usually undesirable)
|
||||
behaviour of srvany. The Windows Services console would show the service
|
||||
as still running even though the application has exited.
|
||||
|
||||
If the value data is "Exit" NSSM will exit gracefully. The Windows Services
|
||||
console would show the service as stopped. If you wish to provide
|
||||
finer-grained control over service recovery you should use this code and
|
||||
edit the failure action manually. Please note that Windows versions prior
|
||||
to Vista will not consider such an exit to be a failure. On older versions
|
||||
of Windows you should use "Suicide" instead.
|
||||
|
||||
If the value data is "Suicide" NSSM will simulate a crash and exit without
|
||||
informing the service manager. This option should only be used for
|
||||
pre-Vista systems where you wish to apply a service recovery action. Note
|
||||
that if the monitored application exits with code 0, NSSM will only honour a
|
||||
request to suicide if you explicitly configure a registry key for exit code 0.
|
||||
If only the default action is set to Suicide NSSM will instead exit gracefully.
|
||||
|
||||
|
||||
Application priority
|
||||
--------------------
|
||||
NSSM can set the priority class of the managed application. NSSM will look in
|
||||
the registry under HKLM\SYSTEM\CurrentControlSet\Services\<service>\Parameters
|
||||
for the REG_DWORD entry AppPriority. Valid values correspond to arguments to
|
||||
SetPriorityClass(). If AppPriority() is missing or invalid the
|
||||
application will be launched with normal priority.
|
||||
|
||||
|
||||
Processor affinity
|
||||
------------------
|
||||
NSSM can set the CPU affinity of the managed application. NSSM will look in
|
||||
the registry under HKLM\SYSTEM\CurrentControlSet\Services\<service>\Parameters
|
||||
for the REG_SZ entry AppAffinity. It should specify a comma-separated listed
|
||||
of zero-indexed processor IDs. A range of processors may optionally be
|
||||
specified with a dash. No other characters are allowed in the string.
|
||||
|
||||
For example, to specify the first; second; third and fifth CPUs, an appropriate
|
||||
AppAffinity would be 0-2,4.
|
||||
|
||||
If AppAffinity is missing or invalid, NSSM will not attempt to restrict the
|
||||
application to specific CPUs.
|
||||
|
||||
Note that the 64-bit version of NSSM can configure a maximum of 64 CPUs in this
|
||||
way and that the 32-bit version can configure a maxium of 32 CPUs even when
|
||||
running on 64-bit Windows.
|
||||
|
||||
|
||||
Stopping the service
|
||||
--------------------
|
||||
When stopping a service NSSM will attempt several different methods of killing
|
||||
the monitored application, each of which can be disabled if necessary.
|
||||
|
||||
First NSSM will attempt to generate a Control-C event and send it to the
|
||||
application's console. Batch scripts or console applications may intercept
|
||||
the event and shut themselves down gracefully. GUI applications do not have
|
||||
consoles and will not respond to this method.
|
||||
|
||||
Secondly NSSM will enumerate all windows created by the application and send
|
||||
them a WM_CLOSE message, requesting a graceful exit.
|
||||
|
||||
Thirdly NSSM will enumerate all threads created by the application and send
|
||||
them a WM_QUIT message, requesting a graceful exit. Not all applications'
|
||||
threads have message queues; those which do not will not respond to this
|
||||
method.
|
||||
|
||||
Finally NSSM will call TerminateProcess() to request that the operating
|
||||
system forcibly terminate the application. TerminateProcess() cannot be
|
||||
trapped or ignored, so in most circumstances the application will be killed.
|
||||
However, there is no guarantee that it will have a chance to perform any
|
||||
tidyup operations before it exits.
|
||||
|
||||
Any or all of the methods above may be disabled. NSSM will look for the
|
||||
HKLM\SYSTEM\CurrentControlSet\Services\<service>\Parameters\AppStopMethodSkip
|
||||
registry value which should be of type REG_DWORD set to a bit field describing
|
||||
which methods should not be applied.
|
||||
|
||||
If AppStopMethodSkip includes 1, Control-C events will not be generated.
|
||||
If AppStopMethodSkip includes 2, WM_CLOSE messages will not be posted.
|
||||
If AppStopMethodSkip includes 4, WM_QUIT messages will not be posted.
|
||||
If AppStopMethodSkip includes 8, TerminateProcess() will not be called.
|
||||
|
||||
If, for example, you knew that an application did not respond to Control-C
|
||||
events and did not have a thread message queue, you could set AppStopMethodSkip
|
||||
to 5 and NSSM would not attempt to use those methods to stop the application.
|
||||
|
||||
Take great care when including 8 in the value of AppStopMethodSkip. If NSSM
|
||||
does not call TerminateProcess() it is possible that the application will not
|
||||
exit when the service stops.
|
||||
|
||||
By default NSSM will allow processes 1500ms to respond to each of the methods
|
||||
described above before proceeding to the next one. The timeout can be
|
||||
configured on a per-method basis by creating REG_DWORD entries in the
|
||||
registry under HKLM\SYSTEM\CurrentControlSet\Services\<service>\Parameters.
|
||||
|
||||
AppStopMethodConsole
|
||||
AppStopMethodWindow
|
||||
AppStopMethodThreads
|
||||
|
||||
Each value should be set to the number of milliseconds to wait. Please note
|
||||
that the timeout applies to each process in the application's process tree,
|
||||
so the actual time to shutdown may be longer than the sum of all configured
|
||||
timeouts if the application spawns multiple subprocesses.
|
||||
|
||||
|
||||
Console window
|
||||
--------------
|
||||
By default, NSSM will create a console window so that applications which
|
||||
are capable of reading user input can do so - subject to the service being
|
||||
allowed to interact with the desktop.
|
||||
|
||||
Creation of the console can be suppressed by setting the integer (REG_DWORD)
|
||||
HKLM\SYSTEM\CurrentControlSet\Services\<service>\Parameters\AppNoConsole
|
||||
registry value to 1.
|
||||
|
||||
|
||||
I/O redirection
|
||||
---------------
|
||||
NSSM can redirect the managed application's I/O to any path capable of being
|
||||
opened by CreateFile(). This enables, for example, capturing the log output
|
||||
of an application which would otherwise only write to the console or accepting
|
||||
input from a serial port.
|
||||
|
||||
NSSM will look in the registry under
|
||||
HKLM\SYSTEM\CurrentControlSet\Services\<service>\Parameters for the keys
|
||||
corresponding to arguments to CreateFile(). All are optional. If no path is
|
||||
given for a particular stream it will not be redirected. If a path is given
|
||||
but any of the other values are omitted they will be receive sensible defaults.
|
||||
|
||||
AppStdin: Path to receive input.
|
||||
AppStdout: Path to receive output.
|
||||
AppStderr: Path to receive error output.
|
||||
|
||||
Parameters for CreateFile() are providing with the "AppStdinShareMode",
|
||||
"AppStdinCreationDisposition" and "AppStdinFlagsAndAttributes" values (and
|
||||
analogously for stdout and stderr).
|
||||
|
||||
In general, if you want the service to log its output, set AppStdout and
|
||||
AppStderr to the same path, eg C:\Users\Public\service.log, and it should
|
||||
work. Remember, however, that the path must be accessible to the user
|
||||
running the service.
|
||||
|
||||
|
||||
File rotation
|
||||
-------------
|
||||
When using I/O redirection, NSSM can rotate existing output files prior to
|
||||
opening stdout and/or stderr. An existing file will be renamed with a
|
||||
suffix based on the file's last write time, to millisecond precision. For
|
||||
example, the file nssm.log might be rotated to nssm-20131221T113939.457.log.
|
||||
|
||||
NSSM will look in the registry under
|
||||
HKLM\SYSTEM\CurrentControlSet\Services\<service>\Parameters for REG_DWORD
|
||||
entries which control how rotation happens.
|
||||
|
||||
If AppRotateFiles is missing or set to 0, rotation is disabled. Any non-zero
|
||||
value enables rotation.
|
||||
|
||||
If AppRotateSeconds is non-zero, a file will not be rotated if its last write
|
||||
time is less than the given number of seconds in the past.
|
||||
|
||||
If AppRotateBytes is non-zero, a file will not be rotated if it is smaller
|
||||
than the given number of bytes. 64-bit file sizes can be handled by setting
|
||||
a non-zero value of AppRotateBytesHigh.
|
||||
|
||||
Rotation is independent of the CreateFile() parameters used to open the files.
|
||||
They will be rotated regardless of whether NSSM would otherwise have appended
|
||||
or replaced them.
|
||||
|
||||
NSSM can also rotate files which hit the configured size threshold while the
|
||||
service is running. Additionally, you can trigger an on-demand rotation by
|
||||
running the command
|
||||
|
||||
nssm rotate <servicename>
|
||||
|
||||
On-demand rotations will happen after the next line of data is read from
|
||||
the managed application, regardless of the value of AppRotateBytes. Be aware
|
||||
that if the application is not particularly verbose the rotation may not
|
||||
happen for some time.
|
||||
|
||||
To enable online and on-demand rotation, set AppRotateOnline to a non-zero
|
||||
value.
|
||||
|
||||
Note that online rotation requires NSSM to intercept the application's I/O
|
||||
and create the output files on its behalf. This is more complex and
|
||||
error-prone than simply redirecting the I/O streams before launching the
|
||||
application. Therefore online rotation is not enabled by default.
|
||||
|
||||
|
||||
Environment variables
|
||||
---------------------
|
||||
NSSM can replace or append to the managed application's environment. Two
|
||||
multi-valued string (REG_MULTI_SZ) registry values are recognised under
|
||||
HKLM\SYSTEM\CurrentControlSet\Services\<service>\Parameters.
|
||||
|
||||
AppEnvironment defines a list of environment variables which will override
|
||||
the service's environment. AppEnvironmentExtra defines a list of
|
||||
environment variables which will be added to the service's environment.
|
||||
|
||||
Each entry in the list should be of the form KEY=VALUE. It is possible to
|
||||
omit the VALUE but the = symbol is mandatory.
|
||||
|
||||
Environment variables listed in both AppEnvironment and AppEnvironmentExtra
|
||||
are subject to normal expansion, so it is possible, for example, to update the
|
||||
system path by setting "PATH=C:\bin;%PATH%" in AppEnvironmentExtra. Variables
|
||||
are expanded in the order in which they appear, so if you want to include the
|
||||
value of one variable in another variable you should declare the dependency
|
||||
first.
|
||||
|
||||
Because variables defined in AppEnvironment override the existing
|
||||
environment it is not possible to refer to any variables which were previously
|
||||
defined.
|
||||
|
||||
For example, the following AppEnvironment block:
|
||||
|
||||
PATH=C:\Windows\System32;C:\Windows
|
||||
PATH=C:\bin;%PATH%
|
||||
|
||||
Would result in a PATH of "C:\bin;C:\Windows\System32;C:\Windows" as expected.
|
||||
|
||||
Whereas the following AppEnvironment block:
|
||||
|
||||
PATH=C:\bin;%PATH%
|
||||
|
||||
Would result in a path containing only C:\bin and probably cause the
|
||||
application to fail to start.
|
||||
|
||||
Most people will want to use AppEnvironmentExtra exclusively. srvany only
|
||||
supports AppEnvironment.
|
||||
|
||||
|
||||
Managing services using the GUI
|
||||
-------------------------------
|
||||
NSSM can edit the settings of existing services with the same GUI that is
|
||||
used to install them. Run
|
||||
|
||||
nssm edit <servicename>
|
||||
|
||||
to bring up the GUI.
|
||||
|
||||
NSSM offers limited editing capabilities for services other than those which
|
||||
run NSSM itself. When NSSM is asked to edit a service which does not have
|
||||
the App* registry settings described above, the GUI will allow editing only
|
||||
system settings such as the service display name and description.
|
||||
|
||||
|
||||
Managing services using the command line
|
||||
----------------------------------------
|
||||
NSSM can retrieve or set individual service parameters from the command line.
|
||||
In general the syntax is as follows, though see below for exceptions.
|
||||
|
||||
nssm get <servicename> <parameter>
|
||||
|
||||
nssm set <servicename> <parameter> <value>
|
||||
|
||||
Parameters can also be reset to their default values.
|
||||
|
||||
nssm reset <servicename> <parameter>
|
||||
|
||||
The parameter names recognised by NSSM are the same as the registry entry
|
||||
names described above, eg AppDirectory.
|
||||
|
||||
NSSM offers limited editing capabilities for Services other than those which
|
||||
run NSSM itself. The parameters recognised are as follows:
|
||||
|
||||
Description: Service description.
|
||||
DisplayName: Service display name.
|
||||
ImagePath: Path to the service executable.
|
||||
ObjectName: User account which runs the service.
|
||||
Name: Service key name.
|
||||
Start: Service startup type.
|
||||
Type: Service type.
|
||||
|
||||
These correspond to the registry values under the service's key
|
||||
HKLM\SYSTEM\CurrentControlSet\Services\<service>.
|
||||
|
||||
|
||||
Note that NSSM will concatenate all arguments passed on the command line
|
||||
with spaces to form the value to set. Thus the following two invocations
|
||||
would have the same effect.
|
||||
|
||||
nssm set <servicename> Description "NSSM managed service"
|
||||
|
||||
nssm set <servicename> Description NSSM managed service
|
||||
|
||||
|
||||
Non-standard parameters
|
||||
-----------------------
|
||||
The AppEnvironment and AppEnvironmentExtra parameters recognise an
|
||||
additional argument when querying the environment. The following syntax
|
||||
will print all extra environment variables configured for a service
|
||||
|
||||
nssm get <servicename> AppEnvironmentExtra
|
||||
|
||||
whereas the syntax below will print only the value of the CLASSPATH
|
||||
variable if it is configured in the environment block, or the empty string
|
||||
if it is not configured.
|
||||
|
||||
nssm get <servicename> AppEnvironmentExtra CLASSPATH
|
||||
|
||||
When setting an environment block, each variable should be specified as a
|
||||
KEY=VALUE pair in separate command line arguments. For example:
|
||||
|
||||
nssm set <servicename> AppEnvironment CLASSPATH=C:\Classes TEMP=C:\Temp
|
||||
|
||||
|
||||
The AppExit parameter requires an additional argument specifying the exit
|
||||
code to get or set. The default action can be specified with the string
|
||||
Default.
|
||||
|
||||
For example, to get the default exit action for a service you should run
|
||||
|
||||
nssm get <servicename> AppExit Default
|
||||
|
||||
To get the exit action when the application exits with exit code 2, run
|
||||
|
||||
nssm get <servicename> AppExit 2
|
||||
|
||||
Note that if no explicit action is configured for a specified exit code,
|
||||
NSSM will print the default exit action.
|
||||
|
||||
To set configure the service to stop when the application exits with an
|
||||
exit code of 2, run
|
||||
|
||||
nssm set <servicename> AppExit 2 Exit
|
||||
|
||||
|
||||
The AppPriority parameter is used to set the priority class of the
|
||||
managed application. Valid priorities are as follows:
|
||||
|
||||
REALTIME_PRIORITY_CLASS
|
||||
HIGH_PRIORITY_CLASS
|
||||
ABOVE_NORMAL_PRIORITY_CLASS
|
||||
NORMAL_PRIORITY_CLASS
|
||||
BELOW_NORMAL_PRIORITY_CLASS
|
||||
IDLE_PRIORITY_CLASS
|
||||
|
||||
|
||||
The DependOnGroup and DependOnService parameters are used to query or set
|
||||
the dependencies for the service. When setting dependencies, each service
|
||||
or service group (preceded with the + symbol) should be specified in
|
||||
separate command line arguments. For example:
|
||||
|
||||
nssm set <servicename> DependOnService RpcSs LanmanWorkstation
|
||||
|
||||
|
||||
The Name parameter can only be queried, not set. It returns the service's
|
||||
registry key name. This may be useful to know if you take advantage of
|
||||
the fact that you can substitute the service's display name anywhere where
|
||||
the syntax calls for <servicename>.
|
||||
|
||||
|
||||
The ObjectName parameter requires an additional argument only when setting
|
||||
a username. The additional argument is the password of the user.
|
||||
|
||||
To retrieve the username, run
|
||||
|
||||
nssm get <servicename> ObjectName
|
||||
|
||||
To set the username and password, run
|
||||
|
||||
nssm set <servicename> ObjectName <username> <password>
|
||||
|
||||
Note that the rules of argument concatenation still apply. The following
|
||||
invocation is valid and will have the expected effect.
|
||||
|
||||
nssm set <servicename> ObjectName <username> correct horse battery staple
|
||||
|
||||
The following well-known usernames do not need a password. The password
|
||||
parameter can be omitted when using them:
|
||||
|
||||
"LocalSystem" aka "System" aka "NT Authority\System"
|
||||
"LocalService" aka "Local Service" aka "NT Authority\Local Service"
|
||||
"NetworkService" aka "Network Service" aka "NT Authority\Network Service"
|
||||
|
||||
|
||||
The Start parameter is used to query or set the startup type of the service.
|
||||
Valid service startup types are as follows:
|
||||
|
||||
SERVICE_AUTO_START: Automatic startup at boot.
|
||||
SERVICE_DELAYED_START: Delayed startup at boot.
|
||||
SERVICE_DEMAND_START: Manual service startup.
|
||||
SERVICE_DISABLED: The service is disabled.
|
||||
|
||||
Note that SERVICE_DELAYED_START is not supported on versions of Windows prior
|
||||
to Vista. NSSM will set the service to automatic startup if delayed start is
|
||||
unavailable.
|
||||
|
||||
|
||||
The Type parameter is used to query or set the service type. NSSM recognises
|
||||
all currently documented service types but will only allow setting one of two
|
||||
types:
|
||||
|
||||
SERVICE_WIN32_OWN_PROCESS: A standalone service. This is the default.
|
||||
SERVICE_INTERACTIVE_PROCESS: A service which can interact with the desktop.
|
||||
|
||||
Note that a service may only be configured as interactive if it runs under
|
||||
the LocalSystem account. The safe way to configure an interactive service
|
||||
is in two stages as follows.
|
||||
|
||||
nssm reset <servicename> ObjectName
|
||||
nssm set <servicename> Type SERVICE_INTERACTIVE_PROCESS
|
||||
|
||||
|
||||
Controlling services using the command line
|
||||
-------------------------------------------
|
||||
NSSM offers rudimentary service control features.
|
||||
|
||||
nssm start <servicename>
|
||||
|
||||
nssm restart <servicename>
|
||||
|
||||
nssm stop <servicename>
|
||||
|
||||
nssm status <servicename>
|
||||
|
||||
|
||||
Removing services using the GUI
|
||||
-------------------------------
|
||||
NSSM can also remove services. Run
|
||||
|
||||
nssm remove <servicename>
|
||||
|
||||
to remove a service. You will prompted for confirmation before the service
|
||||
is removed. Try not to remove essential system services...
|
||||
|
||||
|
||||
Removing service using the command line
|
||||
---------------------------------------
|
||||
To remove a service without confirmation from the GUI, run
|
||||
|
||||
nssm remove <servicename> confirm
|
||||
|
||||
Try not to remove essential system services...
|
||||
|
||||
|
||||
Logging
|
||||
-------
|
||||
NSSM logs to the Windows event log. It registers itself as an event log source
|
||||
and uses unique event IDs for each type of message it logs. New versions may
|
||||
add event types but existing event IDs will never be changed.
|
||||
|
||||
Because of the way NSSM registers itself you should be aware that you may not
|
||||
be able to replace the NSSM binary if you have the event viewer open and that
|
||||
running multiple instances of NSSM from different locations may be confusing if
|
||||
they are not all the same version.
|
||||
|
||||
|
||||
Example usage
|
||||
-------------
|
||||
To install an Unreal Tournament server:
|
||||
|
||||
nssm install UT2004 c:\games\ut2004\system\ucc.exe server
|
||||
|
||||
To run the server as the "games" user:
|
||||
|
||||
nssm set UT2004 ObjectName games password
|
||||
|
||||
To configure the server to log to a file:
|
||||
|
||||
nssm set UT2004 AppStdout c:\games\ut2004\service.log
|
||||
|
||||
To restrict the server to a single CPU:
|
||||
|
||||
nssm set UT2004 AppAffinity 0
|
||||
|
||||
To remove the server:
|
||||
|
||||
nssm remove UT2004 confirm
|
||||
|
||||
To find out the service name of a service with a display name:
|
||||
|
||||
nssm get "Background Intelligent Transfer Service" Name
|
||||
|
||||
|
||||
Building NSSM from source
|
||||
-------------------------
|
||||
NSSM is known to compile with Visual Studio 2008 and later. Older Visual
|
||||
Studio releases may or may not work if you install an appropriate SDK and
|
||||
edit the nssm.vcproj and nssm.sln files to set a lower version number.
|
||||
They are known not to work with default settings.
|
||||
|
||||
NSSM will also compile with Visual Studio 2010 but the resulting executable
|
||||
will not run on versions of Windows older than XP SP2. If you require
|
||||
compatiblity with older Windows releases you should change the Platform
|
||||
Toolset to v90 in the General section of the project's Configuration
|
||||
Properties.
|
||||
|
||||
|
||||
Credits
|
||||
-------
|
||||
Thanks to Bernard Loh for finding a bug with service recovery.
|
||||
Thanks to Benjamin Mayrargue (www.softlion.com) for adding 64-bit support.
|
||||
Thanks to Joel Reingold for spotting a command line truncation bug.
|
||||
Thanks to Arve Knudsen for spotting that child processes of the monitored
|
||||
application could be left running on service shutdown, and that a missing
|
||||
registry value for AppDirectory confused NSSM.
|
||||
Thanks to Peter Wagemans and Laszlo Keresztfalvi for suggesting throttling
|
||||
restarts.
|
||||
Thanks to Eugene Lifshitz for finding an edge case in CreateProcess() and for
|
||||
advising how to build messages.mc correctly in paths containing spaces.
|
||||
Thanks to Rob Sharp for pointing out that NSSM did not respect the
|
||||
AppEnvironment registry value used by srvany.
|
||||
Thanks to Szymon Nowak for help with Windows 2000 compatibility.
|
||||
Thanks to François-Régis Tardy and Gildas le Nadan for French translation.
|
||||
Thanks to Emilio Frini for spotting that French was inadvertently set as
|
||||
the default language when the user's display language was not translated.
|
||||
Thanks to Riccardo Gusmeroli and Marco Certelli for Italian translation.
|
||||
Thanks to Eric Cheldelin for the inspiration to generate a Control-C event
|
||||
on shutdown.
|
||||
Thanks to Brian Baxter for suggesting how to escape quotes from the command
|
||||
prompt.
|
||||
Thanks to Russ Holmann for suggesting that the shutdown timeout be configurable.
|
||||
Thanks to Paul Spause for spotting a bug with default registry entries.
|
||||
Thanks to BUGHUNTER for spotting more GUI bugs.
|
||||
Thanks to Doug Watson for suggesting file rotation.
|
||||
Thanks to Арслан Сайдуганов for suggesting setting process priority.
|
||||
Thanks to Robert Middleton for suggestion and draft implementation of process
|
||||
affinity support.
|
||||
Thanks to Andrew RedzMax for suggesting an unconditional restart delay.
|
||||
Thanks to Bryan Senseman for noticing that applications with redirected stdout
|
||||
and/or stderr which attempt to read from stdin would fail.
|
||||
Thanks to Czenda Czendov for help with Visual Studio 2013 and Server 2012R2.
|
||||
Thanks to Alessandro Gherardi for reporting and draft fix of the bug whereby
|
||||
the second restart of the application would have a corrupted environment.
|
||||
Thanks to Hadrien Kohl for suggesting to disable the console window's menu.
|
||||
Thanks to Allen Vailliencourt for noticing bugs with configuring the service to
|
||||
run under a local user account.
|
||||
Thanks to Sam Townsend for noticing a regression with TerminateProcess().
|
||||
|
||||
Licence
|
||||
-------
|
||||
NSSM is public domain. You may unconditionally use it and/or its source code
|
||||
for any purpose you wish.
|
||||
@ -0,0 +1,346 @@
|
||||
#include "nssm.h"
|
||||
|
||||
#include <sddl.h>
|
||||
|
||||
extern imports_t imports;
|
||||
|
||||
/* Open Policy object. */
|
||||
int open_lsa_policy(LSA_HANDLE *policy) {
|
||||
LSA_OBJECT_ATTRIBUTES attributes;
|
||||
ZeroMemory(&attributes, sizeof(attributes));
|
||||
|
||||
NTSTATUS status = LsaOpenPolicy(0, &attributes, POLICY_ALL_ACCESS, policy);
|
||||
if (status) {
|
||||
print_message(stderr, NSSM_MESSAGE_LSAOPENPOLICY_FAILED, error_string(LsaNtStatusToWinError(status)));
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Look up SID for an account. */
|
||||
int username_sid(const TCHAR *username, SID **sid, LSA_HANDLE *policy) {
|
||||
LSA_HANDLE handle;
|
||||
if (! policy) {
|
||||
policy = &handle;
|
||||
if (open_lsa_policy(policy)) return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
LsaLookupNames() can't look up .\username but can look up
|
||||
%COMPUTERNAME%\username. ChangeServiceConfig() writes .\username to the
|
||||
registry when %COMPUTERNAME%\username is a passed as a parameter. We
|
||||
need to preserve .\username when calling ChangeServiceConfig() without
|
||||
changing the username, but expand to %COMPUTERNAME%\username when calling
|
||||
LsaLookupNames().
|
||||
*/
|
||||
TCHAR *expanded;
|
||||
unsigned long expandedlen;
|
||||
if (_tcsnicmp(_T(".\\"), username, 2)) {
|
||||
expandedlen = (unsigned long) (_tcslen(username) + 1) * sizeof(TCHAR);
|
||||
expanded = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, expandedlen);
|
||||
if (! expanded) {
|
||||
print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("expanded"), _T("username_sid"));
|
||||
if (policy == &handle) LsaClose(handle);
|
||||
return 2;
|
||||
}
|
||||
memmove(expanded, username, expandedlen);
|
||||
}
|
||||
else {
|
||||
TCHAR computername[MAX_COMPUTERNAME_LENGTH + 1];
|
||||
expandedlen = _countof(computername);
|
||||
GetComputerName(computername, &expandedlen);
|
||||
expandedlen += (unsigned long) _tcslen(username);
|
||||
|
||||
expanded = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, expandedlen * sizeof(TCHAR));
|
||||
if (! expanded) {
|
||||
print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("expanded"), _T("username_sid"));
|
||||
if (policy == &handle) LsaClose(handle);
|
||||
return 2;
|
||||
}
|
||||
_sntprintf_s(expanded, expandedlen, _TRUNCATE, _T("%s\\%s"), computername, username + 2);
|
||||
}
|
||||
|
||||
LSA_UNICODE_STRING lsa_username;
|
||||
#ifdef UNICODE
|
||||
lsa_username.Buffer = (wchar_t *) expanded;
|
||||
lsa_username.Length = (unsigned short) _tcslen(expanded) * sizeof(TCHAR);
|
||||
lsa_username.MaximumLength = lsa_username.Length + sizeof(TCHAR);
|
||||
#else
|
||||
size_t buflen;
|
||||
mbstowcs_s(&buflen, NULL, 0, expanded, _TRUNCATE);
|
||||
lsa_username.MaximumLength = (unsigned short) buflen * sizeof(wchar_t);
|
||||
lsa_username.Length = lsa_username.MaximumLength - sizeof(wchar_t);
|
||||
lsa_username.Buffer = (wchar_t *) HeapAlloc(GetProcessHeap(), 0, lsa_username.MaximumLength);
|
||||
if (lsa_username.Buffer) mbstowcs_s(&buflen, lsa_username.Buffer, lsa_username.MaximumLength, expanded, _TRUNCATE);
|
||||
else {
|
||||
if (policy == &handle) LsaClose(handle);
|
||||
HeapFree(GetProcessHeap(), 0, expanded);
|
||||
print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("LSA_UNICODE_STRING"), _T("username_sid()"));
|
||||
return 4;
|
||||
}
|
||||
#endif
|
||||
|
||||
LSA_REFERENCED_DOMAIN_LIST *translated_domains;
|
||||
LSA_TRANSLATED_SID *translated_sid;
|
||||
NTSTATUS status = LsaLookupNames(*policy, 1, &lsa_username, &translated_domains, &translated_sid);
|
||||
#ifndef UNICODE
|
||||
HeapFree(GetProcessHeap(), 0, lsa_username.Buffer);
|
||||
#endif
|
||||
HeapFree(GetProcessHeap(), 0, expanded);
|
||||
if (policy == &handle) LsaClose(handle);
|
||||
if (status) {
|
||||
LsaFreeMemory(translated_domains);
|
||||
LsaFreeMemory(translated_sid);
|
||||
print_message(stderr, NSSM_MESSAGE_LSALOOKUPNAMES_FAILED, username, error_string(LsaNtStatusToWinError(status)));
|
||||
return 5;
|
||||
}
|
||||
|
||||
if (translated_sid->Use != SidTypeUser && translated_sid->Use != SidTypeWellKnownGroup) {
|
||||
LsaFreeMemory(translated_domains);
|
||||
LsaFreeMemory(translated_sid);
|
||||
print_message(stderr, NSSM_GUI_INVALID_USERNAME, username);
|
||||
return 6;
|
||||
}
|
||||
|
||||
LSA_TRUST_INFORMATION *trust = &translated_domains->Domains[translated_sid->DomainIndex];
|
||||
if (! trust || ! IsValidSid(trust->Sid)) {
|
||||
LsaFreeMemory(translated_domains);
|
||||
LsaFreeMemory(translated_sid);
|
||||
print_message(stderr, NSSM_GUI_INVALID_USERNAME, username);
|
||||
return 7;
|
||||
}
|
||||
|
||||
/* GetSidSubAuthority*() return pointers! */
|
||||
unsigned char *n = GetSidSubAuthorityCount(trust->Sid);
|
||||
|
||||
/* Convert translated SID to SID. */
|
||||
*sid = (SID *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, GetSidLengthRequired(*n + 1));
|
||||
if (! *sid) {
|
||||
LsaFreeMemory(translated_domains);
|
||||
LsaFreeMemory(translated_sid);
|
||||
print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("SID"), _T("username_sid"));
|
||||
return 8;
|
||||
}
|
||||
|
||||
unsigned long error;
|
||||
if (! InitializeSid(*sid, GetSidIdentifierAuthority(trust->Sid), *n + 1)) {
|
||||
error = GetLastError();
|
||||
HeapFree(GetProcessHeap(), 0, *sid);
|
||||
LsaFreeMemory(translated_domains);
|
||||
LsaFreeMemory(translated_sid);
|
||||
print_message(stderr, NSSM_MESSAGE_INITIALIZESID_FAILED, username, error_string(error));
|
||||
return 9;
|
||||
}
|
||||
|
||||
for (unsigned char i = 0; i <= *n; i++) {
|
||||
unsigned long *sub = GetSidSubAuthority(*sid, i);
|
||||
if (i < *n) *sub = *GetSidSubAuthority(trust->Sid, i);
|
||||
else *sub = translated_sid->RelativeId;
|
||||
}
|
||||
|
||||
int ret = 0;
|
||||
if (translated_sid->Use == SidTypeWellKnownGroup && ! well_known_sid(*sid)) {
|
||||
print_message(stderr, NSSM_GUI_INVALID_USERNAME, username);
|
||||
ret = 10;
|
||||
}
|
||||
|
||||
LsaFreeMemory(translated_domains);
|
||||
LsaFreeMemory(translated_sid);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int username_sid(const TCHAR *username, SID **sid) {
|
||||
return username_sid(username, sid, 0);
|
||||
}
|
||||
|
||||
int canonicalise_username(const TCHAR *username, TCHAR **canon) {
|
||||
LSA_HANDLE policy;
|
||||
if (open_lsa_policy(&policy)) return 1;
|
||||
|
||||
SID *sid;
|
||||
if (username_sid(username, &sid, &policy)) return 2;
|
||||
PSID sids = { sid };
|
||||
|
||||
LSA_REFERENCED_DOMAIN_LIST *translated_domains;
|
||||
LSA_TRANSLATED_NAME *translated_name;
|
||||
NTSTATUS status = LsaLookupSids(policy, 1, &sids, &translated_domains, &translated_name);
|
||||
if (status) {
|
||||
LsaFreeMemory(translated_domains);
|
||||
LsaFreeMemory(translated_name);
|
||||
print_message(stderr, NSSM_MESSAGE_LSALOOKUPSIDS_FAILED, error_string(LsaNtStatusToWinError(status)));
|
||||
return 3;
|
||||
}
|
||||
|
||||
LSA_TRUST_INFORMATION *trust = &translated_domains->Domains[translated_name->DomainIndex];
|
||||
LSA_UNICODE_STRING lsa_canon;
|
||||
lsa_canon.Length = translated_name->Name.Length + trust->Name.Length + sizeof(wchar_t);
|
||||
lsa_canon.MaximumLength = lsa_canon.Length + sizeof(wchar_t);
|
||||
lsa_canon.Buffer = (wchar_t *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, lsa_canon.MaximumLength);
|
||||
if (! lsa_canon.Buffer) {
|
||||
LsaFreeMemory(translated_domains);
|
||||
LsaFreeMemory(translated_name);
|
||||
print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("lsa_canon"), _T("username_sid"));
|
||||
return 9;
|
||||
}
|
||||
|
||||
/* Buffer is wchar_t but Length is in bytes. */
|
||||
memmove((char *) lsa_canon.Buffer, trust->Name.Buffer, trust->Name.Length);
|
||||
memmove((char *) lsa_canon.Buffer + trust->Name.Length, L"\\", sizeof(wchar_t));
|
||||
memmove((char *) lsa_canon.Buffer + trust->Name.Length + sizeof(wchar_t), translated_name->Name.Buffer, translated_name->Name.Length);
|
||||
|
||||
#ifdef UNICODE
|
||||
*canon = lsa_canon.Buffer;
|
||||
#else
|
||||
size_t buflen;
|
||||
wcstombs_s(&buflen, NULL, 0, lsa_canon.Buffer, _TRUNCATE);
|
||||
*canon = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, buflen);
|
||||
if (! *canon) {
|
||||
LsaFreeMemory(translated_domains);
|
||||
LsaFreeMemory(translated_name);
|
||||
print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("canon"), _T("username_sid"));
|
||||
return 10;
|
||||
}
|
||||
wcstombs_s(&buflen, *canon, buflen, lsa_canon.Buffer, _TRUNCATE);
|
||||
HeapFree(GetProcessHeap(), 0, lsa_canon.Buffer);
|
||||
#endif
|
||||
|
||||
LsaFreeMemory(translated_domains);
|
||||
LsaFreeMemory(translated_name);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Do two usernames map to the same SID? */
|
||||
int username_equiv(const TCHAR *a, const TCHAR *b) {
|
||||
SID *sid_a, *sid_b;
|
||||
if (username_sid(a, &sid_a)) return 0;
|
||||
|
||||
if (username_sid(b, &sid_b)) {
|
||||
FreeSid(sid_a);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ret = 0;
|
||||
if (EqualSid(sid_a, sid_b)) ret = 1;
|
||||
|
||||
FreeSid(sid_a);
|
||||
FreeSid(sid_b);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Does the username represent the LocalSystem account? */
|
||||
int is_localsystem(const TCHAR *username) {
|
||||
if (str_equiv(username, NSSM_LOCALSYSTEM_ACCOUNT)) return 1;
|
||||
if (! imports.IsWellKnownSid) return 0;
|
||||
|
||||
SID *sid;
|
||||
if (username_sid(username, &sid)) return 0;
|
||||
|
||||
int ret = 0;
|
||||
if (imports.IsWellKnownSid(sid, WinLocalSystemSid)) ret = 1;
|
||||
|
||||
FreeSid(sid);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
Get well-known alias for LocalSystem and friends.
|
||||
Returns a pointer to a static string. DO NOT try to free it.
|
||||
*/
|
||||
const TCHAR *well_known_sid(SID *sid) {
|
||||
if (! imports.IsWellKnownSid) return 0;
|
||||
if (imports.IsWellKnownSid(sid, WinLocalSystemSid)) return NSSM_LOCALSYSTEM_ACCOUNT;
|
||||
if (imports.IsWellKnownSid(sid, WinLocalServiceSid)) return NSSM_LOCALSERVICE_ACCOUNT;
|
||||
if (imports.IsWellKnownSid(sid, WinNetworkServiceSid)) return NSSM_NETWORKSERVICE_ACCOUNT;
|
||||
return 0;
|
||||
}
|
||||
|
||||
const TCHAR *well_known_username(const TCHAR *username) {
|
||||
if (! username) return NSSM_LOCALSYSTEM_ACCOUNT;
|
||||
if (str_equiv(username, NSSM_LOCALSYSTEM_ACCOUNT)) return NSSM_LOCALSYSTEM_ACCOUNT;
|
||||
SID *sid;
|
||||
if (username_sid(username, &sid)) return 0;
|
||||
|
||||
const TCHAR *well_known = well_known_sid(sid);
|
||||
FreeSid(sid);
|
||||
|
||||
return well_known;
|
||||
}
|
||||
|
||||
int grant_logon_as_service(const TCHAR *username) {
|
||||
if (! username) return 0;
|
||||
|
||||
/* Open Policy object. */
|
||||
LSA_OBJECT_ATTRIBUTES attributes;
|
||||
ZeroMemory(&attributes, sizeof(attributes));
|
||||
|
||||
LSA_HANDLE policy;
|
||||
NTSTATUS status;
|
||||
|
||||
if (open_lsa_policy(&policy)) return 1;
|
||||
|
||||
/* Look up SID for the account. */
|
||||
SID *sid;
|
||||
if (username_sid(username, &sid, &policy)) {
|
||||
LsaClose(policy);
|
||||
return 2;
|
||||
}
|
||||
|
||||
/*
|
||||
Shouldn't happen because it should have been checked before callling this function.
|
||||
*/
|
||||
if (well_known_sid(sid)) {
|
||||
LsaClose(policy);
|
||||
return 3;
|
||||
}
|
||||
|
||||
/* Check if the SID has the "Log on as a service" right. */
|
||||
LSA_UNICODE_STRING lsa_right;
|
||||
lsa_right.Buffer = NSSM_LOGON_AS_SERVICE_RIGHT;
|
||||
lsa_right.Length = (unsigned short) wcslen(lsa_right.Buffer) * sizeof(wchar_t);
|
||||
lsa_right.MaximumLength = lsa_right.Length + sizeof(wchar_t);
|
||||
|
||||
LSA_UNICODE_STRING *rights;
|
||||
unsigned long count = ~0;
|
||||
status = LsaEnumerateAccountRights(policy, sid, &rights, &count);
|
||||
if (status) {
|
||||
/*
|
||||
If the account has no rights set LsaEnumerateAccountRights() will return
|
||||
STATUS_OBJECT_NAME_NOT_FOUND and set count to 0.
|
||||
*/
|
||||
unsigned long error = LsaNtStatusToWinError(status);
|
||||
if (error != ERROR_FILE_NOT_FOUND) {
|
||||
FreeSid(sid);
|
||||
LsaClose(policy);
|
||||
print_message(stderr, NSSM_MESSAGE_LSAENUMERATEACCOUNTRIGHTS_FAILED, username, error_string(error));
|
||||
return 4;
|
||||
}
|
||||
}
|
||||
|
||||
for (unsigned long i = 0; i < count; i++) {
|
||||
if (rights[i].Length != lsa_right.Length) continue;
|
||||
if (_wcsnicmp(rights[i].Buffer, lsa_right.Buffer, lsa_right.MaximumLength)) continue;
|
||||
/* The SID has the right. */
|
||||
FreeSid(sid);
|
||||
LsaFreeMemory(rights);
|
||||
LsaClose(policy);
|
||||
return 0;
|
||||
}
|
||||
LsaFreeMemory(rights);
|
||||
|
||||
/* Add the right. */
|
||||
status = LsaAddAccountRights(policy, sid, &lsa_right, 1);
|
||||
FreeSid(sid);
|
||||
LsaClose(policy);
|
||||
if (status) {
|
||||
print_message(stderr, NSSM_MESSAGE_LSAADDACCOUNTRIGHTS_FAILED, error_string(LsaNtStatusToWinError(status)));
|
||||
return 5;
|
||||
}
|
||||
|
||||
print_message(stdout, NSSM_MESSAGE_GRANTED_LOGON_AS_SERVICE, username);
|
||||
return 0;
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
#ifndef ACCOUNT_H
|
||||
#define ACCOUNT_H
|
||||
|
||||
#include <ntsecapi.h>
|
||||
|
||||
/* Not really an account. The canonical name is NT Authority\System. */
|
||||
#define NSSM_LOCALSYSTEM_ACCOUNT _T("LocalSystem")
|
||||
/* Other well-known accounts which can start a service without a password. */
|
||||
#define NSSM_LOCALSERVICE_ACCOUNT _T("NT Authority\\LocalService")
|
||||
#define NSSM_NETWORKSERVICE_ACCOUNT _T("NT Authority\\NetworkService")
|
||||
/* This is explicitly a wide string. */
|
||||
#define NSSM_LOGON_AS_SERVICE_RIGHT L"SeServiceLogonRight"
|
||||
|
||||
int open_lsa_policy(LSA_HANDLE *);
|
||||
int username_sid(const TCHAR *, SID **, LSA_HANDLE *);
|
||||
int username_sid(const TCHAR *, SID **);
|
||||
int username_equiv(const TCHAR *, const TCHAR *);
|
||||
int canonicalise_username(const TCHAR *, TCHAR **);
|
||||
int is_localsystem(const TCHAR *);
|
||||
const TCHAR *well_known_sid(SID *);
|
||||
const TCHAR *well_known_username(const TCHAR *);
|
||||
int grant_logon_as_service(const TCHAR *);
|
||||
|
||||
#endif
|
||||
@ -0,0 +1,175 @@
|
||||
#include "nssm.h"
|
||||
|
||||
/* See if we were launched from a console window. */
|
||||
void check_console() {
|
||||
/* If we're running in a service context there will be no console window. */
|
||||
HWND console = GetConsoleWindow();
|
||||
if (! console) return;
|
||||
|
||||
unsigned long pid;
|
||||
if (! GetWindowThreadProcessId(console, &pid)) return;
|
||||
|
||||
/*
|
||||
If the process associated with the console window handle is the same as
|
||||
this process, we were not launched from an existing console. The user
|
||||
probably double-clicked our executable.
|
||||
*/
|
||||
if (GetCurrentProcessId() != pid) return;
|
||||
|
||||
/* We close our new console so that subsequent messages appear in a popup. */
|
||||
FreeConsole();
|
||||
}
|
||||
|
||||
/* Helpers for drawing the banner. */
|
||||
static inline void block(unsigned int a, short x, short y, unsigned long n) {
|
||||
HANDLE h = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
TCHAR s = _T(' ');
|
||||
|
||||
unsigned long out;
|
||||
COORD c = { x, y };
|
||||
FillConsoleOutputAttribute(h, a, n, c, &out);
|
||||
FillConsoleOutputCharacter(h, s, n, c, &out);
|
||||
}
|
||||
|
||||
static inline void R(short x, short y, unsigned long n) {
|
||||
block(BACKGROUND_RED | BACKGROUND_INTENSITY, x, y, n);
|
||||
}
|
||||
|
||||
static inline void r(short x, short y, unsigned long n) {
|
||||
block(BACKGROUND_RED, x, y, n);
|
||||
}
|
||||
|
||||
static inline void b(short x, short y, unsigned long n) {
|
||||
block(0, x, y, n);
|
||||
}
|
||||
|
||||
void alloc_console(nssm_service_t *service) {
|
||||
if (service->no_console) return;
|
||||
|
||||
AllocConsole();
|
||||
|
||||
/* Disable accidental closure. */
|
||||
HWND window = GetConsoleWindow();
|
||||
HMENU menu = GetSystemMenu(window, false);
|
||||
EnableMenuItem(menu, SC_CLOSE, MF_GRAYED);
|
||||
|
||||
/* Set a title like "[NSSM] Jenkins" */
|
||||
TCHAR displayname[SERVICE_NAME_LENGTH];
|
||||
unsigned long len = _countof(displayname);
|
||||
SC_HANDLE services = open_service_manager(SC_MANAGER_CONNECT);
|
||||
if (services) {
|
||||
if (! GetServiceDisplayName(services, service->name, displayname, &len)) ZeroMemory(displayname, sizeof(displayname));
|
||||
CloseServiceHandle(services);
|
||||
}
|
||||
if (! displayname[0]) _sntprintf_s(displayname, _countof(displayname), _TRUNCATE, _T("%s"), service->name);
|
||||
|
||||
TCHAR title[65535];
|
||||
_sntprintf_s(title, _countof(title), _TRUNCATE, _T("[%s] %s"), NSSM, displayname);
|
||||
SetConsoleTitle(title);
|
||||
|
||||
/* Draw the NSSM logo on the console window. */
|
||||
short y = 0;
|
||||
|
||||
b(0, y, 80);
|
||||
y++;
|
||||
|
||||
b(0, y, 80);
|
||||
y++;
|
||||
|
||||
b(0, y, 80);
|
||||
y++;
|
||||
|
||||
b(0, y, 80);
|
||||
y++;
|
||||
|
||||
b(0, y, 80);
|
||||
r(18, y, 5); r(28, y, 4); r(41, y, 4); r(68, y, 1);
|
||||
R(6, y, 5); R(19, y, 4); R(29, y, 1); R(32, y, 3); R(42, y, 1); R(45, y, 3); R(52, y, 5); R(69, y, 4);
|
||||
y++;
|
||||
|
||||
b(0, y, 80);
|
||||
r(8, y, 4); r(20, y, 1); r(28, y, 1); r(33, y, 3); r(41, y, 1); r(46, y, 3); r (57, y, 1);
|
||||
R(9, y, 2); R(21, y, 1); R(27, y, 1); R(34, y, 1); R(40, y, 1); R(47, y, 1); R(54, y, 3); R(68, y, 3);
|
||||
y++;
|
||||
|
||||
b(0, y, 80);
|
||||
r(12, y, 1); r(20, y, 1); r(26, y, 1); r(34, y, 2); r(39, y, 1); r(47, y, 2); r(67, y, 2);
|
||||
R(9, y, 3); R(21, y, 1); R(27, y, 1); R(40, y, 1); R(54, y, 1); R(56, y, 2); R(67, y, 1); R(69, y, 2);
|
||||
y++;
|
||||
|
||||
b(0, y, 80);
|
||||
r(9, y, 1); r(20, y, 1); r(26, y, 1); r (35, y, 1); r(39, y, 1); r(48, y, 1); r(58, y, 1);
|
||||
R(10, y, 3); R(21, y, 1); R(27, y, 1); R(40, y, 1); R(54, y, 1); R(56, y, 2); R(67, y, 1); R(69, y, 2);
|
||||
y++;
|
||||
|
||||
b(0, y, 80);
|
||||
r(9, y, 1); r(56, y, 1); r(66, y, 2);
|
||||
R(11, y, 3); R(21, y, 1); R(26, y, 2); R(39, y, 2); R(54, y, 1); R(57, y, 2); R(69, y, 2);
|
||||
y++;
|
||||
|
||||
b(0, y, 80);
|
||||
r(9, y, 1); r(26, y, 1); r(39, y, 1); r(59, y, 1);
|
||||
R(12, y, 3); R(21, y, 1); R(27, y, 2); R(40, y, 2); R(54, y, 1); R(57, y, 2); R(66, y, 1); R(69, y, 2);
|
||||
y++;
|
||||
|
||||
b(0, y, 80);
|
||||
r(9, y, 1); r(12, y, 4); r(30, y, 1); r(43, y, 1); r(57, y, 1); r(65, y, 2);
|
||||
R(13, y, 2); R(21, y, 1); R(27, y, 3); R(40, y, 3); R(54, y, 1); R(58, y, 2); R(69, y, 2);
|
||||
y++;
|
||||
|
||||
b(0, y, 80);
|
||||
r(9, y, 1); r(13, y, 4); r(27, y, 7); r(40, y, 7);
|
||||
R(14, y, 2); R(21, y, 1); R(28, y, 5); R(41, y, 5); R(54, y, 1); R(58, y, 2); R(65, y, 1); R(69, y, 2);
|
||||
y++;
|
||||
|
||||
b(0, y, 80);
|
||||
r(9, y, 1); r(60, y, 1); r(65, y, 1);
|
||||
R(14, y, 3); R(21, y, 1); R(29, y, 6); R(42, y, 6); R(54, y, 1); R(58, y, 2); R(69, y, 2);
|
||||
y++;
|
||||
|
||||
b(0, y, 80);
|
||||
r(9, y, 1); r(31, y, 1); r(44, y, 1); r(58, y, 1); r(64, y, 1);
|
||||
R(15, y, 3); R(21, y, 1); R(32, y, 4); R(45, y, 4); R(54, y, 1); R(59, y, 2); R(69, y, 2);
|
||||
y++;
|
||||
|
||||
b(0, y, 80);
|
||||
r(9, y, 1); r(33, y, 1); r(46, y, 1); r(61, y, 1); r(64, y, 1);
|
||||
R(16, y, 3); R(21, y, 1); R(34, y, 2); R(47, y, 2); R(54, y, 1); R(59, y, 2); R(69, y, 2);
|
||||
y++;
|
||||
|
||||
b(0, y, 80);
|
||||
r(9, y, 1); r(16, y, 4); r(36, y, 1); r(49, y, 1); r(59, y, 1); r(63, y, 1);
|
||||
R(17, y, 2); R(21, y, 1); R(34, y, 2); R(47, y, 2); R(54, y, 1); R(60, y, 2); R(69, y, 2);
|
||||
y++;
|
||||
|
||||
b(0, y, 80);
|
||||
r(9, y, 1); r(17, y, 4); r(26, y, 1); r(36, y, 1); r(39, y, 1); r(49, y, 1);
|
||||
R(18, y, 2); R(21, y, 1); R(35, y, 1); R(48, y, 1); R(54, y, 1); R(60, y, 2); R(63, y, 1); R(69, y, 2);
|
||||
y++;
|
||||
|
||||
b(0, y, 80);
|
||||
r(26, y, 2); r(39, y, 2); r(63, y, 1);
|
||||
R(9, y, 1); R(18, y, 4); R(35, y, 1); R(48, y, 1); R(54, y, 1); R(60, y, 3); R(69, y, 2);
|
||||
y++;
|
||||
|
||||
b(0, y, 80);
|
||||
r(34, y, 1); r(47, y, 1); r(60, y, 1);
|
||||
R(9, y, 1); R(19, y, 3); R(26, y, 2); R(35, y, 1); R(39, y, 2); R(48, y, 1); R(54, y, 1); R(61, y, 2); R(69, y, 2);
|
||||
y++;
|
||||
|
||||
b(0, y, 80);
|
||||
r(8, y, 1); r(35, y, 1); r(48, y, 1); r(62, y, 1); r(71, y, 1);
|
||||
R(9, y, 1); R(20, y, 2); R(26, y, 3); R(34, y, 1); R(39, y, 3); R(47, y, 1); R(54, y, 1); R(61, y, 1); R(69, y, 2);
|
||||
y++;
|
||||
|
||||
b(0, y, 80);
|
||||
r(11, y, 1); r(26, y, 1); r(28, y, 5); r(39, y, 1); r(41, y, 5); r(51, y, 7); r(61, y, 1); r(66, y, 8);
|
||||
R(7, y, 4); R(21, y, 1); R(29, y, 1); R(33, y, 1); R(42, y, 1); R(46, y, 1); R(52, y, 5); R(67, y, 7);
|
||||
y++;
|
||||
|
||||
b(0, y, 80);
|
||||
y++;
|
||||
|
||||
b(0, y, 80);
|
||||
y++;
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
#ifndef CONSOLE_H
|
||||
#define CONSOLE_H
|
||||
|
||||
void check_console();
|
||||
void alloc_console(nssm_service_t *);
|
||||
|
||||
#endif
|
||||
@ -0,0 +1,173 @@
|
||||
#include "nssm.h"
|
||||
|
||||
/* Copy an environment block. */
|
||||
TCHAR *copy_environment_block(TCHAR *env) {
|
||||
unsigned long len;
|
||||
|
||||
if (! env) return 0;
|
||||
for (len = 0; env[len]; len++) while (env[len]) len++;
|
||||
if (! len++) return 0;
|
||||
|
||||
TCHAR *newenv = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, len * sizeof(TCHAR));
|
||||
if (! newenv) {
|
||||
log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("environment"), _T("copy_environment_block()"), 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
memmove(newenv, env, len * sizeof(TCHAR));
|
||||
return newenv;
|
||||
}
|
||||
|
||||
/*
|
||||
The environment block starts with variables of the form
|
||||
=C:=C:\Windows\System32 which we ignore.
|
||||
*/
|
||||
TCHAR *useful_environment(TCHAR *rawenv) {
|
||||
TCHAR *env = rawenv;
|
||||
|
||||
if (env) {
|
||||
while (*env == _T('=')) {
|
||||
for ( ; *env; env++);
|
||||
env++;
|
||||
}
|
||||
}
|
||||
|
||||
return env;
|
||||
}
|
||||
|
||||
/* Expand an environment variable. Must call HeapFree() on the result. */
|
||||
TCHAR *expand_environment_string(TCHAR *string) {
|
||||
unsigned long len;
|
||||
|
||||
len = ExpandEnvironmentStrings(string, 0, 0);
|
||||
if (! len) {
|
||||
log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_EXPANDENVIRONMENTSTRINGS_FAILED, string, error_string(GetLastError()), 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
TCHAR *ret = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, len * sizeof(TCHAR));
|
||||
if (! ret) {
|
||||
log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("ExpandEnvironmentStrings()"), _T("expand_environment_string"), 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (! ExpandEnvironmentStrings(string, ret, len)) {
|
||||
log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_EXPANDENVIRONMENTSTRINGS_FAILED, string, error_string(GetLastError()), 0);
|
||||
HeapFree(GetProcessHeap(), 0, ret);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
Set all the environment variables from an environment block in the current
|
||||
environment or remove all the variables in the block from the current
|
||||
environment.
|
||||
*/
|
||||
static int set_environment_block(TCHAR *env, bool set) {
|
||||
int ret = 0;
|
||||
|
||||
TCHAR *s, *t;
|
||||
for (s = env; *s; s++) {
|
||||
for (t = s; *t && *t != _T('='); t++);
|
||||
if (*t == _T('=')) {
|
||||
*t = _T('\0');
|
||||
if (set) {
|
||||
TCHAR *expanded = expand_environment_string(++t);
|
||||
if (expanded) {
|
||||
if (! SetEnvironmentVariable(s, expanded)) ret++;
|
||||
HeapFree(GetProcessHeap(), 0, expanded);
|
||||
}
|
||||
else {
|
||||
if (! SetEnvironmentVariable(s, t)) ret++;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (! SetEnvironmentVariable(s, NULL)) ret++;
|
||||
}
|
||||
for (t++ ; *t; t++);
|
||||
}
|
||||
s = t;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int set_environment_block(TCHAR *env) {
|
||||
return set_environment_block(env, true);
|
||||
}
|
||||
|
||||
static int unset_environment_block(TCHAR *env) {
|
||||
return set_environment_block(env, false);
|
||||
}
|
||||
|
||||
/* Remove all variables from the process environment. */
|
||||
int clear_environment() {
|
||||
TCHAR *rawenv = GetEnvironmentStrings();
|
||||
TCHAR *env = useful_environment(rawenv);
|
||||
|
||||
int ret = unset_environment_block(env);
|
||||
|
||||
if (rawenv) FreeEnvironmentStrings(rawenv);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Set the current environment to exactly duplicate an environment block. */
|
||||
int duplicate_environment(TCHAR *rawenv) {
|
||||
int ret = clear_environment();
|
||||
TCHAR *env = useful_environment(rawenv);
|
||||
ret += set_environment_block(env);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
Verify an environment block.
|
||||
Returns: 1 if environment is invalid.
|
||||
0 if environment is OK.
|
||||
-1 on error.
|
||||
*/
|
||||
int test_environment(TCHAR *env) {
|
||||
TCHAR path[PATH_LENGTH];
|
||||
GetModuleFileName(0, path, _countof(path));
|
||||
STARTUPINFO si;
|
||||
ZeroMemory(&si, sizeof(si));
|
||||
si.cb = sizeof(si);
|
||||
PROCESS_INFORMATION pi;
|
||||
ZeroMemory(&pi, sizeof(pi));
|
||||
unsigned long flags = CREATE_SUSPENDED;
|
||||
#ifdef UNICODE
|
||||
flags |= CREATE_UNICODE_ENVIRONMENT;
|
||||
#endif
|
||||
|
||||
/*
|
||||
Try to relaunch ourselves but with the candidate environment set.
|
||||
Assuming no solar flare activity, the only reason this would fail is if
|
||||
the environment were invalid.
|
||||
*/
|
||||
if (CreateProcess(0, path, 0, 0, 0, flags, env, 0, &si, &pi)) {
|
||||
TerminateProcess(pi.hProcess, 0);
|
||||
}
|
||||
else {
|
||||
unsigned long error = GetLastError();
|
||||
if (error == ERROR_INVALID_PARAMETER) return 1;
|
||||
else return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
Duplicate an environment block returned by GetEnvironmentStrings().
|
||||
Since such a block is by definition readonly, and duplicate_environment()
|
||||
modifies its inputs, this function takes a copy of the input and operates
|
||||
on that.
|
||||
*/
|
||||
void duplicate_environment_strings(TCHAR *env) {
|
||||
TCHAR *newenv = copy_environment_block(env);
|
||||
if (! newenv) return;
|
||||
|
||||
duplicate_environment(newenv);
|
||||
HeapFree(GetProcessHeap(), 0, newenv);
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
#ifndef ENV_H
|
||||
#define ENV_H
|
||||
|
||||
TCHAR *copy_environment_block(TCHAR *);
|
||||
TCHAR *useful_environment(TCHAR *);
|
||||
TCHAR *expand_environment_string(TCHAR *);
|
||||
int set_environment_block(TCHAR *);
|
||||
int clear_environment();
|
||||
int duplicate_environment(TCHAR *);
|
||||
int test_environment(TCHAR *);
|
||||
void duplicate_environment_strings(TCHAR *);
|
||||
|
||||
#endif
|
||||
@ -0,0 +1,111 @@
|
||||
#include "nssm.h"
|
||||
|
||||
#define NSSM_SOURCE _T("nssm")
|
||||
#define NSSM_ERROR_BUFSIZE 65535
|
||||
#define NSSM_NUM_EVENT_STRINGS 16
|
||||
unsigned long tls_index;
|
||||
|
||||
/* Convert error code to error string - must call LocalFree() on return value */
|
||||
TCHAR *error_string(unsigned long error) {
|
||||
/* Thread-safe buffer */
|
||||
TCHAR *error_message = (TCHAR *) TlsGetValue(tls_index);
|
||||
if (! error_message) {
|
||||
error_message = (TCHAR *) LocalAlloc(LPTR, NSSM_ERROR_BUFSIZE);
|
||||
if (! error_message) return _T("<out of memory for error message>");
|
||||
TlsSetValue(tls_index, (void *) error_message);
|
||||
}
|
||||
|
||||
if (! FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, 0, error, GetUserDefaultLangID(), (TCHAR *) error_message, NSSM_ERROR_BUFSIZE, 0)) {
|
||||
if (! FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, 0, error, 0, (TCHAR *) error_message, NSSM_ERROR_BUFSIZE, 0)) {
|
||||
if (_sntprintf_s(error_message, NSSM_ERROR_BUFSIZE, _TRUNCATE, _T("system error %lu"), error) < 0) return 0;
|
||||
}
|
||||
}
|
||||
return error_message;
|
||||
}
|
||||
|
||||
/* Convert message code to format string */
|
||||
TCHAR *message_string(unsigned long error) {
|
||||
TCHAR *ret;
|
||||
if (! FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_IGNORE_INSERTS, 0, error, GetUserDefaultLangID(), (LPTSTR) &ret, NSSM_ERROR_BUFSIZE, 0)) {
|
||||
if (! FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_IGNORE_INSERTS, 0, error, 0, (LPTSTR) &ret, NSSM_ERROR_BUFSIZE, 0)) {
|
||||
ret = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, 32 * sizeof(TCHAR));
|
||||
if (_sntprintf_s(ret, NSSM_ERROR_BUFSIZE, _TRUNCATE, _T("system error %lu"), error) < 0) return 0;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Log a message to the Event Log */
|
||||
void log_event(unsigned short type, unsigned long id, ...) {
|
||||
va_list arg;
|
||||
int count;
|
||||
TCHAR *s;
|
||||
TCHAR *strings[NSSM_NUM_EVENT_STRINGS];
|
||||
|
||||
/* Open event log */
|
||||
HANDLE handle = RegisterEventSource(0, NSSM_SOURCE);
|
||||
if (! handle) return;
|
||||
|
||||
/* Log it */
|
||||
count = 0;
|
||||
va_start(arg, id);
|
||||
while ((s = va_arg(arg, TCHAR *)) && count < NSSM_NUM_EVENT_STRINGS - 1) strings[count++] = s;
|
||||
strings[count] = 0;
|
||||
va_end(arg);
|
||||
ReportEvent(handle, type, 0, id, 0, count, 0, (const TCHAR **) strings, 0);
|
||||
|
||||
/* Close event log */
|
||||
DeregisterEventSource(handle);
|
||||
}
|
||||
|
||||
/* Log a message to the console */
|
||||
void print_message(FILE *file, unsigned long id, ...) {
|
||||
va_list arg;
|
||||
|
||||
TCHAR *format = message_string(id);
|
||||
if (! format) return;
|
||||
|
||||
va_start(arg, id);
|
||||
_vftprintf(file, format, arg);
|
||||
va_end(arg);
|
||||
|
||||
LocalFree(format);
|
||||
}
|
||||
|
||||
/* Show a GUI dialogue */
|
||||
int popup_message(HWND owner, unsigned int type, unsigned long id, ...) {
|
||||
va_list arg;
|
||||
|
||||
TCHAR *format = message_string(id);
|
||||
if (! format) {
|
||||
return MessageBox(0, _T("The message which was supposed to go here is missing!"), NSSM, MB_OK | MB_ICONEXCLAMATION);
|
||||
}
|
||||
|
||||
TCHAR blurb[NSSM_ERROR_BUFSIZE];
|
||||
va_start(arg, id);
|
||||
if (_vsntprintf_s(blurb, _countof(blurb), _TRUNCATE, format, arg) < 0) {
|
||||
va_end(arg);
|
||||
LocalFree(format);
|
||||
return MessageBox(0, _T("The message which was supposed to go here is too big!"), NSSM, MB_OK | MB_ICONEXCLAMATION);
|
||||
}
|
||||
va_end(arg);
|
||||
|
||||
MSGBOXPARAMS params;
|
||||
ZeroMemory(¶ms, sizeof(params));
|
||||
params.cbSize = sizeof(params);
|
||||
params.hInstance = GetModuleHandle(0);
|
||||
params.hwndOwner = owner;
|
||||
params.lpszText = blurb;
|
||||
params.lpszCaption = NSSM;
|
||||
params.dwStyle = type;
|
||||
if (type == MB_OK) {
|
||||
params.dwStyle |= MB_USERICON;
|
||||
params.lpszIcon = MAKEINTRESOURCE(IDI_NSSM);
|
||||
}
|
||||
|
||||
int ret = MessageBoxIndirect(¶ms);
|
||||
|
||||
LocalFree(format);
|
||||
|
||||
return ret;
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
#ifndef EVENT_H
|
||||
#define EVENT_H
|
||||
|
||||
TCHAR *error_string(unsigned long);
|
||||
TCHAR *message_string(unsigned long);
|
||||
void log_event(unsigned short, unsigned long, ...);
|
||||
void print_message(FILE *, unsigned long, ...);
|
||||
int popup_message(HWND, unsigned int, unsigned long, ...);
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,18 @@
|
||||
#ifndef GUI_H
|
||||
#define GUI_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include <windows.h>
|
||||
#include <commctrl.h>
|
||||
#include "resource.h"
|
||||
|
||||
int nssm_gui(int, nssm_service_t *);
|
||||
void centre_window(HWND);
|
||||
int configure(HWND, nssm_service_t *, nssm_service_t *);
|
||||
int install(HWND);
|
||||
int remove(HWND);
|
||||
int edit(HWND, nssm_service_t *);
|
||||
void browse(HWND);
|
||||
INT_PTR CALLBACK nssm_dlg(HWND, UINT, WPARAM, LPARAM);
|
||||
|
||||
#endif
|
||||
@ -0,0 +1,92 @@
|
||||
#include "nssm.h"
|
||||
|
||||
imports_t imports;
|
||||
|
||||
/*
|
||||
Try to set up function pointers.
|
||||
In this first implementation it is not an error if we can't load them
|
||||
because we aren't currently trying to load any functions which we
|
||||
absolutely need. If we later add some indispensible imports we can
|
||||
return non-zero here to force an application exit.
|
||||
*/
|
||||
HMODULE get_dll(const TCHAR *dll, unsigned long *error) {
|
||||
*error = 0;
|
||||
|
||||
HMODULE ret = LoadLibrary(dll);
|
||||
if (! ret) {
|
||||
*error = GetLastError();
|
||||
log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_LOADLIBRARY_FAILED, dll, error_string(*error), 0);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
FARPROC get_import(HMODULE library, const char *function, unsigned long *error) {
|
||||
*error = 0;
|
||||
|
||||
FARPROC ret = GetProcAddress(library, function);
|
||||
if (! ret) {
|
||||
*error = GetLastError();
|
||||
TCHAR *function_name;
|
||||
#ifdef UNICODE
|
||||
size_t buflen;
|
||||
mbstowcs_s(&buflen, NULL, 0, function, _TRUNCATE);
|
||||
function_name = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, buflen * sizeof(TCHAR));
|
||||
if (function_name) mbstowcs_s(&buflen, function_name, buflen * sizeof(TCHAR), function, _TRUNCATE);
|
||||
#else
|
||||
function_name = (TCHAR *) function;
|
||||
#endif
|
||||
if (*error != ERROR_PROC_NOT_FOUND) log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_GETPROCADDRESS_FAILED, function_name, error_string(*error), 0);
|
||||
#ifdef UNICODE
|
||||
if (function_name) HeapFree(GetProcessHeap(), 0, function_name);
|
||||
#endif
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int get_imports() {
|
||||
unsigned long error;
|
||||
|
||||
ZeroMemory(&imports, sizeof(imports));
|
||||
|
||||
imports.kernel32 = get_dll(_T("kernel32.dll"), &error);
|
||||
if (imports.kernel32) {
|
||||
imports.AttachConsole = (AttachConsole_ptr) get_import(imports.kernel32, "AttachConsole", &error);
|
||||
if (! imports.AttachConsole) {
|
||||
if (error != ERROR_PROC_NOT_FOUND) return 2;
|
||||
}
|
||||
|
||||
imports.SleepConditionVariableCS = (SleepConditionVariableCS_ptr) get_import(imports.kernel32, "SleepConditionVariableCS", &error);
|
||||
if (! imports.SleepConditionVariableCS) {
|
||||
if (error != ERROR_PROC_NOT_FOUND) return 3;
|
||||
}
|
||||
|
||||
imports.WakeConditionVariable = (WakeConditionVariable_ptr) get_import(imports.kernel32, "WakeConditionVariable", &error);
|
||||
if (! imports.WakeConditionVariable) {
|
||||
if (error != ERROR_PROC_NOT_FOUND) return 4;
|
||||
}
|
||||
}
|
||||
else if (error != ERROR_MOD_NOT_FOUND) return 1;
|
||||
|
||||
imports.advapi32 = get_dll(_T("advapi32.dll"), &error);
|
||||
if (imports.advapi32) {
|
||||
imports.CreateWellKnownSid = (CreateWellKnownSid_ptr) get_import(imports.advapi32, "CreateWellKnownSid", &error);
|
||||
if (! imports.CreateWellKnownSid) {
|
||||
if (error != ERROR_PROC_NOT_FOUND) return 6;
|
||||
}
|
||||
imports.IsWellKnownSid = (IsWellKnownSid_ptr) get_import(imports.advapi32, "IsWellKnownSid", &error);
|
||||
if (! imports.IsWellKnownSid) {
|
||||
if (error != ERROR_PROC_NOT_FOUND) return 7;
|
||||
}
|
||||
}
|
||||
else if (error != ERROR_MOD_NOT_FOUND) return 5;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void free_imports() {
|
||||
if (imports.kernel32) FreeLibrary(imports.kernel32);
|
||||
if (imports.advapi32) FreeLibrary(imports.advapi32);
|
||||
ZeroMemory(&imports, sizeof(imports));
|
||||
}
|
||||
@ -0,0 +1,25 @@
|
||||
#ifndef IMPORTS_H
|
||||
#define IMPORTS_H
|
||||
|
||||
typedef BOOL (WINAPI *AttachConsole_ptr)(DWORD);
|
||||
typedef BOOL (WINAPI *SleepConditionVariableCS_ptr)(PCONDITION_VARIABLE, PCRITICAL_SECTION, DWORD);
|
||||
typedef void (WINAPI *WakeConditionVariable_ptr)(PCONDITION_VARIABLE);
|
||||
typedef BOOL (WINAPI *CreateWellKnownSid_ptr)(WELL_KNOWN_SID_TYPE, SID *, SID *, unsigned long *);
|
||||
typedef BOOL (WINAPI *IsWellKnownSid_ptr)(SID *, WELL_KNOWN_SID_TYPE);
|
||||
|
||||
typedef struct {
|
||||
HMODULE kernel32;
|
||||
HMODULE advapi32;
|
||||
AttachConsole_ptr AttachConsole;
|
||||
SleepConditionVariableCS_ptr SleepConditionVariableCS;
|
||||
WakeConditionVariable_ptr WakeConditionVariable;
|
||||
CreateWellKnownSid_ptr CreateWellKnownSid;
|
||||
IsWellKnownSid_ptr IsWellKnownSid;
|
||||
} imports_t;
|
||||
|
||||
HMODULE get_dll(const TCHAR *, unsigned long *);
|
||||
FARPROC get_import(HMODULE, const char *, unsigned long *);
|
||||
int get_imports();
|
||||
void free_imports();
|
||||
|
||||
#endif
|
||||
@ -0,0 +1,560 @@
|
||||
#include "nssm.h"
|
||||
|
||||
#define COMPLAINED_READ (1 << 0)
|
||||
#define COMPLAINED_WRITE (1 << 1)
|
||||
#define COMPLAINED_ROTATE (1 << 2)
|
||||
|
||||
static HANDLE create_logging_thread(TCHAR *service_name, TCHAR *path, unsigned long sharing, unsigned long disposition, unsigned long flags, HANDLE *read_handle_ptr, HANDLE *pipe_handle_ptr, HANDLE *write_handle_ptr, unsigned long rotate_bytes_low, unsigned long rotate_bytes_high, unsigned long *tid_ptr, unsigned long *rotate_online) {
|
||||
*tid_ptr = 0;
|
||||
|
||||
/* Pipe between application's stdout/stderr and our logging handle. */
|
||||
if (read_handle_ptr && ! *read_handle_ptr) {
|
||||
if (pipe_handle_ptr && ! *pipe_handle_ptr) {
|
||||
if (CreatePipe(read_handle_ptr, pipe_handle_ptr, 0, 0)) {
|
||||
SetHandleInformation(*pipe_handle_ptr, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT);
|
||||
}
|
||||
else {
|
||||
log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPIPE_FAILED, service_name, path, error_string(GetLastError()));
|
||||
return (HANDLE) 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logger_t *logger = (logger_t *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(logger_t));
|
||||
if (! logger) {
|
||||
log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("logger"), _T("create_logging_thread()"), 0);
|
||||
return (HANDLE) 0;
|
||||
}
|
||||
|
||||
ULARGE_INTEGER size;
|
||||
size.LowPart = rotate_bytes_low;
|
||||
size.HighPart = rotate_bytes_high;
|
||||
|
||||
logger->service_name = service_name;
|
||||
logger->path = path;
|
||||
logger->sharing = sharing;
|
||||
logger->disposition = disposition;
|
||||
logger->flags = flags;
|
||||
logger->read_handle = *read_handle_ptr;
|
||||
logger->write_handle = *write_handle_ptr;
|
||||
logger->size = (__int64) size.QuadPart;
|
||||
logger->tid_ptr = tid_ptr;
|
||||
logger->rotate_online = rotate_online;
|
||||
|
||||
HANDLE thread_handle = CreateThread(NULL, 0, log_and_rotate, (void *) logger, 0, logger->tid_ptr);
|
||||
if (! thread_handle) {
|
||||
log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETHREAD_FAILED, error_string(GetLastError()), 0);
|
||||
HeapFree(GetProcessHeap(), 0, logger);
|
||||
}
|
||||
|
||||
return thread_handle;
|
||||
}
|
||||
|
||||
static inline unsigned long guess_charsize(void *address, unsigned long bufsize) {
|
||||
if (IsTextUnicode(address, bufsize, 0)) return (unsigned long) sizeof(wchar_t);
|
||||
else return (unsigned long) sizeof(char);
|
||||
}
|
||||
|
||||
static inline void write_bom(logger_t *logger, unsigned long *out) {
|
||||
wchar_t bom = L'\ufeff';
|
||||
if (! WriteFile(logger->write_handle, (void *) &bom, sizeof(bom), out, 0)) {
|
||||
log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_SOMEBODY_SET_UP_US_THE_BOM, logger->service_name, logger->path, error_string(GetLastError()), 0);
|
||||
}
|
||||
}
|
||||
|
||||
/* Get path, share mode, creation disposition and flags for a stream. */
|
||||
int get_createfile_parameters(HKEY key, TCHAR *prefix, TCHAR *path, unsigned long *sharing, unsigned long default_sharing, unsigned long *disposition, unsigned long default_disposition, unsigned long *flags, unsigned long default_flags) {
|
||||
TCHAR value[NSSM_STDIO_LENGTH];
|
||||
|
||||
/* Path. */
|
||||
if (_sntprintf_s(value, _countof(value), _TRUNCATE, _T("%s"), prefix) < 0) {
|
||||
log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, prefix, _T("get_createfile_parameters()"), 0);
|
||||
return 1;
|
||||
}
|
||||
switch (expand_parameter(key, value, path, PATH_LENGTH, true, false)) {
|
||||
case 0: if (! path[0]) return 0; break; /* OK. */
|
||||
default: return 2; /* Error. */
|
||||
}
|
||||
|
||||
/* ShareMode. */
|
||||
if (_sntprintf_s(value, _countof(value), _TRUNCATE, _T("%s%s"), prefix, NSSM_REG_STDIO_SHARING) < 0) {
|
||||
log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, NSSM_REG_STDIO_SHARING, _T("get_createfile_parameters()"), 0);
|
||||
return 3;
|
||||
}
|
||||
switch (get_number(key, value, sharing, false)) {
|
||||
case 0: *sharing = default_sharing; break; /* Missing. */
|
||||
case 1: break; /* Found. */
|
||||
case -2: return 4; break; /* Error. */
|
||||
}
|
||||
|
||||
/* CreationDisposition. */
|
||||
if (_sntprintf_s(value, _countof(value), _TRUNCATE, _T("%s%s"), prefix, NSSM_REG_STDIO_DISPOSITION) < 0) {
|
||||
log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, NSSM_REG_STDIO_DISPOSITION, _T("get_createfile_parameters()"), 0);
|
||||
return 5;
|
||||
}
|
||||
switch (get_number(key, value, disposition, false)) {
|
||||
case 0: *disposition = default_disposition; break; /* Missing. */
|
||||
case 1: break; /* Found. */
|
||||
case -2: return 6; break; /* Error. */
|
||||
}
|
||||
|
||||
/* Flags. */
|
||||
if (_sntprintf_s(value, _countof(value), _TRUNCATE, _T("%s%s"), prefix, NSSM_REG_STDIO_FLAGS) < 0) {
|
||||
log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, NSSM_REG_STDIO_FLAGS, _T("get_createfile_parameters()"), 0);
|
||||
return 7;
|
||||
}
|
||||
switch (get_number(key, value, flags, false)) {
|
||||
case 0: *flags = default_flags; break; /* Missing. */
|
||||
case 1: break; /* Found. */
|
||||
case -2: return 8; break; /* Error. */
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int set_createfile_parameter(HKEY key, TCHAR *prefix, TCHAR *suffix, unsigned long number) {
|
||||
TCHAR value[NSSM_STDIO_LENGTH];
|
||||
|
||||
if (_sntprintf_s(value, _countof(value), _TRUNCATE, _T("%s%s"), prefix, suffix) < 0) {
|
||||
log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, suffix, _T("set_createfile_parameter()"), 0);
|
||||
return 1;
|
||||
}
|
||||
|
||||
return set_number(key, value, number);
|
||||
}
|
||||
|
||||
int delete_createfile_parameter(HKEY key, TCHAR *prefix, TCHAR *suffix) {
|
||||
TCHAR value[NSSM_STDIO_LENGTH];
|
||||
|
||||
if (_sntprintf_s(value, _countof(value), _TRUNCATE, _T("%s%s"), prefix, suffix) < 0) {
|
||||
log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, suffix, _T("delete_createfile_parameter()"), 0);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (RegDeleteValue(key, value)) return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
HANDLE write_to_file(TCHAR *path, unsigned long sharing, SECURITY_ATTRIBUTES *attributes, unsigned long disposition, unsigned long flags) {
|
||||
HANDLE ret = CreateFile(path, FILE_WRITE_DATA, sharing, attributes, disposition, flags, 0);
|
||||
if (ret) {
|
||||
if (SetFilePointer(ret, 0, 0, FILE_END) != INVALID_SET_FILE_POINTER) SetEndOfFile(ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEFILE_FAILED, path, error_string(GetLastError()), 0);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void rotated_filename(TCHAR *path, TCHAR *rotated, unsigned long rotated_len, SYSTEMTIME *st) {
|
||||
if (! st) {
|
||||
SYSTEMTIME now;
|
||||
st = &now;
|
||||
GetSystemTime(st);
|
||||
}
|
||||
|
||||
TCHAR buffer[PATH_LENGTH];
|
||||
memmove(buffer, path, sizeof(buffer));
|
||||
TCHAR *ext = PathFindExtension(buffer);
|
||||
TCHAR extension[PATH_LENGTH];
|
||||
_sntprintf_s(extension, _countof(extension), _TRUNCATE, _T("-%04u%02u%02uT%02u%02u%02u.%03u%s"), st->wYear, st->wMonth, st->wDay, st->wHour, st->wMinute, st->wSecond, st->wMilliseconds, ext);
|
||||
*ext = _T('\0');
|
||||
_sntprintf_s(rotated, rotated_len, _TRUNCATE, _T("%s%s"), buffer, extension);
|
||||
}
|
||||
|
||||
void rotate_file(TCHAR *service_name, TCHAR *path, unsigned long seconds, unsigned long low, unsigned long high) {
|
||||
unsigned long error;
|
||||
|
||||
/* Now. */
|
||||
SYSTEMTIME st;
|
||||
GetSystemTime(&st);
|
||||
|
||||
BY_HANDLE_FILE_INFORMATION info;
|
||||
|
||||
/* Try to open the file to check if it exists and to get attributes. */
|
||||
HANDLE file = CreateFile(path, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
|
||||
if (file) {
|
||||
/* Get file attributes. */
|
||||
if (! GetFileInformationByHandle(file, &info)) {
|
||||
/* Reuse current time for rotation timestamp. */
|
||||
seconds = low = high = 0;
|
||||
SystemTimeToFileTime(&st, &info.ftLastWriteTime);
|
||||
}
|
||||
|
||||
CloseHandle(file);
|
||||
}
|
||||
else {
|
||||
error = GetLastError();
|
||||
if (error == ERROR_FILE_NOT_FOUND) return;
|
||||
log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_ROTATE_FILE_FAILED, service_name, path, _T("CreateFile()"), path, error_string(error), 0);
|
||||
/* Reuse current time for rotation timestamp. */
|
||||
seconds = low = high = 0;
|
||||
SystemTimeToFileTime(&st, &info.ftLastWriteTime);
|
||||
}
|
||||
|
||||
/* Check file age. */
|
||||
if (seconds) {
|
||||
FILETIME ft;
|
||||
SystemTimeToFileTime(&st, &ft);
|
||||
|
||||
ULARGE_INTEGER s;
|
||||
s.LowPart = ft.dwLowDateTime;
|
||||
s.HighPart = ft.dwHighDateTime;
|
||||
s.QuadPart -= seconds * 10000000LL;
|
||||
ft.dwLowDateTime = s.LowPart;
|
||||
ft.dwHighDateTime = s.HighPart;
|
||||
if (CompareFileTime(&info.ftLastWriteTime, &ft) > 0) return;
|
||||
}
|
||||
|
||||
/* Check file size. */
|
||||
if (low || high) {
|
||||
if (info.nFileSizeHigh < high) return;
|
||||
if (info.nFileSizeHigh == high && info.nFileSizeLow < low) return;
|
||||
}
|
||||
|
||||
/* Get new filename. */
|
||||
FileTimeToSystemTime(&info.ftLastWriteTime, &st);
|
||||
|
||||
TCHAR rotated[PATH_LENGTH];
|
||||
rotated_filename(path, rotated, _countof(rotated), &st);
|
||||
|
||||
/* Rotate. */
|
||||
if (MoveFile(path, rotated)) {
|
||||
log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_ROTATED, service_name, path, rotated, 0);
|
||||
return;
|
||||
}
|
||||
error = GetLastError();
|
||||
|
||||
if (error == ERROR_FILE_NOT_FOUND) return;
|
||||
log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_ROTATE_FILE_FAILED, service_name, path, _T("MoveFile()"), rotated, error_string(error), 0);
|
||||
return;
|
||||
}
|
||||
|
||||
int get_output_handles(nssm_service_t *service, STARTUPINFO *si) {
|
||||
if (! si) return 1;
|
||||
|
||||
/* Allocate a new console so we get a fresh stdin, stdout and stderr. */
|
||||
alloc_console(service);
|
||||
|
||||
/* stdin */
|
||||
if (service->stdin_path[0]) {
|
||||
si->hStdInput = CreateFile(service->stdin_path, FILE_READ_DATA, service->stdin_sharing, 0, service->stdin_disposition, service->stdin_flags, 0);
|
||||
if (! si->hStdInput) {
|
||||
log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEFILE_FAILED, service->stdin_path, error_string(GetLastError()), 0);
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
/* stdout */
|
||||
if (service->stdout_path[0]) {
|
||||
if (service->rotate_files) rotate_file(service->name, service->stdout_path, service->rotate_seconds, service->rotate_bytes_low, service->rotate_bytes_high);
|
||||
HANDLE stdout_handle = write_to_file(service->stdout_path, service->stdout_sharing, 0, service->stdout_disposition, service->stdout_flags);
|
||||
if (! stdout_handle) return 4;
|
||||
|
||||
if (service->rotate_files && service->rotate_stdout_online) {
|
||||
service->stdout_pipe = si->hStdOutput = 0;
|
||||
service->stdout_thread = create_logging_thread(service->name, service->stdout_path, service->stdout_sharing, service->stdout_disposition, service->stdout_flags, &service->stdout_pipe, &si->hStdOutput, &stdout_handle, service->rotate_bytes_low, service->rotate_bytes_high, &service->stdout_tid, &service->rotate_stdout_online);
|
||||
if (! service->stdout_thread) {
|
||||
CloseHandle(service->stdout_pipe);
|
||||
CloseHandle(si->hStdOutput);
|
||||
}
|
||||
}
|
||||
else service->stdout_thread = 0;
|
||||
|
||||
if (! service->stdout_thread) {
|
||||
if (! DuplicateHandle(GetCurrentProcess(), stdout_handle, GetCurrentProcess(), &si->hStdOutput, 0, true, DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) {
|
||||
log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_DUPLICATEHANDLE_FAILED, NSSM_REG_STDOUT, _T("stdout"), error_string(GetLastError()), 0);
|
||||
return 4;
|
||||
}
|
||||
service->rotate_stdout_online = NSSM_ROTATE_OFFLINE;
|
||||
}
|
||||
}
|
||||
|
||||
/* stderr */
|
||||
if (service->stderr_path[0]) {
|
||||
/* Same as stdout? */
|
||||
if (str_equiv(service->stderr_path, service->stdout_path)) {
|
||||
service->stderr_sharing = service->stdout_sharing;
|
||||
service->stderr_disposition = service->stdout_disposition;
|
||||
service->stderr_flags = service->stdout_flags;
|
||||
service->rotate_stderr_online = NSSM_ROTATE_OFFLINE;
|
||||
|
||||
/* Two handles to the same file will create a race. */
|
||||
if (! DuplicateHandle(GetCurrentProcess(), si->hStdOutput, GetCurrentProcess(), &si->hStdError, 0, true, DUPLICATE_SAME_ACCESS)) {
|
||||
log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_DUPLICATEHANDLE_FAILED, NSSM_REG_STDOUT, _T("stderr"), error_string(GetLastError()), 0);
|
||||
return 6;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (service->rotate_files) rotate_file(service->name, service->stderr_path, service->rotate_seconds, service->rotate_bytes_low, service->rotate_bytes_high);
|
||||
HANDLE stderr_handle = write_to_file(service->stderr_path, service->stderr_sharing, 0, service->stderr_disposition, service->stderr_flags);
|
||||
if (! stderr_handle) return 7;
|
||||
|
||||
if (service->rotate_files && service->rotate_stderr_online) {
|
||||
service->stderr_pipe = si->hStdError = 0;
|
||||
service->stderr_thread = create_logging_thread(service->name, service->stderr_path, service->stderr_sharing, service->stderr_disposition, service->stderr_flags, &service->stderr_pipe, &si->hStdError, &stderr_handle, service->rotate_bytes_low, service->rotate_bytes_high, &service->stderr_tid, &service->rotate_stderr_online);
|
||||
if (! service->stderr_thread) {
|
||||
CloseHandle(service->stderr_pipe);
|
||||
CloseHandle(si->hStdError);
|
||||
}
|
||||
}
|
||||
else service->stderr_thread = 0;
|
||||
|
||||
if (! service->stderr_thread) {
|
||||
if (! DuplicateHandle(GetCurrentProcess(), stderr_handle, GetCurrentProcess(), &si->hStdError, 0, true, DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) {
|
||||
log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_DUPLICATEHANDLE_FAILED, NSSM_REG_STDERR, _T("stderr"), error_string(GetLastError()), 0);
|
||||
return 7;
|
||||
}
|
||||
service->rotate_stderr_online = NSSM_ROTATE_OFFLINE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
We need to set the startup_info flags to make the new handles
|
||||
inheritable by the new process.
|
||||
*/
|
||||
si->dwFlags |= STARTF_USESTDHANDLES;
|
||||
|
||||
if (service->no_console) return 0;
|
||||
|
||||
/* Redirect other handles. */
|
||||
if (! si->hStdInput) {
|
||||
if (! DuplicateHandle(GetCurrentProcess(), GetStdHandle(STD_INPUT_HANDLE), GetCurrentProcess(), &si->hStdInput, 0, true, DUPLICATE_SAME_ACCESS)) {
|
||||
log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_DUPLICATEHANDLE_FAILED, _T("STD_INPUT_HANDLE"), _T("stdin"), error_string(GetLastError()), 0);
|
||||
return 8;
|
||||
}
|
||||
}
|
||||
if (! si->hStdOutput) {
|
||||
if (! DuplicateHandle(GetCurrentProcess(), GetStdHandle(STD_OUTPUT_HANDLE), GetCurrentProcess(), &si->hStdOutput, 0, true, DUPLICATE_SAME_ACCESS)) {
|
||||
log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_DUPLICATEHANDLE_FAILED, _T("STD_OUTPUT_HANDLE"), _T("stdout"), error_string(GetLastError()), 0);
|
||||
return 9;
|
||||
}
|
||||
}
|
||||
if (! si->hStdError) {
|
||||
if (! DuplicateHandle(GetCurrentProcess(), GetStdHandle(STD_ERROR_HANDLE), GetCurrentProcess(), &si->hStdError, 0, true, DUPLICATE_SAME_ACCESS)) {
|
||||
log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_DUPLICATEHANDLE_FAILED, _T("STD_ERROR_HANDLE"), _T("stderr"), error_string(GetLastError()), 0);
|
||||
return 10;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void close_output_handles(STARTUPINFO *si) {
|
||||
if (si->hStdInput) CloseHandle(si->hStdInput);
|
||||
if (si->hStdOutput) CloseHandle(si->hStdOutput);
|
||||
if (si->hStdError) CloseHandle(si->hStdError);
|
||||
}
|
||||
|
||||
/*
|
||||
Try multiple times to read from a file.
|
||||
Returns: 0 on success.
|
||||
1 on non-fatal error.
|
||||
-1 on fatal error.
|
||||
*/
|
||||
static int try_read(logger_t *logger, void *address, unsigned long bufsize, unsigned long *in, int *complained) {
|
||||
int ret = 1;
|
||||
unsigned long error;
|
||||
for (int tries = 0; tries < 5; tries++) {
|
||||
if (ReadFile(logger->read_handle, address, bufsize, in, 0)) return 0;
|
||||
|
||||
error = GetLastError();
|
||||
switch (error) {
|
||||
/* Other end closed the pipe. */
|
||||
case ERROR_BROKEN_PIPE:
|
||||
ret = -1;
|
||||
goto complain_read;
|
||||
|
||||
/* Couldn't lock the buffer. */
|
||||
case ERROR_NOT_ENOUGH_QUOTA:
|
||||
Sleep(2000 + tries * 3000);
|
||||
ret = 1;
|
||||
continue;
|
||||
|
||||
/* Write was cancelled by the other end. */
|
||||
case ERROR_OPERATION_ABORTED:
|
||||
ret = 1;
|
||||
goto complain_read;
|
||||
|
||||
default:
|
||||
ret = -1;
|
||||
}
|
||||
}
|
||||
|
||||
complain_read:
|
||||
/* Ignore the error if we've been requested to exit anyway. */
|
||||
if (*logger->rotate_online != NSSM_ROTATE_ONLINE) return ret;
|
||||
if (! (*complained & COMPLAINED_READ)) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_READFILE_FAILED, logger->service_name, logger->path, error_string(error), 0);
|
||||
*complained |= COMPLAINED_READ;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
Try multiple times to write to a file.
|
||||
Returns: 0 on success.
|
||||
1 on non-fatal error.
|
||||
-1 on fatal error.
|
||||
*/
|
||||
static int try_write(logger_t *logger, void *address, unsigned long bufsize, unsigned long *out, int *complained) {
|
||||
int ret = 1;
|
||||
unsigned long error;
|
||||
for (int tries = 0; tries < 5; tries++) {
|
||||
if (WriteFile(logger->write_handle, address, bufsize, out, 0)) return 0;
|
||||
|
||||
error = GetLastError();
|
||||
if (error == ERROR_IO_PENDING) {
|
||||
/* Operation was successful pending flush to disk. */
|
||||
return 0;
|
||||
}
|
||||
|
||||
switch (error) {
|
||||
/* Other end closed the pipe. */
|
||||
case ERROR_BROKEN_PIPE:
|
||||
ret = -1;
|
||||
goto complain_write;
|
||||
|
||||
/* Couldn't lock the buffer. */
|
||||
case ERROR_NOT_ENOUGH_QUOTA:
|
||||
/* Out of disk space. */
|
||||
case ERROR_DISK_FULL:
|
||||
Sleep(2000 + tries * 3000);
|
||||
ret = 1;
|
||||
continue;
|
||||
|
||||
default:
|
||||
/* We'll lose this line but try to read and write subsequent ones. */
|
||||
ret = 1;
|
||||
}
|
||||
}
|
||||
|
||||
complain_write:
|
||||
if (! (*complained & COMPLAINED_WRITE)) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_WRITEFILE_FAILED, logger->service_name, logger->path, error_string(error), 0);
|
||||
*complained |= COMPLAINED_WRITE;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Wrapper to be called in a new thread for logging. */
|
||||
unsigned long WINAPI log_and_rotate(void *arg) {
|
||||
logger_t *logger = (logger_t *) arg;
|
||||
if (! logger) return 1;
|
||||
|
||||
__int64 size;
|
||||
BY_HANDLE_FILE_INFORMATION info;
|
||||
|
||||
/* Find initial file size. */
|
||||
if (! GetFileInformationByHandle(logger->write_handle, &info)) logger->size = 0LL;
|
||||
else {
|
||||
ULARGE_INTEGER l;
|
||||
l.HighPart = info.nFileSizeHigh;
|
||||
l.LowPart = info.nFileSizeLow;
|
||||
size = l.QuadPart;
|
||||
}
|
||||
|
||||
char buffer[1024];
|
||||
void *address;
|
||||
unsigned long in, out;
|
||||
unsigned long charsize = 0;
|
||||
unsigned long error;
|
||||
int ret;
|
||||
int complained = 0;
|
||||
|
||||
while (true) {
|
||||
/* Read data from the pipe. */
|
||||
address = &buffer;
|
||||
ret = try_read(logger, address, sizeof(buffer), &in, &complained);
|
||||
if (ret < 0) {
|
||||
CloseHandle(logger->read_handle);
|
||||
CloseHandle(logger->write_handle);
|
||||
HeapFree(GetProcessHeap(), 0, logger);
|
||||
return 2;
|
||||
}
|
||||
else if (ret) continue;
|
||||
|
||||
if (*logger->rotate_online == NSSM_ROTATE_ONLINE_ASAP || (logger->size && size + (__int64) in >= logger->size)) {
|
||||
/* Look for newline. */
|
||||
unsigned long i;
|
||||
for (i = 0; i < in; i++) {
|
||||
if (buffer[i] == '\n') {
|
||||
if (! charsize) charsize = guess_charsize(address, in);
|
||||
i += charsize;
|
||||
|
||||
/* Write up to the newline. */
|
||||
ret = try_write(logger, address, i, &out, &complained);
|
||||
if (ret < 0) {
|
||||
HeapFree(GetProcessHeap(), 0, logger);
|
||||
CloseHandle(logger->read_handle);
|
||||
CloseHandle(logger->write_handle);
|
||||
return 3;
|
||||
}
|
||||
size += (__int64) out;
|
||||
|
||||
/* Rotate. */
|
||||
*logger->rotate_online = NSSM_ROTATE_ONLINE;
|
||||
TCHAR rotated[PATH_LENGTH];
|
||||
rotated_filename(logger->path, rotated, _countof(rotated), 0);
|
||||
|
||||
/*
|
||||
Ideally we'd try the rename first then close the handle but
|
||||
MoveFile() will fail if the handle is still open so we must
|
||||
risk losing everything.
|
||||
*/
|
||||
CloseHandle(logger->write_handle);
|
||||
if (MoveFile(logger->path, rotated)) {
|
||||
log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_ROTATED, logger->service_name, logger->path, rotated, 0);
|
||||
size = 0LL;
|
||||
}
|
||||
else {
|
||||
error = GetLastError();
|
||||
if (error != ERROR_FILE_NOT_FOUND) {
|
||||
if (! (complained & COMPLAINED_ROTATE)) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_ROTATE_FILE_FAILED, logger->service_name, logger->path, _T("MoveFile()"), rotated, error_string(error), 0);
|
||||
complained |= COMPLAINED_ROTATE;
|
||||
/* We can at least try to re-open the existing file. */
|
||||
logger->disposition = OPEN_ALWAYS;
|
||||
}
|
||||
}
|
||||
|
||||
/* Reopen. */
|
||||
logger->write_handle = write_to_file(logger->path, logger->sharing, 0, logger->disposition, logger->flags);
|
||||
if (! logger->write_handle) {
|
||||
error = GetLastError();
|
||||
log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEFILE_FAILED, logger->path, error_string(error), 0);
|
||||
/* Oh dear. Now we can't log anything further. */
|
||||
HeapFree(GetProcessHeap(), 0, logger);
|
||||
CloseHandle(logger->read_handle);
|
||||
CloseHandle(logger->write_handle);
|
||||
return 4;
|
||||
}
|
||||
|
||||
/* Resume writing after the newline. */
|
||||
address = (void *) ((char *) address + i);
|
||||
in -= i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (! size) {
|
||||
/* Write a BOM to the new file. */
|
||||
if (! charsize) charsize = guess_charsize(address, in);
|
||||
if (charsize == sizeof(wchar_t)) write_bom(logger, &out);
|
||||
size += (__int64) out;
|
||||
}
|
||||
|
||||
/* Write the data, if any. */
|
||||
if (! in) continue;
|
||||
|
||||
ret = try_write(logger, address, in, &out, &complained);
|
||||
size += (__int64) out;
|
||||
if (ret < 0) {
|
||||
HeapFree(GetProcessHeap(), 0, logger);
|
||||
CloseHandle(logger->read_handle);
|
||||
CloseHandle(logger->write_handle);
|
||||
return 3;
|
||||
}
|
||||
}
|
||||
|
||||
HeapFree(GetProcessHeap(), 0, logger);
|
||||
CloseHandle(logger->read_handle);
|
||||
CloseHandle(logger->write_handle);
|
||||
return 0;
|
||||
}
|
||||
@ -0,0 +1,36 @@
|
||||
#ifndef IO_H
|
||||
#define IO_H
|
||||
|
||||
#define NSSM_STDIN_SHARING FILE_SHARE_WRITE
|
||||
#define NSSM_STDIN_DISPOSITION OPEN_EXISTING
|
||||
#define NSSM_STDIN_FLAGS FILE_ATTRIBUTE_NORMAL
|
||||
#define NSSM_STDOUT_SHARING (FILE_SHARE_READ | FILE_SHARE_WRITE)
|
||||
#define NSSM_STDOUT_DISPOSITION OPEN_ALWAYS
|
||||
#define NSSM_STDOUT_FLAGS FILE_ATTRIBUTE_NORMAL
|
||||
#define NSSM_STDERR_SHARING (FILE_SHARE_READ | FILE_SHARE_WRITE)
|
||||
#define NSSM_STDERR_DISPOSITION OPEN_ALWAYS
|
||||
#define NSSM_STDERR_FLAGS FILE_ATTRIBUTE_NORMAL
|
||||
|
||||
typedef struct {
|
||||
TCHAR *service_name;
|
||||
TCHAR *path;
|
||||
unsigned long sharing;
|
||||
unsigned long disposition;
|
||||
unsigned long flags;
|
||||
HANDLE read_handle;
|
||||
HANDLE write_handle;
|
||||
__int64 size;
|
||||
unsigned long *tid_ptr;
|
||||
unsigned long *rotate_online;
|
||||
} logger_t;
|
||||
|
||||
int get_createfile_parameters(HKEY, TCHAR *, TCHAR *, unsigned long *, unsigned long, unsigned long *, unsigned long, unsigned long *, unsigned long);
|
||||
int set_createfile_parameter(HKEY, TCHAR *, TCHAR *, unsigned long);
|
||||
int delete_createfile_parameter(HKEY, TCHAR *, TCHAR *);
|
||||
HANDLE write_to_file(TCHAR *, unsigned long, SECURITY_ATTRIBUTES *, unsigned long, unsigned long);
|
||||
void rotate_file(TCHAR *, TCHAR *, unsigned long, unsigned long, unsigned long);
|
||||
int get_output_handles(nssm_service_t *, STARTUPINFO *);
|
||||
void close_output_handles(STARTUPINFO *);
|
||||
unsigned long WINAPI log_and_rotate(void *);
|
||||
|
||||
#endif
|
||||
Binary file not shown.
@ -0,0 +1,187 @@
|
||||
#include "nssm.h"
|
||||
|
||||
extern unsigned long tls_index;
|
||||
extern bool is_admin;
|
||||
extern imports_t imports;
|
||||
|
||||
/* Are two strings case-insensitively equivalent? */
|
||||
int str_equiv(const TCHAR *a, const TCHAR *b) {
|
||||
size_t len = _tcslen(a);
|
||||
if (_tcslen(b) != len) return 0;
|
||||
if (_tcsnicmp(a, b, len)) return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Convert a string to a number. */
|
||||
int str_number(const TCHAR *string, unsigned long *number, TCHAR **bogus) {
|
||||
if (! string) return 1;
|
||||
|
||||
*number = _tcstoul(string, bogus, 0);
|
||||
if (**bogus) return 2;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int str_number(const TCHAR *string, unsigned long *number) {
|
||||
TCHAR *bogus;
|
||||
return str_number(string, number, &bogus);
|
||||
}
|
||||
|
||||
/* Remove basename of a path. */
|
||||
void strip_basename(TCHAR *buffer) {
|
||||
size_t len = _tcslen(buffer);
|
||||
size_t i;
|
||||
for (i = len; i && buffer[i] != _T('\\') && buffer[i] != _T('/'); i--);
|
||||
/* X:\ is OK. */
|
||||
if (i && buffer[i - 1] == _T(':')) i++;
|
||||
buffer[i] = _T('\0');
|
||||
}
|
||||
|
||||
/* How to use me correctly */
|
||||
int usage(int ret) {
|
||||
if (GetConsoleWindow()) print_message(stderr, NSSM_MESSAGE_USAGE, NSSM_VERSION, NSSM_CONFIGURATION, NSSM_DATE);
|
||||
else popup_message(0, MB_OK, NSSM_MESSAGE_USAGE, NSSM_VERSION, NSSM_CONFIGURATION, NSSM_DATE);
|
||||
return(ret);
|
||||
}
|
||||
|
||||
void check_admin() {
|
||||
is_admin = false;
|
||||
|
||||
/* Lifted from MSDN examples */
|
||||
PSID AdministratorsGroup;
|
||||
SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY;
|
||||
if (! AllocateAndInitializeSid(&NtAuthority, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &AdministratorsGroup)) return;
|
||||
CheckTokenMembership(0, AdministratorsGroup, /*XXX*/(PBOOL) &is_admin);
|
||||
FreeSid(AdministratorsGroup);
|
||||
}
|
||||
|
||||
static int elevate(int argc, TCHAR **argv, unsigned long message) {
|
||||
print_message(stderr, message);
|
||||
|
||||
SHELLEXECUTEINFO sei;
|
||||
ZeroMemory(&sei, sizeof(sei));
|
||||
sei.cbSize = sizeof(sei);
|
||||
sei.lpVerb = _T("runas");
|
||||
sei.lpFile = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, PATH_LENGTH);
|
||||
if (! sei.lpFile) {
|
||||
print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("GetModuleFileName()"), _T("elevate()"));
|
||||
return 111;
|
||||
}
|
||||
GetModuleFileName(0, (TCHAR *) sei.lpFile, PATH_LENGTH);
|
||||
|
||||
TCHAR *args = (TCHAR *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, EXE_LENGTH * sizeof(TCHAR));
|
||||
if (! args) {
|
||||
HeapFree(GetProcessHeap(), 0, (void *) sei.lpFile);
|
||||
print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("GetCommandLine()"), _T("elevate()"));
|
||||
return 111;
|
||||
}
|
||||
|
||||
/* Get command line, which includes the path to NSSM, and skip that part. */
|
||||
_sntprintf_s(args, EXE_LENGTH, _TRUNCATE, _T("%s"), GetCommandLine());
|
||||
size_t s = _tcslen(argv[0]) + 1;
|
||||
if (args[0] == _T('"')) s += 2;
|
||||
while (isspace(args[s])) s++;
|
||||
|
||||
sei.lpParameters = args + s;
|
||||
sei.nShow = SW_SHOW;
|
||||
|
||||
unsigned long exitcode = 0;
|
||||
if (! ShellExecuteEx(&sei)) exitcode = 100;
|
||||
|
||||
HeapFree(GetProcessHeap(), 0, (void *) sei.lpFile);
|
||||
HeapFree(GetProcessHeap(), 0, (void *) args);
|
||||
return exitcode;
|
||||
}
|
||||
|
||||
int num_cpus() {
|
||||
DWORD_PTR i, affinity, system_affinity;
|
||||
if (! GetProcessAffinityMask(GetCurrentProcess(), &affinity, &system_affinity)) return 64;
|
||||
for (i = 0; system_affinity & (1LL << i); i++);
|
||||
return (int) i;
|
||||
}
|
||||
|
||||
int _tmain(int argc, TCHAR **argv) {
|
||||
check_console();
|
||||
|
||||
#ifdef UNICODE
|
||||
/*
|
||||
Ensure we write in UTF-16 mode, so that non-ASCII characters don't get
|
||||
mangled. If we were compiled in ANSI mode it won't work.
|
||||
*/
|
||||
_setmode(_fileno(stdout), _O_U16TEXT);
|
||||
_setmode(_fileno(stderr), _O_U16TEXT);
|
||||
#endif
|
||||
|
||||
/* Remember if we are admin */
|
||||
check_admin();
|
||||
|
||||
/* Set up function pointers. */
|
||||
if (get_imports()) exit(111);
|
||||
|
||||
/* Elevate */
|
||||
if (argc > 1) {
|
||||
/*
|
||||
Valid commands are:
|
||||
start, stop, pause, continue, install, edit, get, set, reset, unset, remove
|
||||
*/
|
||||
if (str_equiv(argv[1], _T("start"))) exit(control_service(NSSM_SERVICE_CONTROL_START, argc - 2, argv + 2));
|
||||
if (str_equiv(argv[1], _T("stop"))) exit(control_service(SERVICE_CONTROL_STOP, argc - 2, argv + 2));
|
||||
if (str_equiv(argv[1], _T("restart"))) {
|
||||
int ret = control_service(SERVICE_CONTROL_STOP, argc - 2, argv + 2);
|
||||
if (ret) exit(ret);
|
||||
exit(control_service(NSSM_SERVICE_CONTROL_START, argc - 2, argv + 2));
|
||||
}
|
||||
if (str_equiv(argv[1], _T("pause"))) exit(control_service(SERVICE_CONTROL_PAUSE, argc - 2, argv + 2));
|
||||
if (str_equiv(argv[1], _T("continue"))) exit(control_service(SERVICE_CONTROL_CONTINUE, argc - 2, argv + 2));
|
||||
if (str_equiv(argv[1], _T("status"))) exit(control_service(SERVICE_CONTROL_INTERROGATE, argc - 2, argv + 2));
|
||||
if (str_equiv(argv[1], _T("rotate"))) exit(control_service(NSSM_SERVICE_CONTROL_ROTATE, argc - 2, argv + 2));
|
||||
if (str_equiv(argv[1], _T("install"))) {
|
||||
if (! is_admin) exit(elevate(argc, argv, NSSM_MESSAGE_NOT_ADMINISTRATOR_CANNOT_INSTALL));
|
||||
exit(pre_install_service(argc - 2, argv + 2));
|
||||
}
|
||||
if (str_equiv(argv[1], _T("edit")) || str_equiv(argv[1], _T("get")) || str_equiv(argv[1], _T("set")) || str_equiv(argv[1], _T("reset")) || str_equiv(argv[1], _T("unset"))) {
|
||||
int ret = pre_edit_service(argc - 1, argv + 1);
|
||||
if (ret == 3 && ! is_admin && argc == 3) exit(elevate(argc, argv, NSSM_MESSAGE_NOT_ADMINISTRATOR_CANNOT_EDIT));
|
||||
/* There might be a password here. */
|
||||
for (int i = 0; i < argc; i++) SecureZeroMemory(argv[i], _tcslen(argv[i]) * sizeof(TCHAR));
|
||||
exit(ret);
|
||||
}
|
||||
if (str_equiv(argv[1], _T("remove"))) {
|
||||
if (! is_admin) exit(elevate(argc, argv, NSSM_MESSAGE_NOT_ADMINISTRATOR_CANNOT_REMOVE));
|
||||
exit(pre_remove_service(argc - 2, argv + 2));
|
||||
}
|
||||
}
|
||||
|
||||
/* Thread local storage for error message buffer */
|
||||
tls_index = TlsAlloc();
|
||||
|
||||
/* Register messages */
|
||||
if (is_admin) create_messages();
|
||||
|
||||
/*
|
||||
Optimisation for Windows 2000:
|
||||
When we're run from the command line the StartServiceCtrlDispatcher() call
|
||||
will time out after a few seconds on Windows 2000. On newer versions the
|
||||
call returns instantly. Check for stdin first and only try to call the
|
||||
function if there's no input stream found. Although it's possible that
|
||||
we're running with input redirected it's much more likely that we're
|
||||
actually running as a service.
|
||||
This will save time when running with no arguments from a command prompt.
|
||||
*/
|
||||
if (! GetStdHandle(STD_INPUT_HANDLE)) {
|
||||
/* Start service magic */
|
||||
SERVICE_TABLE_ENTRY table[] = { { NSSM, service_main }, { 0, 0 } };
|
||||
if (! StartServiceCtrlDispatcher(table)) {
|
||||
unsigned long error = GetLastError();
|
||||
/* User probably ran nssm with no argument */
|
||||
if (error == ERROR_FAILED_SERVICE_CONTROLLER_CONNECT) exit(usage(1));
|
||||
log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_DISPATCHER_FAILED, error_string(error), 0);
|
||||
free_imports();
|
||||
exit(100);
|
||||
}
|
||||
}
|
||||
else exit(usage(1));
|
||||
|
||||
/* And nothing more to do */
|
||||
exit(0);
|
||||
}
|
||||
@ -0,0 +1,136 @@
|
||||
#ifndef NSSM_H
|
||||
#define NSSM_H
|
||||
|
||||
/*
|
||||
MSDN says, basically, that the maximum length of a path is 260 characters,
|
||||
which is represented by the constant MAX_PATH. Except when it isn't.
|
||||
|
||||
The maximum length of a directory path is MAX_PATH - 12 because it must be
|
||||
possible to create a file in 8.3 format under any valid directory.
|
||||
|
||||
Unicode versions of filesystem API functions accept paths up to 32767
|
||||
characters if the first four (wide) characters are L"\\?\" and each component
|
||||
of the path, separated by L"\", does not exceed the value of
|
||||
lpMaximumComponentLength returned by GetVolumeInformation(), which is
|
||||
probably 255. But might not be.
|
||||
|
||||
Relative paths are always limited to MAX_PATH because the L"\\?\" prefix
|
||||
is not valid for a relative path.
|
||||
|
||||
Note that we don't care about the last two paragraphs because we're only
|
||||
concerned with allocating buffers big enough to store valid paths. If the
|
||||
user tries to store invalid paths they will fit in the buffers but the
|
||||
application will fail. The reason for the failure will end up in the
|
||||
event log and the user will realise the mistake.
|
||||
|
||||
So that's that cleared up, then.
|
||||
*/
|
||||
#ifdef UNICODE
|
||||
#define PATH_LENGTH 32767
|
||||
#else
|
||||
#define PATH_LENGTH MAX_PATH
|
||||
#endif
|
||||
#define DIR_LENGTH PATH_LENGTH - 12
|
||||
|
||||
#define _WIN32_WINNT 0x0500
|
||||
#include <fcntl.h>
|
||||
#include <io.h>
|
||||
#include <shlwapi.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <tchar.h>
|
||||
#include <windows.h>
|
||||
#include "service.h"
|
||||
#include "account.h"
|
||||
#include "console.h"
|
||||
#include "env.h"
|
||||
#include "event.h"
|
||||
#include "imports.h"
|
||||
#include "messages.h"
|
||||
#include "process.h"
|
||||
#include "registry.h"
|
||||
#include "settings.h"
|
||||
#include "io.h"
|
||||
#include "gui.h"
|
||||
|
||||
int str_equiv(const TCHAR *, const TCHAR *);
|
||||
void strip_basename(TCHAR *);
|
||||
int str_number(const TCHAR *, unsigned long *, TCHAR **);
|
||||
int str_number(const TCHAR *, unsigned long *);
|
||||
int num_cpus();
|
||||
int usage(int);
|
||||
|
||||
#define NSSM _T("NSSM")
|
||||
#ifdef _WIN64
|
||||
#define NSSM_ARCHITECTURE _T("64-bit")
|
||||
#else
|
||||
#define NSSM_ARCHITECTURE _T("32-bit")
|
||||
#endif
|
||||
#ifdef _DEBUG
|
||||
#define NSSM_DEBUG _T(" debug")
|
||||
#else
|
||||
#define NSSM_DEBUG _T("")
|
||||
#endif
|
||||
#define NSSM_CONFIGURATION NSSM_ARCHITECTURE NSSM_DEBUG
|
||||
#include "version.h"
|
||||
|
||||
/*
|
||||
Throttle the restart of the service if it stops before this many
|
||||
milliseconds have elapsed since startup. Override in registry.
|
||||
*/
|
||||
#define NSSM_RESET_THROTTLE_RESTART 1500
|
||||
|
||||
/*
|
||||
How many milliseconds to wait for the application to die after sending
|
||||
a Control-C event to its console. Override in registry.
|
||||
*/
|
||||
#define NSSM_KILL_CONSOLE_GRACE_PERIOD 1500
|
||||
/*
|
||||
How many milliseconds to wait for the application to die after posting to
|
||||
its windows' message queues. Override in registry.
|
||||
*/
|
||||
#define NSSM_KILL_WINDOW_GRACE_PERIOD 1500
|
||||
/*
|
||||
How many milliseconds to wait for the application to die after posting to
|
||||
its threads' message queues. Override in registry.
|
||||
*/
|
||||
#define NSSM_KILL_THREADS_GRACE_PERIOD 1500
|
||||
|
||||
/* Margin of error for service status wait hints in milliseconds. */
|
||||
#define NSSM_WAITHINT_MARGIN 2000
|
||||
|
||||
/* Methods used to try to stop the application. */
|
||||
#define NSSM_STOP_METHOD_CONSOLE (1 << 0)
|
||||
#define NSSM_STOP_METHOD_WINDOW (1 << 1)
|
||||
#define NSSM_STOP_METHOD_THREADS (1 << 2)
|
||||
#define NSSM_STOP_METHOD_TERMINATE (1 << 3)
|
||||
|
||||
/* Startup types. */
|
||||
#define NSSM_STARTUP_AUTOMATIC 0
|
||||
#define NSSM_STARTUP_DELAYED 1
|
||||
#define NSSM_STARTUP_MANUAL 2
|
||||
#define NSSM_STARTUP_DISABLED 3
|
||||
|
||||
/* Exit actions. */
|
||||
#define NSSM_EXIT_RESTART 0
|
||||
#define NSSM_EXIT_IGNORE 1
|
||||
#define NSSM_EXIT_REALLY 2
|
||||
#define NSSM_EXIT_UNCLEAN 3
|
||||
#define NSSM_NUM_EXIT_ACTIONS 4
|
||||
|
||||
/* Process priority. */
|
||||
#define NSSM_REALTIME_PRIORITY 0
|
||||
#define NSSM_HIGH_PRIORITY 1
|
||||
#define NSSM_ABOVE_NORMAL_PRIORITY 2
|
||||
#define NSSM_NORMAL_PRIORITY 3
|
||||
#define NSSM_BELOW_NORMAL_PRIORITY 4
|
||||
#define NSSM_IDLE_PRIORITY 5
|
||||
|
||||
/* How many milliseconds to wait before updating service status. */
|
||||
#define NSSM_SERVICE_STATUS_DEADLINE 20000
|
||||
|
||||
/* User-defined service controls can be in the range 128-255. */
|
||||
#define NSSM_SERVICE_CONTROL_START 0
|
||||
#define NSSM_SERVICE_CONTROL_ROTATE 128
|
||||
|
||||
#endif
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 31 KiB |
Binary file not shown.
@ -0,0 +1,793 @@
|
||||
<?xml version="1.0" encoding="Windows-1252"?>
|
||||
<VisualStudioProject
|
||||
ProjectType="Visual C++"
|
||||
Version="9.00"
|
||||
Name="nssm"
|
||||
ProjectGUID="{32995E05-606F-4D83-A2E6-C2B361B34DF1}"
|
||||
RootNamespace="nssm"
|
||||
TargetFrameworkVersion="0"
|
||||
>
|
||||
<Platforms>
|
||||
<Platform
|
||||
Name="Win32"
|
||||
/>
|
||||
<Platform
|
||||
Name="x64"
|
||||
/>
|
||||
</Platforms>
|
||||
<ToolFiles>
|
||||
</ToolFiles>
|
||||
<Configurations>
|
||||
<Configuration
|
||||
Name="Debug|Win32"
|
||||
OutputDirectory="out\$(ConfigurationName)\win32"
|
||||
IntermediateDirectory="tmp\$(ConfigurationName)\win32"
|
||||
ConfigurationType="1"
|
||||
InheritedPropertySheets="$(VCInstallDir)VCProjectDefaults\UpgradeFromVC60.vsprops"
|
||||
UseOfMFC="0"
|
||||
ATLMinimizesCRunTimeLibraryUsage="false"
|
||||
CharacterSet="1"
|
||||
>
|
||||
<Tool
|
||||
Name="VCPreBuildEventTool"
|
||||
Description="Setting version information"
|
||||
CommandLine="version.cmd"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCCustomBuildTool"
|
||||
Description=""
|
||||
CommandLine=""
|
||||
Outputs=""
|
||||
/>
|
||||
<Tool
|
||||
Name="VCXMLDataGeneratorTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCWebServiceProxyGeneratorTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCMIDLTool"
|
||||
TypeLibraryName="$(IntDir)/$(ProjectName).tlb"
|
||||
HeaderFileName=""
|
||||
/>
|
||||
<Tool
|
||||
Name="VCCLCompilerTool"
|
||||
Optimization="0"
|
||||
PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE"
|
||||
MinimalRebuild="true"
|
||||
BasicRuntimeChecks="3"
|
||||
RuntimeLibrary="1"
|
||||
PrecompiledHeaderFile="$(IntDir)/$(ProjectName).pch"
|
||||
AssemblerListingLocation="$(IntDir)/"
|
||||
ObjectFile="$(IntDir)/"
|
||||
ProgramDataBaseFileName="$(IntDir)/"
|
||||
WarningLevel="3"
|
||||
SuppressStartupBanner="true"
|
||||
DebugInformationFormat="4"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCManagedResourceCompilerTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCResourceCompilerTool"
|
||||
PreprocessorDefinitions="_DEBUG"
|
||||
Culture="2057"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCPreLinkEventTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCLinkerTool"
|
||||
AdditionalDependencies="shlwapi.lib"
|
||||
LinkIncremental="2"
|
||||
SuppressStartupBanner="true"
|
||||
GenerateDebugInformation="true"
|
||||
ProgramDatabaseFile="$(OutDir)/$(ProjectName).pdb"
|
||||
SubSystem="1"
|
||||
RandomizedBaseAddress="1"
|
||||
DataExecutionPrevention="0"
|
||||
TargetMachine="1"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCALinkTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCManifestTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCXDCMakeTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCBscMakeTool"
|
||||
SuppressStartupBanner="true"
|
||||
OutputFile="$(IntDir)/$(ProjectName).bsc"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCFxCopTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCAppVerifierTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCPostBuildEventTool"
|
||||
/>
|
||||
</Configuration>
|
||||
<Configuration
|
||||
Name="Debug|x64"
|
||||
OutputDirectory="out\$(ConfigurationName)\win64"
|
||||
IntermediateDirectory="tmp\$(ConfigurationName)\win64"
|
||||
ConfigurationType="1"
|
||||
InheritedPropertySheets="$(VCInstallDir)VCProjectDefaults\UpgradeFromVC60.vsprops"
|
||||
UseOfMFC="0"
|
||||
ATLMinimizesCRunTimeLibraryUsage="false"
|
||||
CharacterSet="1"
|
||||
>
|
||||
<Tool
|
||||
Name="VCPreBuildEventTool"
|
||||
Description="Setting version information"
|
||||
CommandLine="version.cmd"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCCustomBuildTool"
|
||||
Description=""
|
||||
CommandLine=""
|
||||
Outputs=""
|
||||
/>
|
||||
<Tool
|
||||
Name="VCXMLDataGeneratorTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCWebServiceProxyGeneratorTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCMIDLTool"
|
||||
TargetEnvironment="3"
|
||||
TypeLibraryName="$(IntDir)/$(ProjectName).tlb"
|
||||
HeaderFileName=""
|
||||
/>
|
||||
<Tool
|
||||
Name="VCCLCompilerTool"
|
||||
Optimization="0"
|
||||
PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE"
|
||||
MinimalRebuild="true"
|
||||
BasicRuntimeChecks="3"
|
||||
RuntimeLibrary="1"
|
||||
PrecompiledHeaderFile="$(IntDir)/$(ProjectName).pch"
|
||||
AssemblerListingLocation="$(IntDir)/"
|
||||
ObjectFile="$(IntDir)/"
|
||||
ProgramDataBaseFileName="$(IntDir)/"
|
||||
WarningLevel="3"
|
||||
SuppressStartupBanner="true"
|
||||
DebugInformationFormat="3"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCManagedResourceCompilerTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCResourceCompilerTool"
|
||||
PreprocessorDefinitions="_DEBUG;_WIN64"
|
||||
Culture="2057"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCPreLinkEventTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCLinkerTool"
|
||||
AdditionalDependencies="shlwapi.lib"
|
||||
LinkIncremental="2"
|
||||
SuppressStartupBanner="true"
|
||||
GenerateDebugInformation="true"
|
||||
ProgramDatabaseFile="$(OutDir)/$(ProjectName).pdb"
|
||||
SubSystem="1"
|
||||
RandomizedBaseAddress="1"
|
||||
DataExecutionPrevention="0"
|
||||
TargetMachine="17"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCALinkTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCManifestTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCXDCMakeTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCBscMakeTool"
|
||||
SuppressStartupBanner="true"
|
||||
OutputFile="$(IntDir)/$(ProjectName).bsc"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCFxCopTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCAppVerifierTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCPostBuildEventTool"
|
||||
/>
|
||||
</Configuration>
|
||||
<Configuration
|
||||
Name="Release|Win32"
|
||||
OutputDirectory="out\$(ConfigurationName)\win32"
|
||||
IntermediateDirectory="tmp\$(ConfigurationName)\win32"
|
||||
ConfigurationType="1"
|
||||
InheritedPropertySheets="$(VCInstallDir)VCProjectDefaults\UpgradeFromVC60.vsprops"
|
||||
UseOfMFC="0"
|
||||
ATLMinimizesCRunTimeLibraryUsage="false"
|
||||
CharacterSet="1"
|
||||
>
|
||||
<Tool
|
||||
Name="VCPreBuildEventTool"
|
||||
Description="Setting version information"
|
||||
CommandLine="version.cmd"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCCustomBuildTool"
|
||||
Description=""
|
||||
CommandLine=""
|
||||
Outputs=""
|
||||
/>
|
||||
<Tool
|
||||
Name="VCXMLDataGeneratorTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCWebServiceProxyGeneratorTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCMIDLTool"
|
||||
TypeLibraryName="$(IntDir)/$(ProjectName).tlb"
|
||||
HeaderFileName=""
|
||||
/>
|
||||
<Tool
|
||||
Name="VCCLCompilerTool"
|
||||
Optimization="2"
|
||||
InlineFunctionExpansion="1"
|
||||
PreprocessorDefinitions="WIN32;NDEBUG;_CONSOLE"
|
||||
StringPooling="true"
|
||||
RuntimeLibrary="0"
|
||||
EnableFunctionLevelLinking="true"
|
||||
PrecompiledHeaderFile="$(IntDir)/$(ProjectName).pch"
|
||||
AssemblerListingLocation="$(IntDir)/"
|
||||
ObjectFile="$(IntDir)/"
|
||||
ProgramDataBaseFileName="$(IntDir)/"
|
||||
WarningLevel="3"
|
||||
SuppressStartupBanner="true"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCManagedResourceCompilerTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCResourceCompilerTool"
|
||||
PreprocessorDefinitions="NDEBUG"
|
||||
Culture="2057"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCPreLinkEventTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCLinkerTool"
|
||||
AdditionalDependencies="shlwapi.lib"
|
||||
LinkIncremental="1"
|
||||
SuppressStartupBanner="true"
|
||||
ProgramDatabaseFile="$(OutDir)/$(ProjectName).pdb"
|
||||
SubSystem="1"
|
||||
RandomizedBaseAddress="1"
|
||||
DataExecutionPrevention="0"
|
||||
TargetMachine="1"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCALinkTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCManifestTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCXDCMakeTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCBscMakeTool"
|
||||
SuppressStartupBanner="true"
|
||||
OutputFile="$(IntDir)/$(ProjectName).bsc"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCFxCopTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCAppVerifierTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCPostBuildEventTool"
|
||||
/>
|
||||
</Configuration>
|
||||
<Configuration
|
||||
Name="Release|x64"
|
||||
OutputDirectory="out\$(ConfigurationName)\win64"
|
||||
IntermediateDirectory="tmp\$(ConfigurationName)\win64"
|
||||
ConfigurationType="1"
|
||||
InheritedPropertySheets="$(VCInstallDir)VCProjectDefaults\UpgradeFromVC60.vsprops"
|
||||
UseOfMFC="0"
|
||||
ATLMinimizesCRunTimeLibraryUsage="false"
|
||||
CharacterSet="1"
|
||||
>
|
||||
<Tool
|
||||
Name="VCPreBuildEventTool"
|
||||
Description="Setting version information"
|
||||
CommandLine="version.cmd"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCCustomBuildTool"
|
||||
Description=""
|
||||
CommandLine=""
|
||||
Outputs=""
|
||||
/>
|
||||
<Tool
|
||||
Name="VCXMLDataGeneratorTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCWebServiceProxyGeneratorTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCMIDLTool"
|
||||
TargetEnvironment="3"
|
||||
TypeLibraryName="$(IntDir)/$(ProjectName).tlb"
|
||||
HeaderFileName=""
|
||||
/>
|
||||
<Tool
|
||||
Name="VCCLCompilerTool"
|
||||
Optimization="2"
|
||||
InlineFunctionExpansion="1"
|
||||
PreprocessorDefinitions="WIN32;NDEBUG;_CONSOLE"
|
||||
StringPooling="true"
|
||||
RuntimeLibrary="0"
|
||||
EnableFunctionLevelLinking="true"
|
||||
PrecompiledHeaderFile="$(IntDir)/$(ProjectName).pch"
|
||||
AssemblerListingLocation="$(IntDir)/"
|
||||
ObjectFile="$(IntDir)/"
|
||||
ProgramDataBaseFileName="$(IntDir)/"
|
||||
WarningLevel="3"
|
||||
SuppressStartupBanner="true"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCManagedResourceCompilerTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCResourceCompilerTool"
|
||||
PreprocessorDefinitions="NDEBUG;_WIN64"
|
||||
Culture="2057"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCPreLinkEventTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCLinkerTool"
|
||||
AdditionalDependencies="shlwapi.lib"
|
||||
LinkIncremental="1"
|
||||
SuppressStartupBanner="true"
|
||||
ProgramDatabaseFile="$(OutDir)/$(ProjectName).pdb"
|
||||
SubSystem="1"
|
||||
RandomizedBaseAddress="1"
|
||||
DataExecutionPrevention="0"
|
||||
TargetMachine="17"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCALinkTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCManifestTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCXDCMakeTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCBscMakeTool"
|
||||
SuppressStartupBanner="true"
|
||||
OutputFile="$(IntDir)/$(ProjectName).bsc"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCFxCopTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCAppVerifierTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCPostBuildEventTool"
|
||||
/>
|
||||
</Configuration>
|
||||
</Configurations>
|
||||
<References>
|
||||
</References>
|
||||
<Files>
|
||||
<Filter
|
||||
Name="Source Files"
|
||||
Filter="cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"
|
||||
>
|
||||
<File
|
||||
RelativePath="account.cpp"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="console.cpp"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="env.cpp"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="event.cpp"
|
||||
>
|
||||
<FileConfiguration
|
||||
Name="Debug|Win32"
|
||||
>
|
||||
<Tool
|
||||
Name="VCCLCompilerTool"
|
||||
PreprocessorDefinitions=""
|
||||
/>
|
||||
</FileConfiguration>
|
||||
<FileConfiguration
|
||||
Name="Debug|x64"
|
||||
>
|
||||
<Tool
|
||||
Name="VCCLCompilerTool"
|
||||
PreprocessorDefinitions=""
|
||||
/>
|
||||
</FileConfiguration>
|
||||
<FileConfiguration
|
||||
Name="Release|Win32"
|
||||
>
|
||||
<Tool
|
||||
Name="VCCLCompilerTool"
|
||||
PreprocessorDefinitions=""
|
||||
/>
|
||||
</FileConfiguration>
|
||||
<FileConfiguration
|
||||
Name="Release|x64"
|
||||
>
|
||||
<Tool
|
||||
Name="VCCLCompilerTool"
|
||||
PreprocessorDefinitions=""
|
||||
/>
|
||||
</FileConfiguration>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="gui.cpp"
|
||||
>
|
||||
<FileConfiguration
|
||||
Name="Debug|Win32"
|
||||
>
|
||||
<Tool
|
||||
Name="VCCLCompilerTool"
|
||||
PreprocessorDefinitions=""
|
||||
/>
|
||||
</FileConfiguration>
|
||||
<FileConfiguration
|
||||
Name="Debug|x64"
|
||||
>
|
||||
<Tool
|
||||
Name="VCCLCompilerTool"
|
||||
PreprocessorDefinitions=""
|
||||
/>
|
||||
</FileConfiguration>
|
||||
<FileConfiguration
|
||||
Name="Release|Win32"
|
||||
>
|
||||
<Tool
|
||||
Name="VCCLCompilerTool"
|
||||
PreprocessorDefinitions=""
|
||||
/>
|
||||
</FileConfiguration>
|
||||
<FileConfiguration
|
||||
Name="Release|x64"
|
||||
>
|
||||
<Tool
|
||||
Name="VCCLCompilerTool"
|
||||
PreprocessorDefinitions=""
|
||||
/>
|
||||
</FileConfiguration>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="imports.cpp"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="io.cpp"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="nssm.cpp"
|
||||
>
|
||||
<FileConfiguration
|
||||
Name="Debug|Win32"
|
||||
>
|
||||
<Tool
|
||||
Name="VCCLCompilerTool"
|
||||
PreprocessorDefinitions=""
|
||||
/>
|
||||
</FileConfiguration>
|
||||
<FileConfiguration
|
||||
Name="Debug|x64"
|
||||
>
|
||||
<Tool
|
||||
Name="VCCLCompilerTool"
|
||||
PreprocessorDefinitions=""
|
||||
/>
|
||||
</FileConfiguration>
|
||||
<FileConfiguration
|
||||
Name="Release|Win32"
|
||||
>
|
||||
<Tool
|
||||
Name="VCCLCompilerTool"
|
||||
PreprocessorDefinitions=""
|
||||
/>
|
||||
</FileConfiguration>
|
||||
<FileConfiguration
|
||||
Name="Release|x64"
|
||||
>
|
||||
<Tool
|
||||
Name="VCCLCompilerTool"
|
||||
PreprocessorDefinitions=""
|
||||
/>
|
||||
</FileConfiguration>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="process.cpp"
|
||||
>
|
||||
<FileConfiguration
|
||||
Name="Debug|Win32"
|
||||
>
|
||||
<Tool
|
||||
Name="VCCLCompilerTool"
|
||||
PreprocessorDefinitions=""
|
||||
/>
|
||||
</FileConfiguration>
|
||||
<FileConfiguration
|
||||
Name="Debug|x64"
|
||||
>
|
||||
<Tool
|
||||
Name="VCCLCompilerTool"
|
||||
PreprocessorDefinitions=""
|
||||
/>
|
||||
</FileConfiguration>
|
||||
<FileConfiguration
|
||||
Name="Release|Win32"
|
||||
>
|
||||
<Tool
|
||||
Name="VCCLCompilerTool"
|
||||
PreprocessorDefinitions=""
|
||||
/>
|
||||
</FileConfiguration>
|
||||
<FileConfiguration
|
||||
Name="Release|x64"
|
||||
>
|
||||
<Tool
|
||||
Name="VCCLCompilerTool"
|
||||
PreprocessorDefinitions=""
|
||||
/>
|
||||
</FileConfiguration>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="registry.cpp"
|
||||
>
|
||||
<FileConfiguration
|
||||
Name="Debug|Win32"
|
||||
>
|
||||
<Tool
|
||||
Name="VCCLCompilerTool"
|
||||
PreprocessorDefinitions=""
|
||||
/>
|
||||
</FileConfiguration>
|
||||
<FileConfiguration
|
||||
Name="Debug|x64"
|
||||
>
|
||||
<Tool
|
||||
Name="VCCLCompilerTool"
|
||||
PreprocessorDefinitions=""
|
||||
/>
|
||||
</FileConfiguration>
|
||||
<FileConfiguration
|
||||
Name="Release|Win32"
|
||||
>
|
||||
<Tool
|
||||
Name="VCCLCompilerTool"
|
||||
PreprocessorDefinitions=""
|
||||
/>
|
||||
</FileConfiguration>
|
||||
<FileConfiguration
|
||||
Name="Release|x64"
|
||||
>
|
||||
<Tool
|
||||
Name="VCCLCompilerTool"
|
||||
PreprocessorDefinitions=""
|
||||
/>
|
||||
</FileConfiguration>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="service.cpp"
|
||||
>
|
||||
<FileConfiguration
|
||||
Name="Debug|Win32"
|
||||
>
|
||||
<Tool
|
||||
Name="VCCLCompilerTool"
|
||||
PreprocessorDefinitions=""
|
||||
/>
|
||||
</FileConfiguration>
|
||||
<FileConfiguration
|
||||
Name="Debug|x64"
|
||||
>
|
||||
<Tool
|
||||
Name="VCCLCompilerTool"
|
||||
PreprocessorDefinitions=""
|
||||
/>
|
||||
</FileConfiguration>
|
||||
<FileConfiguration
|
||||
Name="Release|Win32"
|
||||
>
|
||||
<Tool
|
||||
Name="VCCLCompilerTool"
|
||||
PreprocessorDefinitions=""
|
||||
/>
|
||||
</FileConfiguration>
|
||||
<FileConfiguration
|
||||
Name="Release|x64"
|
||||
>
|
||||
<Tool
|
||||
Name="VCCLCompilerTool"
|
||||
PreprocessorDefinitions=""
|
||||
/>
|
||||
</FileConfiguration>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="settings.cpp"
|
||||
>
|
||||
</File>
|
||||
</Filter>
|
||||
<Filter
|
||||
Name="Header Files"
|
||||
Filter="h;hpp;hxx;hm;inl"
|
||||
>
|
||||
<File
|
||||
RelativePath="account.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="console.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="env.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="event.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="gui.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="imports.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="io.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="nssm.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="process.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="registry.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="service.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="settings.h"
|
||||
>
|
||||
</File>
|
||||
</Filter>
|
||||
<Filter
|
||||
Name="Resource Files"
|
||||
Filter="ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe"
|
||||
>
|
||||
<File
|
||||
RelativePath="nssm.ico"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="nssm.rc"
|
||||
>
|
||||
<FileConfiguration
|
||||
Name="Debug|Win32"
|
||||
>
|
||||
<Tool
|
||||
Name="VCResourceCompilerTool"
|
||||
PreprocessorDefinitions=""
|
||||
/>
|
||||
</FileConfiguration>
|
||||
<FileConfiguration
|
||||
Name="Debug|x64"
|
||||
>
|
||||
<Tool
|
||||
Name="VCResourceCompilerTool"
|
||||
PreprocessorDefinitions=""
|
||||
/>
|
||||
</FileConfiguration>
|
||||
<FileConfiguration
|
||||
Name="Release|Win32"
|
||||
>
|
||||
<Tool
|
||||
Name="VCResourceCompilerTool"
|
||||
PreprocessorDefinitions=""
|
||||
/>
|
||||
</FileConfiguration>
|
||||
<FileConfiguration
|
||||
Name="Release|x64"
|
||||
>
|
||||
<Tool
|
||||
Name="VCResourceCompilerTool"
|
||||
PreprocessorDefinitions=""
|
||||
/>
|
||||
</FileConfiguration>
|
||||
</File>
|
||||
</Filter>
|
||||
<File
|
||||
RelativePath="messages.mc"
|
||||
>
|
||||
<FileConfiguration
|
||||
Name="Debug|Win32"
|
||||
>
|
||||
<Tool
|
||||
Name="VCCustomBuildTool"
|
||||
Description="Compiling messages"
|
||||
CommandLine="mc -u -U $(InputName).mc -r . -h ."
|
||||
Outputs="$(InputName).rc;$(InputName).h"
|
||||
/>
|
||||
</FileConfiguration>
|
||||
<FileConfiguration
|
||||
Name="Debug|x64"
|
||||
>
|
||||
<Tool
|
||||
Name="VCCustomBuildTool"
|
||||
Description="Compiling messages"
|
||||
CommandLine="mc -u -U $(InputName).mc -r . -h ."
|
||||
Outputs="$(InputName).rc;$(InputName).h"
|
||||
/>
|
||||
</FileConfiguration>
|
||||
<FileConfiguration
|
||||
Name="Release|Win32"
|
||||
>
|
||||
<Tool
|
||||
Name="VCCustomBuildTool"
|
||||
Description="Compiling messages"
|
||||
CommandLine="mc -u -U $(InputName).mc -r . -h ."
|
||||
AdditionalDependencies=""
|
||||
Outputs="$(InputName).rc;$(InputName).h"
|
||||
/>
|
||||
</FileConfiguration>
|
||||
<FileConfiguration
|
||||
Name="Release|x64"
|
||||
>
|
||||
<Tool
|
||||
Name="VCCustomBuildTool"
|
||||
Description="Compiling messages"
|
||||
CommandLine="mc -u -U $(InputName).mc -r . -h ."
|
||||
AdditionalDependencies=""
|
||||
Outputs="$(InputName).rc;$(InputName).h"
|
||||
/>
|
||||
</FileConfiguration>
|
||||
</File>
|
||||
</Files>
|
||||
<Globals>
|
||||
</Globals>
|
||||
</VisualStudioProject>
|
||||
@ -0,0 +1,308 @@
|
||||
#include "nssm.h"
|
||||
|
||||
extern imports_t imports;
|
||||
|
||||
int get_process_creation_time(HANDLE process_handle, FILETIME *ft) {
|
||||
FILETIME creation_time, exit_time, kernel_time, user_time;
|
||||
|
||||
if (! GetProcessTimes(process_handle, &creation_time, &exit_time, &kernel_time, &user_time)) {
|
||||
log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GETPROCESSTIMES_FAILED, error_string(GetLastError()), 0);
|
||||
return 1;
|
||||
}
|
||||
|
||||
memmove(ft, &creation_time, sizeof(creation_time));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int get_process_exit_time(HANDLE process_handle, FILETIME *ft) {
|
||||
FILETIME creation_time, exit_time, kernel_time, user_time;
|
||||
|
||||
if (! GetProcessTimes(process_handle, &creation_time, &exit_time, &kernel_time, &user_time)) {
|
||||
log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GETPROCESSTIMES_FAILED, error_string(GetLastError()), 0);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (! (exit_time.dwLowDateTime || exit_time.dwHighDateTime)) return 2;
|
||||
memmove(ft, &exit_time, sizeof(exit_time));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int check_parent(nssm_service_t *service, PROCESSENTRY32 *pe, unsigned long ppid) {
|
||||
/* Check parent process ID matches. */
|
||||
if (pe->th32ParentProcessID != ppid) return 1;
|
||||
|
||||
/*
|
||||
Process IDs can be reused so do a sanity check by making sure the child
|
||||
has been running for less time than the parent.
|
||||
Though unlikely, it's possible that the parent exited and its process ID
|
||||
was already reused, so we'll also compare against its exit time.
|
||||
*/
|
||||
HANDLE process_handle = OpenProcess(PROCESS_QUERY_INFORMATION, false, pe->th32ProcessID);
|
||||
if (! process_handle) {
|
||||
TCHAR pid_string[16];
|
||||
_sntprintf_s(pid_string, _countof(pid_string), _TRUNCATE, _T("%lu"), pe->th32ProcessID);
|
||||
log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OPENPROCESS_FAILED, pid_string, service->name, error_string(GetLastError()), 0);
|
||||
return 2;
|
||||
}
|
||||
|
||||
FILETIME ft;
|
||||
if (get_process_creation_time(process_handle, &ft)) {
|
||||
CloseHandle(process_handle);
|
||||
return 3;
|
||||
}
|
||||
|
||||
CloseHandle(process_handle);
|
||||
|
||||
/* Verify that the parent's creation time is not later. */
|
||||
if (CompareFileTime(&service->creation_time, &ft) > 0) return 4;
|
||||
|
||||
/* Verify that the parent's exit time is not earlier. */
|
||||
if (CompareFileTime(&service->exit_time, &ft) < 0) return 5;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Send some window messages and hope the window respects one or more. */
|
||||
int CALLBACK kill_window(HWND window, LPARAM arg) {
|
||||
kill_t *k = (kill_t *) arg;
|
||||
|
||||
unsigned long pid;
|
||||
if (! GetWindowThreadProcessId(window, &pid)) return 1;
|
||||
if (pid != k->pid) return 1;
|
||||
|
||||
/* First try sending WM_CLOSE to request that the window close. */
|
||||
k->signalled |= PostMessage(window, WM_CLOSE, k->exitcode, 0);
|
||||
|
||||
/*
|
||||
Then tell the window that the user is logging off and it should exit
|
||||
without worrying about saving any data.
|
||||
*/
|
||||
k->signalled |= PostMessage(window, WM_ENDSESSION, 1, ENDSESSION_CLOSEAPP | ENDSESSION_CRITICAL | ENDSESSION_LOGOFF);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
Try to post a message to the message queues of threads associated with the
|
||||
given process ID. Not all threads have message queues so there's no
|
||||
guarantee of success, and we don't want to be left waiting for unsignalled
|
||||
processes so this function returns only true if at least one thread was
|
||||
successfully prodded.
|
||||
*/
|
||||
int kill_threads(TCHAR *service_name, kill_t *k) {
|
||||
int ret = 0;
|
||||
|
||||
/* Get a snapshot of all threads in the system. */
|
||||
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
|
||||
if (! snapshot) {
|
||||
log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETOOLHELP32SNAPSHOT_THREAD_FAILED, service_name, error_string(GetLastError()), 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
THREADENTRY32 te;
|
||||
ZeroMemory(&te, sizeof(te));
|
||||
te.dwSize = sizeof(te);
|
||||
|
||||
if (! Thread32First(snapshot, &te)) {
|
||||
log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_THREAD_ENUMERATE_FAILED, service_name, error_string(GetLastError()), 0);
|
||||
CloseHandle(snapshot);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* This thread belongs to the doomed process so signal it. */
|
||||
if (te.th32OwnerProcessID == k->pid) {
|
||||
ret |= PostThreadMessage(te.th32ThreadID, WM_QUIT, k->exitcode, 0);
|
||||
}
|
||||
|
||||
while (true) {
|
||||
/* Try to get the next thread. */
|
||||
if (! Thread32Next(snapshot, &te)) {
|
||||
unsigned long error = GetLastError();
|
||||
if (error == ERROR_NO_MORE_FILES) break;
|
||||
log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_THREAD_ENUMERATE_FAILED, service_name, error_string(GetLastError()), 0);
|
||||
CloseHandle(snapshot);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (te.th32OwnerProcessID == k->pid) {
|
||||
ret |= PostThreadMessage(te.th32ThreadID, WM_QUIT, k->exitcode, 0);
|
||||
}
|
||||
}
|
||||
|
||||
CloseHandle(snapshot);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Give the process a chance to die gracefully. */
|
||||
int kill_process(nssm_service_t *service, HANDLE process_handle, unsigned long pid, unsigned long exitcode) {
|
||||
/* Shouldn't happen. */
|
||||
if (! service) return 1;
|
||||
if (! pid) return 1;
|
||||
if (! process_handle) return 1;
|
||||
|
||||
unsigned long ret;
|
||||
if (GetExitCodeProcess(process_handle, &ret)) {
|
||||
if (ret != STILL_ACTIVE) return 1;
|
||||
}
|
||||
|
||||
kill_t k = { pid, exitcode, 0 };
|
||||
|
||||
/* Try to send a Control-C event to the console. */
|
||||
if (service->stop_method & NSSM_STOP_METHOD_CONSOLE) {
|
||||
if (! kill_console(service, &k)) return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
Try to post messages to the windows belonging to the given process ID.
|
||||
If the process is a console application it won't have any windows so there's
|
||||
no guarantee of success.
|
||||
*/
|
||||
if (service->stop_method & NSSM_STOP_METHOD_WINDOW) {
|
||||
EnumWindows((WNDENUMPROC) kill_window, (LPARAM) &k);
|
||||
if (k.signalled) {
|
||||
if (! await_shutdown(service, _T(__FUNCTION__), service->kill_window_delay)) return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Try to post messages to any thread message queues associated with the
|
||||
process. Console applications might have them (but probably won't) so
|
||||
there's still no guarantee of success.
|
||||
*/
|
||||
if (service->stop_method & NSSM_STOP_METHOD_THREADS) {
|
||||
if (kill_threads(service->name, &k)) {
|
||||
if (! await_shutdown(service, _T(__FUNCTION__), service->kill_threads_delay)) return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* We tried being nice. Time for extreme prejudice. */
|
||||
if (service->stop_method & NSSM_STOP_METHOD_TERMINATE) {
|
||||
return TerminateProcess(process_handle, exitcode);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Simulate a Control-C event to our console (shared with the app). */
|
||||
int kill_console(nssm_service_t *service, kill_t *k) {
|
||||
unsigned long ret;
|
||||
|
||||
if (! service) return 1;
|
||||
|
||||
/* Check we loaded AttachConsole(). */
|
||||
if (! imports.AttachConsole) return 4;
|
||||
|
||||
/* Try to attach to the process's console. */
|
||||
if (! imports.AttachConsole(k->pid)) {
|
||||
ret = GetLastError();
|
||||
|
||||
switch (ret) {
|
||||
case ERROR_INVALID_HANDLE:
|
||||
/* The app doesn't have a console. */
|
||||
return 1;
|
||||
|
||||
case ERROR_GEN_FAILURE:
|
||||
/* The app already exited. */
|
||||
return 2;
|
||||
|
||||
case ERROR_ACCESS_DENIED:
|
||||
default:
|
||||
/* We already have a console. */
|
||||
log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_ATTACHCONSOLE_FAILED, service->name, error_string(ret), 0);
|
||||
return 3;
|
||||
}
|
||||
}
|
||||
|
||||
/* Ignore the event ourselves. */
|
||||
ret = 0;
|
||||
if (! SetConsoleCtrlHandler(0, TRUE)) {
|
||||
log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SETCONSOLECTRLHANDLER_FAILED, service->name, error_string(GetLastError()), 0);
|
||||
ret = 4;
|
||||
}
|
||||
|
||||
/* Send the event. */
|
||||
if (! ret) {
|
||||
if (! GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0)) {
|
||||
log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GENERATECONSOLECTRLEVENT_FAILED, service->name, error_string(GetLastError()), 0);
|
||||
ret = 5;
|
||||
}
|
||||
}
|
||||
|
||||
/* Detach from the console. */
|
||||
if (! FreeConsole()) {
|
||||
log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_FREECONSOLE_FAILED, service->name, error_string(GetLastError()), 0);
|
||||
}
|
||||
|
||||
/* Wait for process to exit. */
|
||||
if (await_shutdown(service, _T(__FUNCTION__), service->kill_console_delay)) ret = 6;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void kill_process_tree(nssm_service_t *service, unsigned long pid, unsigned long exitcode, unsigned long ppid) {
|
||||
/* Shouldn't happen unless the service failed to start. */
|
||||
if (! pid) return;
|
||||
|
||||
TCHAR pid_string[16], code[16];
|
||||
_sntprintf_s(pid_string, _countof(pid_string), _TRUNCATE, _T("%lu"), pid);
|
||||
_sntprintf_s(code, _countof(code), _TRUNCATE, _T("%lu"), exitcode);
|
||||
log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_KILLING, service->name, pid_string, code, 0);
|
||||
|
||||
/* We will need a process handle in order to call TerminateProcess() later. */
|
||||
HANDLE process_handle = OpenProcess(SYNCHRONIZE | PROCESS_QUERY_INFORMATION | PROCESS_VM_READ | PROCESS_TERMINATE, false, pid);
|
||||
if (process_handle) {
|
||||
/* Kill this process first, then its descendents. */
|
||||
TCHAR ppid_string[16];
|
||||
_sntprintf_s(ppid_string, _countof(ppid_string), _TRUNCATE, _T("%lu"), ppid);
|
||||
log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_KILL_PROCESS_TREE, pid_string, ppid_string, service->name, 0);
|
||||
if (! kill_process(service, process_handle, pid, exitcode)) {
|
||||
/* Maybe it already died. */
|
||||
unsigned long ret;
|
||||
if (! GetExitCodeProcess(process_handle, &ret) || ret == STILL_ACTIVE) {
|
||||
if (service->stop_method & NSSM_STOP_METHOD_TERMINATE) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_TERMINATEPROCESS_FAILED, pid_string, service->name, error_string(GetLastError()), 0);
|
||||
else log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_PROCESS_STILL_ACTIVE, service->name, pid_string, NSSM, NSSM_REG_STOP_METHOD_SKIP, 0);
|
||||
}
|
||||
}
|
||||
|
||||
CloseHandle(process_handle);
|
||||
}
|
||||
else log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OPENPROCESS_FAILED, pid_string, service->name, error_string(GetLastError()), 0);
|
||||
|
||||
/* Get a snapshot of all processes in the system. */
|
||||
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
||||
if (! snapshot) {
|
||||
log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETOOLHELP32SNAPSHOT_PROCESS_FAILED, service->name, error_string(GetLastError()), 0);
|
||||
return;
|
||||
}
|
||||
|
||||
PROCESSENTRY32 pe;
|
||||
ZeroMemory(&pe, sizeof(pe));
|
||||
pe.dwSize = sizeof(pe);
|
||||
|
||||
if (! Process32First(snapshot, &pe)) {
|
||||
log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_PROCESS_ENUMERATE_FAILED, service->name, error_string(GetLastError()), 0);
|
||||
CloseHandle(snapshot);
|
||||
return;
|
||||
}
|
||||
|
||||
/* This is a child of the doomed process so kill it. */
|
||||
if (! check_parent(service, &pe, pid)) kill_process_tree(service, pe.th32ProcessID, exitcode, ppid);
|
||||
|
||||
while (true) {
|
||||
/* Try to get the next process. */
|
||||
if (! Process32Next(snapshot, &pe)) {
|
||||
unsigned long ret = GetLastError();
|
||||
if (ret == ERROR_NO_MORE_FILES) break;
|
||||
log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_PROCESS_ENUMERATE_FAILED, service->name, error_string(GetLastError()), 0);
|
||||
CloseHandle(snapshot);
|
||||
return;
|
||||
}
|
||||
|
||||
if (! check_parent(service, &pe, pid)) kill_process_tree(service, pe.th32ProcessID, exitcode, ppid);
|
||||
}
|
||||
|
||||
CloseHandle(snapshot);
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
#ifndef PROCESS_H
|
||||
#define PROCESS_H
|
||||
|
||||
#include <tlhelp32.h>
|
||||
|
||||
typedef struct {
|
||||
unsigned long pid;
|
||||
unsigned long exitcode;
|
||||
int signalled;
|
||||
} kill_t;
|
||||
|
||||
int get_process_creation_time(HANDLE, FILETIME *);
|
||||
int get_process_exit_time(HANDLE, FILETIME *);
|
||||
int check_parent(TCHAR *, PROCESSENTRY32 *, unsigned long, FILETIME *, FILETIME *);
|
||||
int CALLBACK kill_window(HWND, LPARAM);
|
||||
int kill_threads(nssm_service_t *, kill_t *);
|
||||
int kill_console(nssm_service_t *, kill_t *);
|
||||
int kill_process(nssm_service_t *, HANDLE, unsigned long, unsigned long);
|
||||
void kill_process_tree(nssm_service_t *, unsigned long, unsigned long, unsigned long);
|
||||
|
||||
#endif
|
||||
@ -0,0 +1,714 @@
|
||||
#include "nssm.h"
|
||||
|
||||
extern const TCHAR *exit_action_strings[];
|
||||
|
||||
int create_messages() {
|
||||
HKEY key;
|
||||
|
||||
TCHAR registry[KEY_LENGTH];
|
||||
if (_sntprintf_s(registry, _countof(registry), _TRUNCATE, _T("SYSTEM\\CurrentControlSet\\Services\\EventLog\\Application\\%s"), NSSM) < 0) {
|
||||
log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("eventlog registry"), _T("create_messages()"), 0);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (RegCreateKeyEx(HKEY_LOCAL_MACHINE, registry, 0, 0, REG_OPTION_NON_VOLATILE, KEY_WRITE, 0, &key, 0) != ERROR_SUCCESS) {
|
||||
log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OPENKEY_FAILED, registry, error_string(GetLastError()), 0);
|
||||
return 2;
|
||||
}
|
||||
|
||||
/* Get path of this program */
|
||||
TCHAR path[PATH_LENGTH];
|
||||
GetModuleFileName(0, path, _countof(path));
|
||||
|
||||
/* Try to register the module but don't worry so much on failure */
|
||||
RegSetValueEx(key, _T("EventMessageFile"), 0, REG_SZ, (const unsigned char *) path, (unsigned long) (_tcslen(path) + 1) * sizeof(TCHAR));
|
||||
unsigned long types = EVENTLOG_INFORMATION_TYPE | EVENTLOG_WARNING_TYPE | EVENTLOG_ERROR_TYPE;
|
||||
RegSetValueEx(key, _T("TypesSupported"), 0, REG_DWORD, (const unsigned char *) &types, sizeof(types));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int create_parameters(nssm_service_t *service, bool editing) {
|
||||
/* Try to open the registry */
|
||||
HKEY key = open_registry(service->name, KEY_WRITE);
|
||||
if (! key) return 1;
|
||||
|
||||
/* Try to create the parameters */
|
||||
if (set_expand_string(key, NSSM_REG_EXE, service->exe)) {
|
||||
RegDeleteKey(HKEY_LOCAL_MACHINE, NSSM_REGISTRY);
|
||||
RegCloseKey(key);
|
||||
return 2;
|
||||
}
|
||||
if (set_expand_string(key, NSSM_REG_FLAGS, service->flags)) {
|
||||
RegDeleteKey(HKEY_LOCAL_MACHINE, NSSM_REGISTRY);
|
||||
RegCloseKey(key);
|
||||
return 3;
|
||||
}
|
||||
if (set_expand_string(key, NSSM_REG_DIR, service->dir)) {
|
||||
RegDeleteKey(HKEY_LOCAL_MACHINE, NSSM_REGISTRY);
|
||||
RegCloseKey(key);
|
||||
return 4;
|
||||
}
|
||||
|
||||
/* Other non-default parameters. May fail. */
|
||||
if (service->priority != NORMAL_PRIORITY_CLASS) set_number(key, NSSM_REG_PRIORITY, service->priority);
|
||||
else if (editing) RegDeleteValue(key, NSSM_REG_PRIORITY);
|
||||
if (service->affinity) {
|
||||
TCHAR *string;
|
||||
if (! affinity_mask_to_string(service->affinity, &string)) {
|
||||
if (RegSetValueEx(key, NSSM_REG_AFFINITY, 0, REG_SZ, (const unsigned char *) string, (unsigned long) (_tcslen(string) + 1) * sizeof(TCHAR)) != ERROR_SUCCESS) {
|
||||
log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SETVALUE_FAILED, NSSM_REG_AFFINITY, error_string(GetLastError()), 0);
|
||||
HeapFree(GetProcessHeap(), 0, string);
|
||||
return 5;
|
||||
}
|
||||
}
|
||||
if (string) HeapFree(GetProcessHeap(), 0, string);
|
||||
}
|
||||
else if (editing) RegDeleteValue(key, NSSM_REG_AFFINITY);
|
||||
unsigned long stop_method_skip = ~service->stop_method;
|
||||
if (stop_method_skip) set_number(key, NSSM_REG_STOP_METHOD_SKIP, stop_method_skip);
|
||||
else if (editing) RegDeleteValue(key, NSSM_REG_STOP_METHOD_SKIP);
|
||||
if (service->default_exit_action < NSSM_NUM_EXIT_ACTIONS) create_exit_action(service->name, exit_action_strings[service->default_exit_action], editing);
|
||||
if (service->restart_delay) set_number(key, NSSM_REG_RESTART_DELAY, service->restart_delay);
|
||||
else if (editing) RegDeleteValue(key, NSSM_REG_RESTART_DELAY);
|
||||
if (service->throttle_delay != NSSM_RESET_THROTTLE_RESTART) set_number(key, NSSM_REG_THROTTLE, service->throttle_delay);
|
||||
else if (editing) RegDeleteValue(key, NSSM_REG_THROTTLE);
|
||||
if (service->kill_console_delay != NSSM_KILL_CONSOLE_GRACE_PERIOD) set_number(key, NSSM_REG_KILL_CONSOLE_GRACE_PERIOD, service->kill_console_delay);
|
||||
else if (editing) RegDeleteValue(key, NSSM_REG_KILL_CONSOLE_GRACE_PERIOD);
|
||||
if (service->kill_window_delay != NSSM_KILL_WINDOW_GRACE_PERIOD) set_number(key, NSSM_REG_KILL_WINDOW_GRACE_PERIOD, service->kill_window_delay);
|
||||
else if (editing) RegDeleteValue(key, NSSM_REG_KILL_WINDOW_GRACE_PERIOD);
|
||||
if (service->kill_threads_delay != NSSM_KILL_THREADS_GRACE_PERIOD) set_number(key, NSSM_REG_KILL_THREADS_GRACE_PERIOD, service->kill_threads_delay);
|
||||
else if (editing) RegDeleteValue(key, NSSM_REG_KILL_THREADS_GRACE_PERIOD);
|
||||
if (service->stdin_path[0] || editing) {
|
||||
if (service->stdin_path[0]) set_expand_string(key, NSSM_REG_STDIN, service->stdin_path);
|
||||
else if (editing) RegDeleteValue(key, NSSM_REG_STDIN);
|
||||
if (service->stdin_sharing != NSSM_STDIN_SHARING) set_createfile_parameter(key, NSSM_REG_STDIN, NSSM_REG_STDIO_SHARING, service->stdin_sharing);
|
||||
else if (editing) delete_createfile_parameter(key, NSSM_REG_STDIN, NSSM_REG_STDIO_SHARING);
|
||||
if (service->stdin_disposition != NSSM_STDIN_DISPOSITION) set_createfile_parameter(key, NSSM_REG_STDIN, NSSM_REG_STDIO_DISPOSITION, service->stdin_disposition);
|
||||
else if (editing) delete_createfile_parameter(key, NSSM_REG_STDIN, NSSM_REG_STDIO_DISPOSITION);
|
||||
if (service->stdin_flags != NSSM_STDIN_FLAGS) set_createfile_parameter(key, NSSM_REG_STDIN, NSSM_REG_STDIO_FLAGS, service->stdin_flags);
|
||||
else if (editing) delete_createfile_parameter(key, NSSM_REG_STDIN, NSSM_REG_STDIO_FLAGS);
|
||||
}
|
||||
if (service->stdout_path[0] || editing) {
|
||||
if (service->stdout_path[0]) set_expand_string(key, NSSM_REG_STDOUT, service->stdout_path);
|
||||
else if (editing) RegDeleteValue(key, NSSM_REG_STDOUT);
|
||||
if (service->stdout_sharing != NSSM_STDOUT_SHARING) set_createfile_parameter(key, NSSM_REG_STDOUT, NSSM_REG_STDIO_SHARING, service->stdout_sharing);
|
||||
else if (editing) delete_createfile_parameter(key, NSSM_REG_STDOUT, NSSM_REG_STDIO_SHARING);
|
||||
if (service->stdout_disposition != NSSM_STDOUT_DISPOSITION) set_createfile_parameter(key, NSSM_REG_STDOUT, NSSM_REG_STDIO_DISPOSITION, service->stdout_disposition);
|
||||
else if (editing) delete_createfile_parameter(key, NSSM_REG_STDOUT, NSSM_REG_STDIO_DISPOSITION);
|
||||
if (service->stdout_flags != NSSM_STDOUT_FLAGS) set_createfile_parameter(key, NSSM_REG_STDOUT, NSSM_REG_STDIO_FLAGS, service->stdout_flags);
|
||||
else if (editing) delete_createfile_parameter(key, NSSM_REG_STDOUT, NSSM_REG_STDIO_FLAGS);
|
||||
}
|
||||
if (service->stderr_path[0] || editing) {
|
||||
if (service->stderr_path[0]) set_expand_string(key, NSSM_REG_STDERR, service->stderr_path);
|
||||
else if (editing) RegDeleteValue(key, NSSM_REG_STDERR);
|
||||
if (service->stderr_sharing != NSSM_STDERR_SHARING) set_createfile_parameter(key, NSSM_REG_STDERR, NSSM_REG_STDIO_SHARING, service->stderr_sharing);
|
||||
else if (editing) delete_createfile_parameter(key, NSSM_REG_STDERR, NSSM_REG_STDIO_SHARING);
|
||||
if (service->stderr_disposition != NSSM_STDERR_DISPOSITION) set_createfile_parameter(key, NSSM_REG_STDERR, NSSM_REG_STDIO_DISPOSITION, service->stderr_disposition);
|
||||
else if (editing) delete_createfile_parameter(key, NSSM_REG_STDERR, NSSM_REG_STDIO_DISPOSITION);
|
||||
if (service->stderr_flags != NSSM_STDERR_FLAGS) set_createfile_parameter(key, NSSM_REG_STDERR, NSSM_REG_STDIO_FLAGS, service->stderr_flags);
|
||||
else if (editing) delete_createfile_parameter(key, NSSM_REG_STDERR, NSSM_REG_STDIO_FLAGS);
|
||||
}
|
||||
if (service->rotate_files) set_number(key, NSSM_REG_ROTATE, 1);
|
||||
else if (editing) RegDeleteValue(key, NSSM_REG_ROTATE);
|
||||
if (service->rotate_stdout_online) set_number(key, NSSM_REG_ROTATE_ONLINE, 1);
|
||||
else if (editing) RegDeleteValue(key, NSSM_REG_ROTATE_ONLINE);
|
||||
if (service->rotate_seconds) set_number(key, NSSM_REG_ROTATE_SECONDS, service->rotate_seconds);
|
||||
else if (editing) RegDeleteValue(key, NSSM_REG_ROTATE_SECONDS);
|
||||
if (service->rotate_bytes_low) set_number(key, NSSM_REG_ROTATE_BYTES_LOW, service->rotate_bytes_low);
|
||||
else if (editing) RegDeleteValue(key, NSSM_REG_ROTATE_BYTES_LOW);
|
||||
if (service->rotate_bytes_high) set_number(key, NSSM_REG_ROTATE_BYTES_HIGH, service->rotate_bytes_high);
|
||||
else if (editing) RegDeleteValue(key, NSSM_REG_ROTATE_BYTES_HIGH);
|
||||
if (service->no_console) set_number(key, NSSM_REG_NO_CONSOLE, 1);
|
||||
else if (editing) RegDeleteValue(key, NSSM_REG_NO_CONSOLE);
|
||||
|
||||
/* Environment */
|
||||
if (service->env) {
|
||||
if (RegSetValueEx(key, NSSM_REG_ENV, 0, REG_MULTI_SZ, (const unsigned char *) service->env, (unsigned long) service->envlen * sizeof(TCHAR)) != ERROR_SUCCESS) {
|
||||
log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SETVALUE_FAILED, NSSM_REG_ENV, error_string(GetLastError()), 0);
|
||||
}
|
||||
}
|
||||
else if (editing) RegDeleteValue(key, NSSM_REG_ENV);
|
||||
if (service->env_extra) {
|
||||
if (RegSetValueEx(key, NSSM_REG_ENV_EXTRA, 0, REG_MULTI_SZ, (const unsigned char *) service->env_extra, (unsigned long) service->env_extralen * sizeof(TCHAR)) != ERROR_SUCCESS) {
|
||||
log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SETVALUE_FAILED, NSSM_REG_ENV_EXTRA, error_string(GetLastError()), 0);
|
||||
}
|
||||
}
|
||||
else if (editing) RegDeleteValue(key, NSSM_REG_ENV_EXTRA);
|
||||
|
||||
/* Close registry. */
|
||||
RegCloseKey(key);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int create_exit_action(TCHAR *service_name, const TCHAR *action_string, bool editing) {
|
||||
/* Get registry */
|
||||
TCHAR registry[KEY_LENGTH];
|
||||
if (_sntprintf_s(registry, _countof(registry), _TRUNCATE, NSSM_REGISTRY _T("\\%s"), service_name, NSSM_REG_EXIT) < 0) {
|
||||
log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("NSSM_REG_EXIT"), _T("create_exit_action()"), 0);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Try to open the registry */
|
||||
HKEY key;
|
||||
unsigned long disposition;
|
||||
if (RegCreateKeyEx(HKEY_LOCAL_MACHINE, registry, 0, 0, REG_OPTION_NON_VOLATILE, KEY_WRITE, 0, &key, &disposition) != ERROR_SUCCESS) {
|
||||
log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OPENKEY_FAILED, registry, error_string(GetLastError()), 0);
|
||||
return 2;
|
||||
}
|
||||
|
||||
/* Do nothing if the key already existed */
|
||||
if (disposition == REG_OPENED_EXISTING_KEY && ! editing) {
|
||||
RegCloseKey(key);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Create the default value */
|
||||
if (RegSetValueEx(key, 0, 0, REG_SZ, (const unsigned char *) action_string, (unsigned long) (_tcslen(action_string) + 1) * sizeof(TCHAR)) != ERROR_SUCCESS) {
|
||||
log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SETVALUE_FAILED, NSSM_REG_EXIT, error_string(GetLastError()), 0);
|
||||
RegCloseKey(key);
|
||||
return 3;
|
||||
}
|
||||
|
||||
/* Close registry */
|
||||
RegCloseKey(key);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int get_environment(TCHAR *service_name, HKEY key, TCHAR *value, TCHAR **env, unsigned long *envlen) {
|
||||
unsigned long type = REG_MULTI_SZ;
|
||||
|
||||
/* Dummy test to find buffer size */
|
||||
unsigned long ret = RegQueryValueEx(key, value, 0, &type, NULL, envlen);
|
||||
if (ret != ERROR_SUCCESS) {
|
||||
*env = 0;
|
||||
*envlen = 0;
|
||||
/* The service probably doesn't have any environment configured */
|
||||
if (ret == ERROR_FILE_NOT_FOUND) return 0;
|
||||
log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_QUERYVALUE_FAILED, value, error_string(GetLastError()), 0);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (type != REG_MULTI_SZ) {
|
||||
*env = 0;
|
||||
*envlen = 0;
|
||||
log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_INVALID_ENVIRONMENT_STRING_TYPE, value, service_name, 0);
|
||||
return 2;
|
||||
}
|
||||
|
||||
/* Probably not possible */
|
||||
if (! *envlen) return 0;
|
||||
|
||||
/* Previously initialised? */
|
||||
if (*env) HeapFree(GetProcessHeap(), 0, *env);
|
||||
|
||||
*env = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, *envlen);
|
||||
if (! *env) {
|
||||
*envlen = 0;
|
||||
log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, value, _T("get_environment()"), 0);
|
||||
return 3;
|
||||
}
|
||||
|
||||
/* Actually get the strings */
|
||||
ret = RegQueryValueEx(key, value, 0, &type, (unsigned char *) *env, envlen);
|
||||
if (ret != ERROR_SUCCESS) {
|
||||
log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_QUERYVALUE_FAILED, value, error_string(GetLastError()), 0);
|
||||
HeapFree(GetProcessHeap(), 0, *env);
|
||||
*env = 0;
|
||||
*envlen = 0;
|
||||
return 4;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int get_string(HKEY key, TCHAR *value, TCHAR *data, unsigned long datalen, bool expand, bool sanitise, bool must_exist) {
|
||||
TCHAR *buffer = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, datalen);
|
||||
if (! buffer) {
|
||||
log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, value, _T("get_string()"), 0);
|
||||
return 1;
|
||||
}
|
||||
|
||||
ZeroMemory(data, datalen);
|
||||
|
||||
unsigned long type = REG_EXPAND_SZ;
|
||||
unsigned long buflen = datalen;
|
||||
|
||||
unsigned long ret = RegQueryValueEx(key, value, 0, &type, (unsigned char *) buffer, &buflen);
|
||||
if (ret != ERROR_SUCCESS) {
|
||||
unsigned long error = GetLastError();
|
||||
HeapFree(GetProcessHeap(), 0, buffer);
|
||||
|
||||
if (ret == ERROR_FILE_NOT_FOUND) {
|
||||
if (! must_exist) return 0;
|
||||
}
|
||||
|
||||
log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_QUERYVALUE_FAILED, value, error_string(error), 0);
|
||||
return 2;
|
||||
}
|
||||
|
||||
/* Paths aren't allowed to contain quotes. */
|
||||
if (sanitise) PathUnquoteSpaces(buffer);
|
||||
|
||||
/* Do we want to expand the string? */
|
||||
if (! expand) {
|
||||
if (type == REG_EXPAND_SZ) type = REG_SZ;
|
||||
}
|
||||
|
||||
/* Technically we shouldn't expand environment strings from REG_SZ values */
|
||||
if (type != REG_EXPAND_SZ) {
|
||||
memmove(data, buffer, buflen);
|
||||
HeapFree(GetProcessHeap(), 0, buffer);
|
||||
return 0;
|
||||
}
|
||||
|
||||
ret = ExpandEnvironmentStrings((TCHAR *) buffer, data, datalen);
|
||||
if (! ret || ret > datalen) {
|
||||
log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_EXPANDENVIRONMENTSTRINGS_FAILED, buffer, error_string(GetLastError()), 0);
|
||||
HeapFree(GetProcessHeap(), 0, buffer);
|
||||
return 3;
|
||||
}
|
||||
|
||||
HeapFree(GetProcessHeap(), 0, buffer);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int get_string(HKEY key, TCHAR *value, TCHAR *data, unsigned long datalen, bool sanitise) {
|
||||
return get_string(key, value, data, datalen, false, sanitise, true);
|
||||
}
|
||||
|
||||
int expand_parameter(HKEY key, TCHAR *value, TCHAR *data, unsigned long datalen, bool sanitise, bool must_exist) {
|
||||
return get_string(key, value, data, datalen, true, sanitise, must_exist);
|
||||
}
|
||||
|
||||
int expand_parameter(HKEY key, TCHAR *value, TCHAR *data, unsigned long datalen, bool sanitise) {
|
||||
return expand_parameter(key, value, data, datalen, sanitise, true);
|
||||
}
|
||||
|
||||
/*
|
||||
Sets a string in the registry.
|
||||
Returns: 0 if it was set.
|
||||
1 on error.
|
||||
*/
|
||||
int set_string(HKEY key, TCHAR *value, TCHAR *string, bool expand) {
|
||||
unsigned long type = expand ? REG_EXPAND_SZ : REG_SZ;
|
||||
if (RegSetValueEx(key, value, 0, type, (const unsigned char *) string, (unsigned long) (_tcslen(string) + 1) * sizeof(TCHAR)) == ERROR_SUCCESS) return 0;
|
||||
log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SETVALUE_FAILED, value, error_string(GetLastError()), 0);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int set_string(HKEY key, TCHAR *value, TCHAR *string) {
|
||||
return set_string(key, value, string, false);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int set_expand_string(HKEY key, TCHAR *value, TCHAR *string) {
|
||||
return set_string(key, value, string, true);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
Set an unsigned long in the registry.
|
||||
Returns: 0 if it was set.
|
||||
1 on error.
|
||||
*/
|
||||
int set_number(HKEY key, TCHAR *value, unsigned long number) {
|
||||
if (RegSetValueEx(key, value, 0, REG_DWORD, (const unsigned char *) &number, sizeof(number)) == ERROR_SUCCESS) return 0;
|
||||
log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SETVALUE_FAILED, value, error_string(GetLastError()), 0);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
Query an unsigned long from the registry.
|
||||
Returns: 1 if a number was retrieved.
|
||||
0 if none was found and must_exist is false.
|
||||
-1 if none was found and must_exist is true.
|
||||
-2 otherwise.
|
||||
*/
|
||||
int get_number(HKEY key, TCHAR *value, unsigned long *number, bool must_exist) {
|
||||
unsigned long type = REG_DWORD;
|
||||
unsigned long number_len = sizeof(unsigned long);
|
||||
|
||||
int ret = RegQueryValueEx(key, value, 0, &type, (unsigned char *) number, &number_len);
|
||||
if (ret == ERROR_SUCCESS) return 1;
|
||||
|
||||
if (ret == ERROR_FILE_NOT_FOUND) {
|
||||
if (! must_exist) return 0;
|
||||
}
|
||||
|
||||
log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_QUERYVALUE_FAILED, value, error_string(GetLastError()), 0);
|
||||
if (ret == ERROR_FILE_NOT_FOUND) return -1;
|
||||
|
||||
return -2;
|
||||
}
|
||||
|
||||
int get_number(HKEY key, TCHAR *value, unsigned long *number) {
|
||||
return get_number(key, value, number, true);
|
||||
}
|
||||
|
||||
/* Replace NULL with CRLF. Leave NULL NULL as the end marker. */
|
||||
int format_double_null(TCHAR *dn, unsigned long dnlen, TCHAR **formatted, unsigned long *newlen) {
|
||||
unsigned long i, j;
|
||||
*newlen = dnlen;
|
||||
|
||||
if (! *newlen) {
|
||||
*formatted = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (i = 0; i < dnlen; i++) if (! dn[i] && dn[i + 1]) ++*newlen;
|
||||
|
||||
*formatted = (TCHAR *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, *newlen * sizeof(TCHAR));
|
||||
if (! *formatted) {
|
||||
*newlen = 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
for (i = 0, j = 0; i < dnlen; i++) {
|
||||
(*formatted)[j] = dn[i];
|
||||
if (! dn[i]) {
|
||||
if (dn[i + 1]) {
|
||||
(*formatted)[j] = _T('\r');
|
||||
(*formatted)[++j] = _T('\n');
|
||||
}
|
||||
}
|
||||
j++;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Strip CR and replace LF with NULL. */
|
||||
int unformat_double_null(TCHAR *dn, unsigned long dnlen, TCHAR **unformatted, unsigned long *newlen) {
|
||||
unsigned long i, j;
|
||||
*newlen = 0;
|
||||
|
||||
if (! dnlen) {
|
||||
*unformatted = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (i = 0; i < dnlen; i++) if (dn[i] != _T('\r')) ++*newlen;
|
||||
|
||||
/* Skip blank lines. */
|
||||
for (i = 0; i < dnlen; i++) {
|
||||
if (dn[i] == _T('\r') && dn[i + 1] == _T('\n')) {
|
||||
/* This is the last CRLF. */
|
||||
if (i >= dnlen - 2) break;
|
||||
|
||||
/*
|
||||
Strip at the start of the block or if the next characters are
|
||||
CRLF too.
|
||||
*/
|
||||
if (! i || (dn[i + 2] == _T('\r') && dn[i + 3] == _T('\n'))) {
|
||||
for (j = i + 2; j < dnlen; j++) dn[j - 2] = dn[j];
|
||||
dn[dnlen--] = _T('\0');
|
||||
dn[dnlen--] = _T('\0');
|
||||
i--;
|
||||
--*newlen;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Must end with two NULLs. */
|
||||
*newlen += 2;
|
||||
|
||||
*unformatted = (TCHAR *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, *newlen * sizeof(TCHAR));
|
||||
if (! *unformatted) return 1;
|
||||
|
||||
for (i = 0, j = 0; i < dnlen; i++) {
|
||||
if (dn[i] == _T('\r')) continue;
|
||||
if (dn[i] == _T('\n')) (*unformatted)[j] = _T('\0');
|
||||
else (*unformatted)[j] = dn[i];
|
||||
j++;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void override_milliseconds(TCHAR *service_name, HKEY key, TCHAR *value, unsigned long *buffer, unsigned long default_value, unsigned long event) {
|
||||
unsigned long type = REG_DWORD;
|
||||
unsigned long buflen = sizeof(unsigned long);
|
||||
bool ok = false;
|
||||
unsigned long ret = RegQueryValueEx(key, value, 0, &type, (unsigned char *) buffer, &buflen);
|
||||
if (ret != ERROR_SUCCESS) {
|
||||
if (ret != ERROR_FILE_NOT_FOUND) {
|
||||
if (type != REG_DWORD) {
|
||||
TCHAR milliseconds[16];
|
||||
_sntprintf_s(milliseconds, _countof(milliseconds), _TRUNCATE, _T("%lu"), default_value);
|
||||
log_event(EVENTLOG_WARNING_TYPE, event, service_name, value, milliseconds, 0);
|
||||
}
|
||||
else log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_QUERYVALUE_FAILED, value, error_string(GetLastError()), 0);
|
||||
}
|
||||
}
|
||||
else ok = true;
|
||||
|
||||
if (! ok) *buffer = default_value;
|
||||
}
|
||||
|
||||
HKEY open_registry(const TCHAR *service_name, const TCHAR *sub, REGSAM sam) {
|
||||
/* Get registry */
|
||||
TCHAR registry[KEY_LENGTH];
|
||||
HKEY key;
|
||||
int ret;
|
||||
|
||||
if (sub) ret = _sntprintf_s(registry, _countof(registry), _TRUNCATE, NSSM_REGISTRY _T("\\%s"), service_name, sub);
|
||||
else ret = _sntprintf_s(registry, _countof(registry), _TRUNCATE, NSSM_REGISTRY, service_name);
|
||||
if (ret < 0) {
|
||||
log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("NSSM_REGISTRY"), _T("open_registry()"), 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (sam & KEY_WRITE) {
|
||||
if (RegCreateKeyEx(HKEY_LOCAL_MACHINE, registry, 0, 0, REG_OPTION_NON_VOLATILE, sam, 0, &key, 0) != ERROR_SUCCESS) {
|
||||
log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OPENKEY_FAILED, registry, error_string(GetLastError()), 0);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, registry, 0, sam, &key) != ERROR_SUCCESS) {
|
||||
log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OPENKEY_FAILED, registry, error_string(GetLastError()), 0);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
HKEY open_registry(const TCHAR *service_name, REGSAM sam) {
|
||||
return open_registry(service_name, 0, sam);
|
||||
}
|
||||
|
||||
int get_io_parameters(nssm_service_t *service, HKEY key) {
|
||||
/* stdin */
|
||||
if (get_createfile_parameters(key, NSSM_REG_STDIN, service->stdin_path, &service->stdin_sharing, NSSM_STDIN_SHARING, &service->stdin_disposition, NSSM_STDIN_DISPOSITION, &service->stdin_flags, NSSM_STDIN_FLAGS)) {
|
||||
service->stdin_sharing = service->stdin_disposition = service->stdin_flags = 0;
|
||||
ZeroMemory(service->stdin_path, _countof(service->stdin_path) * sizeof(TCHAR));
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* stdout */
|
||||
if (get_createfile_parameters(key, NSSM_REG_STDOUT, service->stdout_path, &service->stdout_sharing, NSSM_STDOUT_SHARING, &service->stdout_disposition, NSSM_STDOUT_DISPOSITION, &service->stdout_flags, NSSM_STDOUT_FLAGS)) {
|
||||
service->stdout_sharing = service->stdout_disposition = service->stdout_flags = 0;
|
||||
ZeroMemory(service->stdout_path, _countof(service->stdout_path) * sizeof(TCHAR));
|
||||
return 2;
|
||||
}
|
||||
|
||||
/* stderr */
|
||||
if (get_createfile_parameters(key, NSSM_REG_STDERR, service->stderr_path, &service->stderr_sharing, NSSM_STDERR_SHARING, &service->stderr_disposition, NSSM_STDERR_DISPOSITION, &service->stderr_flags, NSSM_STDERR_FLAGS)) {
|
||||
service->stderr_sharing = service->stderr_disposition = service->stderr_flags = 0;
|
||||
ZeroMemory(service->stderr_path, _countof(service->stderr_path) * sizeof(TCHAR));
|
||||
return 3;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int get_parameters(nssm_service_t *service, STARTUPINFO *si) {
|
||||
unsigned long ret;
|
||||
|
||||
/* Try to open the registry */
|
||||
HKEY key = open_registry(service->name, KEY_READ);
|
||||
if (! key) return 1;
|
||||
|
||||
/* Don't expand parameters when retrieving for the GUI. */
|
||||
bool expand = si ? true : false;
|
||||
|
||||
/* Try to get executable file - MUST succeed */
|
||||
if (get_string(key, NSSM_REG_EXE, service->exe, sizeof(service->exe), expand, false, true)) {
|
||||
RegCloseKey(key);
|
||||
return 3;
|
||||
}
|
||||
|
||||
/* Try to get flags - may fail and we don't care */
|
||||
if (get_string(key, NSSM_REG_FLAGS, service->flags, sizeof(service->flags), expand, false, true)) {
|
||||
log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_NO_FLAGS, NSSM_REG_FLAGS, service->name, service->exe, 0);
|
||||
ZeroMemory(service->flags, sizeof(service->flags));
|
||||
}
|
||||
|
||||
/* Try to get startup directory - may fail and we fall back to a default */
|
||||
if (get_string(key, NSSM_REG_DIR, service->dir, sizeof(service->dir), expand, true, true) || ! service->dir[0]) {
|
||||
_sntprintf_s(service->dir, _countof(service->dir), _TRUNCATE, _T("%s"), service->exe);
|
||||
strip_basename(service->dir);
|
||||
if (service->dir[0] == _T('\0')) {
|
||||
/* Help! */
|
||||
ret = GetWindowsDirectory(service->dir, sizeof(service->dir));
|
||||
if (! ret || ret > sizeof(service->dir)) {
|
||||
log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_NO_DIR_AND_NO_FALLBACK, NSSM_REG_DIR, service->name, 0);
|
||||
RegCloseKey(key);
|
||||
return 4;
|
||||
}
|
||||
}
|
||||
log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_NO_DIR, NSSM_REG_DIR, service->name, service->dir, 0);
|
||||
}
|
||||
|
||||
/* Try to get processor affinity - may fail. */
|
||||
TCHAR buffer[512];
|
||||
if (get_string(key, NSSM_REG_AFFINITY, buffer, sizeof(buffer), false, false, false) || ! buffer[0]) service->affinity = 0LL;
|
||||
else if (affinity_string_to_mask(buffer, &service->affinity)) {
|
||||
log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_BOGUS_AFFINITY_MASK, service->name, buffer);
|
||||
service->affinity = 0LL;
|
||||
}
|
||||
else {
|
||||
DWORD_PTR affinity, system_affinity;
|
||||
|
||||
if (GetProcessAffinityMask(GetCurrentProcess(), &affinity, &system_affinity)) {
|
||||
_int64 effective_affinity = service->affinity & system_affinity;
|
||||
if (effective_affinity != service->affinity) {
|
||||
TCHAR *system = 0;
|
||||
if (! affinity_mask_to_string(system_affinity, &system)) {
|
||||
TCHAR *effective = 0;
|
||||
if (! affinity_mask_to_string(effective_affinity, &effective)) {
|
||||
log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_EFFECTIVE_AFFINITY_MASK, service->name, buffer, system, effective, 0);
|
||||
}
|
||||
HeapFree(GetProcessHeap(), 0, effective);
|
||||
}
|
||||
HeapFree(GetProcessHeap(), 0, system);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Try to get environment variables - may fail */
|
||||
get_environment(service->name, key, NSSM_REG_ENV, &service->env, &service->envlen);
|
||||
/* Environment variables to add to existing rather than replace - may fail. */
|
||||
get_environment(service->name, key, NSSM_REG_ENV_EXTRA, &service->env_extra, &service->env_extralen);
|
||||
|
||||
/* Try to get priority - may fail. */
|
||||
unsigned long priority;
|
||||
if (get_number(key, NSSM_REG_PRIORITY, &priority, false) == 1) {
|
||||
if (priority == (priority & priority_mask())) service->priority = priority;
|
||||
else log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_BOGUS_PRIORITY, service->name, NSSM_REG_PRIORITY, 0);
|
||||
}
|
||||
|
||||
/* Try to get file rotation settings - may fail. */
|
||||
unsigned long rotate_files;
|
||||
if (get_number(key, NSSM_REG_ROTATE, &rotate_files, false) == 1) {
|
||||
if (rotate_files) service->rotate_files = true;
|
||||
else service->rotate_files = false;
|
||||
}
|
||||
else service->rotate_files = false;
|
||||
if (get_number(key, NSSM_REG_ROTATE_ONLINE, &rotate_files, false) == 1) {
|
||||
if (rotate_files) service->rotate_stdout_online = service->rotate_stderr_online = true;
|
||||
else service->rotate_stdout_online = service->rotate_stderr_online = false;
|
||||
}
|
||||
else service->rotate_stdout_online = service->rotate_stderr_online = false;
|
||||
if (get_number(key, NSSM_REG_ROTATE_SECONDS, &service->rotate_seconds, false) != 1) service->rotate_seconds = 0;
|
||||
if (get_number(key, NSSM_REG_ROTATE_BYTES_LOW, &service->rotate_bytes_low, false) != 1) service->rotate_bytes_low = 0;
|
||||
if (get_number(key, NSSM_REG_ROTATE_BYTES_HIGH, &service->rotate_bytes_high, false) != 1) service->rotate_bytes_high = 0;
|
||||
|
||||
/* Try to get force new console setting - may fail. */
|
||||
if (get_number(key, NSSM_REG_NO_CONSOLE, &service->no_console, false) != 1) service->no_console = 0;
|
||||
|
||||
/* Change to startup directory in case stdout/stderr are relative paths. */
|
||||
TCHAR cwd[PATH_LENGTH];
|
||||
GetCurrentDirectory(_countof(cwd), cwd);
|
||||
SetCurrentDirectory(service->dir);
|
||||
|
||||
/* Try to get stdout and stderr */
|
||||
if (get_io_parameters(service, key)) {
|
||||
log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GET_OUTPUT_HANDLES_FAILED, service->name, 0);
|
||||
RegCloseKey(key);
|
||||
SetCurrentDirectory(cwd);
|
||||
return 5;
|
||||
}
|
||||
|
||||
/* Change back in case the startup directory needs to be deleted. */
|
||||
SetCurrentDirectory(cwd);
|
||||
|
||||
/* Try to get mandatory restart delay */
|
||||
override_milliseconds(service->name, key, NSSM_REG_RESTART_DELAY, &service->restart_delay, 0, NSSM_EVENT_BOGUS_RESTART_DELAY);
|
||||
|
||||
/* Try to get throttle restart delay */
|
||||
override_milliseconds(service->name, key, NSSM_REG_THROTTLE, &service->throttle_delay, NSSM_RESET_THROTTLE_RESTART, NSSM_EVENT_BOGUS_THROTTLE);
|
||||
|
||||
/* Try to get service stop flags. */
|
||||
unsigned long type = REG_DWORD;
|
||||
unsigned long stop_method_skip;
|
||||
unsigned long buflen = sizeof(stop_method_skip);
|
||||
bool stop_ok = false;
|
||||
ret = RegQueryValueEx(key, NSSM_REG_STOP_METHOD_SKIP, 0, &type, (unsigned char *) &stop_method_skip, &buflen);
|
||||
if (ret != ERROR_SUCCESS) {
|
||||
if (ret != ERROR_FILE_NOT_FOUND) {
|
||||
if (type != REG_DWORD) {
|
||||
log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_BOGUS_STOP_METHOD_SKIP, service->name, NSSM_REG_STOP_METHOD_SKIP, NSSM, 0);
|
||||
}
|
||||
else log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_QUERYVALUE_FAILED, NSSM_REG_STOP_METHOD_SKIP, error_string(GetLastError()), 0);
|
||||
}
|
||||
}
|
||||
else stop_ok = true;
|
||||
|
||||
/* Try all methods except those requested to be skipped. */
|
||||
service->stop_method = ~0;
|
||||
if (stop_ok) service->stop_method &= ~stop_method_skip;
|
||||
|
||||
/* Try to get kill delays - may fail. */
|
||||
override_milliseconds(service->name, key, NSSM_REG_KILL_CONSOLE_GRACE_PERIOD, &service->kill_console_delay, NSSM_KILL_CONSOLE_GRACE_PERIOD, NSSM_EVENT_BOGUS_KILL_CONSOLE_GRACE_PERIOD);
|
||||
override_milliseconds(service->name, key, NSSM_REG_KILL_WINDOW_GRACE_PERIOD, &service->kill_window_delay, NSSM_KILL_WINDOW_GRACE_PERIOD, NSSM_EVENT_BOGUS_KILL_WINDOW_GRACE_PERIOD);
|
||||
override_milliseconds(service->name, key, NSSM_REG_KILL_THREADS_GRACE_PERIOD, &service->kill_threads_delay, NSSM_KILL_THREADS_GRACE_PERIOD, NSSM_EVENT_BOGUS_KILL_THREADS_GRACE_PERIOD);
|
||||
|
||||
/* Try to get default exit action. */
|
||||
bool default_action;
|
||||
service->default_exit_action = NSSM_EXIT_RESTART;
|
||||
TCHAR action_string[ACTION_LEN];
|
||||
if (! get_exit_action(service->name, 0, action_string, &default_action)) {
|
||||
for (int i = 0; exit_action_strings[i]; i++) {
|
||||
if (! _tcsnicmp((const TCHAR *) action_string, exit_action_strings[i], ACTION_LEN)) {
|
||||
service->default_exit_action = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Close registry */
|
||||
RegCloseKey(key);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
Sets the string for the exit action corresponding to the exit code.
|
||||
|
||||
ret is a pointer to an unsigned long containing the exit code.
|
||||
If ret is NULL, we retrieve the default exit action unconditionally.
|
||||
|
||||
action is a buffer which receives the string.
|
||||
|
||||
default_action is a pointer to a bool which is set to false if there
|
||||
was an explicit string for the given exit code, or true if we are
|
||||
returning the default action.
|
||||
|
||||
Returns: 0 on success.
|
||||
1 on error.
|
||||
*/
|
||||
int get_exit_action(const TCHAR *service_name, unsigned long *ret, TCHAR *action, bool *default_action) {
|
||||
/* Are we returning the default action or a status-specific one? */
|
||||
*default_action = ! ret;
|
||||
|
||||
/* Try to open the registry */
|
||||
HKEY key = open_registry(service_name, NSSM_REG_EXIT, KEY_READ);
|
||||
if (! key) return 1;
|
||||
|
||||
unsigned long type = REG_SZ;
|
||||
unsigned long action_len = ACTION_LEN;
|
||||
|
||||
TCHAR code[16];
|
||||
if (! ret) code[0] = _T('\0');
|
||||
else if (_sntprintf_s(code, _countof(code), _TRUNCATE, _T("%lu"), *ret) < 0) {
|
||||
RegCloseKey(key);
|
||||
return get_exit_action(service_name, 0, action, default_action);
|
||||
}
|
||||
if (RegQueryValueEx(key, code, 0, &type, (unsigned char *) action, &action_len) != ERROR_SUCCESS) {
|
||||
RegCloseKey(key);
|
||||
/* Try again with * as the key if an exit code was defined */
|
||||
if (ret) return get_exit_action(service_name, 0, action, default_action);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Close registry */
|
||||
RegCloseKey(key);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -0,0 +1,58 @@
|
||||
#ifndef REGISTRY_H
|
||||
#define REGISTRY_H
|
||||
|
||||
#define NSSM_REGISTRY _T("SYSTEM\\CurrentControlSet\\Services\\%s\\Parameters")
|
||||
#define NSSM_REGISTRY_GROUPS _T("SYSTEM\\CurrentControlSet\\Control\\ServiceGroupOrder")
|
||||
#define NSSM_REG_GROUPS _T("List")
|
||||
#define NSSM_REG_EXE _T("Application")
|
||||
#define NSSM_REG_FLAGS _T("AppParameters")
|
||||
#define NSSM_REG_DIR _T("AppDirectory")
|
||||
#define NSSM_REG_ENV _T("AppEnvironment")
|
||||
#define NSSM_REG_ENV_EXTRA _T("AppEnvironmentExtra")
|
||||
#define NSSM_REG_EXIT _T("AppExit")
|
||||
#define NSSM_REG_RESTART_DELAY _T("AppRestartDelay")
|
||||
#define NSSM_REG_THROTTLE _T("AppThrottle")
|
||||
#define NSSM_REG_STOP_METHOD_SKIP _T("AppStopMethodSkip")
|
||||
#define NSSM_REG_KILL_CONSOLE_GRACE_PERIOD _T("AppStopMethodConsole")
|
||||
#define NSSM_REG_KILL_WINDOW_GRACE_PERIOD _T("AppStopMethodWindow")
|
||||
#define NSSM_REG_KILL_THREADS_GRACE_PERIOD _T("AppStopMethodThreads")
|
||||
#define NSSM_REG_STDIN _T("AppStdin")
|
||||
#define NSSM_REG_STDOUT _T("AppStdout")
|
||||
#define NSSM_REG_STDERR _T("AppStderr")
|
||||
#define NSSM_REG_STDIO_SHARING _T("ShareMode")
|
||||
#define NSSM_REG_STDIO_DISPOSITION _T("CreationDisposition")
|
||||
#define NSSM_REG_STDIO_FLAGS _T("FlagsAndAttributes")
|
||||
#define NSSM_REG_ROTATE _T("AppRotateFiles")
|
||||
#define NSSM_REG_ROTATE_ONLINE _T("AppRotateOnline")
|
||||
#define NSSM_REG_ROTATE_SECONDS _T("AppRotateSeconds")
|
||||
#define NSSM_REG_ROTATE_BYTES_LOW _T("AppRotateBytes")
|
||||
#define NSSM_REG_ROTATE_BYTES_HIGH _T("AppRotateBytesHigh")
|
||||
#define NSSM_REG_PRIORITY _T("AppPriority")
|
||||
#define NSSM_REG_AFFINITY _T("AppAffinity")
|
||||
#define NSSM_REG_NO_CONSOLE _T("AppNoConsole")
|
||||
#define NSSM_STDIO_LENGTH 29
|
||||
|
||||
HKEY open_registry(const TCHAR *, const TCHAR *, REGSAM sam);
|
||||
HKEY open_registry(const TCHAR *, REGSAM sam);
|
||||
int create_messages();
|
||||
int create_parameters(nssm_service_t *, bool);
|
||||
int create_exit_action(TCHAR *, const TCHAR *, bool);
|
||||
int get_environment(TCHAR *, HKEY, TCHAR *, TCHAR **, unsigned long *);
|
||||
int get_string(HKEY, TCHAR *, TCHAR *, unsigned long, bool, bool, bool);
|
||||
int get_string(HKEY, TCHAR *, TCHAR *, unsigned long, bool);
|
||||
int expand_parameter(HKEY, TCHAR *, TCHAR *, unsigned long, bool, bool);
|
||||
int expand_parameter(HKEY, TCHAR *, TCHAR *, unsigned long, bool);
|
||||
int set_string(HKEY, TCHAR *, TCHAR *, bool);
|
||||
int set_string(HKEY, TCHAR *, TCHAR *);
|
||||
int set_expand_string(HKEY, TCHAR *, TCHAR *);
|
||||
int set_number(HKEY, TCHAR *, unsigned long);
|
||||
int get_number(HKEY, TCHAR *, unsigned long *, bool);
|
||||
int get_number(HKEY, TCHAR *, unsigned long *);
|
||||
int format_double_null(TCHAR *, unsigned long, TCHAR **, unsigned long *);
|
||||
int unformat_double_null(TCHAR *, unsigned long, TCHAR **, unsigned long *);
|
||||
void override_milliseconds(TCHAR *, HKEY, TCHAR *, unsigned long *, unsigned long, unsigned long);
|
||||
int get_io_parameters(nssm_service_t *, HKEY);
|
||||
int get_parameters(nssm_service_t *, STARTUPINFO *);
|
||||
int get_exit_action(const TCHAR *, unsigned long *, TCHAR *, bool *);
|
||||
|
||||
#endif
|
||||
@ -0,0 +1,78 @@
|
||||
//{{NO_DEPENDENCIES}}
|
||||
// Microsoft Developer Studio generated include file.
|
||||
// Used by nssm.rc
|
||||
//
|
||||
#define IDC_STATIC (-1)
|
||||
#define IDI_NSSM 101
|
||||
#define IDD_INSTALL 102
|
||||
#define IDD_REMOVE 103
|
||||
#define IDD_EDIT 104
|
||||
#define IDD_APPLICATION 105
|
||||
#define IDD_DETAILS 106
|
||||
#define IDD_LOGON 107
|
||||
#define IDD_IO 108
|
||||
#define IDD_ROTATION 109
|
||||
#define IDD_APPEXIT 110
|
||||
#define IDD_SHUTDOWN 111
|
||||
#define IDD_ENVIRONMENT 112
|
||||
#define IDD_NATIVE 113
|
||||
#define IDD_PROCESS 114
|
||||
#define IDD_DEPENDENCIES 115
|
||||
#define IDC_PATH 1000
|
||||
#define IDC_TAB1 1001
|
||||
#define IDC_CANCEL 1002
|
||||
#define IDC_BROWSE 1003
|
||||
#define IDC_FLAGS 1004
|
||||
#define IDC_NAME 1005
|
||||
#define IDC_REMOVE 1007
|
||||
#define IDC_METHOD_CONSOLE 1008
|
||||
#define IDC_METHOD_WINDOW 1009
|
||||
#define IDC_METHOD_THREADS 1010
|
||||
#define IDC_METHOD_TERMINATE 1011
|
||||
#define IDC_KILL_CONSOLE 1012
|
||||
#define IDC_KILL_WINDOW 1013
|
||||
#define IDC_KILL_THREADS 1014
|
||||
#define IDC_STDIN 1015
|
||||
#define IDC_STDOUT 1016
|
||||
#define IDC_STDERR 1017
|
||||
#define IDC_BROWSE_STDIN 1018
|
||||
#define IDC_BROWSE_STDOUT 1019
|
||||
#define IDC_BROWSE_STDERR 1020
|
||||
#define IDC_THROTTLE 1021
|
||||
#define IDC_APPEXIT 1022
|
||||
#define IDC_RESTART_DELAY 1023
|
||||
#define IDC_DIR 1024
|
||||
#define IDC_BROWSE_DIR 1025
|
||||
#define IDC_ENVIRONMENT 1026
|
||||
#define IDC_ENVIRONMENT_REPLACE 1027
|
||||
#define IDC_TRUNCATE 1028
|
||||
#define IDC_ROTATE 1029
|
||||
#define IDC_ROTATE_ONLINE 1030
|
||||
#define IDC_ROTATE_SECONDS 1031
|
||||
#define IDC_ROTATE_BYTES_LOW 1032
|
||||
#define IDC_DISPLAYNAME 1033
|
||||
#define IDC_DESCRIPTION 1034
|
||||
#define IDC_STARTUP 1035
|
||||
#define IDC_LOCALSYSTEM 1036
|
||||
#define IDC_INTERACT 1037
|
||||
#define IDC_ACCOUNT 1038
|
||||
#define IDC_USERNAME 1039
|
||||
#define IDC_PASSWORD1 1040
|
||||
#define IDC_PASSWORD2 1041
|
||||
#define IDC_PRIORITY 1042
|
||||
#define IDC_AFFINITY_ALL 1043
|
||||
#define IDC_AFFINITY 1044
|
||||
#define IDC_CONSOLE 1045
|
||||
#define IDC_DEPENDENCIES 1046
|
||||
|
||||
// Next default values for new objects
|
||||
//
|
||||
#ifdef APSTUDIO_INVOKED
|
||||
#ifndef APSTUDIO_READONLY_SYMBOLS
|
||||
#define _APS_NEXT_RESOURCE_VALUE 115
|
||||
#define _APS_NEXT_COMMAND_VALUE 40001
|
||||
#define _APS_NEXT_CONTROL_VALUE 1047
|
||||
#define _APS_NEXT_SYMED_VALUE 101
|
||||
#endif
|
||||
#endif
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,146 @@
|
||||
#ifndef SERVICE_H
|
||||
#define SERVICE_H
|
||||
|
||||
/*
|
||||
MSDN says the commandline in CreateProcess() is limited to 32768 characters
|
||||
and the application name to MAX_PATH.
|
||||
A service name and service display name are limited to 256 characters.
|
||||
A registry key is limited to 255 characters.
|
||||
A registry value is limited to 16383 characters.
|
||||
Therefore we limit the service name to accommodate the path under HKLM.
|
||||
*/
|
||||
#define EXE_LENGTH PATH_LENGTH
|
||||
#define CMD_LENGTH 32768
|
||||
#define KEY_LENGTH 255
|
||||
#define VALUE_LENGTH 16383
|
||||
#define SERVICE_NAME_LENGTH 256
|
||||
|
||||
#define ACTION_LEN 16
|
||||
|
||||
#define NSSM_KERNEL_DRIVER _T("SERVICE_KERNEL_DRIVER")
|
||||
#define NSSM_FILE_SYSTEM_DRIVER _T("SERVICE_FILE_SYSTEM_DRIVER")
|
||||
#define NSSM_WIN32_OWN_PROCESS _T("SERVICE_WIN32_OWN_PROCESS")
|
||||
#define NSSM_WIN32_SHARE_PROCESS _T("SERVICE_WIN32_SHARE_PROCESS")
|
||||
#define NSSM_INTERACTIVE_PROCESS _T("SERVICE_INTERACTIVE_PROCESS")
|
||||
#define NSSM_SHARE_INTERACTIVE_PROCESS NSSM_WIN32_SHARE_PROCESS _T("|") NSSM_INTERACTIVE_PROCESS
|
||||
#define NSSM_UNKNOWN _T("?")
|
||||
|
||||
#define NSSM_ROTATE_OFFLINE 0
|
||||
#define NSSM_ROTATE_ONLINE 1
|
||||
#define NSSM_ROTATE_ONLINE_ASAP 2
|
||||
|
||||
typedef struct {
|
||||
bool native;
|
||||
TCHAR name[SERVICE_NAME_LENGTH];
|
||||
TCHAR displayname[SERVICE_NAME_LENGTH];
|
||||
TCHAR description[VALUE_LENGTH];
|
||||
unsigned long startup;
|
||||
TCHAR *username;
|
||||
size_t usernamelen;
|
||||
TCHAR *password;
|
||||
size_t passwordlen;
|
||||
unsigned long type;
|
||||
TCHAR image[PATH_LENGTH];
|
||||
TCHAR exe[EXE_LENGTH];
|
||||
TCHAR flags[VALUE_LENGTH];
|
||||
TCHAR dir[DIR_LENGTH];
|
||||
TCHAR *env;
|
||||
__int64 affinity;
|
||||
TCHAR *dependencies;
|
||||
unsigned long dependencieslen;
|
||||
unsigned long envlen;
|
||||
TCHAR *env_extra;
|
||||
unsigned long env_extralen;
|
||||
unsigned long priority;
|
||||
unsigned long no_console;
|
||||
TCHAR stdin_path[PATH_LENGTH];
|
||||
unsigned long stdin_sharing;
|
||||
unsigned long stdin_disposition;
|
||||
unsigned long stdin_flags;
|
||||
TCHAR stdout_path[PATH_LENGTH];
|
||||
unsigned long stdout_sharing;
|
||||
unsigned long stdout_disposition;
|
||||
unsigned long stdout_flags;
|
||||
HANDLE stdout_pipe;
|
||||
HANDLE stdout_thread;
|
||||
unsigned long stdout_tid;
|
||||
TCHAR stderr_path[PATH_LENGTH];
|
||||
unsigned long stderr_sharing;
|
||||
unsigned long stderr_disposition;
|
||||
unsigned long stderr_flags;
|
||||
HANDLE stderr_pipe;
|
||||
HANDLE stderr_thread;
|
||||
unsigned long stderr_tid;
|
||||
bool rotate_files;
|
||||
unsigned long rotate_stdout_online;
|
||||
unsigned long rotate_stderr_online;
|
||||
unsigned long rotate_seconds;
|
||||
unsigned long rotate_bytes_low;
|
||||
unsigned long rotate_bytes_high;
|
||||
unsigned long default_exit_action;
|
||||
unsigned long restart_delay;
|
||||
unsigned long throttle_delay;
|
||||
unsigned long stop_method;
|
||||
unsigned long kill_console_delay;
|
||||
unsigned long kill_window_delay;
|
||||
unsigned long kill_threads_delay;
|
||||
SC_HANDLE handle;
|
||||
SERVICE_STATUS status;
|
||||
SERVICE_STATUS_HANDLE status_handle;
|
||||
HANDLE process_handle;
|
||||
unsigned long pid;
|
||||
HANDLE wait_handle;
|
||||
bool stopping;
|
||||
bool allow_restart;
|
||||
unsigned long throttle;
|
||||
CRITICAL_SECTION throttle_section;
|
||||
bool throttle_section_initialised;
|
||||
CONDITION_VARIABLE throttle_condition;
|
||||
HANDLE throttle_timer;
|
||||
LARGE_INTEGER throttle_duetime;
|
||||
FILETIME creation_time;
|
||||
FILETIME exit_time;
|
||||
TCHAR *initial_env;
|
||||
} nssm_service_t;
|
||||
|
||||
void WINAPI service_main(unsigned long, TCHAR **);
|
||||
TCHAR *service_control_text(unsigned long);
|
||||
TCHAR *service_status_text(unsigned long);
|
||||
void log_service_control(TCHAR *, unsigned long, bool);
|
||||
unsigned long WINAPI service_control_handler(unsigned long, unsigned long, void *, void *);
|
||||
|
||||
int affinity_mask_to_string(__int64, TCHAR **);
|
||||
int affinity_string_to_mask(TCHAR *, __int64 *);
|
||||
unsigned long priority_mask();
|
||||
int priority_constant_to_index(unsigned long);
|
||||
unsigned long priority_index_to_constant(int);
|
||||
|
||||
nssm_service_t *alloc_nssm_service();
|
||||
void set_nssm_service_defaults(nssm_service_t *);
|
||||
void cleanup_nssm_service(nssm_service_t *);
|
||||
SC_HANDLE open_service_manager(unsigned long);
|
||||
SC_HANDLE open_service(SC_HANDLE, TCHAR *, unsigned long, TCHAR *, unsigned long);
|
||||
QUERY_SERVICE_CONFIG *query_service_config(const TCHAR *, SC_HANDLE);
|
||||
int set_service_dependencies(const TCHAR *, SC_HANDLE, TCHAR *);
|
||||
int get_service_dependencies(const TCHAR *, SC_HANDLE, TCHAR **, unsigned long *, int);
|
||||
int get_service_dependencies(const TCHAR *, SC_HANDLE, TCHAR **, unsigned long *);
|
||||
int set_service_description(const TCHAR *, SC_HANDLE, TCHAR *);
|
||||
int get_service_description(const TCHAR *, SC_HANDLE, unsigned long, TCHAR *);
|
||||
int get_service_startup(const TCHAR *, SC_HANDLE, const QUERY_SERVICE_CONFIG *, unsigned long *);
|
||||
int get_service_username(const TCHAR *, const QUERY_SERVICE_CONFIG *, TCHAR **, size_t *);
|
||||
int pre_install_service(int, TCHAR **);
|
||||
int pre_remove_service(int, TCHAR **);
|
||||
int pre_edit_service(int, TCHAR **);
|
||||
int install_service(nssm_service_t *);
|
||||
int remove_service(nssm_service_t *);
|
||||
int edit_service(nssm_service_t *, bool);
|
||||
int control_service(unsigned long, int, TCHAR **);
|
||||
void set_service_recovery(nssm_service_t *);
|
||||
int monitor_service(nssm_service_t *);
|
||||
int start_service(nssm_service_t *);
|
||||
int stop_service(nssm_service_t *, unsigned long, bool, bool);
|
||||
void CALLBACK end_service(void *, unsigned char);
|
||||
void throttle_restart(nssm_service_t *);
|
||||
int await_shutdown(nssm_service_t *, TCHAR *, unsigned long);
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,47 @@
|
||||
#ifndef SETTINGS_H
|
||||
#define SETTINGS_H
|
||||
|
||||
#define NSSM_NATIVE_DEPENDONGROUP _T("DependOnGroup")
|
||||
#define NSSM_NATIVE_DEPENDONSERVICE _T("DependOnService")
|
||||
#define NSSM_NATIVE_DESCRIPTION _T("Description")
|
||||
#define NSSM_NATIVE_DISPLAYNAME _T("DisplayName")
|
||||
#define NSSM_NATIVE_IMAGEPATH _T("ImagePath")
|
||||
#define NSSM_NATIVE_NAME _T("Name")
|
||||
#define NSSM_NATIVE_OBJECTNAME _T("ObjectName")
|
||||
#define NSSM_NATIVE_STARTUP _T("Start")
|
||||
#define NSSM_NATIVE_TYPE _T("Type")
|
||||
|
||||
/* Are additional arguments needed? */
|
||||
#define ADDITIONAL_GETTING (1 << 0)
|
||||
#define ADDITIONAL_SETTING (1 << 1)
|
||||
#define ADDITIONAL_RESETTING (1 << 2)
|
||||
#define ADDITIONAL_CRLF (1 << 3)
|
||||
#define ADDITIONAL_MANDATORY ADDITIONAL_GETTING|ADDITIONAL_SETTING|ADDITIONAL_RESETTING
|
||||
|
||||
#define DEPENDENCY_SERVICES (1 << 0)
|
||||
#define DEPENDENCY_GROUPS (1 << 1)
|
||||
#define DEPENDENCY_ALL (DEPENDENCY_SERVICES|DEPENDENCY_GROUPS)
|
||||
|
||||
typedef union {
|
||||
unsigned long numeric;
|
||||
TCHAR *string;
|
||||
} value_t;
|
||||
|
||||
typedef int (*setting_function_t)(const TCHAR *, void *, const TCHAR *, void *, value_t *, const TCHAR *);
|
||||
|
||||
typedef struct {
|
||||
const TCHAR *name;
|
||||
unsigned long type;
|
||||
void *default_value;
|
||||
bool native;
|
||||
int additional;
|
||||
setting_function_t set;
|
||||
setting_function_t get;
|
||||
} settings_t;
|
||||
|
||||
int set_setting(const TCHAR *, HKEY, settings_t *, value_t *, const TCHAR *);
|
||||
int set_setting(const TCHAR *, SC_HANDLE, settings_t *, value_t *, const TCHAR *);
|
||||
int get_setting(const TCHAR *, HKEY, settings_t *, value_t *, const TCHAR *);
|
||||
int get_setting(const TCHAR *, SC_HANDLE, settings_t *, value_t *, const TCHAR *);
|
||||
|
||||
#endif
|
||||
@ -0,0 +1,49 @@
|
||||
@rem Set default version in case git isn't available.
|
||||
set description=0.0-0-prerelease
|
||||
@rem Get canonical version from git tags, eg v2.21-24-g2c60e53.
|
||||
for /f %%v in ('git describe --tags --long') do set description=%%v
|
||||
|
||||
@rem Strip leading v if present, eg 2.21-24-g2c60e53.
|
||||
set description=%description:v=%
|
||||
set version=%description%
|
||||
|
||||
@rem Get the number of commits and commit hash, eg 24-g2c60e53.
|
||||
set n=%version:*-=%
|
||||
set commit=%n:*-=%
|
||||
call set n=%%n:%commit%=%%
|
||||
set n=%n:~0,-1%
|
||||
|
||||
@rem Strip n and commit, eg 2.21.
|
||||
call set version=%%version:%n%-%commit%=%%
|
||||
set version=%version:~0,-1%
|
||||
|
||||
@rem Find major and minor.
|
||||
set minor=%version:*.=%
|
||||
call set major=%%version:.%minor%=%%
|
||||
|
||||
@rem Build flags.
|
||||
set flags=0L
|
||||
|
||||
@rem Don't include n and commit if we match a tag exactly.
|
||||
if "%n%" == "0" (set description=%major%.%minor%) else set flags=VS_FF_PRERELEASE
|
||||
@rem Maybe we couldn't get the git tag.
|
||||
if "%commit%" == "prerelease" set flags=VS_FF_PRERELEASE
|
||||
|
||||
@rem Ignore the build number if this isn't Jenkins.
|
||||
if "%BUILD_NUMBER%" == "" set BUILD_NUMBER=0
|
||||
|
||||
@rem Copyright year provided by Jenkins.
|
||||
set md=%BUILD_ID:*-=%
|
||||
call set year=%%BUILD_ID:%md%=%%
|
||||
set year=%year:~0,-1%
|
||||
if "%BUILD_ID%" == "" set year=
|
||||
|
||||
@rem Create version.h.
|
||||
@echo>version.h.new #define NSSM_VERSION _T("%description%")
|
||||
@echo>>version.h.new #define NSSM_VERSIONINFO %major%,%minor%,%n%,%BUILD_NUMBER%
|
||||
@echo>>version.h.new #define NSSM_DATE _T("%DATE%")
|
||||
@echo>>version.h.new #define NSSM_FILEFLAGS %flags%
|
||||
@echo>>version.h.new #define NSSM_COPYRIGHT _T("Public Domain; Author Iain Patterson 2003-%year%")
|
||||
|
||||
fc version.h version.h.new >NUL: 2>NUL:
|
||||
if %ERRORLEVEL% == 0 (del version.h.new) else (move /y version.h.new version.h)
|
||||
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue