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 uns max_line = 1024;
31 static struct cf_section cfsec_logoutput = {
33 CF_UNS("LineMax", &max_line),
38 static clist filedescriptors;
46 struct main_rec_io rio;
50 close_fd(struct fds *fd)
54 clist_remove(&fd->node);
55 if (clist_empty(&filedescriptors))
61 do_msg (struct fds *fd, char *l_msg, int long_continue)
63 msg(fd->level, "%s%s", (fd->long_continue ? "... " : ""), l_msg);
64 fd->long_continue = long_continue;
68 handle_read(struct main_rec_io *r)
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);
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);
82 byte *b = r->read_rec_start;
83 while ((uns)(eol - b) > max_line) {
84 char cc = b[max_line];
86 do_msg(r->data, b, 1);
90 do_msg(r->data, (char *)b, 0);
91 return eol - r->read_rec_start + 1;
95 handle_notify(struct main_rec_io *r, int status)
97 struct fds *fd = r->data;
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);
119 add_level_fd(int fdnum, int level)
121 struct fds *fd = xmalloc_zero(sizeof(*fd));
126 fd->rio.read_handler = handle_read;
128 fd->rio.notify_handler = handle_notify;
129 fd->long_continue = 0;
130 clist_add_tail(&filedescriptors, &fd->node);
138 die("Cannot dup(): %m");
139 DBG(" Dup(%i) -> %i", fd, rfd);
144 xdup2(int fd1, int fd2)
146 int rfd = dup2(fd1, fd2);
148 die("Cannot dup2(): %m");
149 DBG(" Dup2(%i, %i) -> %i", fd1, fd2, rfd);
154 xdupavoid(int *fd1, int fd2)
156 DBG("Dupavoid: !%i -> %i", fd2, *fd1);
160 DBG(" Close: %i", ofd);
166 xdupto(int *fd1, int fd2)
168 DBG("Dupto: %i -> %i", *fd1, fd2);
171 DBG(" Close: %i", fd2);
174 DBG(" Close: %i", *fd1);
180 set_cloexec_flag(int fd, int value)
182 int flags = fcntl(fd, F_GETFD, 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");
190 /* The "+" stands for end at first argument (i.e. do not parse options after
192 #define MY_SHORT_OPTS "+" CF_SHORT_OPTS "f:n:l:ih"
193 const struct option my_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'},
204 #define CF_USAGE_TAB "\t\t"
205 static char usage[] =
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"
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";
222 main(int argc, char **argv)
224 int register_default = 1;
226 char *logfile = NULL;
227 char *logname = NULL;
228 struct fds *stderrfd = NULL;
231 log_init("logoutput");
232 clist_init(&filedescriptors);
233 cf_declare_section("LogOutput", &cfsec_logoutput, 0);
236 int opt = cf_getopt(argc, argv, MY_SHORT_OPTS, my_long_opts, NULL);
261 register_default = 0;
264 if ( (c[0]<'0') || (c[0] > '9') )
266 while ( (!parseerror) && (c[0] >= '0') && (c[0] <= '9') )
267 { fdnum = fdnum*10 + c[0] - '0'; c++; }
268 if ( (!parseerror) && (c[0] != ':') )
271 if ( (!parseerror) && (c[0] == 0) )
273 if ( (!parseerror) && (c[1] != 0) )
275 if (parseerror) die("Bad argument `%s' to -l, expects number:letter.", optarg);
278 while (level < L_MAX && LS_LEVEL_LETTER(level) != c[0])
281 die("Unknown logging level `%s'", c);
283 add_level_fd(fdnum, level);
295 if (loginput && (optind < argc))
296 die("No cmd is allowed for -i. Use -h for help.");
298 if ((!loginput) && (optind >= argc)) {
299 msg(L_FATAL, "Either command or --input expected.");
304 write(2, usage, sizeof(usage));
305 return (help == 1) ? 0 : 1;
308 if (register_default) {
310 add_level_fd(0, L_INFO);
312 add_level_fd(1, L_INFO);
313 add_level_fd(2, L_WARN);
318 /* Just check, that we don't want open stderr for reading. */
319 CLIST_FOR_EACH(struct fds *, fd, filedescriptors) {
321 die("Stderr is reserved for output");
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. */
330 if (fdcheck->fdnum == fd->fdnum) {
331 die("Duplicate filedescriptor %i", fd->fdnum);
333 xdupavoid(fdcheck->pipe + 0, fd->fdnum);
334 xdupavoid(fdcheck->pipe + 1, fd->fdnum);
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);
341 if (fd->fdnum == 2) {
342 stderrfd = fd; //We need to redirect stderr later.
344 xdupto(fd->pipe + 1, fd->fdnum);
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);
352 /* Initialize main loop. */
355 CLIST_FOR_EACH(struct fds *, fd, filedescriptors) {
356 /* Our pipe is created, let fd->fdnum be the reading end. */
358 fd->fdnum = fd->pipe[0];
359 fd->rio.read_rec_max = max_line + 1;
360 rec_io_add(&fd->rio, fd->fdnum);
365 /* Launch the child and close filedescriptors. */
368 die("Cannot fork: %m");
372 /* Move stderr where it should be. */
374 xdupto(stderrfd->pipe + 1, 2);
376 execvp(argv[optind], argv + optind);
378 /* We translate stderr, just print. */
379 perror("Cannot exec child");
382 /* No stderr translation: use logging function. */
383 die("Cannot exec child: %m");
386 /* Close writing filedescriptors. */
387 CLIST_FOR_EACH(struct fds *, fd, filedescriptors) {
392 /* Open logfile or stderr. */
399 /* Inform about launching of the command. */
401 for (int i = optind; i < argc; i++) buflen += strlen(argv[i]) + 1;
402 char *buf = xmalloc(buflen);
404 for (int i = optind; i < argc; i++) {
405 strcpy(buf2, argv[i]);
406 buf2 += strlen(argv[i]);
412 msg(L_INFO, "Launching command: %s", buf);
419 log_init(argv[optind]);
421 /* Start reading from pipes. */
422 CLIST_FOR_EACH(struct fds *, fd, filedescriptors)
423 rec_io_start_read(&fd->rio);
428 log_init("logoutput");
430 /* Wait for status of the child and inform about finish. */
434 while (waitpid(pid, &status, 0) == -1) {
436 die("Cannot wait for child: %m");
439 if (format_exit_status(buf, status)) {
440 msg(L_WARN, "Child %s", buf);
441 return WIFEXITED(status) ? WEXITSTATUS(status) : 127;
443 msg(L_INFO, "Child terminated successfully.");