3 static HANDLE create_logging_thread(logger_t *logger) {
\r
4 HANDLE thread_handle = CreateThread(NULL, 0, log_and_rotate, (void *) logger, 0, logger->tid_ptr);
\r
5 if (! thread_handle) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETHREAD_FAILED, error_string(GetLastError()), 0);
\r
6 return thread_handle;
\r
9 static inline void write_bom(logger_t *logger) {
\r
10 wchar_t bom = L'\ufeff';
\r
12 if (! WriteFile(logger->write_handle, (void *) &bom, sizeof(bom), &out, 0)) {
\r
13 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_SOMEBODY_SET_UP_US_THE_BOM, logger->service_name, logger->path, error_string(GetLastError()), 0);
\r
17 /* Get path, share mode, creation disposition and flags for a stream. */
\r
18 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) {
\r
19 TCHAR value[NSSM_STDIO_LENGTH];
\r
22 if (_sntprintf_s(value, _countof(value), _TRUNCATE, _T("%s"), prefix) < 0) {
\r
23 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, prefix, _T("get_createfile_parameters()"), 0);
\r
26 switch (expand_parameter(key, value, path, MAX_PATH, true, false)) {
\r
27 case 0: if (! path[0]) return 0; break; /* OK. */
\r
28 default: return 2; /* Error. */
\r
32 if (_sntprintf_s(value, _countof(value), _TRUNCATE, _T("%s%s"), prefix, NSSM_REG_STDIO_SHARING) < 0) {
\r
33 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, NSSM_REG_STDIO_SHARING, _T("get_createfile_parameters()"), 0);
\r
36 switch (get_number(key, value, sharing, false)) {
\r
37 case 0: *sharing = default_sharing; break; /* Missing. */
\r
38 case 1: break; /* Found. */
\r
39 case -2: return 4; break; /* Error. */
\r
42 /* CreationDisposition. */
\r
43 if (_sntprintf_s(value, _countof(value), _TRUNCATE, _T("%s%s"), prefix, NSSM_REG_STDIO_DISPOSITION) < 0) {
\r
44 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, NSSM_REG_STDIO_DISPOSITION, _T("get_createfile_parameters()"), 0);
\r
47 switch (get_number(key, value, disposition, false)) {
\r
48 case 0: *disposition = default_disposition; break; /* Missing. */
\r
49 case 1: break; /* Found. */
\r
50 case -2: return 6; break; /* Error. */
\r
54 if (_sntprintf_s(value, _countof(value), _TRUNCATE, _T("%s%s"), prefix, NSSM_REG_STDIO_FLAGS) < 0) {
\r
55 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, NSSM_REG_STDIO_FLAGS, _T("get_createfile_parameters()"), 0);
\r
58 switch (get_number(key, value, flags, false)) {
\r
59 case 0: *flags = default_flags; break; /* Missing. */
\r
60 case 1: break; /* Found. */
\r
61 case -2: return 8; break; /* Error. */
\r
67 int set_createfile_parameter(HKEY key, TCHAR *prefix, TCHAR *suffix, unsigned long number) {
\r
68 TCHAR value[NSSM_STDIO_LENGTH];
\r
70 if (_sntprintf_s(value, _countof(value), _TRUNCATE, _T("%s%s"), prefix, suffix) < 0) {
\r
71 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, suffix, _T("set_createfile_parameter()"), 0);
\r
75 return set_number(key, value, number);
\r
78 int delete_createfile_parameter(HKEY key, TCHAR *prefix, TCHAR *suffix) {
\r
79 TCHAR value[NSSM_STDIO_LENGTH];
\r
81 if (_sntprintf_s(value, _countof(value), _TRUNCATE, _T("%s%s"), prefix, suffix) < 0) {
\r
82 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, suffix, _T("delete_createfile_parameter()"), 0);
\r
86 if (RegDeleteValue(key, value)) return 0;
\r
90 HANDLE append_to_file(TCHAR *path, unsigned long sharing, SECURITY_ATTRIBUTES *attributes, unsigned long disposition, unsigned long flags) {
\r
93 /* Try to append to the file first. */
\r
94 ret = CreateFile(path, FILE_APPEND_DATA, sharing, attributes, disposition, flags, 0);
\r
100 unsigned long error = GetLastError();
\r
101 if (error != ERROR_FILE_NOT_FOUND) {
\r
102 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEFILE_FAILED, path, error_string(error), 0);
\r
106 /* It didn't exist. Create it. */
\r
107 return CreateFile(path, FILE_WRITE_DATA, sharing, attributes, disposition, flags, 0);
\r
110 static void rotated_filename(TCHAR *path, TCHAR *rotated, unsigned long rotated_len, SYSTEMTIME *st) {
\r
117 TCHAR buffer[MAX_PATH];
\r
118 memmove(buffer, path, sizeof(buffer));
\r
119 TCHAR *ext = PathFindExtension(buffer);
\r
120 TCHAR extension[MAX_PATH];
\r
121 _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);
\r
123 _sntprintf_s(rotated, rotated_len, _TRUNCATE, _T("%s%s"), buffer, extension);
\r
126 void rotate_file(TCHAR *service_name, TCHAR *path, unsigned long seconds, unsigned long low, unsigned long high) {
\r
127 unsigned long error;
\r
131 GetSystemTime(&st);
\r
133 BY_HANDLE_FILE_INFORMATION info;
\r
135 /* Try to open the file to check if it exists and to get attributes. */
\r
136 HANDLE file = CreateFile(path, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
\r
138 /* Get file attributes. */
\r
139 if (! GetFileInformationByHandle(file, &info)) {
\r
140 /* Reuse current time for rotation timestamp. */
\r
141 seconds = low = high = 0;
\r
142 SystemTimeToFileTime(&st, &info.ftLastWriteTime);
\r
148 error = GetLastError();
\r
149 if (error == ERROR_FILE_NOT_FOUND) return;
\r
150 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_ROTATE_FILE_FAILED, service_name, path, _T("CreateFile()"), path, error_string(error), 0);
\r
151 /* Reuse current time for rotation timestamp. */
\r
152 seconds = low = high = 0;
\r
153 SystemTimeToFileTime(&st, &info.ftLastWriteTime);
\r
156 /* Check file age. */
\r
159 SystemTimeToFileTime(&st, &ft);
\r
162 s.LowPart = ft.dwLowDateTime;
\r
163 s.HighPart = ft.dwHighDateTime;
\r
164 s.QuadPart -= seconds * 10000000LL;
\r
165 ft.dwLowDateTime = s.LowPart;
\r
166 ft.dwHighDateTime = s.HighPart;
\r
167 if (CompareFileTime(&info.ftLastWriteTime, &ft) > 0) return;
\r
170 /* Check file size. */
\r
172 if (info.nFileSizeHigh < high) return;
\r
173 if (info.nFileSizeHigh == high && info.nFileSizeLow < low) return;
\r
176 /* Get new filename. */
\r
177 FileTimeToSystemTime(&info.ftLastWriteTime, &st);
\r
179 TCHAR rotated[MAX_PATH];
\r
180 rotated_filename(path, rotated, _countof(rotated), &st);
\r
183 if (MoveFile(path, rotated)) return;
\r
184 error = GetLastError();
\r
186 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_ROTATE_FILE_FAILED, service_name, path, _T("MoveFile()"), rotated, error_string(error), 0);
\r
190 int get_output_handles(nssm_service_t *service, HKEY key, STARTUPINFO *si) {
\r
191 bool set_flags = false;
\r
193 /* Standard security attributes allowing inheritance. */
\r
194 SECURITY_ATTRIBUTES attributes;
\r
195 ZeroMemory(&attributes, sizeof(attributes));
\r
196 attributes.bInheritHandle = true;
\r
199 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)) {
\r
200 service->stdin_sharing = service->stdin_disposition = service->stdin_flags = 0;
\r
201 ZeroMemory(service->stdin_path, _countof(service->stdin_path) * sizeof(TCHAR));
\r
204 if (si && service->stdin_path[0]) {
\r
205 si->hStdInput = CreateFile(service->stdin_path, FILE_READ_DATA, service->stdin_sharing, &attributes, service->stdin_disposition, service->stdin_flags, 0);
\r
206 if (! si->hStdInput) {
\r
207 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEFILE_FAILED, service->stdin_path, error_string(GetLastError()), 0);
\r
213 ULARGE_INTEGER size;
\r
214 size.LowPart = service->rotate_bytes_low;
\r
215 size.HighPart = service->rotate_bytes_high;
\r
218 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)) {
\r
219 service->stdout_sharing = service->stdout_disposition = service->stdout_flags = 0;
\r
220 ZeroMemory(service->stdout_path, _countof(service->stdout_path) * sizeof(TCHAR));
\r
223 if (si && service->stdout_path[0]) {
\r
224 if (service->rotate_files) rotate_file(service->name, service->stdout_path, service->rotate_seconds, service->rotate_bytes_low, service->rotate_bytes_high);
\r
225 HANDLE stdout_handle = append_to_file(service->stdout_path, service->stdout_sharing, 0, service->stdout_disposition, service->stdout_flags);
\r
226 if (! stdout_handle) {
\r
227 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEFILE_FAILED, service->stdout_path, error_string(GetLastError()), 0);
\r
231 /* Try online rotation only if a size threshold is set. */
\r
232 logger_t *stdout_logger = 0;
\r
233 if (service->rotate_files && service->rotate_stdout_online && size.QuadPart) {
\r
234 stdout_logger = (logger_t *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(logger_t));
\r
235 if (stdout_logger) {
\r
236 /* Pipe between application's stdout and our logging handle. */
\r
237 if (CreatePipe(&service->stdout_pipe, &si->hStdOutput, &attributes, 0)) {
\r
238 stdout_logger->service_name = service->name;
\r
239 stdout_logger->path = service->stdout_path;
\r
240 stdout_logger->sharing = service->stdout_sharing;
\r
241 stdout_logger->disposition = service->stdout_disposition;
\r
242 stdout_logger->flags = service->stdout_flags;
\r
243 stdout_logger->read_handle = service->stdout_pipe;
\r
244 stdout_logger->write_handle = stdout_handle;
\r
245 stdout_logger->size = (__int64) size.QuadPart;
\r
246 stdout_logger->tid_ptr = &service->stdout_tid;
\r
248 /* Logging thread. */
\r
249 service->stdout_thread = create_logging_thread(stdout_logger);
\r
250 if (! service->stdout_thread) {
\r
251 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPIPE_FAILED, service->name, service->stdout_path, error_string(GetLastError()));
\r
252 CloseHandle(service->stdout_pipe);
\r
253 CloseHandle(si->hStdOutput);
\r
254 service->stdout_tid = 0;
\r
257 else service->stdout_tid = 0;
\r
260 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("stdout_logger"), _T("get_output_handles()"), 0);
\r
261 service->stdout_tid = 0;
\r
264 /* Fall through to try direct I/O. */
\r
265 if (! service->stdout_tid) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPIPE_FAILED, service->name, service->stdout_path, error_string(GetLastError()));
\r
268 if (! service->stdout_tid) {
\r
269 if (stdout_logger) HeapFree(GetProcessHeap(), 0, stdout_logger);
\r
270 if (! DuplicateHandle(GetCurrentProcess(), stdout_handle, GetCurrentProcess(), &si->hStdOutput, 0, true, DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) {
\r
271 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_DUPLICATEHANDLE_FAILED, NSSM_REG_STDOUT, error_string(GetLastError()), 0);
\r
274 service->rotate_stdout_online = false;
\r
281 if (get_createfile_parameters(key, NSSM_REG_STDERR, service->stderr_path, &service->stdout_sharing, NSSM_STDERR_SHARING, &service->stdout_disposition, NSSM_STDERR_DISPOSITION, &service->stdout_flags, NSSM_STDERR_FLAGS)) {
\r
282 service->stderr_sharing = service->stderr_disposition = service->stderr_flags = 0;
\r
283 ZeroMemory(service->stderr_path, _countof(service->stderr_path) * sizeof(TCHAR));
\r
286 if (service->stderr_path[0]) {
\r
287 /* Same as stdout? */
\r
288 if (str_equiv(service->stderr_path, service->stdout_path)) {
\r
289 service->stderr_sharing = service->stdout_sharing;
\r
290 service->stderr_disposition = service->stdout_disposition;
\r
291 service->stderr_flags = service->stdout_flags;
\r
292 service->rotate_stderr_online = false;
\r
295 /* Two handles to the same file will create a race. */
\r
296 if (! DuplicateHandle(GetCurrentProcess(), si->hStdOutput, GetCurrentProcess(), &si->hStdError, 0, true, DUPLICATE_SAME_ACCESS)) {
\r
297 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_DUPLICATEHANDLE_FAILED, NSSM_REG_STDOUT, error_string(GetLastError()), 0);
\r
303 if (service->rotate_files) rotate_file(service->name, service->stderr_path, service->rotate_seconds, service->rotate_bytes_low, service->rotate_bytes_high);
\r
304 HANDLE stderr_handle = append_to_file(service->stderr_path, service->stdout_sharing, 0, service->stdout_disposition, service->stdout_flags);
\r
305 if (! stderr_handle) {
\r
306 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEFILE_FAILED, service->stderr_path, error_string(GetLastError()), 0);
\r
310 /* Try online rotation only if a size threshold is set. */
\r
311 logger_t *stderr_logger = 0;
\r
312 if (service->rotate_files && service->rotate_stderr_online && size.QuadPart) {
\r
313 stderr_logger = (logger_t *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(logger_t));
\r
314 if (stderr_logger) {
\r
315 /* Pipe between application's stderr and our logging handle. */
\r
316 if (CreatePipe(&service->stderr_pipe, &si->hStdError, &attributes, 0)) {
\r
317 stderr_logger->service_name = service->name;
\r
318 stderr_logger->path = service->stderr_path;
\r
319 stderr_logger->sharing = service->stderr_sharing;
\r
320 stderr_logger->disposition = service->stderr_disposition;
\r
321 stderr_logger->flags = service->stderr_flags;
\r
322 stderr_logger->read_handle = service->stderr_pipe;
\r
323 stderr_logger->write_handle = stderr_handle;
\r
324 stderr_logger->size = (__int64) size.QuadPart;
\r
325 stderr_logger->tid_ptr = &service->stderr_tid;
\r
327 /* Logging thread. */
\r
328 service->stderr_thread = create_logging_thread(stderr_logger);
\r
329 if (! service->stderr_thread) {
\r
330 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPIPE_FAILED, service->name, service->stderr_path, error_string(GetLastError()));
\r
331 CloseHandle(service->stderr_pipe);
\r
332 CloseHandle(si->hStdError);
\r
333 service->stderr_tid = 0;
\r
336 else service->stderr_tid = 0;
\r
339 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("stderr_logger"), _T("get_output_handles()"), 0);
\r
340 service->stderr_tid = 0;
\r
343 /* Fall through to try direct I/O. */
\r
344 if (! service->stderr_tid) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPIPE_FAILED, service->name, service->stderr_path, error_string(GetLastError()));
\r
347 if (! service->stderr_tid) {
\r
348 if (stderr_logger) HeapFree(GetProcessHeap(), 0, stderr_logger);
\r
349 if (! DuplicateHandle(GetCurrentProcess(), stderr_handle, GetCurrentProcess(), &si->hStdError, 0, true, DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) {
\r
350 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_DUPLICATEHANDLE_FAILED, NSSM_REG_STDERR, error_string(GetLastError()), 0);
\r
353 service->rotate_stderr_online = false;
\r
360 if (! set_flags) return 0;
\r
363 We need to set the startup_info flags to make the new handles
\r
364 inheritable by the new process.
\r
366 if (si) si->dwFlags |= STARTF_USESTDHANDLES;
\r
371 void close_output_handles(STARTUPINFO *si, bool close_stdout, bool close_stderr) {
\r
372 if (si->hStdInput) CloseHandle(si->hStdInput);
\r
373 if (si->hStdOutput && close_stdout) CloseHandle(si->hStdOutput);
\r
374 if (si->hStdError && close_stderr) CloseHandle(si->hStdError);
\r
377 void close_output_handles(STARTUPINFO *si) {
\r
378 return close_output_handles(si, true, true);
\r
381 /* Wrapper to be called in a new thread for logging. */
\r
382 unsigned long WINAPI log_and_rotate(void *arg) {
\r
383 logger_t *logger = (logger_t *) arg;
\r
384 if (! logger) return 1;
\r
387 BY_HANDLE_FILE_INFORMATION info;
\r
389 /* Find initial file size. */
\r
390 if (! GetFileInformationByHandle(logger->write_handle, &info)) logger->size = 0LL;
\r
393 l.HighPart = info.nFileSizeHigh;
\r
394 l.LowPart = info.nFileSizeLow;
\r
400 unsigned long in, out;
\r
402 /* Read data from the pipe. */
\r
404 if (! ReadFile(logger->read_handle, address, sizeof(buffer), &in, 0)) {
\r
405 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_READFILE_FAILED, logger->service_name, logger->path, error_string(GetLastError()), 0);
\r
409 if (size + (__int64) in >= logger->size) {
\r
410 /* Look for newline. */
\r
412 for (i = 0; i < in; i++) {
\r
413 if (buffer[i] == '\n') {
\r
414 unsigned char unicode = IsTextUnicode(address, sizeof(buffer), 0);
\r
415 if (unicode) i += sizeof(wchar_t);
\r
416 else i += sizeof(char);
\r
418 /* Write up to the newline. */
\r
419 if (! WriteFile(logger->write_handle, address, i, &out, 0)) {
\r
420 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_WRITEFILE_FAILED, logger->service_name, logger->path, error_string(GetLastError()), 0);
\r
425 TCHAR rotated[MAX_PATH];
\r
426 rotated_filename(logger->path, rotated, _countof(rotated), 0);
\r
429 Ideally we'd try the rename first then close the handle but
\r
430 MoveFile() will fail if the handle is still open so we must
\r
431 risk losing everything.
\r
433 CloseHandle(logger->write_handle);
\r
434 if (! MoveFile(logger->path, rotated)) {
\r
435 unsigned long error = GetLastError();
\r
436 if (error != ERROR_FILE_NOT_FOUND) {
\r
437 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_ROTATE_FILE_FAILED, logger->service_name, logger->path, _T("MoveFile()"), rotated, error_string(error), 0);
\r
438 /* We can at least try to re-open the existing file. */
\r
439 logger->disposition = OPEN_ALWAYS;
\r
444 logger->write_handle = append_to_file(logger->path, logger->sharing, 0, logger->disposition, logger->flags);
\r
445 if (! logger->write_handle) {
\r
446 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEFILE_FAILED, logger->path, error_string(GetLastError()), 0);
\r
447 /* Oh dear. Now we can't log anything further. */
\r
451 /* Unicode files need a new BOM. */
\r
452 if (unicode) write_bom(logger);
\r
454 /* Resume writing after the newline. */
\r
456 address = (void *) ((char *) address + i);
\r
464 /* Write a BOM to the new file. */
\r
465 if (IsTextUnicode(address, sizeof(buffer), 0)) write_bom(logger);
\r
468 /* Write the data. */
\r
469 if (! WriteFile(logger->write_handle, address, in, &out, 0)) {
\r
470 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_WRITEFILE_FAILED, logger->service_name, logger->path, error_string(GetLastError()), 0);
\r
474 size += (__int64) out;
\r