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