2 * UCW Library Utilities -- A Simple Logger for use in shell scripts
4 * (c) 2001--2009 Martin Mares <mj@ucw.cz>
5 * (c) 2011 Tomas Ebenlendr <ebik@ucw.cz>
7 * This software may be freely distributed and used according to the terms
8 * of the GNU Lesser General Public License.
15 #include <ucw/mainloop.h>
16 #include <ucw/clists.h>
17 #include <ucw/getopt.h>
19 #include <ucw/process.h>
25 #include <sys/types.h>
29 static uint max_line = 1024;
30 static int launch_finish_messages = 1;
31 static int nonzero_status_message = 1;
33 static struct cf_section cfsec_logoutput = {
35 CF_UINT("LineMax", &max_line),
40 static clist filedescriptors;
48 struct main_rec_io rio;
52 close_fd(struct fds *fd)
56 clist_remove(&fd->node);
57 if (clist_empty(&filedescriptors))
63 do_msg (struct fds *fd, char *l_msg, int long_continue)
65 msg(fd->level, "%s%s", (fd->long_continue ? "... " : ""), l_msg);
66 fd->long_continue = long_continue;
70 handle_read(struct main_rec_io *r)
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);
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);
84 byte *b = r->read_rec_start;
85 while ((uint)(eol - b) > max_line) {
86 char cc = b[max_line];
88 do_msg(r->data, b, 1);
92 do_msg(r->data, (char *)b, 0);
93 return eol - r->read_rec_start + 1;
97 handle_notify(struct main_rec_io *r, int status)
99 struct fds *fd = r->data;
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);
121 add_level_fd(int fdnum, int level)
123 struct fds *fd = xmalloc_zero(sizeof(*fd));
128 fd->rio.read_handler = handle_read;
130 fd->rio.notify_handler = handle_notify;
131 fd->long_continue = 0;
132 clist_add_tail(&filedescriptors, &fd->node);
140 die("Cannot dup(): %m");
141 DBG(" Dup(%i) -> %i", fd, rfd);
146 xdup2(int fd1, int fd2)
148 int rfd = dup2(fd1, fd2);
150 die("Cannot dup2(): %m");
151 DBG(" Dup2(%i, %i) -> %i", fd1, fd2, rfd);
156 xdupavoid(int *fd1, int fd2)
158 DBG("Dupavoid: !%i -> %i", fd2, *fd1);
162 DBG(" Close: %i", ofd);
168 xdupto(int *fd1, int fd2)
170 DBG("Dupto: %i -> %i", *fd1, fd2);
173 DBG(" Close: %i", fd2);
176 DBG(" Close: %i", *fd1);
182 set_cloexec_flag(int fd, int value)
184 int flags = fcntl(fd, F_GETFD, 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");
192 /* The "+" stands for end at first argument (i.e. do not parse options after
194 #define MY_SHORT_OPTS "+" CF_SHORT_OPTS "f:n:l:ih"
195 const struct option my_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'},
203 { "nonverbose", 0, 0, 'q'},
204 { "non-verbose", 0, 0, 'q'},
205 { "non_verbose", 0, 0, 'q'},
206 { "silent", 0, 0, 's'},
211 #define CF_USAGE_TAB "\t "
212 static char usage[] =
214 "ucw-logoutput -h|--help\t\t This help.\n"
215 "ucw-logoutput <options> -i|--input Read file descriptors and log them.\n"
216 "\t\t\t\t default: stdin at level I.\n"
217 "ucw-logoutput <opts> [--] <cmd> [arguments for cmd ...]\n"
218 "\t\t\t\t Open file descriptors for writing for command <cmd> and log them.\n"
219 "\t\t\t\t default: stdout:I, stderr:W.\n\n"
222 "-n, --logname <name>\t\t Use <name> as program name in logs.\n"
223 "-l, --descriptor <fdnum>:<level> Open file descriptor <fdnum> and log it at level <level> (replaces defaults).\n"
224 "-f, --logfile <logfile>\t\t Log to file <logfile>.\n"
225 "-q, --nv, --nonverbose\t\t Suppress launching and successful finish messages.\n"
226 "-s, --silent\t\t\t Suppress launching message and all finish messages.\n"
227 "\t\t\t\t (i.e., no warning if it terminates with a nonzero exit code or by a signal)\n";
230 main(int argc, char **argv)
232 int register_default = 1;
234 char *logfile = NULL;
235 char *logname = NULL;
236 struct fds *stderrfd = NULL;
239 log_init("ucw-logoutput");
240 clist_init(&filedescriptors);
241 cf_declare_section("LogOutput", &cfsec_logoutput, 0);
244 int opt = cf_getopt(argc, argv, MY_SHORT_OPTS, my_long_opts, NULL);
269 register_default = 0;
272 if ( (c[0]<'0') || (c[0] > '9') )
274 while ( (!parseerror) && (c[0] >= '0') && (c[0] <= '9') )
275 { fdnum = fdnum*10 + c[0] - '0'; c++; }
276 if ( (!parseerror) && (c[0] != ':') )
279 if ( (!parseerror) && (c[0] == 0) )
281 if ( (!parseerror) && (c[1] != 0) )
283 if (parseerror) die("Bad argument `%s' to -l, expects number:letter.", optarg);
286 while (level < L_MAX && LS_LEVEL_LETTER(level) != c[0])
289 die("Unknown logging level `%s'", c);
291 add_level_fd(fdnum, level);
296 nonzero_status_message = 0;
299 launch_finish_messages = 0;
310 if (loginput && (optind < argc))
311 die("No cmd is allowed for -i. Use -h for help.");
313 if ((!loginput) && (optind >= argc)) {
314 msg(L_FATAL, "Either command or --input expected.");
319 if (write(2, usage, sizeof(usage)) < 0) {
321 return (help == 1) ? 0 : 1;
324 if (register_default) {
326 add_level_fd(0, L_INFO);
328 add_level_fd(1, L_INFO);
329 add_level_fd(2, L_WARN);
334 /* Just check, that we don't want open stderr for reading. */
335 CLIST_FOR_EACH(struct fds *, fd, filedescriptors) {
337 die("Stderr is reserved for output");
340 /* Open all filedescriptors and their duplicates. */
341 CLIST_FOR_EACH(struct fds *, fd, filedescriptors) {
342 CLIST_FOR_EACH(struct fds *, fdcheck, filedescriptors) {
343 /* We do a dummy check for collisions of filedescriptors. */
346 if (fdcheck->fdnum == fd->fdnum) {
347 die("Duplicate filedescriptor %i", fd->fdnum);
349 xdupavoid(fdcheck->pipe + 0, fd->fdnum);
350 xdupavoid(fdcheck->pipe + 1, fd->fdnum);
352 if (pipe(fd->pipe) == -1)
353 die("Cannot create pipe: %m");
354 DBG("Pipe [%i, %i] for %i", fd->pipe[0], fd->pipe[1], fd->fdnum);
355 xdupavoid(fd->pipe + 0, fd->fdnum);
357 if (fd->fdnum == 2) {
358 stderrfd = fd; //We need to redirect stderr later.
360 xdupto(fd->pipe + 1, fd->fdnum);
362 DBG("---> [%i, %i] for %i", fd->pipe[0], fd->pipe[1], fd->fdnum);
363 set_cloexec_flag(fd->pipe[0], 1);
364 set_cloexec_flag(fd->pipe[1], 0);
368 /* Initialize main loop. */
371 CLIST_FOR_EACH(struct fds *, fd, filedescriptors) {
372 /* Our pipe is created, let fd->fdnum be the reading end. */
374 fd->fdnum = fd->pipe[0];
375 fd->rio.read_rec_max = max_line + 1;
376 rec_io_add(&fd->rio, fd->fdnum);
381 /* Launch the child and close filedescriptors. */
384 die("Cannot fork: %m");
388 /* Move stderr where it should be. */
390 xdupto(stderrfd->pipe + 1, 2);
392 execvp(argv[optind], argv + optind);
394 /* We translate stderr, just print. */
395 perror("Cannot exec child");
398 /* No stderr translation: use logging function. */
399 die("Cannot exec child: %m");
402 /* Close writing filedescriptors. */
403 CLIST_FOR_EACH(struct fds *, fd, filedescriptors) {
408 /* Open logfile or stderr. */
415 /* Inform about launching of the command. */
417 for (int i = optind; i < argc; i++) buflen += strlen(argv[i]) + 1;
418 char *buf = xmalloc(buflen);
420 for (int i = optind; i < argc; i++) {
421 strcpy(buf2, argv[i]);
422 buf2 += strlen(argv[i]);
428 if (launch_finish_messages)
429 msg(L_INFO, "Launching command: %s", buf);
436 log_init(argv[optind]);
438 /* Start reading from pipes. */
439 CLIST_FOR_EACH(struct fds *, fd, filedescriptors)
440 rec_io_start_read(&fd->rio);
445 log_init("ucw-logoutput");
447 /* Wait for status of the child and inform about finish. */
451 while (waitpid(pid, &status, 0) == -1) {
453 die("Cannot wait for child: %m");
456 if (format_exit_status(buf, status)) {
457 if (nonzero_status_message)
458 msg(L_WARN, "Child %s", buf);
459 return WIFEXITED(status) ? WEXITSTATUS(status) : 127;
461 if (launch_finish_messages)
462 msg(L_INFO, "Child terminated successfully.");