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