diff options
-rw-r--r-- | inotail.1 | 15 | ||||
-rw-r--r-- | inotail.c | 85 |
2 files changed, 90 insertions, 10 deletions
@@ -2,14 +2,14 @@ .\" ** The above line should force tbl to be a preprocessor ** .\" Man page for inotail .\" -.\" Copyright (c) 2006 Tobias Klauser <tklauser@distanz.ch> +.\" Copyright (c) 2006-2008 Tobias Klauser <tklauser@distanz.ch> .\" .\" You may distribute under the terms of the GNU General Public .\" License as specified in the file COPYING that comes with .\" inotail. .pc -.TH INOTAIL 1 "2006-08-13" "" "Inotify enhanced tail" +.TH INOTAIL 1 "2008-07-03" "" "Inotify enhanced tail" .SH NAME inotail \- A fast and lightweight version of tail using inotify .SH SYNOPSIS @@ -34,8 +34,15 @@ be in the future. output the last N bytes. If the first character of N is a '+', begin printing with the Nth character from the start of each file. .TP -.B \-f, \fB\-\-follow -keep the file(s) open and print appended data as the file grows +.B \-f, \fB\-\-follow\fR[={descriptor|name}] +keep the file(s) open and print appended data as the file grows; \fB\-f\fR, +\fB\-\-follow\fR, and \fB\-\-follow=\fIdescriptor\fR are equivalent and follow +the file descriptor even if the file is renamed or moved. Use +\fB\-\-follow=name\fR if you want to track the file by name. In this case the +file is reopened as soon as it got recreated. +.TP +.B \-F +equivalent to \fB\-\-follow=name\fR. .TP .B \-n \fIN\fR, \fB\-\-lines\fR=\fIN\fR output the last N lines (default: 10) If the first character of N is a '+', @@ -30,6 +30,7 @@ #include <fcntl.h> #include <errno.h> #include <getopt.h> +#include <libgen.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/inotify.h> @@ -37,11 +38,21 @@ #include "inotail.h" #define PROGRAM_NAME "inotail" +/* inotify event buffer length for one file */ +#define INOTIFY_BUFLEN (4 * sizeof(struct inotify_event)) /* Print header with filename before tailing the file? */ static char verbose = 0; + /* Tailing relative to begin or end of file */ static char from_begin = 0; + +/* Follow the file by name or by fd */ +static enum follow_mode follow = FOLLOW_DESCRIPTOR; + +/* Retry accessing the file if it got lost (e.g. deleted, moved) */ +static char retry = 0; + /* Number of ignored files */ static int n_ignored = 0; @@ -72,7 +83,9 @@ static void usage(const int status) { fprintf(stdout, "Usage: %s [OPTION]... [FILE]...\n\n" " -c N, --bytes=N output the last N bytes\n" - " -f, --follow output as the file grows\n" + " -f, --follow[={descriptor|name}]\n" + " output as the file grows (default: descriptor)\n" + " -F same as --follow=name\n" " -n N, --lines=N output the last N lines (default: %d)\n" " -v, --verbose print headers with file names\n" " -h, --help show this help and exit\n" @@ -86,7 +99,7 @@ static void usage(const int status) static inline void setup_file(struct file_struct *f) { - f->fd = f->i_watch = -1; + f->fd = f->i_watch = f->i_d_watch = -1; f->size = 0; f->blksize = BUFSIZ; f->ignore = 0; @@ -94,6 +107,7 @@ static inline void setup_file(struct file_struct *f) static void ignore_file(struct file_struct *f) { + dprintf("File '%s' ignored\n", f->name); if (f->fd != -1) { close(f->fd); f->fd = -1; @@ -568,10 +582,23 @@ static int tail_file(struct file_struct *f, unsigned long n_units, mode_t mode, return 0; } +static int follow_file_by_name(struct file_struct *f) +{ + inotify_rm_watch(f->fd, INOTAIL_EVENT_MASK); + close(f->fd); + + /* TODO: Set up a watch for the directory the file was in and + * wait for the file to be created again. + */ + return 0; +} + static int handle_inotify_event(struct inotify_event *inev, struct file_struct *f) { int ret = 0; + ddump(inev); + if (inev->mask & IN_MODIFY) { char *fbuf; ssize_t bytes_read; @@ -608,11 +635,22 @@ static int handle_inotify_event(struct inotify_event *inev, struct file_struct * return ret; } else if (inev->mask & IN_DELETE_SELF) { fprintf(stderr, "File '%s' deleted.\n", f->name); + + if (retry && follow == FOLLOW_NAME) { + follow_file_by_name(f); + } } else if (inev->mask & IN_MOVE_SELF) { fprintf(stderr, "File '%s' moved.\n", f->name); + + if (follow == FOLLOW_NAME) { + follow_file_by_name(f); + } + return 0; } else if (inev->mask & IN_UNMOUNT) { fprintf(stderr, "Device containing file '%s' unmounted.\n", f->name); + } else if (f->dir && (inev->mask & (IN_CREATE | IN_MOVED_TO))) { + /* File was moved/deleted and became available again */ } else if (inev->mask & IN_IGNORED) { return 0; } @@ -638,14 +676,24 @@ static int watch_files(struct file_struct *files, int n_files) for (i = 0; i < n_files; i++) { if (!files[i].ignore) { - files[i].i_watch = inotify_add_watch(ifd, files[i].name, - IN_MODIFY|IN_DELETE_SELF|IN_MOVE_SELF|IN_UNMOUNT); + files[i].i_watch = inotify_add_watch(ifd, files[i].name, INOTAIL_EVENT_MASK); if (files[i].i_watch < 0) { fprintf(stderr, "Error: Could not create inotify watch on file '%s' (%s)\n", files[i].name, strerror(errno)); ignore_file(&files[i]); } + + if (files[i].dir) { + files[i].i_d_watch = inotify_add_watch(ifd, files[i].dir, + IN_CREATE | IN_MOVED_TO | IN_DELETE | IN_ONLYDIR); + + if (files[i].i_d_watch < 0) { + fprintf(stderr, "Error: Could not create inotify watch on directory '%s' (%s)\n", + files[i].dir, strerror(errno)); + files[i].dir = NULL; + } + } } } @@ -704,7 +752,7 @@ int main(int argc, char **argv) char **filenames; struct file_struct *files = NULL; - while ((c = getopt_long(argc, argv, "c:n:fvVh", long_opts, NULL)) != -1) { + while ((c = getopt_long(argc, argv, "c:n:fFvVh", long_opts, NULL)) != -1) { switch (c) { case 'c': mode = M_BYTES; @@ -722,8 +770,23 @@ int main(int argc, char **argv) } n_units = strtoul(optarg, NULL, 0); break; - case 'f': + case 'f': + forever = 1; + if (!optarg || strncmp(optarg, "descriptor", 11) == 0) + follow = FOLLOW_DESCRIPTOR; + else if (strncmp(optarg, "name", 5) == 0) + follow = FOLLOW_NAME; + else { + fprintf(stderr, + "Error: Invalid follow mode: %s\nSee %s --help for valid options\n", + optarg, PROGRAM_NAME); + exit(EXIT_FAILURE); + } + break; + case 'F': forever = 1; + follow = FOLLOW_NAME; + retry = 1; break; case 'v': verbose = 1; @@ -768,6 +831,16 @@ int main(int argc, char **argv) for (i = 0; i < n_files; i++) { files[i].name = filenames[i]; + + dprintf("basename: %s\n", basename(files[i].name)); + dprintf("dirname: %s\n", dirname(files[i].name)); + + if (follow == FOLLOW_NAME) { + /* TODO: Set dir member of file_struct */ + } else { + files[i].dir = NULL; + } + setup_file(&files[i]); ret = tail_file(&files[i], n_units, mode, forever); if (ret < 0) |