logtap.c (4628B)
1 #include <errno.h> // for errno 2 #include <fcntl.h> // for fcntl, O_NONBLOCK, open, F_GETFL 3 #include <poll.h> // for POLLERR, POLLHUP, POLLIN 4 #include <signal.h> // for signal, SIGPIPE, SIG_IGN 5 #include <stdarg.h> // for va_end, va_list, va_start 6 #include <stdio.h> // for fprintf, stderr, vfprintf 7 #include <stdlib.h> // for malloc, exit 8 #include <string.h> // for strlen, strcat, strcpy, strerror 9 #include <sys/stat.h> // for stat, mkfifo, S_IFIFO 10 #include <unistd.h> // for write, STDOUT_FILENO, read 11 12 #define PROGRAM_NAME "logtap" 13 14 #ifdef _GNU_SOURCE 15 #include <error.h> 16 #else 17 #include <stdarg.h> 18 static void error(int status, int errnum, const char* fmt, ...) 19 { 20 va_list ap; 21 va_start(ap, fmt); 22 23 fprintf(stderr, PROGRAM_NAME ": "); 24 vfprintf(stderr, fmt, ap); 25 if (errnum) 26 fprintf(stderr, ": %s", strerror(errnum)); 27 va_end(ap); 28 29 fprintf(stderr, "\n"); 30 if (status) 31 exit(status); 32 } 33 #endif 34 35 #define FIFO_RETRY_TIMEOUT_MAX (50) 36 37 struct pollfd fds[] = { 38 { .fd = 0, .events = POLLIN }, 39 { .fd = 1, .events = POLLERR }, 40 { .fd = -1, .events = POLLERR }, 41 }; 42 43 #define START_SENTINEL "# LOG-SHIPPING-START\n" 44 #define STOP_SENTINEL "# LOG-SHIPPING-STOP\n" 45 46 static int fifo_connected(void) 47 { 48 return (fds[2].fd >= 0); 49 } 50 51 int fifo_retry_timeout = -1; 52 53 int open_shipper_fifo(char* pathname) 54 { 55 struct stat statbuf; 56 int fd; 57 58 if (stat(pathname, &statbuf)) { 59 switch (errno) { 60 case ENOENT: 61 if (mkfifo(pathname, 0700)) { 62 error(1, errno, "mkfifo %s failed", pathname); 63 } 64 break; 65 default: 66 error(1, errno, "stat %s failed", pathname); 67 break; 68 } 69 } else { 70 if (!(statbuf.st_mode & S_IFIFO)) { 71 error(1, errno, "%s exists already and is not a fifo", pathname); 72 } 73 } 74 75 fd = open(pathname, O_WRONLY | O_NONBLOCK, 0); 76 if (fd >= 0) { 77 fifo_retry_timeout = -1; 78 /* write cookie to stdout so that the backfill process knows 79 * we are now logging realtime 80 */ 81 write(fds[1].fd, START_SENTINEL, sizeof START_SENTINEL); 82 } else { 83 fifo_retry_timeout = FIFO_RETRY_TIMEOUT_MAX; 84 } 85 86 return fd; 87 } 88 89 int main(int argc, char* argv[]) 90 { 91 92 char* buf = malloc(8192); 93 int out_bytes = 0; 94 int tee_bytes = 0; 95 96 if (argc != 3) { 97 error(1, 0, "usage: " PROGRAM_NAME " /path/to/fifo"); 98 } 99 100 signal(SIGPIPE, SIG_IGN); 101 102 int flags = fcntl(STDOUT_FILENO, F_GETFL); 103 fcntl(STDOUT_FILENO, F_SETFL, flags | O_NONBLOCK); 104 105 fds[2].fd = open_shipper_fifo(argv[1]); 106 107 int quitting = 0; 108 109 while (!quitting) { 110 int nfds = poll(fds, 3, 2000); 111 if (nfds > 0) { 112 if (fds[1].revents & (POLLERR | POLLHUP)) { 113 exit(1); // can't even log an error if the logging stream fails 114 }; 115 if (fifo_connected() && (fds[2].revents & (POLLERR | POLLHUP))) { 116 close(fds[2].fd); 117 fds[2].fd = -1; 118 tee_bytes = 0; 119 /* FIXME: nonblocking write, if stdout is not ready 120 * we will lose the message */ 121 (void) write(1, STOP_SENTINEL, sizeof STOP_SENTINEL); 122 fifo_retry_timeout = FIFO_RETRY_TIMEOUT_MAX; 123 }; 124 125 if ((fds[0].revents & (POLLIN | POLLHUP)) && (out_bytes == 0) && (tee_bytes == 0)) { 126 out_bytes = read(fds[0].fd, buf, 8192); 127 if (out_bytes == 0) { 128 quitting = 1; 129 buf = PROGRAM_NAME " detected eof of file on stdin, exiting\n"; 130 out_bytes = strlen(buf); 131 }; 132 if (fifo_connected()) 133 tee_bytes = out_bytes; 134 }; 135 136 if (out_bytes) { 137 out_bytes -= write(fds[1].fd, buf, out_bytes); 138 }; 139 if (tee_bytes) { 140 int written = write(fds[2].fd, buf, tee_bytes); 141 if (written >= 0) 142 tee_bytes -= written; 143 }; 144 if (out_bytes == 0 && fifo_retry_timeout > 0) { 145 fifo_retry_timeout--; 146 if (fifo_retry_timeout == 0) { 147 fds[2].fd = open_shipper_fifo(argv[1]); 148 }; 149 }; 150 } else { 151 if (!fifo_connected()) { 152 fds[2].fd = open_shipper_fifo(argv[1]); 153 } 154 }; 155 }; 156 }