Fake stdin for applications which exit on EOF.
[nssm.git] / io.cpp
1 #include "nssm.h"\r
2 \r
3 #define COMPLAINED_READ (1 << 0)\r
4 #define COMPLAINED_WRITE (1 << 1)\r
5 #define COMPLAINED_ROTATE (1 << 2)\r
6 \r
7 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) {\r
8   *tid_ptr = 0;\r
9 \r
10   /* Pipe between application's stdout/stderr and our logging handle. */\r
11   if (read_handle_ptr && ! *read_handle_ptr) {\r
12     if (pipe_handle_ptr && ! *pipe_handle_ptr) {\r
13       if (CreatePipe(read_handle_ptr, pipe_handle_ptr, 0, 0)) {\r
14         SetHandleInformation(*pipe_handle_ptr, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT);\r
15       }\r
16       else {\r
17         log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPIPE_FAILED, service_name, path, error_string(GetLastError()));\r
18         return (HANDLE) 0;\r
19       }\r
20     }\r
21   }\r
22 \r
23   logger_t *logger = (logger_t *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(logger_t));\r
24   if (! logger) {\r
25     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("logger"), _T("create_logging_thread()"), 0);\r
26     return (HANDLE) 0;\r
27   }\r
28 \r
29   ULARGE_INTEGER size;\r
30   size.LowPart = rotate_bytes_low;\r
31   size.HighPart = rotate_bytes_high;\r
32 \r
33   logger->service_name = service_name;\r
34   logger->path = path;\r
35   logger->sharing = sharing;\r
36   logger->disposition = disposition;\r
37   logger->flags = flags;\r
38   logger->read_handle = *read_handle_ptr;\r
39   logger->write_handle = *write_handle_ptr;\r
40   logger->size = (__int64) size.QuadPart;\r
41   logger->tid_ptr = tid_ptr;\r
42   logger->rotate_online = rotate_online;\r
43 \r
44   HANDLE thread_handle = CreateThread(NULL, 0, log_and_rotate, (void *) logger, 0, logger->tid_ptr);\r
45   if (! thread_handle) {\r
46     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETHREAD_FAILED, error_string(GetLastError()), 0);\r
47     HeapFree(GetProcessHeap(), 0, logger);\r
48   }\r
49 \r
50   return thread_handle;\r
51 }\r
52 \r
53 static inline unsigned long guess_charsize(void *address, unsigned long bufsize) {\r
54   if (IsTextUnicode(address, bufsize, 0)) return (unsigned long) sizeof(wchar_t);\r
55   else return (unsigned long) sizeof(char);\r
56 }\r
57 \r
58 static inline void write_bom(logger_t *logger, unsigned long *out) {\r
59   wchar_t bom = L'\ufeff';\r
60   if (! WriteFile(logger->write_handle, (void *) &bom, sizeof(bom), out, 0)) {\r
61     log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_SOMEBODY_SET_UP_US_THE_BOM, logger->service_name, logger->path, error_string(GetLastError()), 0);\r
62   }\r
63 }\r
64 \r
65 /* Get path, share mode, creation disposition and flags for a stream. */\r
66 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
67   TCHAR value[NSSM_STDIO_LENGTH];\r
68 \r
69   /* Path. */\r
70   if (_sntprintf_s(value, _countof(value), _TRUNCATE, _T("%s"), prefix) < 0) {\r
71     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, prefix, _T("get_createfile_parameters()"), 0);\r
72     return 1;\r
73   }\r
74   switch (expand_parameter(key, value, path, PATH_LENGTH, true, false)) {\r
75     case 0: if (! path[0]) return 0; break; /* OK. */\r
76     default: return 2; /* Error. */\r
77   }\r
78 \r
79   /* ShareMode. */\r
80   if (_sntprintf_s(value, _countof(value), _TRUNCATE, _T("%s%s"), prefix, NSSM_REG_STDIO_SHARING) < 0) {\r
81     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, NSSM_REG_STDIO_SHARING, _T("get_createfile_parameters()"), 0);\r
82     return 3;\r
83   }\r
84   switch (get_number(key, value, sharing, false)) {\r
85     case 0: *sharing = default_sharing; break; /* Missing. */\r
86     case 1: break; /* Found. */\r
87     case -2: return 4; break; /* Error. */\r
88   }\r
89 \r
90   /* CreationDisposition. */\r
91   if (_sntprintf_s(value, _countof(value), _TRUNCATE, _T("%s%s"), prefix, NSSM_REG_STDIO_DISPOSITION) < 0) {\r
92     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, NSSM_REG_STDIO_DISPOSITION, _T("get_createfile_parameters()"), 0);\r
93     return 5;\r
94   }\r
95   switch (get_number(key, value, disposition, false)) {\r
96     case 0: *disposition = default_disposition; break; /* Missing. */\r
97     case 1: break; /* Found. */\r
98     case -2: return 6; break; /* Error. */\r
99   }\r
100 \r
101   /* Flags. */\r
102   if (_sntprintf_s(value, _countof(value), _TRUNCATE, _T("%s%s"), prefix, NSSM_REG_STDIO_FLAGS) < 0) {\r
103     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, NSSM_REG_STDIO_FLAGS, _T("get_createfile_parameters()"), 0);\r
104     return 7;\r
105   }\r
106   switch (get_number(key, value, flags, false)) {\r
107     case 0: *flags = default_flags; break; /* Missing. */\r
108     case 1: break; /* Found. */\r
109     case -2: return 8; break; /* Error. */\r
110   }\r
111 \r
112   return 0;\r
113 }\r
114 \r
115 int set_createfile_parameter(HKEY key, TCHAR *prefix, TCHAR *suffix, unsigned long number) {\r
116   TCHAR value[NSSM_STDIO_LENGTH];\r
117 \r
118   if (_sntprintf_s(value, _countof(value), _TRUNCATE, _T("%s%s"), prefix, suffix) < 0) {\r
119     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, suffix, _T("set_createfile_parameter()"), 0);\r
120     return 1;\r
121   }\r
122 \r
123   return set_number(key, value, number);\r
124 }\r
125 \r
126 int delete_createfile_parameter(HKEY key, TCHAR *prefix, TCHAR *suffix) {\r
127   TCHAR value[NSSM_STDIO_LENGTH];\r
128 \r
129   if (_sntprintf_s(value, _countof(value), _TRUNCATE, _T("%s%s"), prefix, suffix) < 0) {\r
130     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, suffix, _T("delete_createfile_parameter()"), 0);\r
131     return 1;\r
132   }\r
133 \r
134   if (RegDeleteValue(key, value)) return 0;\r
135   return 1;\r
136 }\r
137 \r
138 HANDLE append_to_file(TCHAR *path, unsigned long sharing, SECURITY_ATTRIBUTES *attributes, unsigned long disposition, unsigned long flags) {\r
139   HANDLE ret;\r
140 \r
141   /* Try to append to the file first. */\r
142   ret = CreateFile(path, FILE_APPEND_DATA, sharing, attributes, disposition, flags, 0);\r
143   if (ret) {\r
144     SetEndOfFile(ret);\r
145     return ret;\r
146   }\r
147 \r
148   unsigned long error = GetLastError();\r
149   if (error != ERROR_FILE_NOT_FOUND) {\r
150     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEFILE_FAILED, path, error_string(error), 0);\r
151     return (HANDLE) 0;\r
152   }\r
153 \r
154   /* It didn't exist.  Create it. */\r
155   ret = CreateFile(path, FILE_WRITE_DATA, sharing, attributes, disposition, flags, 0);\r
156   if (! ret) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEFILE_FAILED, path, error_string(GetLastError()), 0);\r
157 \r
158   return ret;\r
159 }\r
160 \r
161 static void rotated_filename(TCHAR *path, TCHAR *rotated, unsigned long rotated_len, SYSTEMTIME *st) {\r
162   if (! st) {\r
163     SYSTEMTIME now;\r
164     st = &now;\r
165     GetSystemTime(st);\r
166   }\r
167 \r
168   TCHAR buffer[PATH_LENGTH];\r
169   memmove(buffer, path, sizeof(buffer));\r
170   TCHAR *ext = PathFindExtension(buffer);\r
171   TCHAR extension[PATH_LENGTH];\r
172   _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
173   *ext = _T('\0');\r
174   _sntprintf_s(rotated, rotated_len, _TRUNCATE, _T("%s%s"), buffer, extension);\r
175 }\r
176 \r
177 void rotate_file(TCHAR *service_name, TCHAR *path, unsigned long seconds, unsigned long low, unsigned long high) {\r
178   unsigned long error;\r
179 \r
180   /* Now. */\r
181   SYSTEMTIME st;\r
182   GetSystemTime(&st);\r
183 \r
184   BY_HANDLE_FILE_INFORMATION info;\r
185 \r
186   /* Try to open the file to check if it exists and to get attributes. */\r
187   HANDLE file = CreateFile(path, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);\r
188   if (file) {\r
189     /* Get file attributes. */\r
190     if (! GetFileInformationByHandle(file, &info)) {\r
191       /* Reuse current time for rotation timestamp. */\r
192       seconds = low = high = 0;\r
193       SystemTimeToFileTime(&st, &info.ftLastWriteTime);\r
194     }\r
195 \r
196     CloseHandle(file);\r
197   }\r
198   else {\r
199     error = GetLastError();\r
200     if (error == ERROR_FILE_NOT_FOUND) return;\r
201     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_ROTATE_FILE_FAILED, service_name, path, _T("CreateFile()"), path, error_string(error), 0);\r
202     /* Reuse current time for rotation timestamp. */\r
203     seconds = low = high = 0;\r
204     SystemTimeToFileTime(&st, &info.ftLastWriteTime);\r
205   }\r
206 \r
207   /* Check file age. */\r
208   if (seconds) {\r
209     FILETIME ft;\r
210     SystemTimeToFileTime(&st, &ft);\r
211 \r
212     ULARGE_INTEGER s;\r
213     s.LowPart = ft.dwLowDateTime;\r
214     s.HighPart = ft.dwHighDateTime;\r
215     s.QuadPart -= seconds * 10000000LL;\r
216     ft.dwLowDateTime = s.LowPart;\r
217     ft.dwHighDateTime = s.HighPart;\r
218     if (CompareFileTime(&info.ftLastWriteTime, &ft) > 0) return;\r
219   }\r
220 \r
221   /* Check file size. */\r
222   if (low || high) {\r
223     if (info.nFileSizeHigh < high) return;\r
224     if (info.nFileSizeHigh == high && info.nFileSizeLow < low) return;\r
225   }\r
226 \r
227   /* Get new filename. */\r
228   FileTimeToSystemTime(&info.ftLastWriteTime, &st);\r
229 \r
230   TCHAR rotated[PATH_LENGTH];\r
231   rotated_filename(path, rotated, _countof(rotated), &st);\r
232 \r
233   /* Rotate. */\r
234   if (MoveFile(path, rotated)) {\r
235     log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_ROTATED, service_name, path, rotated, 0);\r
236     return;\r
237   }\r
238   error = GetLastError();\r
239 \r
240   if (error == ERROR_FILE_NOT_FOUND) return;\r
241   log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_ROTATE_FILE_FAILED, service_name, path, _T("MoveFile()"), rotated, error_string(error), 0);\r
242   return;\r
243 }\r
244 \r
245 int get_output_handles(nssm_service_t *service, HKEY key, STARTUPINFO *si) {\r
246   bool set_flags = false;\r
247 \r
248   /* Standard security attributes allowing inheritance. */\r
249   SECURITY_ATTRIBUTES attributes;\r
250   ZeroMemory(&attributes, sizeof(attributes));\r
251   attributes.bInheritHandle = true;\r
252 \r
253   /* stdout */\r
254   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
255     service->stdout_sharing = service->stdout_disposition = service->stdout_flags = 0;\r
256     ZeroMemory(service->stdout_path, _countof(service->stdout_path) * sizeof(TCHAR));\r
257     return 3;\r
258   }\r
259   if (si && service->stdout_path[0]) {\r
260     if (service->rotate_files) rotate_file(service->name, service->stdout_path, service->rotate_seconds, service->rotate_bytes_low, service->rotate_bytes_high);\r
261     HANDLE stdout_handle = append_to_file(service->stdout_path, service->stdout_sharing, 0, service->stdout_disposition, service->stdout_flags);\r
262     if (! stdout_handle) return 4;\r
263 \r
264     if (service->rotate_files && service->rotate_stdout_online) {\r
265       service->stdout_pipe = si->hStdOutput = 0;\r
266       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);\r
267       if (! service->stdout_thread) {\r
268         CloseHandle(service->stdout_pipe);\r
269         CloseHandle(si->hStdOutput);\r
270       }\r
271     }\r
272     else service->stdout_thread = 0;\r
273 \r
274     if (! service->stdout_thread) {\r
275       if (! DuplicateHandle(GetCurrentProcess(), stdout_handle, GetCurrentProcess(), &si->hStdOutput, 0, true, DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) {\r
276         log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_DUPLICATEHANDLE_FAILED, NSSM_REG_STDOUT, _T("stdout"), error_string(GetLastError()), 0);\r
277         return 4;\r
278       }\r
279       service->rotate_stdout_online = NSSM_ROTATE_OFFLINE;\r
280     }\r
281 \r
282     set_flags = true;\r
283   }\r
284 \r
285   /* stderr */\r
286   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)) {\r
287     service->stderr_sharing = service->stderr_disposition = service->stderr_flags = 0;\r
288     ZeroMemory(service->stderr_path, _countof(service->stderr_path) * sizeof(TCHAR));\r
289     return 5;\r
290   }\r
291   if (service->stderr_path[0]) {\r
292     /* Same as stdout? */\r
293     if (str_equiv(service->stderr_path, service->stdout_path)) {\r
294       service->stderr_sharing = service->stdout_sharing;\r
295       service->stderr_disposition = service->stdout_disposition;\r
296       service->stderr_flags = service->stdout_flags;\r
297       service->rotate_stderr_online = NSSM_ROTATE_OFFLINE;\r
298 \r
299       if (si) {\r
300         /* Two handles to the same file will create a race. */\r
301         if (! DuplicateHandle(GetCurrentProcess(), si->hStdOutput, GetCurrentProcess(), &si->hStdError, 0, true, DUPLICATE_SAME_ACCESS)) {\r
302           log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_DUPLICATEHANDLE_FAILED, NSSM_REG_STDOUT, _T("stderr"), error_string(GetLastError()), 0);\r
303           return 6;\r
304         }\r
305       }\r
306     }\r
307     else if (si) {\r
308       if (service->rotate_files) rotate_file(service->name, service->stderr_path, service->rotate_seconds, service->rotate_bytes_low, service->rotate_bytes_high);\r
309       HANDLE stderr_handle = append_to_file(service->stderr_path, service->stderr_sharing, 0, service->stderr_disposition, service->stderr_flags);\r
310       if (! stderr_handle) return 7;\r
311 \r
312       if (service->rotate_files && service->rotate_stderr_online) {\r
313         service->stderr_pipe = si->hStdError = 0;\r
314         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);\r
315         if (! service->stderr_thread) {\r
316           CloseHandle(service->stderr_pipe);\r
317           CloseHandle(si->hStdError);\r
318         }\r
319       }\r
320       else service->stderr_thread = 0;\r
321 \r
322       if (! service->stderr_thread) {\r
323         if (! DuplicateHandle(GetCurrentProcess(), stderr_handle, GetCurrentProcess(), &si->hStdError, 0, true, DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) {\r
324           log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_DUPLICATEHANDLE_FAILED, NSSM_REG_STDERR, _T("stderr"), error_string(GetLastError()), 0);\r
325           return 7;\r
326         }\r
327         service->rotate_stderr_online = NSSM_ROTATE_OFFLINE;\r
328       }\r
329 \r
330       set_flags = true;\r
331     }\r
332   }\r
333 \r
334   /* stdin */\r
335   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
336     service->stdin_sharing = service->stdin_disposition = service->stdin_flags = 0;\r
337     ZeroMemory(service->stdin_path, _countof(service->stdin_path) * sizeof(TCHAR));\r
338     return 1;\r
339   }\r
340   if (si && service->stdin_path[0]) {\r
341     if (str_equiv(service->stdin_path, _T("|"))) {\r
342       /* Fake stdin with a pipe. */\r
343       if (set_flags) {\r
344         /*\r
345           None of this is necessary if we aren't redirecting stdout and/or\r
346           stderr as well.\r
347 \r
348           If we don't redirect any handles the application will start and be\r
349           quite happy with its console.  If we start it with\r
350           STARTF_USESTDHANDLES set it will interpret a NULL value for\r
351           hStdInput as meaning no input.  Because the service starts with\r
352           no stdin we can't just pass GetStdHandle(STD_INPUT_HANDLE) to\r
353           the application.\r
354 \r
355           The only way we can successfully redirect the application's output\r
356           while preventing programs which exit after reading all input from\r
357           exiting prematurely is to create a pipe between ourselves and the\r
358           application but write nothing to it.\r
359         */\r
360         if (! CreatePipe(&si->hStdInput, &service->stdin_pipe, 0, 0)) {\r
361           log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_STDIN_CREATEPIPE_FAILED, service->name, error_string(GetLastError()), 0);\r
362           return 2;\r
363         }\r
364         SetHandleInformation(si->hStdInput, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT);\r
365       }\r
366     }\r
367     else {\r
368       si->hStdInput = CreateFile(service->stdin_path, FILE_READ_DATA, service->stdin_sharing, &attributes, service->stdin_disposition, service->stdin_flags, 0);\r
369       if (! si->hStdInput) {\r
370         log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEFILE_FAILED, service->stdin_path, error_string(GetLastError()), 0);\r
371         return 2;\r
372       }\r
373 \r
374       set_flags = true;\r
375     }\r
376   }\r
377 \r
378   if (! set_flags) return 0;\r
379 \r
380   /*\r
381     We need to set the startup_info flags to make the new handles\r
382     inheritable by the new process.\r
383   */\r
384   if (si) si->dwFlags |= STARTF_USESTDHANDLES;\r
385 \r
386   return 0;\r
387 }\r
388 \r
389 void close_output_handles(STARTUPINFO *si) {\r
390   if (si->hStdInput) CloseHandle(si->hStdInput);\r
391   if (si->hStdOutput) CloseHandle(si->hStdOutput);\r
392   if (si->hStdError) CloseHandle(si->hStdError);\r
393 }\r
394 \r
395 /*\r
396   Try multiple times to read from a file.\r
397   Returns:  0 on success.\r
398             1 on non-fatal error.\r
399            -1 on fatal error.\r
400 */\r
401 static int try_read(logger_t *logger, void *address, unsigned long bufsize, unsigned long *in, int *complained) {\r
402   int ret = 1;\r
403   unsigned long error;\r
404   for (int tries = 0; tries < 5; tries++) {\r
405     if (ReadFile(logger->read_handle, address, bufsize, in, 0)) return 0;\r
406 \r
407     error = GetLastError();\r
408     switch (error) {\r
409       /* Other end closed the pipe. */\r
410       case ERROR_BROKEN_PIPE:\r
411         ret = -1;\r
412         goto complain_read;\r
413 \r
414       /* Couldn't lock the buffer. */\r
415       case ERROR_NOT_ENOUGH_QUOTA:\r
416         Sleep(2000 + tries * 3000);\r
417         ret = 1;\r
418         continue;\r
419 \r
420       /* Write was cancelled by the other end. */\r
421       case ERROR_OPERATION_ABORTED:\r
422         ret = 1;\r
423         goto complain_read;\r
424 \r
425       default:\r
426         ret = -1;\r
427     }\r
428   }\r
429 \r
430 complain_read:\r
431   /* Ignore the error if we've been requested to exit anyway. */\r
432   if (*logger->rotate_online != NSSM_ROTATE_ONLINE) return ret;\r
433   if (! (*complained & COMPLAINED_READ)) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_READFILE_FAILED, logger->service_name, logger->path, error_string(error), 0);\r
434   *complained |= COMPLAINED_READ;\r
435   return ret;\r
436 }\r
437 \r
438 /*\r
439   Try multiple times to write to a file.\r
440   Returns:  0 on success.\r
441             1 on non-fatal error.\r
442            -1 on fatal error.\r
443 */\r
444 static int try_write(logger_t *logger, void *address, unsigned long bufsize, unsigned long *out, int *complained) {\r
445   int ret = 1;\r
446   unsigned long error;\r
447   for (int tries = 0; tries < 5; tries++) {\r
448     if (WriteFile(logger->write_handle, address, bufsize, out, 0)) return 0;\r
449 \r
450     error = GetLastError();\r
451     if (error == ERROR_IO_PENDING) {\r
452       /* Operation was successful pending flush to disk. */\r
453       return 0;\r
454     }\r
455 \r
456     switch (error) {\r
457       /* Other end closed the pipe. */\r
458       case ERROR_BROKEN_PIPE:\r
459         ret = -1;\r
460         goto complain_write;\r
461 \r
462       /* Couldn't lock the buffer. */\r
463       case ERROR_NOT_ENOUGH_QUOTA:\r
464       /* Out of disk space. */\r
465       case ERROR_DISK_FULL:\r
466         Sleep(2000 + tries * 3000);\r
467         ret = 1;\r
468         continue;\r
469 \r
470       default:\r
471         /* We'll lose this line but try to read and write subsequent ones. */\r
472         ret = 1;\r
473     }\r
474   }\r
475 \r
476 complain_write:\r
477   if (! (*complained & COMPLAINED_WRITE)) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_WRITEFILE_FAILED, logger->service_name, logger->path, error_string(error), 0);\r
478   *complained |= COMPLAINED_WRITE;\r
479   return ret;\r
480 }\r
481 \r
482 /* Wrapper to be called in a new thread for logging. */\r
483 unsigned long WINAPI log_and_rotate(void *arg) {\r
484   logger_t *logger = (logger_t *) arg;\r
485   if (! logger) return 1;\r
486 \r
487   __int64 size;\r
488   BY_HANDLE_FILE_INFORMATION info;\r
489 \r
490   /* Find initial file size. */\r
491   if (! GetFileInformationByHandle(logger->write_handle, &info)) logger->size = 0LL;\r
492   else {\r
493     ULARGE_INTEGER l;\r
494     l.HighPart = info.nFileSizeHigh;\r
495     l.LowPart = info.nFileSizeLow;\r
496     size = l.QuadPart;\r
497   }\r
498 \r
499   char buffer[1024];\r
500   void *address;\r
501   unsigned long in, out;\r
502   unsigned long charsize = 0;\r
503   unsigned long error;\r
504   int ret;\r
505   int complained = 0;\r
506 \r
507   while (true) {\r
508     /* Read data from the pipe. */\r
509     address = &buffer;\r
510     ret = try_read(logger, address, sizeof(buffer), &in, &complained);\r
511     if (ret < 0) {\r
512       CloseHandle(logger->read_handle);\r
513       CloseHandle(logger->write_handle);\r
514       HeapFree(GetProcessHeap(), 0, logger);\r
515       return 2;\r
516     }\r
517     else if (ret) continue;\r
518 \r
519     if (*logger->rotate_online == NSSM_ROTATE_ONLINE_ASAP || (logger->size && size + (__int64) in >= logger->size)) {\r
520       /* Look for newline. */\r
521       unsigned long i;\r
522       for (i = 0; i < in; i++) {\r
523         if (buffer[i] == '\n') {\r
524           if (! charsize) charsize = guess_charsize(address, in);\r
525           i += charsize;\r
526 \r
527           /* Write up to the newline. */\r
528           ret = try_write(logger, address, i, &out, &complained);\r
529           if (ret < 0) {\r
530             HeapFree(GetProcessHeap(), 0, logger);\r
531             CloseHandle(logger->read_handle);\r
532             CloseHandle(logger->write_handle);\r
533             return 3;\r
534           }\r
535           size += (__int64) out;\r
536 \r
537           /* Rotate. */\r
538           *logger->rotate_online = NSSM_ROTATE_ONLINE;\r
539           TCHAR rotated[PATH_LENGTH];\r
540           rotated_filename(logger->path, rotated, _countof(rotated), 0);\r
541 \r
542           /*\r
543             Ideally we'd try the rename first then close the handle but\r
544             MoveFile() will fail if the handle is still open so we must\r
545             risk losing everything.\r
546           */\r
547           CloseHandle(logger->write_handle);\r
548           if (MoveFile(logger->path, rotated)) {\r
549             log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_ROTATED, logger->service_name, logger->path, rotated, 0);\r
550             size = 0LL;\r
551           }\r
552           else {\r
553             error = GetLastError();\r
554             if (error != ERROR_FILE_NOT_FOUND) {\r
555               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);\r
556               complained |= COMPLAINED_ROTATE;\r
557               /* We can at least try to re-open the existing file. */\r
558               logger->disposition = OPEN_ALWAYS;\r
559             }\r
560           }\r
561 \r
562           /* Reopen. */\r
563           logger->write_handle = append_to_file(logger->path, logger->sharing, 0, logger->disposition, logger->flags);\r
564           if (! logger->write_handle) {\r
565             error = GetLastError();\r
566             log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEFILE_FAILED, logger->path, error_string(error), 0);\r
567             /* Oh dear.  Now we can't log anything further. */\r
568             HeapFree(GetProcessHeap(), 0, logger);\r
569             CloseHandle(logger->read_handle);\r
570             CloseHandle(logger->write_handle);\r
571             return 4;\r
572           }\r
573 \r
574           /* Resume writing after the newline. */\r
575           address = (void *) ((char *) address + i);\r
576           in -= i;\r
577         }\r
578       }\r
579     }\r
580 \r
581     if (! size) {\r
582       /* Write a BOM to the new file. */\r
583       if (! charsize) charsize = guess_charsize(address, in);\r
584       if (charsize == sizeof(wchar_t)) write_bom(logger, &out);\r
585       size += (__int64) out;\r
586     }\r
587 \r
588     /* Write the data, if any. */\r
589     if (! in) continue;\r
590 \r
591     ret = try_write(logger, address, in, &out, &complained);\r
592     size += (__int64) out;\r
593     if (ret < 0) {\r
594       HeapFree(GetProcessHeap(), 0, logger);\r
595       CloseHandle(logger->read_handle);\r
596       CloseHandle(logger->write_handle);\r
597       return 3;\r
598     }\r
599   }\r
600 \r
601   HeapFree(GetProcessHeap(), 0, logger);\r
602   CloseHandle(logger->read_handle);\r
603   CloseHandle(logger->write_handle);\r
604   return 0;\r
605 }\r