svc - Implement more features
[dragonfly.git] / sbin / svc / execute.c
1 /*
2  * Copyright (c) 2014 The DragonFly Project.  All rights reserved.
3  *
4  * This code is derived from software contributed to The DragonFly Project
5  * by Matthew Dillon <dillon@backplane.com>
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in
15  *    the documentation and/or other materials provided with the
16  *    distribution.
17  * 3. Neither the name of The DragonFly Project nor the names of its
18  *    contributors may be used to endorse or promote products derived
19  *    from this software without specific, prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
25  * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26  * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
27  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
29  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
30  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
31  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32  * SUCH DAMAGE.
33  */
34 /*
35  * Handle remote listen/connect and parsing operations.
36  */
37
38 #include "svc.h"
39
40 pthread_mutex_t serial_mtx;
41 time_t LastStart;       /* uptime */
42 time_t LastStop;        /* uptime */
43 pid_t DirectPid = -1;
44 runstate_t RunState = RS_STOPPED;
45 command_t *InitCmd;
46 int RestartCounter;
47
48 static void *logger_thread(void *arg);
49 static void setstate_stopped(command_t *cmd, struct timespec *ts);
50 static int escapewrite(FILE *fp, char *buf, int n, int *statep);
51
52 int
53 execute_init(command_t *cmd)
54 {
55         char buf[32];
56         pid_t pid;
57         pid_t stoppingpid = -1;
58         pthread_t logtd;
59         time_t nextstop = 0;
60         int lfd;        /* unix domain listen socket */
61         int pfd;        /* pid file */
62         int xfd;
63         int rc;
64         int fds[2];
65         char c;
66
67         if (cmd->label == NULL || cmd->ext_ac == 0) {
68                 fprintf(cmd->fp, "init requires a label and command\n");
69                 return 1;
70         }
71         fprintf(cmd->fp, "initializing new service: %s\n", cmd->label);
72
73         if ((xfd = open("/dev/null", O_RDWR)) < 0) {
74                 fprintf(cmd->fp, "Unable to open /dev/null: %s\n",
75                         strerror(errno));
76                 return 1;
77         }
78
79         /*
80          * Setup pidfile and unix domain listen socket and lock the
81          * pidfile.
82          */
83         rc = setup_pid_and_socket(cmd, &lfd, &pfd);
84         if (rc)
85                 return rc;
86
87         /*
88          * Detach the service
89          */
90         if (cmd->foreground) {
91                 /*
92                  * Stay in foreground.
93                  */
94                 fds[0] = -1;
95                 fds[1] = -1;
96                 pid = 0;
97         } else {
98                 if (pipe(fds) < 0) {
99                         fprintf(cmd->fp, "Unable to create pipe: %s\n",
100                                 strerror(errno));
101                         close(lfd);
102                         close(pfd);
103                         remove_pid_and_socket(cmd, cmd->label);
104                         return 1;
105                 }
106                 pid = fork();
107         }
108
109         if (pid != 0) {
110                 /*
111                  * Parent
112                  */
113                 close(fds[1]);
114                 if (pid < 0) {
115                         fprintf(cmd->fp, "fork failed: %s\n", strerror(errno));
116                         close(lfd);
117                         close(pfd);
118                         close(fds[0]);
119                         close(fds[1]);
120                         remove_pid_and_socket(cmd, cmd->label);
121                         return 1;
122                 } else {
123                         /*
124                          * Fill-in pfd before returning.
125                          */
126                         snprintf(buf, sizeof(buf), "%d\n", (int)pid);
127                         write(pfd, buf, strlen(buf));
128                 }
129                 close(lfd);
130                 close(pfd);
131
132                 /*
133                  * Wait for child to completely detach from the tty
134                  * before returning.
135                  */
136                 read(fds[0], &c, 1);
137                 close(fds[0]);
138
139                 return 0;
140         }
141
142         /*
143          * Forked child is now the service demon.
144          *
145          * Detach from terminal, scrap tty.
146          */
147         if (cmd->foreground == 0) {
148                 close(fds[0]);
149                 fds[0] = -1;
150         }
151
152         if (xfd != 0)                           /* scrap tty inputs */
153                 dup2(xfd, 0);
154         if (cmd->foreground == 0) {
155                 int tfd;
156
157                 if (xfd != 1)                   /* scrap tty outputs */
158                         dup2(xfd, 1);
159                 if (xfd != 2)
160                         dup2(xfd, 2);
161
162                 if ((tfd = open("/dev/tty", O_RDWR)) >= 0) {
163                         ioctl(tfd, TIOCNOTTY, 0);       /* no controlling tty */
164                         close(tfd);
165                 }
166                 setsid();                               /* new session */
167         }
168
169         /*
170          * Setup log file.  The log file must not use descriptors 0, 1, or 2.
171          */
172         if (cmd->logfile && strcmp(cmd->logfile, "/dev/null") == 0)
173                 cmd->logfd = -1;
174         else if (cmd->logfile)
175                 cmd->logfd = open(cmd->logfile, O_WRONLY|O_CREAT|O_APPEND, 0640);
176         else if (cmd->foreground)
177                 cmd->logfd = dup(1);
178         else
179                 cmd->logfd = -1;
180
181         /*
182          * Signal parent that we are completely detached now.
183          */
184         c = 1;
185         if (cmd->foreground == 0) {
186                 write(fds[1], &c, 1);
187                 close(fds[1]);
188                 fds[1] = -1;
189         }
190         InitCmd = cmd;
191
192         /*
193          * Setup log pipe.  The logger thread copies the pipe to a buffer
194          * for the 'log' directive and also writes it to logfd.
195          */
196         pipe(cmd->logfds);
197         if (cmd->fp != stdout)
198                 fclose(cmd->fp);
199         cmd->fp = fdopen(cmd->logfds[1], "w");
200
201         if (xfd > 2) {
202                 close(xfd);
203                 xfd = -1;
204         }
205
206         pthread_cond_init(&cmd->logcond, NULL);
207
208         /*
209          * Start accept thread for unix domain listen socket.
210          */
211         pthread_mutex_lock(&serial_mtx);
212         pthread_create(&logtd, NULL, logger_thread, cmd);
213         remote_listener(cmd, lfd);
214
215         /*
216          * Become the reaper for all children recursively.
217          */
218         if (procctl(P_PID, getpid(), PROC_REAP_ACQUIRE, NULL) < 0) {
219                 fprintf(cmd->fp, "svc is unable to become the "
220                                  "reaper for its children\n");
221                 fflush(cmd->fp);
222         }
223
224         /*
225          * Initial service start
226          */
227         execute_start(cmd);
228
229         /*
230          * Main loop is the reaper
231          */
232         for (;;) {
233                 union reaper_info info;
234                 struct timespec ts;
235                 int status;
236                 int dt;
237                 pid_t usepid;
238
239                 /*
240                  * If we are running just block doing normal reaping,
241                  * if we are stopping we have to poll for reaping while
242                  * we handle stopping.
243                  */
244                 fflush(cmd->fp);
245                 if (RunState == RS_STARTED) {
246                         pthread_mutex_unlock(&serial_mtx);
247                         pid = wait3(&status, 0, NULL);
248                         pthread_mutex_lock(&serial_mtx);
249                 } else {
250                         pid = wait3(&status, WNOHANG, NULL);
251                 }
252                 clock_gettime(CLOCK_MONOTONIC_FAST, &ts);
253
254                 if (pid > 0) {
255                         if (pid == DirectPid) {
256                                 fprintf(cmd->fp,
257                                         "svc %s: lost direct child %d\n",
258                                         cmd->label, pid);
259                                 fflush(cmd->fp);
260                                 DirectPid = -1;
261                                 if (cmd->restart_some) {
262                                         setstate_stopped(cmd, &ts);
263                                 } /* else still considered normal run state */
264                         } else if (cmd->debug) {
265                                 /*
266                                  * Reap random disconnected child, but don't
267                                  * spew to the log unless debugging is
268                                  * enabled.
269                                  */
270                                 fprintf(cmd->fp,
271                                         "svc %s: reap indirect child %d\n",
272                                         cmd->label,
273                                         (int)pid);
274                         }
275                 }
276
277                 /*
278                  * Calculate the pid to potentially act on and/or
279                  * determine if any children still exist.
280                  */
281                 if (DirectPid >= 0) {
282                         usepid = DirectPid;
283                 } else if (procctl(P_PID, getpid(),
284                                    PROC_REAP_STATUS, &info) == 0) {
285                         usepid = info.status.pid_head;
286                 } else {
287                         usepid = -1;
288                 }
289                 if (cmd->debug) {
290                         fprintf(stderr, "svc %s: usepid %d\n",
291                                 cmd->label, usepid);
292                         fflush(stderr);
293                 }
294
295                 /*
296                  * If stoppingpid changes we have to reset the TERM->KILL
297                  * timer.
298                  */
299                 if (usepid < 0) {
300                         setstate_stopped(cmd, &ts);
301                 } else if (stoppingpid != usepid &&
302                            (RunState == RS_STOPPING2 ||
303                             RunState == RS_STOPPING3)) {
304                         RunState = RS_STOPPING1;
305                 }
306                 stoppingpid = usepid;
307
308                 /*
309                  * State machine
310                  */
311                 switch(RunState) {
312                 case RS_STARTED:
313                         if (usepid < 0)
314                                 setstate_stopped(cmd, &ts);
315                         break;
316                 case RS_STOPPED:
317                         dt = (int)(ts.tv_sec - LastStop);
318
319                         if (cmd->exit_mode) {
320                                 /*
321                                  * Service demon was told to exit on service
322                                  * stop (-x passed to init).
323                                  */
324                                 fprintf(cmd->fp,
325                                         "svc %s: service demon exiting\n",
326                                         cmd->label);
327                                 remove_pid_and_socket(cmd, cmd->label);
328                                 exit(0);
329                         } else if (cmd->manual_stop) {
330                                 /*
331                                  * Service demon was told to stop via
332                                  * commanded (not automatic) action.  We
333                                  * do not auto-restart the service in
334                                  * this situation.
335                                  */
336                                 pthread_mutex_unlock(&serial_mtx);
337                                 if (dt < 0 || dt > 60)
338                                         sleep(60);
339                                 else
340                                         sleep(1);
341                                 pthread_mutex_lock(&serial_mtx);
342                         } else if (cmd->restart_some || cmd->restart_all) {
343                                 /*
344                                  * Handle automatic restarts
345                                  */
346                                 if (dt > cmd->restart_timo) {
347                                         execute_start(cmd);
348                                 } else {
349                                         pthread_mutex_unlock(&serial_mtx);
350                                         sleep(1);
351                                         pthread_mutex_lock(&serial_mtx);
352                                 }
353                         } else {
354                                 /*
355                                  * No automatic restart was configured,
356                                  * wait for commanded action.
357                                  */
358                                 pthread_mutex_unlock(&serial_mtx);
359                                 if (dt < 0 || dt > 60)
360                                         sleep(60);
361                                 else
362                                         sleep(1);
363                                 pthread_mutex_lock(&serial_mtx);
364                         }
365                         break;
366                 case RS_STOPPING1:
367                         /*
368                          * Reset TERM->KILL timer
369                          */
370                         nextstop = ts.tv_sec;
371                         RunState = RS_STOPPING2;
372                         /* fall through */
373                 case RS_STOPPING2:
374                         if (cmd->termkill_timo == 0) {
375                                 nextstop = ts.tv_sec - 1;
376                         } else {
377                                 kill(stoppingpid, SIGTERM);
378                                 fprintf(cmd->fp, "svc %s: sigterm %d\n",
379                                         cmd->label, stoppingpid);
380                                 sleep(1);
381                         }
382                         RunState = RS_STOPPING3;
383                         /* fall through */
384                 case RS_STOPPING3:
385                         dt = (int)(ts.tv_sec - nextstop);
386                         if (dt > cmd->termkill_timo) {
387                                 fprintf(cmd->fp, "svc %s: sigkill %d\n",
388                                         cmd->label, stoppingpid);
389                                 kill(stoppingpid, SIGKILL);
390                         }
391                         sleep(1);
392                         break;
393                 }
394         }
395         pthread_mutex_unlock(&serial_mtx);
396         exit(0);
397         /* does not return */
398 }
399
400 int
401 execute_start(command_t *cmd)
402 {
403         struct timespec ts;
404         int maxwait = 60;
405
406         while (RunState == RS_STOPPING1 ||
407                RunState == RS_STOPPING2 ||
408                RunState == RS_STOPPING3) {
409                 fprintf(cmd->fp,
410                         "svc %s: Waiting for previous action to complete\n",
411                         cmd->label);
412                 fflush(cmd->fp);
413                 pthread_mutex_unlock(&serial_mtx);
414                 sleep(1);
415                 pthread_mutex_lock(&serial_mtx);
416                 if (--maxwait == 0) {
417                         fprintf(cmd->fp,
418                                 "svc %s: Giving up waiting for action\n",
419                                 cmd->label);
420                         fflush(cmd->fp);
421                         break;
422                 }
423         }
424         if (RunState == RS_STARTED) {
425                 fprintf(cmd->fp, "svc %s: Already started pid %d\n",
426                         cmd->label, DirectPid);
427                 fflush(cmd->fp);
428                 return 0;
429         }
430
431         clock_gettime(CLOCK_MONOTONIC_FAST, &ts);
432         if ((DirectPid = fork()) == 0) {
433                 fflush(InitCmd->fp);
434                                                 /* leave stdin /dev/null */
435                 dup2(fileno(InitCmd->fp), 1);   /* setup stdout */
436                 dup2(fileno(InitCmd->fp), 2);   /* setup stderr */
437                 closefrom(3);
438                 execvp(InitCmd->ext_av[0], InitCmd->ext_av);
439                 exit(99);
440         }
441         if (DirectPid >= 0) {
442                 RunState = RS_STARTED;
443                 LastStart = ts.tv_sec;
444         } else {
445                 setstate_stopped(InitCmd, &ts);
446         }
447         InitCmd->manual_stop = 0;
448         fprintf(cmd->fp, "svc %s: Starting pid %d\n", cmd->label, DirectPid);
449         fflush(cmd->fp);
450
451         return 0;
452 }
453
454 int
455 execute_restart(command_t *cmd)
456 {
457         int rc;
458
459         rc = execute_stop(cmd) + execute_start(cmd);
460         return rc;
461 }
462
463 int
464 execute_stop(command_t *cmd)
465 {
466         union reaper_info info;
467         struct timespec ts;
468         int save_restart_some;
469         int save_restart_all;
470         int maxwait = 60;
471
472         save_restart_some = InitCmd->restart_some;
473         save_restart_all = InitCmd->restart_all;
474         if (cmd->commanded)
475                 InitCmd->manual_stop = 1;
476         if (cmd->commanded && (cmd->restart_some || cmd->restart_all)) {
477                 InitCmd->restart_some = cmd->restart_some;
478                 InitCmd->restart_all = cmd->restart_all;
479         }
480         fprintf(cmd->fp, "svc %s: Stopping\n", cmd->label);
481         fflush(cmd->fp);
482
483         /*
484          * Start the kill chain going so the master loop's wait3 wakes up.
485          */
486         if (DirectPid >= 0) {
487                 kill(DirectPid, SIGTERM);
488         } else {
489                 if (procctl(P_PID, getpid(), PROC_REAP_STATUS, &info) == 0 &&
490                     info.status.pid_head > 0) {
491                         kill(info.status.pid_head, SIGTERM);
492                 }
493         }
494
495         clock_gettime(CLOCK_MONOTONIC_FAST, &ts);
496         LastStop = ts.tv_sec;
497         RunState = RS_STOPPING1;
498
499         /*
500          * If commanded (verses automatic), we are running remote in our
501          * own thread and we need to wait for the action to complete.
502          */
503         if (cmd->commanded) {
504                 while (RunState == RS_STOPPING1 ||
505                        RunState == RS_STOPPING2 ||
506                        RunState == RS_STOPPING3) {
507                         fprintf(cmd->fp,
508                                 "svc %s: Waiting for service to stop\n",
509                                 cmd->label);
510                         fflush(cmd->fp);
511                         pthread_mutex_unlock(&serial_mtx);
512                         sleep(1);
513                         pthread_mutex_lock(&serial_mtx);
514                         if (--maxwait == 0) {
515                                 fprintf(cmd->fp,
516                                         "svc %s: Giving up waiting for stop\n",
517                                         cmd->label);
518                                 fflush(cmd->fp);
519                                 break;
520                         }
521                 }
522                 if (cmd->restart_some || cmd->restart_all) {
523                         InitCmd->restart_some = save_restart_some;
524                         InitCmd->restart_all = save_restart_all;
525                 }
526         }
527
528         return 0;
529 }
530
531 int
532 execute_exit(command_t *cmd)
533 {
534         if (cmd->commanded) {
535                 InitCmd->restart_some = 0;
536                 InitCmd->restart_all = 1;       /* kill all children */
537                 InitCmd->exit_mode = 1;         /* exit after stop */
538         }
539         fprintf(cmd->fp, "svc %s: Stopping and Exiting\n", cmd->label);
540         execute_stop(cmd);
541
542         exit(0);
543 }
544
545 int
546 execute_list(command_t *cmd)
547 {
548         fprintf(cmd->fp, "%-16s\n", cmd->label);
549
550         return 0;
551 }
552
553 int
554 execute_status(command_t *cmd)
555 {
556         const char *state;
557
558         switch(RunState) {
559         case RS_STOPPED:
560                 if (InitCmd && InitCmd->exit_mode)
561                         state = "stopped (exiting)";
562                 else if (InitCmd && InitCmd->manual_stop)
563                         state = "stopped (manual)";
564                 else
565                         state = "stopped";
566                 break;
567         case RS_STARTED:
568                 state = "running";
569                 break;
570         case RS_STOPPING1:
571         case RS_STOPPING2:
572         case RS_STOPPING3:
573                 state = "killing";
574                 break;
575         default:
576                 state = "unknown";
577                 break;
578         }
579
580         fprintf(cmd->fp, "%-16s %s\n", cmd->label, state);
581
582         return 0;
583 }
584
585 int
586 execute_log(command_t *cmd)
587 {
588         int lbsize = (int)sizeof(cmd->logbuf);
589         int lbmask = lbsize - 1;
590         int windex;
591         int n;
592         int lastnl;
593         int dotstate;
594         char buf[LOGCHUNK];
595
596         assert(InitCmd);
597
598         /*
599          * mode 0 - Dump everything then exit
600          * mode 1 - Dump everything then block/loop
601          * mode 2 - Skeep to end then block/loop
602          */
603         if (cmd->tail_mode == 2)
604                 windex = InitCmd->logwindex;
605         else
606                 windex = InitCmd->logwindex - InitCmd->logcount;
607         lastnl = 1;
608         dotstate = 0;   /* 0=start-of-line 1=middle-of-line 2=dot */
609
610         for (;;) {
611                 /*
612                  * Calculate the amount of data we missed and determine
613                  * if some data was lost.
614                  */
615                 n = InitCmd->logwindex - windex;
616                 if (n < 0 || n > InitCmd->logcount) {
617                         windex = InitCmd->logwindex - InitCmd->logcount;
618                         pthread_mutex_unlock(&serial_mtx);
619                         fprintf(cmd->fp, "\n(LOG DATA LOST)\n");
620                         pthread_mutex_lock(&serial_mtx);
621                         continue;
622                 }
623
624                 /*
625                  * Circular buffer and copy size limitations.  If no
626                  * data ready, wait for some.
627                  */
628                 if (n > lbsize - (windex & lbmask))
629                         n = lbsize - (windex & lbmask);
630                 if (n > LOGCHUNK)
631                         n = LOGCHUNK;
632                 if (n == 0) {
633                         if (cmd->tail_mode == 0)
634                                 break;
635                         pthread_cond_wait(&InitCmd->logcond, &serial_mtx);
636                         continue;
637                 }
638                 bcopy(InitCmd->logbuf + (windex & lbmask), buf, n);
639
640                 /*
641                  * Dump log output, escape any '.' on a line by itself.
642                  */
643                 pthread_mutex_unlock(&serial_mtx);
644                 n = escapewrite(cmd->fp, buf, n, &dotstate);
645                 fflush(cmd->fp);
646                 if (n > 0)
647                         lastnl = (buf[n-1] == '\n');
648                 pthread_mutex_lock(&serial_mtx);
649
650                 if (n < 0)
651                         break;
652                 windex += n;
653         }
654         if (lastnl == 0) {
655                 pthread_mutex_unlock(&serial_mtx);
656                 fprintf(cmd->fp, "\n");
657                 pthread_mutex_lock(&serial_mtx);
658         }
659         return 0;
660 }
661
662 /*
663  * Change or reopen logfile.
664  */
665 int
666 execute_logfile(command_t *cmd)
667 {
668         char *logfile;
669         int fd;
670         int rc;
671
672         assert(InitCmd);
673
674         logfile = cmd->logfile;
675         if (cmd->ext_av && cmd->ext_av[0])
676                 logfile = cmd->ext_av[0];
677         if (logfile == NULL)
678                 logfile = InitCmd->logfile;
679
680         rc = 0;
681         if (logfile) {
682                 if (InitCmd->logfile &&
683                     strcmp(InitCmd->logfile, logfile) == 0) {
684                         fprintf(cmd->fp, "svc %s: Reopen logfile %s\n",
685                                 cmd->label, logfile);
686                 } else {
687                         fprintf(cmd->fp, "svc %s: Change logfile to %s\n",
688                                 cmd->label, logfile);
689                 }
690                 if (InitCmd->logfd >= 0) {
691                         close(InitCmd->logfd);
692                         InitCmd->logfd = -1;
693                 }
694                 if (strcmp(logfile, "/dev/null") == 0) {
695                         sreplace(&InitCmd->logfile, logfile);
696                 } else {
697                         fd = open(logfile, O_WRONLY|O_CREAT|O_APPEND, 0640);
698                         if (fd >= 0) {
699                                 InitCmd->logfd = fd;
700                                 sreplace(&InitCmd->logfile, logfile);
701                         } else {
702                                 fprintf(cmd->fp,
703                                         "svc %s: Unable to open/create "
704                                         "\"%s\": %s\n",
705                                         cmd->label,
706                                         logfile, strerror(errno));
707                                 rc = 1;
708                         }
709                 }
710         }
711         return rc;
712 }
713
714 int
715 execute_help(command_t *cmd)
716 {
717         fprintf(cmd->fp,
718                 "svc [options] directive [label [additional_args]]\n"
719                 "\n"
720                 "Directives: init start stop stopall restart exit\n"
721                 "            kill list status log logf tailf logfile\n"
722                 "            help\n"
723         );
724         return 0;
725 }
726
727 static
728 void *
729 logger_thread(void *arg)
730 {
731         command_t *cmd = arg;
732         int lbsize = (int)sizeof(cmd->logbuf);
733         int lbmask = lbsize - 1;
734         int windex;
735         int n;
736
737         pthread_detach(pthread_self());
738         pthread_mutex_lock(&serial_mtx);
739         for (;;) {
740                 /*
741                  * slip circular buffer to make room for new data.
742                  */
743                 n = cmd->logcount - (lbsize - LOGCHUNK);
744                 if (n > 0) {
745                         cmd->logcount -= n;
746                         cmd->logwindex += n;
747                 }
748                 windex = cmd->logwindex & lbmask;
749                 n = lbsize - windex;
750                 if (n > LOGCHUNK)
751                         n = LOGCHUNK;
752                 pthread_mutex_unlock(&serial_mtx);
753                 n = read(cmd->logfds[0], cmd->logbuf + windex, n);
754                 pthread_mutex_lock(&serial_mtx);
755                 if (n > 0) {
756                         if (cmd->logfd >= 0)
757                                 write(cmd->logfd, cmd->logbuf + windex, n);
758                         cmd->logcount += n;
759                         cmd->logwindex += n;
760                         pthread_cond_signal(&cmd->logcond);
761                 }
762                 if (n == 0 || (n < 0 && errno != EINTR))
763                         break;
764         }
765         pthread_mutex_unlock(&serial_mtx);
766         return NULL;
767 }
768
769 static
770 void
771 setstate_stopped(command_t *cmd, struct timespec *ts)
772 {
773         if (RunState != RS_STOPPED) {
774                 RunState = RS_STOPPED;
775                 LastStop = ts->tv_sec;
776                 if (cmd->sync_mode)     /* support -s option */
777                         sync();
778         }
779 }
780
781 static
782 int
783 escapewrite(FILE *fp, char *buf, int n, int *statep)
784 {
785         int b;
786         int i;
787         int r;
788         char c;
789
790         b = 0;
791         r = 0;
792         while (i < n) {
793                 for (i = b; i < n; ++i) {
794                         c = buf[i];
795
796                         switch(*statep) {
797                         case 0:
798                                 /*
799                                  * beginning of line
800                                  */
801                                 if (c == '.')
802                                         *statep = 2;
803                                 else if (c != '\n')
804                                         *statep = 1;
805                                 break;
806                         case 1:
807                                 /*
808                                  * middle of line
809                                  */
810                                 if (c == '\n')
811                                         *statep = 0;
812                                 break;
813                         case 2:
814                                 /*
815                                  * dot was output at beginning of line
816                                  */
817                                 if (c == '\n')
818                                         *statep = 3;
819                                 else
820                                         *statep = 1;
821                                 break;
822                         default:
823                                 break;
824                         }
825                         if (*statep == 3)       /* flush with escape */
826                                 break;
827                 }
828                 if (i != b) {
829                         n = fwrite(buf, 1, i - b, fp);
830                         if (n > 0)
831                                 r += n;
832                         if (n < 0)
833                                 r = -1;
834                 }
835                 if (*statep == 3) {             /* added escape */
836                         n = fwrite(".", 1, 1, fp);
837                         /* escapes not counted in r */
838                         *statep = 1;
839                         if (n < 0)
840                                 r = -1;
841                 }
842                 if (r < 0)
843                         break;
844         }
845         return r;
846 }