summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--inotail.115
-rw-r--r--inotail.c85
2 files changed, 90 insertions, 10 deletions
diff --git a/inotail.1 b/inotail.1
index d543e52..0fd679b 100644
--- a/inotail.1
+++ b/inotail.1
@@ -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 '+',
diff --git a/inotail.c b/inotail.c
index 670d9fe..9dae0b8 100644
--- a/inotail.c
+++ b/inotail.c
@@ -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)