Hi -- I recently needed to wire up httpd's rotatelogs to a named pipe (a FIFO) instead of to stdin. For context, we have a non-httpd server process configured to write to the pipe instead of a log file; further, this server likes to intermittently open, write to, and close its various log files.
I wanted/needed to open the pipe in non-blocking mode, so rotatelogs would start right away (we're using rotatelogs -cf) instead of blocking until a writer process happened to open the pipe to write some log data. Also, because the server process repeatedly opens and closes its log files/pipes, a normal reader will exit on EOF after the first close, and therefore miss all subsequent writes. I played around for some time with variants on "tail -f pipe | rotatelogs" and "cat pipe | rotatelogs" but both have problems. The tail -f seems to miss data, especially at first; I can't explain that but it's what I observed. The cat command works to capture all data while running, but it blocks at startup and (worse for us) exits on the first EOF. What I really wanted was a simpler command line, passing the pipe to rotatelogs directly, and having it (a) open in non-blocking mode so the -cf options are effective right away, and (b) ignore EOFs until I kill it explicitly. The latter is ungainly but that's how the server also has to be stopped, via kill -TERM, so it's acceptable in this case for us. First I puzzled over how to open a pipe in non-blocking mode from the start using APR, and ended up with a set of patches for that; see http://marc.info/?l=apr-dev&m=135586687408393&w=2 for the details, but the gist is an apr_file_namedpipe_open() function to do this. Probably not sufficiently full of trademark portable goodness, but effective for the moment. Next the following patch adds a "-i inputpipe [-o]" set of options, when possible, so that rotatelogs will listen to the pipe instead of stdin and will ignore EOFs. I'm posting both the APR and httpd patches because I thought it was an interesting issue, and I suspect there are alternate and perhaps better approaches; certainly the APR patch isn't ideal. But after a long time spent digging into the source code for tail and cat and trying many variants, this now seems to work smoothly for us, at least. Chris. =================================================================== --- support/rotatelogs.c.orig 2012-12-18 13:44:02.000000000 -0800 +++ support/rotatelogs.c 2012-12-18 14:11:56.000000000 -0800 @@ -67,6 +67,8 @@ int truncate; const char *linkfile; const char *postrotate_prog; + const char *inputpipe; + int ignore_eof; #if APR_FILES_AS_SOCKETS int create_empty; #endif @@ -101,9 +103,9 @@ } fprintf(stderr, #if APR_FILES_AS_SOCKETS - "Usage: %s [-v] [-l] [-L linkname] [-p prog] [-f] [-t] [-e] [-c] <logfile> " + "Usage: %s [-v] [-l] [-L linkname] [-p prog] [-i inputpipe [-o]] [-f] [-t] [-e] [-c] <logfile> " #else - "Usage: %s [-v] [-l] [-L linkname] [-p prog] [-f] [-t] [-e] <logfile> " + "Usage: %s [-v] [-l] [-L linkname] [-p prog] [-i inputpipe] [-f] [-t] [-e] <logfile> " #endif "{<rotation time in seconds>|<rotation size>(B|K|M|G)} " "[offset minutes from UTC]\n\n", @@ -137,7 +139,9 @@ " -f Force opening of log on program start.\n" " -t Truncate logfile instead of rotating, tail friendly.\n" " -e Echo log to stdout for further processing.\n" + " -i path Use specified named pipe as input.\n" #if APR_FILES_AS_SOCKETS + " -o Hold input pipe open when writers exit (with -i only).\n" " -c Create log even if it is empty.\n" #endif "\n" @@ -194,7 +198,9 @@ fprintf(stderr, "Rotation verbose: %12s\n", config->verbose ? "yes" : "no"); #if APR_FILES_AS_SOCKETS fprintf(stderr, "Rotation create empty logs: %12s\n", config->create_empty ? "yes" : "no"); + fprintf(stderr, "Input pipe held open: %12s\n", config->ignore_eof ? "yes" : "no"); #endif + fprintf(stderr, "Input pipe: %21s\n", config->inputpipe); fprintf(stderr, "Rotation file name: %21s\n", config->szLogRoot); fprintf(stderr, "Post-rotation prog: %21s\n", config->postrotate_prog); } @@ -529,9 +535,9 @@ apr_pool_create(&status.pool, NULL); apr_getopt_init(&opt, status.pool, argc, argv); #if APR_FILES_AS_SOCKETS - while ((rv = apr_getopt(opt, "lL:p:ftvec", &c, &opt_arg)) == APR_SUCCESS) { + while ((rv = apr_getopt(opt, "lL:p:i:oftvec", &c, &opt_arg)) == APR_SUCCESS) { #else - while ((rv = apr_getopt(opt, "lL:p:ftve", &c, &opt_arg)) == APR_SUCCESS) { + while ((rv = apr_getopt(opt, "lL:p:i:ftve", &c, &opt_arg)) == APR_SUCCESS) { #endif switch (c) { case 'l': @@ -543,6 +549,12 @@ case 'p': config.postrotate_prog = opt_arg; break; + case 'i': + config.inputpipe = opt_arg; + break; + case 'o': + config.ignore_eof = 1; + break; case 'f': config.force_open = 1; break; @@ -588,9 +600,25 @@ config.use_strftime = (strchr(config.szLogRoot, '%') != NULL); - if (apr_file_open_stdin(&f_stdin, status.pool) != APR_SUCCESS) { - fprintf(stderr, "Unable to open stdin\n"); - exit(1); + if (config.inputpipe) { + rv = apr_file_namedpipe_open(&f_stdin, config.inputpipe, + APR_FOPEN_READ | + (config.ignore_eof ? APR_FOPEN_WRITE : 0), + APR_FULL_NONBLOCK, status.pool); + + if (rv != APR_SUCCESS) { + fprintf(stderr, "Unable to open pipe %s\n", config.inputpipe); + exit(1); + } + } else { + if (config.ignore_eof) { + usage(argv[0], "-o requires -i <inputpipe>"); + } + + if (apr_file_open_stdin(&f_stdin, status.pool) != APR_SUCCESS) { + fprintf(stderr, "Unable to open stdin\n"); + exit(1); + } } if (apr_file_open_stdout(&f_stdout, status.pool) != APR_SUCCESS) { @@ -637,7 +665,11 @@ if (pollret == APR_SUCCESS) { rv = apr_file_read(f_stdin, buf, &nRead); if (APR_STATUS_IS_EOF(rv)) { - break; + if (config.ignore_eof) { + continue; + } else { + break; + } } else if (rv != APR_SUCCESS) { exit(3); =================================================================== -- GPG Key ID: 088335A9 GPG Key Fingerprint: 86CD 3297 7493 75BC F820 6715 F54F E648 0883 35A9