Fixed CreateFile() flags when opening log files.
[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 write_to_file(TCHAR *path, unsigned long sharing, SECURITY_ATTRIBUTES *attributes, unsigned long disposition, unsigned long flags) {\r
139   HANDLE ret = CreateFile(path, FILE_WRITE_DATA, sharing, attributes, disposition, flags, 0);\r
140   if (ret) {\r
141     if (SetFilePointer(ret, 0, 0, FILE_END) != INVALID_SET_FILE_POINTER) SetEndOfFile(ret);\r
142     return ret;\r
143   }\r
144 \r
145   log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEFILE_FAILED, path, error_string(GetLastError()), 0);\r
146   return ret;\r
147 }\r
148 \r
149 static void rotated_filename(TCHAR *path, TCHAR *rotated, unsigned long rotated_len, SYSTEMTIME *st) {\r
150   if (! st) {\r
151     SYSTEMTIME now;\r
152     st = &now;\r
153     GetSystemTime(st);\r
154   }\r
155 \r
156   TCHAR buffer[PATH_LENGTH];\r
157   memmove(buffer, path, sizeof(buffer));\r
158   TCHAR *ext = PathFindExtension(buffer);\r
159   TCHAR extension[PATH_LENGTH];\r
160   _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
161   *ext = _T('\0');\r
162   _sntprintf_s(rotated, rotated_len, _TRUNCATE, _T("%s%s"), buffer, extension);\r
163 }\r
164 \r
165 void rotate_file(TCHAR *service_name, TCHAR *path, unsigned long seconds, unsigned long low, unsigned long high) {\r
166   unsigned long error;\r
167 \r
168   /* Now. */\r
169   SYSTEMTIME st;\r
170   GetSystemTime(&st);\r
171 \r
172   BY_HANDLE_FILE_INFORMATION info;\r
173 \r
174   /* Try to open the file to check if it exists and to get attributes. */\r
175   HANDLE file = CreateFile(path, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);\r
176   if (file) {\r
177     /* Get file attributes. */\r
178     if (! GetFileInformationByHandle(file, &info)) {\r
179       /* Reuse current time for rotation timestamp. */\r
180       seconds = low = high = 0;\r
181       SystemTimeToFileTime(&st, &info.ftLastWriteTime);\r
182     }\r
183 \r
184     CloseHandle(file);\r
185   }\r
186   else {\r
187     error = GetLastError();\r
188     if (error == ERROR_FILE_NOT_FOUND) return;\r
189     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_ROTATE_FILE_FAILED, service_name, path, _T("CreateFile()"), path, error_string(error), 0);\r
190     /* Reuse current time for rotation timestamp. */\r
191     seconds = low = high = 0;\r
192     SystemTimeToFileTime(&st, &info.ftLastWriteTime);\r
193   }\r
194 \r
195   /* Check file age. */\r
196   if (seconds) {\r
197     FILETIME ft;\r
198     SystemTimeToFileTime(&st, &ft);\r
199 \r
200     ULARGE_INTEGER s;\r
201     s.LowPart = ft.dwLowDateTime;\r
202     s.HighPart = ft.dwHighDateTime;\r
203     s.QuadPart -= seconds * 10000000LL;\r
204     ft.dwLowDateTime = s.LowPart;\r
205     ft.dwHighDateTime = s.HighPart;\r
206     if (CompareFileTime(&info.ftLastWriteTime, &ft) > 0) return;\r
207   }\r
208 \r
209   /* Check file size. */\r
210   if (low || high) {\r
211     if (info.nFileSizeHigh < high) return;\r
212     if (info.nFileSizeHigh == high && info.nFileSizeLow < low) return;\r
213   }\r
214 \r
215   /* Get new filename. */\r
216   FileTimeToSystemTime(&info.ftLastWriteTime, &st);\r
217 \r
218   TCHAR rotated[PATH_LENGTH];\r
219   rotated_filename(path, rotated, _countof(rotated), &st);\r
220 \r
221   /* Rotate. */\r
222   if (MoveFile(path, rotated)) {\r
223     log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_ROTATED, service_name, path, rotated, 0);\r
224     return;\r
225   }\r
226   error = GetLastError();\r
227 \r
228   if (error == ERROR_FILE_NOT_FOUND) return;\r
229   log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_ROTATE_FILE_FAILED, service_name, path, _T("MoveFile()"), rotated, error_string(error), 0);\r
230   return;\r
231 }\r
232 \r
233 int get_output_handles(nssm_service_t *service, HKEY key, STARTUPINFO *si) {\r
234   bool set_flags = false;\r
235 \r
236   /* Standard security attributes allowing inheritance. */\r
237   SECURITY_ATTRIBUTES attributes;\r
238   ZeroMemory(&attributes, sizeof(attributes));\r
239   attributes.bInheritHandle = true;\r
240 \r
241   /* stdout */\r
242   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
243     service->stdout_sharing = service->stdout_disposition = service->stdout_flags = 0;\r
244     ZeroMemory(service->stdout_path, _countof(service->stdout_path) * sizeof(TCHAR));\r
245     return 3;\r
246   }\r
247   if (si && service->stdout_path[0]) {\r
248     if (service->rotate_files) rotate_file(service->name, service->stdout_path, service->rotate_seconds, service->rotate_bytes_low, service->rotate_bytes_high);\r
249     HANDLE stdout_handle = write_to_file(service->stdout_path, service->stdout_sharing, 0, service->stdout_disposition, service->stdout_flags);\r
250     if (! stdout_handle) return 4;\r
251 \r
252     if (service->rotate_files && service->rotate_stdout_online) {\r
253       service->stdout_pipe = si->hStdOutput = 0;\r
254       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
255       if (! service->stdout_thread) {\r
256         CloseHandle(service->stdout_pipe);\r
257         CloseHandle(si->hStdOutput);\r
258       }\r
259     }\r
260     else service->stdout_thread = 0;\r
261 \r
262     if (! service->stdout_thread) {\r
263       if (! DuplicateHandle(GetCurrentProcess(), stdout_handle, GetCurrentProcess(), &si->hStdOutput, 0, true, DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) {\r
264         log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_DUPLICATEHANDLE_FAILED, NSSM_REG_STDOUT, _T("stdout"), error_string(GetLastError()), 0);\r
265         return 4;\r
266       }\r
267       service->rotate_stdout_online = NSSM_ROTATE_OFFLINE;\r
268     }\r
269 \r
270     set_flags = true;\r
271   }\r
272 \r
273   /* stderr */\r
274   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
275     service->stderr_sharing = service->stderr_disposition = service->stderr_flags = 0;\r
276     ZeroMemory(service->stderr_path, _countof(service->stderr_path) * sizeof(TCHAR));\r
277     return 5;\r
278   }\r
279   if (service->stderr_path[0]) {\r
280     /* Same as stdout? */\r
281     if (str_equiv(service->stderr_path, service->stdout_path)) {\r
282       service->stderr_sharing = service->stdout_sharing;\r
283       service->stderr_disposition = service->stdout_disposition;\r
284       service->stderr_flags = service->stdout_flags;\r
285       service->rotate_stderr_online = NSSM_ROTATE_OFFLINE;\r
286 \r
287       if (si) {\r
288         /* Two handles to the same file will create a race. */\r
289         if (! DuplicateHandle(GetCurrentProcess(), si->hStdOutput, GetCurrentProcess(), &si->hStdError, 0, true, DUPLICATE_SAME_ACCESS)) {\r
290           log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_DUPLICATEHANDLE_FAILED, NSSM_REG_STDOUT, _T("stderr"), error_string(GetLastError()), 0);\r
291           return 6;\r
292         }\r
293       }\r
294     }\r
295     else if (si) {\r
296       if (service->rotate_files) rotate_file(service->name, service->stderr_path, service->rotate_seconds, service->rotate_bytes_low, service->rotate_bytes_high);\r
297       HANDLE stderr_handle = write_to_file(service->stderr_path, service->stderr_sharing, 0, service->stderr_disposition, service->stderr_flags);\r
298       if (! stderr_handle) return 7;\r
299 \r
300       if (service->rotate_files && service->rotate_stderr_online) {\r
301         service->stderr_pipe = si->hStdError = 0;\r
302         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
303         if (! service->stderr_thread) {\r
304           CloseHandle(service->stderr_pipe);\r
305           CloseHandle(si->hStdError);\r
306         }\r
307       }\r
308       else service->stderr_thread = 0;\r
309 \r
310       if (! service->stderr_thread) {\r
311         if (! DuplicateHandle(GetCurrentProcess(), stderr_handle, GetCurrentProcess(), &si->hStdError, 0, true, DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) {\r
312           log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_DUPLICATEHANDLE_FAILED, NSSM_REG_STDERR, _T("stderr"), error_string(GetLastError()), 0);\r
313           return 7;\r
314         }\r
315         service->rotate_stderr_online = NSSM_ROTATE_OFFLINE;\r
316       }\r
317 \r
318       set_flags = true;\r
319     }\r
320   }\r
321 \r
322   /* stdin */\r
323   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
324     service->stdin_sharing = service->stdin_disposition = service->stdin_flags = 0;\r
325     ZeroMemory(service->stdin_path, _countof(service->stdin_path) * sizeof(TCHAR));\r
326     return 1;\r
327   }\r
328   if (si && service->stdin_path[0]) {\r
329     if (str_equiv(service->stdin_path, _T("|"))) {\r
330       /* Fake stdin with a pipe. */\r
331       if (set_flags) {\r
332         /*\r
333           None of this is necessary if we aren't redirecting stdout and/or\r
334           stderr as well.\r
335 \r
336           If we don't redirect any handles the application will start and be\r
337           quite happy with its console.  If we start it with\r
338           STARTF_USESTDHANDLES set it will interpret a NULL value for\r
339           hStdInput as meaning no input.  Because the service starts with\r
340           no stdin we can't just pass GetStdHandle(STD_INPUT_HANDLE) to\r
341           the application.\r
342 \r
343           The only way we can successfully redirect the application's output\r
344           while preventing programs which exit after reading all input from\r
345           exiting prematurely is to create a pipe between ourselves and the\r
346           application but write nothing to it.\r
347         */\r
348         if (! CreatePipe(&si->hStdInput, &service->stdin_pipe, 0, 0)) {\r
349           log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_STDIN_CREATEPIPE_FAILED, service->name, error_string(GetLastError()), 0);\r
350           return 2;\r
351         }\r
352         SetHandleInformation(si->hStdInput, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT);\r
353       }\r
354     }\r
355     else {\r
356       si->hStdInput = CreateFile(service->stdin_path, FILE_READ_DATA, service->stdin_sharing, &attributes, service->stdin_disposition, service->stdin_flags, 0);\r
357       if (! si->hStdInput) {\r
358         log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEFILE_FAILED, service->stdin_path, error_string(GetLastError()), 0);\r
359         return 2;\r
360       }\r
361 \r
362       set_flags = true;\r
363     }\r
364   }\r
365 \r
366   if (! set_flags) return 0;\r
367 \r
368   /*\r
369     We need to set the startup_info flags to make the new handles\r
370     inheritable by the new process.\r
371   */\r
372   if (si) si->dwFlags |= STARTF_USESTDHANDLES;\r
373 \r
374   return 0;\r
375 }\r
376 \r
377 void close_output_handles(STARTUPINFO *si) {\r
378   if (si->hStdInput) CloseHandle(si->hStdInput);\r
379   if (si->hStdOutput) CloseHandle(si->hStdOutput);\r
380   if (si->hStdError) CloseHandle(si->hStdError);\r
381 }\r
382 \r
383 /*\r
384   Try multiple times to read from a file.\r
385   Returns:  0 on success.\r
386             1 on non-fatal error.\r
387            -1 on fatal error.\r
388 */\r
389 static int try_read(logger_t *logger, void *address, unsigned long bufsize, unsigned long *in, int *complained) {\r
390   int ret = 1;\r
391   unsigned long error;\r
392   for (int tries = 0; tries < 5; tries++) {\r
393     if (ReadFile(logger->read_handle, address, bufsize, in, 0)) return 0;\r
394 \r
395     error = GetLastError();\r
396     switch (error) {\r
397       /* Other end closed the pipe. */\r
398       case ERROR_BROKEN_PIPE:\r
399         ret = -1;\r
400         goto complain_read;\r
401 \r
402       /* Couldn't lock the buffer. */\r
403       case ERROR_NOT_ENOUGH_QUOTA:\r
404         Sleep(2000 + tries * 3000);\r
405         ret = 1;\r
406         continue;\r
407 \r
408       /* Write was cancelled by the other end. */\r
409       case ERROR_OPERATION_ABORTED:\r
410         ret = 1;\r
411         goto complain_read;\r
412 \r
413       default:\r
414         ret = -1;\r
415     }\r
416   }\r
417 \r
418 complain_read:\r
419   /* Ignore the error if we've been requested to exit anyway. */\r
420   if (*logger->rotate_online != NSSM_ROTATE_ONLINE) return ret;\r
421   if (! (*complained & COMPLAINED_READ)) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_READFILE_FAILED, logger->service_name, logger->path, error_string(error), 0);\r
422   *complained |= COMPLAINED_READ;\r
423   return ret;\r
424 }\r
425 \r
426 /*\r
427   Try multiple times to write to a file.\r
428   Returns:  0 on success.\r
429             1 on non-fatal error.\r
430            -1 on fatal error.\r
431 */\r
432 static int try_write(logger_t *logger, void *address, unsigned long bufsize, unsigned long *out, int *complained) {\r
433   int ret = 1;\r
434   unsigned long error;\r
435   for (int tries = 0; tries < 5; tries++) {\r
436     if (WriteFile(logger->write_handle, address, bufsize, out, 0)) return 0;\r
437 \r
438     error = GetLastError();\r
439     if (error == ERROR_IO_PENDING) {\r
440       /* Operation was successful pending flush to disk. */\r
441       return 0;\r
442     }\r
443 \r
444     switch (error) {\r
445       /* Other end closed the pipe. */\r
446       case ERROR_BROKEN_PIPE:\r
447         ret = -1;\r
448         goto complain_write;\r
449 \r
450       /* Couldn't lock the buffer. */\r
451       case ERROR_NOT_ENOUGH_QUOTA:\r
452       /* Out of disk space. */\r
453       case ERROR_DISK_FULL:\r
454         Sleep(2000 + tries * 3000);\r
455         ret = 1;\r
456         continue;\r
457 \r
458       default:\r
459         /* We'll lose this line but try to read and write subsequent ones. */\r
460         ret = 1;\r
461     }\r
462   }\r
463 \r
464 complain_write:\r
465   if (! (*complained & COMPLAINED_WRITE)) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_WRITEFILE_FAILED, logger->service_name, logger->path, error_string(error), 0);\r
466   *complained |= COMPLAINED_WRITE;\r
467   return ret;\r
468 }\r
469 \r
470 /* Wrapper to be called in a new thread for logging. */\r
471 unsigned long WINAPI log_and_rotate(void *arg) {\r
472   logger_t *logger = (logger_t *) arg;\r
473   if (! logger) return 1;\r
474 \r
475   __int64 size;\r
476   BY_HANDLE_FILE_INFORMATION info;\r
477 \r
478   /* Find initial file size. */\r
479   if (! GetFileInformationByHandle(logger->write_handle, &info)) logger->size = 0LL;\r
480   else {\r
481     ULARGE_INTEGER l;\r
482     l.HighPart = info.nFileSizeHigh;\r
483     l.LowPart = info.nFileSizeLow;\r
484     size = l.QuadPart;\r
485   }\r
486 \r
487   char buffer[1024];\r
488   void *address;\r
489   unsigned long in, out;\r
490   unsigned long charsize = 0;\r
491   unsigned long error;\r
492   int ret;\r
493   int complained = 0;\r
494 \r
495   while (true) {\r
496     /* Read data from the pipe. */\r
497     address = &buffer;\r
498     ret = try_read(logger, address, sizeof(buffer), &in, &complained);\r
499     if (ret < 0) {\r
500       CloseHandle(logger->read_handle);\r
501       CloseHandle(logger->write_handle);\r
502       HeapFree(GetProcessHeap(), 0, logger);\r
503       return 2;\r
504     }\r
505     else if (ret) continue;\r
506 \r
507     if (*logger->rotate_online == NSSM_ROTATE_ONLINE_ASAP || (logger->size && size + (__int64) in >= logger->size)) {\r
508       /* Look for newline. */\r
509       unsigned long i;\r
510       for (i = 0; i < in; i++) {\r
511         if (buffer[i] == '\n') {\r
512           if (! charsize) charsize = guess_charsize(address, in);\r
513           i += charsize;\r
514 \r
515           /* Write up to the newline. */\r
516           ret = try_write(logger, address, i, &out, &complained);\r
517           if (ret < 0) {\r
518             HeapFree(GetProcessHeap(), 0, logger);\r
519             CloseHandle(logger->read_handle);\r
520             CloseHandle(logger->write_handle);\r
521             return 3;\r
522           }\r
523           size += (__int64) out;\r
524 \r
525           /* Rotate. */\r
526           *logger->rotate_online = NSSM_ROTATE_ONLINE;\r
527           TCHAR rotated[PATH_LENGTH];\r
528           rotated_filename(logger->path, rotated, _countof(rotated), 0);\r
529 \r
530           /*\r
531             Ideally we'd try the rename first then close the handle but\r
532             MoveFile() will fail if the handle is still open so we must\r
533             risk losing everything.\r
534           */\r
535           CloseHandle(logger->write_handle);\r
536           if (MoveFile(logger->path, rotated)) {\r
537             log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_ROTATED, logger->service_name, logger->path, rotated, 0);\r
538             size = 0LL;\r
539           }\r
540           else {\r
541             error = GetLastError();\r
542             if (error != ERROR_FILE_NOT_FOUND) {\r
543               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
544               complained |= COMPLAINED_ROTATE;\r
545               /* We can at least try to re-open the existing file. */\r
546               logger->disposition = OPEN_ALWAYS;\r
547             }\r
548           }\r
549 \r
550           /* Reopen. */\r
551           logger->write_handle = write_to_file(logger->path, logger->sharing, 0, logger->disposition, logger->flags);\r
552           if (! logger->write_handle) {\r
553             error = GetLastError();\r
554             log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEFILE_FAILED, logger->path, error_string(error), 0);\r
555             /* Oh dear.  Now we can't log anything further. */\r
556             HeapFree(GetProcessHeap(), 0, logger);\r
557             CloseHandle(logger->read_handle);\r
558             CloseHandle(logger->write_handle);\r
559             return 4;\r
560           }\r
561 \r
562           /* Resume writing after the newline. */\r
563           address = (void *) ((char *) address + i);\r
564           in -= i;\r
565         }\r
566       }\r
567     }\r
568 \r
569     if (! size) {\r
570       /* Write a BOM to the new file. */\r
571       if (! charsize) charsize = guess_charsize(address, in);\r
572       if (charsize == sizeof(wchar_t)) write_bom(logger, &out);\r
573       size += (__int64) out;\r
574     }\r
575 \r
576     /* Write the data, if any. */\r
577     if (! in) continue;\r
578 \r
579     ret = try_write(logger, address, in, &out, &complained);\r
580     size += (__int64) out;\r
581     if (ret < 0) {\r
582       HeapFree(GetProcessHeap(), 0, logger);\r
583       CloseHandle(logger->read_handle);\r
584       CloseHandle(logger->write_handle);\r
585       return 3;\r
586     }\r
587   }\r
588 \r
589   HeapFree(GetProcessHeap(), 0, logger);\r
590   CloseHandle(logger->read_handle);\r
591   CloseHandle(logger->write_handle);\r
592   return 0;\r
593 }\r