diff options
-rw-r--r-- | Makefile | 8 | ||||
-rw-r--r-- | changelog | 11 | ||||
-rw-r--r-- | debian/changelog | 6 | ||||
-rw-r--r-- | inotail.c | 149 | ||||
-rw-r--r-- | inotail.h | 7 |
5 files changed, 133 insertions, 48 deletions
@@ -4,7 +4,7 @@ # # Licensed under the terms of the GNU General Public License; version 2 or later. -VERSION = 0.3 +VERSION = 0.4 # Paths prefix = /usr/local @@ -12,7 +12,7 @@ BINDIR = $(prefix)/bin MANDIR = $(prefix)/share/man/man1 CC := gcc -CFLAGS := $(CFLAGS) -W -Wall -pipe -D_USE_SOURCE -DVERSION="\"$(VERSION)\"" \ +CFLAGS := $(CFLAGS) -pipe -D_USE_SOURCE -DVERSION="\"$(VERSION)\"" -W -Wall \ -Wstrict-prototypes -Wsign-compare -Wshadow -Wchar-subscripts \ -Wmissing-declarations -Wpointer-arith -Wcast-align -Wmissing-prototypes @@ -31,6 +31,10 @@ inotail: inotail.o install: inotail install -m 775 -D inotail $(BINDIR)/inotail install -m 644 -D inotail.1 $(MANDIR)/inotail.1 + gzip -9 $(MANDIR)/inotail.1 + +uninstall: + rm $(BINDIR)/inotail $(MANDIR)/inotail.1* cscope: cscope -b @@ -1,3 +1,14 @@ +inotail 0.4 + + * Use dynamic buffers of optimal size (st_blksize in struct stat) for + filesystem I/O (patch by Folkert van Heusden) + * Added handling of EINTR/EAGAIN while watching files for changes (patch by + Anthony Martinez) + * Better error checking and handling (patch by Folkert van Heusden) + * Various cleanups + + -- Tobias Klauser <tklauser@distanz.ch> 2007-06-20 15:00 + inotail 0.3 * Follow files even if they were moved diff --git a/debian/changelog b/debian/changelog index ca3a68f..0ef3352 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +inotail (0.4-1) unstable; urgency=low + + * New upstream release + + -- Tobias Klauser <tklauser@access.unizh.ch> Wed, 20 Jun 2007 15:10:19 +0200 + inotail (0.3-1) unstable; urgency=low * New upstream release @@ -30,7 +30,6 @@ #include <fcntl.h> #include <errno.h> #include <getopt.h> -#include <ctype.h> #include <sys/types.h> #include <sys/stat.h> @@ -40,7 +39,7 @@ #include "inotail.h" #define PROGRAM_NAME "inotail" -#define BUFFER_SIZE 4096 +#define DEFAULT_BUFFER_SIZE 4096 /* inotify event buffer length for one file */ #define INOTIFY_BUFLEN (4 * sizeof(struct inotify_event)) @@ -64,6 +63,18 @@ static const struct option long_opts[] = { { NULL, 0, NULL, 0 } }; +static void *emalloc(size_t size) +{ + void *ret = malloc(size); + + if (unlikely(!ret)) { + fprintf(stderr, "Error: Failed to allocate %d bytes of memory (%s)\n", size, strerror(errno)); + exit(EXIT_FAILURE); + } + + return ret; +} + static void usage(const int status) { fprintf(stdout, "Usage: %s [OPTION]... [FILE]...\n\n" @@ -82,10 +93,10 @@ static void usage(const int status) static inline void setup_file(struct file_struct *f) { - f->fd = -1; + f->fd = f->i_watch = -1; f->st_size = 0; + f->st_blksize = DEFAULT_BUFFER_SIZE; f->ignore = 0; - f->i_watch = -1; } static void ignore_file(struct file_struct *f) @@ -117,43 +128,51 @@ static void write_header(char *filename) static off_t lines_to_offset_from_end(struct file_struct *f, unsigned long n_lines) { - char buf[BUFFER_SIZE]; off_t offset = f->st_size; + char *buf = emalloc(f->st_blksize); n_lines++; /* We also count the last \n */ while (offset > 0 && n_lines > 0) { int i; - ssize_t rc, block_size = BUFFER_SIZE; /* Size of the current block we're reading */ + ssize_t rc, block_size = f->st_blksize; /* Size of the current block we're reading */ - if (offset < BUFFER_SIZE) + if (offset < block_size) block_size = offset; /* Start of current block */ offset -= block_size; - lseek(f->fd, offset, SEEK_SET); + if (lseek(f->fd, offset, SEEK_SET) == (off_t) -1) { + fprintf(stderr, "Error: Could not seek in file '%s' (%s)\n", f->name, strerror(errno)); + free(buf); + return -1; + } - rc = read(f->fd, &buf, block_size); + rc = read(f->fd, buf, block_size); if (unlikely(rc < 0)) { fprintf(stderr, "Error: Could not read from file '%s' (%s)\n", f->name, strerror(errno)); + free(buf); return -1; } for (i = block_size - 1; i > 0; i--) { if (buf[i] == '\n') { - if (--n_lines == 0) + if (--n_lines == 0) { + free(buf); return offset += i + 1; /* We don't want the first \n */ + } } } } + free(buf); return offset; } static off_t lines_to_offset_from_begin(struct file_struct *f, unsigned long n_lines) { - char buf[BUFFER_SIZE]; + char *buf; off_t offset = 0; /* tail everything for 'inotail -n +0' */ @@ -161,30 +180,39 @@ static off_t lines_to_offset_from_begin(struct file_struct *f, unsigned long n_l return 0; n_lines--; + buf = emalloc(f->st_blksize); while (offset <= f->st_size && n_lines > 0) { int i; - ssize_t rc, block_size = BUFFER_SIZE; + ssize_t rc, block_size = f->st_blksize; - lseek(f->fd, offset, SEEK_SET); + if (lseek(f->fd, offset, SEEK_SET) == (off_t) -1) { + fprintf(stderr, "Error: Could not seek in file '%s' (%s)\n", f->name, strerror(errno)); + free(buf); + return -1; + } - rc = read(f->fd, &buf, block_size); + rc = read(f->fd, buf, block_size); if (unlikely(rc < 0)) { fprintf(stderr, "Error: Could not read from file '%s' (%s)\n", f->name, strerror(errno)); + free(buf); return -1; } else if (rc < block_size) block_size = rc; for (i = 0; i < block_size; i++) { if (buf[i] == '\n') { - if (--n_lines == 0) + if (--n_lines == 0) { + free(buf); return offset + i + 1; + } } } offset += block_size; } + free(buf); return offset; } @@ -204,10 +232,8 @@ static off_t bytes_to_offset(struct file_struct *f, unsigned long n_bytes) if (from_begin) { if (n_bytes > 0) offset = (off_t) n_bytes - 1; - } else { - if ((off_t) n_bytes < f->st_size) - offset = f->st_size - (off_t) n_bytes; - } + } else if ((off_t) n_bytes < f->st_size) + offset = f->st_size - (off_t) n_bytes; return offset; } @@ -215,15 +241,22 @@ static off_t bytes_to_offset(struct file_struct *f, unsigned long n_bytes) static ssize_t tail_pipe(struct file_struct *f) { ssize_t rc; - char buf[BUFFER_SIZE]; + char *buf = emalloc(f->st_blksize); if (verbose) write_header(f->name); /* We will just tail everything here */ - while ((rc = read(f->fd, &buf, BUFFER_SIZE)) > 0) - write(STDOUT_FILENO, buf, (size_t) rc); + while ((rc = read(f->fd, buf, f->st_blksize)) > 0) { + if (write(STDOUT_FILENO, buf, (size_t) rc) <= 0) { + /* e.g. when writing to a pipe which gets closed */ + fprintf(stderr, "Error: Could not write to stdout (%s)\n", strerror(errno)); + rc = -1; + break; + } + } + free(buf); return rc; } @@ -231,7 +264,7 @@ static int tail_file(struct file_struct *f, unsigned long n_units, char mode, ch { ssize_t bytes_read = 0; off_t offset = 0; - char buf[BUFFER_SIZE]; + char *buf; struct stat finfo; if (strcmp(f->name, "-") == 0) @@ -262,6 +295,7 @@ static int tail_file(struct file_struct *f, unsigned long n_units, char mode, ch return tail_pipe(f); f->st_size = finfo.st_size; + f->st_blksize = finfo.st_blksize; /* TODO: Can this value be 0? */ if (mode == M_LINES) offset = lines_to_offset(f, n_units); @@ -277,35 +311,50 @@ static int tail_file(struct file_struct *f, unsigned long n_units, char mode, ch if (verbose) write_header(f->name); - lseek(f->fd, offset, SEEK_SET); + if (lseek(f->fd, offset, SEEK_SET) == (off_t) -1) { + fprintf(stderr, "Error: Could not seek in file '%s' (%s)\n", f->name, strerror(errno)); + return -1; + } + + buf = emalloc(f->st_blksize); - while ((bytes_read = read(f->fd, &buf, BUFFER_SIZE)) > 0) + while ((bytes_read = read(f->fd, buf, f->st_blksize)) > 0) write(STDOUT_FILENO, buf, (size_t) bytes_read); if (!forever) { if (close(f->fd) < 0) { fprintf(stderr, "Error: Could not close file '%s' (%s)\n", f->name, strerror(errno)); + free(buf); return -1; } } /* Let the fd open otherwise, we'll need it */ + free(buf); return 0; } static int handle_inotify_event(struct inotify_event *inev, struct file_struct *f) { + char *fbuf; int ret = 0; if (inev->mask & IN_MODIFY) { ssize_t rc; - char fbuf[BUFFER_SIZE]; struct stat finfo; + fbuf = emalloc(f->st_blksize); + if (verbose) write_header(f->name); - lseek(f->fd, f->st_size, SEEK_SET); /* Old file size */ - while ((rc = read(f->fd, &fbuf, BUFFER_SIZE)) != 0) + /* Seek to old file size */ + if (lseek(f->fd, f->st_size, SEEK_SET) == (off_t) -1) { + fprintf(stderr, "Error: Could not seek in file '%s' (%s)\n", f->name, strerror(errno)); + ret = -1; + goto ignore; + } + + while ((rc = read(f->fd, fbuf, f->st_blksize)) != 0) write(STDOUT_FILENO, fbuf, (size_t) rc); if (fstat(f->fd, &finfo) < 0) { @@ -316,6 +365,7 @@ static int handle_inotify_event(struct inotify_event *inev, struct file_struct * f->st_size = finfo.st_size; + free(fbuf); return ret; } else if (inev->mask & IN_DELETE_SELF) { fprintf(stderr, "File '%s' deleted.\n", f->name); @@ -328,7 +378,7 @@ static int handle_inotify_event(struct inotify_event *inev, struct file_struct * ignore: ignore_file(f); - + free(fbuf); return ret; } @@ -339,10 +389,10 @@ static int watch_files(struct file_struct *files, int n_files) ifd = inotify_init(); if (errno == ENOSYS) { - fprintf(stderr, "Error: Inotify is not supported by the kernel you're currently running.\n"); + fprintf(stderr, "Error: inotify is not supported by the kernel you're currently running.\n"); exit(EXIT_FAILURE); } else if (unlikely(ifd < 0)) { - fprintf(stderr, "Error: Could not initialize Inotify (%s)\n", strerror(errno)); + fprintf(stderr, "Error: Could not initialize inotify (%s)\n", strerror(errno)); exit(EXIT_FAILURE); } @@ -363,10 +413,15 @@ static int watch_files(struct file_struct *files, int n_files) ssize_t len; int ev_idx = 0; - len = read(ifd, &buf, (n_files * INOTIFY_BUFLEN)); + len = read(ifd, buf, (n_files * INOTIFY_BUFLEN)); if (unlikely(len < 0)) { - fprintf(stderr, "Error: Could not read inotify events (%s)\n", strerror(errno)); - exit(EXIT_FAILURE); + /* Some signal, likely ^Z/fg's STOP and CONT interrupted the inotify read, retry */ + if (errno == EINTR || errno == EAGAIN) + continue; + else { + fprintf(stderr, "Error: Could not read inotify events (%s)\n", strerror(errno)); + exit(EXIT_FAILURE); + } } while (ev_idx < len) { @@ -377,7 +432,9 @@ static int watch_files(struct file_struct *files, int n_files) /* Which file has produced the event? */ for (i = 0; i < n_files; i++) { - if (!files[i].ignore && files[i].fd >= 0 && files[i].i_watch == inev->wd) { + if (!files[i].ignore + && files[i].fd >= 0 + && files[i].i_watch == inev->wd) { f = &files[i]; break; } @@ -388,12 +445,12 @@ static int watch_files(struct file_struct *files, int n_files) if (handle_inotify_event(inev, f) < 0) break; - ev_idx += INOTIFY_BUFLEN + inev->len; + + ev_idx += sizeof(struct inotify_event) + inev->len; } } close(ifd); - return -1; } @@ -418,7 +475,7 @@ int main(int argc, char **argv) } else if (*optarg == '-') optarg++; - if (!isdigit(*optarg)) { + if (!is_digit(*optarg)) { fprintf(stderr, "Invalid number of lines: %s\n", optarg); exit(EXIT_FAILURE); } @@ -454,17 +511,19 @@ int main(int argc, char **argv) specified and standard input is a pipe. */ if (forever) { struct stat finfo; - if (fstat(STDIN_FILENO, &finfo) == 0 - && IS_PIPELIKE(finfo.st_mode)) + int rc = fstat(STDIN_FILENO, &finfo); + + if (unlikely(rc == -1)) { + fprintf(stderr, "Error: Could not stat stdin (%s)\n", strerror(errno)); + exit(EXIT_FAILURE); + } + + if (rc == 0 && IS_PIPELIKE(finfo.st_mode)) forever = 0; } } - files = malloc(n_files * sizeof(struct file_struct)); - if (unlikely(!files)) { - fprintf(stderr, "Error: Not enough memory (%s)\n", strerror(errno)); - exit(EXIT_FAILURE); - } + files = emalloc(n_files * sizeof(struct file_struct)); for (i = 0; i < n_files; i++) { files[i].name = filenames[i]; @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2006, Tobias Klauser <tklauser@distanz.ch> + * Copyright (C) 2005-2007, Tobias Klauser <tklauser@distanz.ch> * * Licensed under the terms of the GNU General Public License; version 2 or later. */ @@ -7,6 +7,8 @@ #ifndef _INOTAIL_H #define _INOTAIL_H +#include <sys/types.h> + /* Number of items to tail. */ #define DEFAULT_N_LINES 10 @@ -18,6 +20,7 @@ struct file_struct { char *name; /* Name of file (or '-' for stdin) */ int fd; /* File descriptor (or -1 if file is not open */ off_t st_size; /* File size */ + blksize_t st_blksize; /* Blocksize for filesystem I/O */ unsigned ignore; /* Whether to ignore the file in further processing */ int i_watch; /* Inotify watch associated with file_struct */ }; @@ -29,6 +32,8 @@ struct file_struct { #define IS_TAILABLE(mode) \ (S_ISREG(mode) || IS_PIPELIKE(mode) || S_ISCHR(mode)) +#define is_digit(c) ((c) >= '0' && (c) <= '9') + #ifdef DEBUG # define dprintf(fmt, args...) fprintf(stderr, fmt, ##args) #else |