]> mj.ucw.cz Git - libucw.git/blob - ucw/shell/logoutput.c
logoutput: A few cleanups
[libucw.git] / ucw / shell / logoutput.c
1 /*
2  *      UCW Library Utilities -- A Simple Logger for use in shell scripts
3  *
4  *      (c) 2001--2009 Martin Mares <mj@ucw.cz>
5  *      (c) 2011 Tomas Ebenlendr <ebik@ucw.cz>
6  *
7  *      This software may be freely distributed and used according to the terms
8  *      of the GNU Lesser General Public License.
9  */
10
11 #undef LOCAL_DEBUG
12
13 #include "ucw/lib.h"
14 #include "ucw/log.h"
15 #include "ucw/mainloop.h"
16 #include "ucw/clists.h"
17 #include "ucw/getopt.h"
18 #include "ucw/conf.h"
19
20 #include <stdio.h>
21 #include <string.h>
22 #include <unistd.h>
23 #include <fcntl.h>
24 #include <sys/types.h>
25 #include <sys/wait.h>
26 #include <errno.h>
27
28 static uns max_line = 1024;
29
30 static struct cf_section cfsec_logoutput = {
31   CF_ITEMS {
32     CF_UNS("LineMax", &max_line),
33     CF_END
34   }
35 };
36
37 static clist filedescriptors;
38
39 struct fds {
40   cnode node;
41   int pipe[2];
42   int fdnum;
43   uns level;
44   int long_continue;
45   struct main_rec_io rio;
46 };
47
48 static void
49 close_fd(struct fds *fd)
50 {
51   rec_io_del(&fd->rio);
52   close(fd->fdnum);
53   clist_remove(&fd->node);
54   if (clist_empty(&filedescriptors))
55     main_shut_down();
56 }
57
58
59 static void
60 do_msg (struct fds *fd, char *l_msg, int long_continue)
61 {
62   msg(fd->level, "%s%s", (fd->long_continue ? "... " : ""), l_msg);
63   fd->long_continue = long_continue;
64 }
65
66 static uns
67 handle_read(struct main_rec_io *r)
68 {
69   char buf[max_line + 5];
70   byte *eol = memchr((char *)r->read_rec_start + r->read_prev_avail, '\n', r->read_avail - r->read_prev_avail);
71   if (eol == NULL) {
72     if (r->read_avail >= max_line) {
73       memcpy(buf, r->read_rec_start, max_line);
74       memcpy(buf + max_line, " ...", 5);
75       do_msg(r->data, buf, 1);
76       return max_line;
77     } else
78       return 0;
79   }
80   *eol = 0;
81   byte *b = r->read_rec_start;
82   while ((uns)(eol - b) > max_line) {
83     char cc = b[max_line];
84     b[max_line]=0;
85     do_msg(r->data, b, 1);
86     b[max_line]=cc;
87     b+=max_line;
88   }
89   do_msg(r->data, (char *)b, 0);
90   return eol - r->read_rec_start + 1;
91 }
92
93 static int
94 handle_notify(struct main_rec_io *r, int status)
95 {
96   struct fds *fd = r->data;
97   switch (status) {
98     case RIO_ERR_READ:
99     case RIO_EVENT_EOF:
100       if (r->read_avail) {
101         char buf[max_line + 10];
102         memcpy(buf, r->read_rec_start, r->read_avail);
103         memcpy(buf + r->read_avail, " [no eol]", 10);
104         do_msg(r->data, buf, 0);
105       } else if (fd->long_continue) {
106         do_msg(r->data, "[no eol]", 0);
107       }
108       close_fd(fd);
109       return HOOK_DONE;
110     default:
111       ASSERT(0);
112   }
113   return HOOK_IDLE;
114 }
115
116
117 static void
118 add_level_fd(int fdnum, int level)
119 {
120   struct fds *fd = xmalloc_zero(sizeof(*fd));
121   fd->level = level;
122   fd->pipe[0] = -1;
123   fd->pipe[1] = -1;
124   fd->fdnum = fdnum;
125   fd->rio.read_handler = handle_read;
126   fd->rio.data = fd;
127   fd->rio.notify_handler = handle_notify;
128   fd->long_continue = 0;
129   clist_add_tail(&filedescriptors, &fd->node);
130 }
131
132 static int
133 xdup(int fd)
134 {
135   int rfd = dup(fd);
136   if (rfd == -1)
137     die("Cannot dup(): %m");
138   DBG("  Dup(%i) -> %i", fd, rfd);
139   return rfd;
140 }
141
142 static int
143 xdup2(int fd1, int fd2)
144 {
145   int rfd = dup2(fd1, fd2);
146   if (rfd == -1)
147     die("Cannot dup2(): %m");
148   DBG("  Dup2(%i, %i) -> %i", fd1, fd2, rfd);
149   return rfd;
150 }
151
152 static void
153 xdupavoid(int *fd1, int fd2)
154 {
155   DBG("Dupavoid: !%i -> %i", fd2, *fd1);
156   int ofd = *fd1;
157   if (ofd == fd2) {
158     *fd1 = xdup(ofd);
159     DBG("  Close: %i", ofd);
160     close(ofd);
161   }
162 }
163
164 static void
165 xdupto(int *fd1, int fd2)
166 {
167   DBG("Dupto: %i -> %i", *fd1, fd2);
168   if (*fd1 == fd2)
169     return;
170   DBG("  Close: %i", fd2);
171   close(fd2);
172   xdup2(*fd1, fd2);
173   DBG("  Close: %i", *fd1);
174   close(*fd1);
175   *fd1 = fd2;
176 }
177
178 static void
179 set_cloexec_flag(int fd, int value)
180 {
181   int flags = fcntl(fd, F_GETFD, 0);
182   if (flags < 0)
183     die("fcntl(..., F_GETFD, ...) : %m");
184   flags = (value) ? flags | FD_CLOEXEC : flags & ~FD_CLOEXEC;
185   if (fcntl(fd, F_SETFD, flags) < 0)
186     die("fcntl(..., F_SETFD, ...) : %m");
187 }
188
189 /* The "+" stands for end at first argument (i.e. do not parse options after
190    first argument.) */
191 #define MY_SHORT_OPTS "+" CF_SHORT_OPTS "f:n:l:ih"
192 const struct option my_long_opts[] = {
193   CF_LONG_OPTS
194   { "help", 0, 0, 'h'},
195   { "input", 0, 0, 'i'},
196   { "logfile", 1, 0, 'f'},
197   { "logname", 1, 0, 'n'},
198   { "descriptor", 1, 0, 'l'},
199   { NULL, 0, 0, 0}
200 };
201
202 #undef CF_USAGE_TAB
203 #define CF_USAGE_TAB "\t\t"
204 static char usage[] =
205   "Usage:\n"
206   "logoutput -h|--help\t\tThis help.\n"
207   "logoutput <options> -i|--input\tRead filedescriptors and log them.\n"
208   "\t\t\t\tdefault: stdin at level I.\n"
209   "logoutput <opts> [--] <cmd> [arguments for cmd ...]\n"
210   "\t\t\t\tOpen filedescriptors for writing for\n"
211   "\t\t\t\tcommand <cmd> and log them.\n"
212   "\t\t\t\tdefault: stdout:I, stderr:W.\n"
213   "Options:\n"
214   CF_USAGE
215   "-n, --logname <name>\t\t\tUse <name> as program name in logs.\n"
216   "-l, --descriptor <fdnum>:<level>\tOpen filedescriptor <fdnum> and log it\n"
217   "\t\t\t\t\tat level <level> (discards defaults).\n"
218   "-f, --logfile <logfile>\t\t\tLog to file <logfile>.\n";
219
220 int
221 main(int argc, char **argv)
222 {
223   int register_default = 1;
224   int loginput = 0;
225   char *logfile = NULL;
226   char *logname = NULL;
227   struct fds *stderrfd = NULL;
228   int help = 0;
229
230   log_init("logoutput");
231   clist_init(&filedescriptors);
232   cf_declare_section("LogOutput", &cfsec_logoutput, 0);
233
234   while (1) {
235     int opt = cf_getopt(argc, argv, MY_SHORT_OPTS, my_long_opts, NULL);
236     switch (opt) {
237       case -1:
238         goto opt_done;
239
240       case 'h':
241         help = 1;
242         break;
243
244       case 'i':
245         loginput = 1;
246         break;
247
248       case 'f':
249         logfile = optarg;
250         break;
251
252       case 'n':
253         logname = optarg;
254         break;
255
256       case 'l':
257         {
258           char *c = optarg;
259
260           register_default = 0;
261           int fdnum = 0;
262           int parseerror = 0;
263           if ( (c[0]<'0') || (c[0] > '9') )
264             parseerror = 1;
265           while ( (!parseerror) && (c[0] >= '0') && (c[0] <= '9') )
266             { fdnum = fdnum*10 + c[0] - '0'; c++; }
267           if ( (!parseerror) && (c[0] != ':') )
268             parseerror = 1;
269           c++;
270           if ( (!parseerror) && (c[0] == 0) )
271             parseerror = 1;
272           if ( (!parseerror) && (c[1] != 0) )
273             parseerror = 1;
274           if (parseerror) die("Bad argument `%s' to -l, expects number:letter.", optarg);
275
276           uns level = 0;
277           while (level < L_MAX && LS_LEVEL_LETTER(level) != c[0])
278             level++;
279           if (level >= L_MAX)
280             die("Unknown logging level `%s'", c);
281
282           add_level_fd(fdnum, level);
283         }
284         break;
285
286       default:
287         optind--;
288         goto opt_done;
289     }
290   }
291 opt_done:
292
293   if (!help) {
294     if (loginput && (optind < argc))
295       die("No cmd is allowed for -i. Use -h for help.");
296
297     if ((!loginput) && (optind >= argc)) {
298       msg(L_FATAL, "Either command or --input expected.");
299       help = 2;
300     }
301   }
302   if (help) {
303     write(2, usage, sizeof(usage));
304     return (help == 1) ? 0 : 1;
305   }
306
307   if (register_default) {
308     if (loginput) {
309       add_level_fd(0, L_INFO);
310     } else {
311       add_level_fd(1, L_INFO);
312       add_level_fd(2, L_WARN);
313     }
314   }
315
316   if (loginput) {
317     /* Just check, that we don't want open stderr for reading. */
318     CLIST_FOR_EACH(struct fds *, fd, filedescriptors) {
319       if (fd->fdnum == 2)
320         die("Stderr is reserved for output");
321     }
322   } else {
323     /* Open all filedescriptors and their duplicates. */
324     CLIST_FOR_EACH(struct fds *, fd, filedescriptors) {
325       CLIST_FOR_EACH(struct fds *, fdcheck, filedescriptors) {
326         /* We do a dummy check for collisions of filedescriptors. */
327         if (fdcheck == fd)
328           break;
329         if (fdcheck->fdnum == fd->fdnum) {
330           die("Duplicate filedescriptor %i", fd->fdnum);
331         }
332         xdupavoid(fdcheck->pipe + 0, fd->fdnum);
333         xdupavoid(fdcheck->pipe + 1, fd->fdnum);
334       }
335       if (pipe(fd->pipe) == -1)
336         die("Cannot create pipe: %m");
337       DBG("Pipe [%i, %i] for %i", fd->pipe[0], fd->pipe[1], fd->fdnum);
338       xdupavoid(fd->pipe + 0, fd->fdnum);
339
340       if (fd->fdnum == 2) {
341         stderrfd = fd; //We need to redirect stderr later.
342       } else {
343         xdupto(fd->pipe + 1, fd->fdnum);
344       }
345       DBG("---> [%i, %i] for %i", fd->pipe[0], fd->pipe[1], fd->fdnum);
346       set_cloexec_flag(fd->pipe[0], 1);
347       set_cloexec_flag(fd->pipe[1], 0);
348       /* Our pipe is created, let fd->fdnum be the reading end. */
349       fd->fdnum = fd->pipe[0];
350     }
351   }
352
353   /* Initialize main loop. */
354   main_init();
355
356   CLIST_FOR_EACH(struct fds *, fd, filedescriptors) {
357     fd->rio.read_rec_max = max_line + 1;
358     rec_io_add(&fd->rio, fd->fdnum);
359   }
360
361   int pid = -1;
362   if (!loginput) {
363     /* Launch the child and close filedescriptors. */
364     pid = fork();
365     if (pid == -1)
366       die("Cannot fork: %m");
367     if (pid == 0 ) {
368       /* Child */
369
370       /* Move stderr where it should be. */
371       if (stderrfd)
372         xdupto(stderrfd->pipe + 1, 2);
373
374       execvp(argv[optind], argv + optind);
375       if (stderrfd) {
376         /* We translate stderr, just print. */
377         perror("Cannot exec child");
378         return 127;
379       }
380       /* No stderr translation: use logging function. */
381       die("Cannot exec child: %m");
382     }
383
384     /* Close writing filedescriptors. */
385     CLIST_FOR_EACH(struct fds *, fd, filedescriptors) {
386       close(fd->pipe[1]);
387     }
388   }
389
390   /* Open logfile or stderr. */
391   if (logfile) {
392     log_file(logfile);
393     close(2);
394   }
395
396   if (!loginput) {
397     /* Inform about launching of the command. */
398     int buflen = 0;
399     for (int i = optind; i < argc; i++) buflen += strlen(argv[i]) + 1;
400     char *buf = xmalloc(buflen);
401     char *buf2 = buf;
402     for (int i = optind; i < argc; i++) {
403       strcpy(buf2, argv[i]);
404       buf2 += strlen(argv[i]);
405       buf2[0] = ' ';
406       buf2++;
407     }
408     buf2[-1] = 0;
409
410     msg(L_INFO, "Launching command: %s", buf);
411   }
412
413   /* Set logname. */
414   if (logname)
415     log_init(logname);
416   else if (!loginput)
417     log_init(argv[optind]);
418
419   /* Start reading from pipes. */
420   CLIST_FOR_EACH(struct fds *, fd, filedescriptors)
421     rec_io_start_read(&fd->rio);
422   main_loop();
423
424   if (!loginput) {
425     /* Unset logname. */
426     log_init("logoutput");
427
428     /* Wait for status of the child and inform about finish. */
429     int status;
430     char buf[256];
431
432     while (waitpid(pid, &status, 0) == -1) {
433       if (errno != EINTR)
434         die("Cannot wait for child: %m");
435     }
436
437     if (format_exit_status(buf, status)) {
438       msg(L_WARN, "Child %s", buf);
439       return WIFEXITED(status) ? WEXITSTATUS(status) : 127;
440     } else {
441       msg(L_INFO, "Child terminated successfully.");
442       return 0;
443     }
444   }
445
446   return 0;
447 }