From 818bf75711938460f43815cfcca8dab16a93ac97 Mon Sep 17 00:00:00 2001 From: Tobias Klauser Date: Thu, 11 May 2006 15:31:33 +0200 Subject: simpletail is the new inotail --- Makefile | 10 +- inotail-old.c | 389 ++++++++++++++++++++++++++++++++++++++++++++++++++++ inotail.c | 428 ++++++++++++++++++++++------------------------------------ inotail.h | 8 +- simpletail.c | 263 ------------------------------------ 5 files changed, 560 insertions(+), 538 deletions(-) create mode 100644 inotail-old.c delete mode 100644 simpletail.c diff --git a/Makefile b/Makefile index 7b8e0ed..6992405 100644 --- a/Makefile +++ b/Makefile @@ -14,21 +14,23 @@ ifeq ($(strip $(DEBUG)),true) CFLAGS += -g -DDEBUG endif -PROGRAMS := inotail inotify-watchdir simpletail +PROGRAMS := inotail inotail-old inotify-watchdir #simpletail all: $(PROGRAMS) inotail: inotail.o +inotail-old: inotail-old.o + inotify-watchdir: inotify-watchdir.o -simpletail: simpletail.o +#simpletail: simpletail.o %.o: %.c $(CC) $(CFLAGS) -c $< -o $@ -install: simpletail - ${INSTALL} -m 775 simpletail ${DESTDIR}${BINDIR} +install: inotail + ${INSTALL} -m 775 inotail ${DESTDIR}${BINDIR} clean: rm -f *.o diff --git a/inotail-old.c b/inotail-old.c new file mode 100644 index 0000000..b3a9114 --- /dev/null +++ b/inotail-old.c @@ -0,0 +1,389 @@ +/* + * inotail.c + * A fast implementation of GNU tail which uses the inotify-API present in + * recent Linux Kernels. + * + * Copyright (C) 2005-2006, Tobias Klauser + * + * Parts of this program are based on GNU tail included in the GNU coreutils + * which is: + * Copyright (C) 1989, 90, 91, 1995-2005 Free Software Foundation, Inc. + * + * The idea and some code were taken from turbotail. + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation; either version 2, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#define _GNU_SOURCE + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "inotify.h" +#include "inotify-syscalls.h" + +#include "inotail.h" + +#define VERSION "0.1" + +/* XXX: Move all global variables into a struct and use :1 */ + +/* If !=0, read from the ends of all specified files until killed. */ +static unsigned short forever; + +/* Print header with filename before tailing the file? */ +static unsigned short print_headers = 0; + +/* Say my name! */ +static char *program_name = "inotail"; + +static int dump_remainder(const char *filename, int fd, ssize_t n_bytes) +{ + ssize_t written = 0; + + dprintf("==> dump_remainder()\n"); + + if (n_bytes > SSIZE_MAX) + n_bytes = SSIZE_MAX; + + return written; +} + +static char *pretty_name(const struct file_struct *f) +{ + return ((strcmp(f->name, "-") == 0) ? "standard input" : f->name); +} + +static void write_header(const struct file_struct *f) +{ + static unsigned short first_file = 1; + + fprintf (stdout, "%s==> %s <==\n", (first_file ? "" : "\n"), pretty_name(f)); + first_file = 0; +} + +static int file_lines(struct file_struct *f, uintmax_t n_lines, off_t start_pos, off_t end_pos) +{ + char buffer[BUFSIZ]; + size_t bytes_read; + off_t pos = end_pos; + + dprintf("==> file_lines()\n"); + + if (n_lines == 0) + return 1; + + dprintf(" start_pos: %lld\n", (unsigned long long) start_pos); + dprintf(" end_pos: %lld\n", (unsigned long long) end_pos); + + /* Set `bytes_read' to the size of the last, probably partial, buffer; + * 0 < `bytes_read' <= `BUFSIZ'. + */ + bytes_read = (pos - start_pos) % BUFSIZ; + if (bytes_read == 0) + bytes_read = BUFSIZ; + + dprintf(" bytes_read: %zd\n", bytes_read); + + /* Make `pos' a multiple of `BUFSIZ' (0 if the file is short), so that + * all + * reads will be on block boundaries, which might increase + * efficiency. */ + pos -= bytes_read; + + dprintf(" pos: %lld\n", pos); + + lseek(f->fd, pos, SEEK_SET); + bytes_read = read(f->fd, buffer, bytes_read); + + /* Count the incomplete line on files that don't end with a newline. */ + if (bytes_read && buffer[bytes_read - 1] != '\n') + --n_lines; + + do { + size_t n = bytes_read; + while (n) { + const char *nl; + nl = memrchr(buffer, '\n', n); + if (nl == NULL) + break; + } + + /* XXX XXX XXX XXX */ + + /* Not enough newlines in that buffer. Just print everything */ + if (pos == start_pos) { + lseek(f->fd, start_pos, SEEK_SET); + return 1; + } + pos -= BUFSIZ; + } while (bytes_read > 0); + + return 1; +} + +static void check_file(struct file_struct *f) +{ + struct stat new_stats; + + dprintf("==> check_file()\n"); + + dprintf(" checking '%s'\n", f->name); +} + +static int tail_forever(struct file_struct *f, int n_files) +{ + int i_fd, len; + unsigned int i; + struct inotify_event *inev; + char buf[1000]; + + dprintf("==> tail_forever()\n"); + + i_fd = inotify_init(); + if (i_fd < 0) + return -1; + + for (i = 0; i < n_files; i++) { + f[i].i_watch = inotify_add_watch(i_fd, f[i].name, IN_ALL_EVENTS | IN_UNMOUNT); + dprintf(" Watch (%d) added to '%s' (%d)\n", f[i].i_watch, f[i].name, i); + } + + memset(&buf, 0, sizeof(buf)); + + while (1) { + int fd; + ssize_t bytes_tailed = 0; + + len = read(i_fd, buf, sizeof(buf)); + inev = (struct inotify_event *) &buf; + + while (len > 0) { + struct file_struct *fil; + + /* Which file has produced the event? */ + for (i = 0; i < n_files; i++) { + if (!f[i].ignore && f[i].fd >= 0 && f[i].i_watch == inev->wd) { + fil = &f[i]; + break; + } + } + + /* We should at least catch the following + * events: + * - IN_MODIFY, thats what we hopefully get + * most of the time + * - IN_ATTRIB, still readable? + * - IN_MOVE, reopen the file at the new + * position? + * - IN_DELETE_SELF, we need to check if the + * file is still there or is really gone + * - IN_MOVE_SELF, ditto + * - IN_UNMOUNT, die gracefully + */ + if (inev->mask & IN_MODIFY) { + dprintf(" File '%s' modified.\n", fil->name); + check_file(fil); + /* Dump new content */ + } else if (inev->mask & IN_ATTRIB) { + dprintf(" File '%s' attributes changed.\n", fil->name); + check_file(fil); + } else if (inev->mask & IN_MOVE) { + } else if (inev->mask & IN_DELETE_SELF) { + dprintf(" File '%s' possibly deleted.\n", fil->name); + check_file(fil); + } else { + /* Ignore */ + } + + /* Shift one event forward */ + len -= sizeof(struct inotify_event) + inev->len; + inev = (struct inotify_event *) ((char *) inev + sizeof(struct inotify_event) + inev->len); + } + } + + /* XXX: Never reached. Catch SIGINT and handle it there? */ + for (i = 0; i < n_files; i++) + inotify_rm_watch(i_fd, f[i].i_watch); + + return 0; +} + +static int tail_lines(struct file_struct *f, uintmax_t n_lines) +{ + struct stat stats; + off_t start_pos = -1; + off_t end_pos; + + dprintf("==> tail_lines()\n"); + + if (fstat(f->fd, &stats)) { + perror("fstat()"); + exit(EXIT_FAILURE); + } + + start_pos = lseek(f->fd, 0, SEEK_CUR); + end_pos = lseek(f->fd, 0, SEEK_END); + + /* Use file_lines only if FD refers to a regular file for + * which lseek(... SEEK_END) worked. + */ + if (S_ISREG(stats.st_mode) && start_pos != -1 && start_pos < end_pos) { + if (end_pos != 0 && !file_lines(f, n_lines, start_pos, end_pos)) + return -1; + } else { + /* Under very unlikely circumstances, it is possible to reach + this point after positioning the file pointer to end of file + via the `lseek (...SEEK_END)' above. In that case, reposition + the file pointer back to start_pos before calling pipe_lines. */ +/* if (start_pos != -1) + xlseek (fd, start_pos, SEEK_SET, pretty_filename); + + return pipe_lines (pretty_filename, fd, n_lines, read_pos); +*/ + } + + return 0; +} + +static int tail(struct file_struct *f, uintmax_t n_units) +{ + dprintf("==> tail()\n"); + + return tail_lines(f, n_units); +} + +static int tail_file(struct file_struct *f, uintmax_t n_units) +{ + int ret = 0; + + dprintf("==> tail_file()\n"); + + if (strcmp(f->name, "-") == 0) { + f->fd = STDIN_FILENO; + } else { + f->fd = open(f->name, O_RDONLY); + } + + if (f->fd == -1) { + perror("open()"); + } else { + if (print_headers) + write_header(f); + ret = tail(f, n_units); + } + + return ret; +} + +static void usage(void) +{ + dprintf("==> usage()\n"); + + fprintf(stderr, "Usage: %s [OPTION]... [FILE]...\n", program_name); +} + +static void parse_options(int argc, char *argv[], int *n_lines) +{ + int c; + + dprintf("==> parse_options()\n"); + + while ((c = getopt_long(argc, argv, "hfn:qvV", long_options, NULL)) != -1) { + switch (c) { + case 'f': + forever = 1; + break; + case 'n': + *n_lines = strtol(optarg, NULL, 0); + if (*n_lines < 0) + *n_lines = 0; + break; + case 'q': + print_headers = 0; + break; + case 'v': + print_headers = 1; + break; + case 'V': + fprintf(stdout, "%s %s by Tobias Klauser \n", + program_name, VERSION); + break; + case 'h': + default: + usage(); + } + } +} + +int main(int argc, char *argv[]) +{ + int n_files = 0; + int n_lines = DEFAULT_N_LINES; + struct file_struct *files; + char **filenames; + unsigned int i; + + parse_options(argc, argv, &n_lines); + + /* Do we have some files to read from? */ + if (optind < argc) { + n_files = argc - optind; + filenames = argv + optind; + } else { /* OK, we read from stdin */ + static char *dummy_stdin = "-"; + + n_files = 1; + filenames = &dummy_stdin; + + /* + * POSIX says that -f is ignored if no file operand is specified + * and standard input is a pipe. + */ + if (forever) { + struct stat stats; + /* stdin might be a socket on some systems */ + if ((fstat(STDIN_FILENO, &stats) == 0) + && (S_ISFIFO(stats.st_mode) || S_ISSOCK(stats.st_mode))) + forever = 0; + } + + fprintf(stderr, "Reading from stdin is currently not supported.\n"); + } + + files = malloc(n_files * sizeof(struct file_struct)); + for (i = 0; i < n_files; i++) { + files[i].name = filenames[i]; + tail_file(&files[i], n_lines); + } + + if (forever) + tail_forever(files, n_files); + + free(files); + + exit(EXIT_SUCCESS); +} diff --git a/inotail.c b/inotail.c index b3a9114..f36fd5e 100644 --- a/inotail.c +++ b/inotail.c @@ -1,14 +1,10 @@ /* - * inotail.c - * A fast implementation of GNU tail which uses the inotify-API present in + * simpletail.c + * A fast implementation of tail which uses the inotify-API present in * recent Linux Kernels. * * Copyright (C) 2005-2006, Tobias Klauser * - * Parts of this program are based on GNU tail included in the GNU coreutils - * which is: - * Copyright (C) 1989, 90, 91, 1995-2005 Free Software Foundation, Inc. - * * The idea and some code were taken from turbotail. * * This program is free software; you can redistribute it and/or modify it under @@ -27,363 +23,261 @@ #define _GNU_SOURCE +#include +#include +#include #include #include #include -#include - #include -#include -#include -#include #include -#include -#include -#include - #include "inotify.h" #include "inotify-syscalls.h" #include "inotail.h" -#define VERSION "0.1" +#define VERSION "0.0" -/* XXX: Move all global variables into a struct and use :1 */ - -/* If !=0, read from the ends of all specified files until killed. */ -static unsigned short forever; +#define BUFFER_SIZE 4096 +#define DEFAULT_N_LINES 10 /* Print header with filename before tailing the file? */ -static unsigned short print_headers = 0; - -/* Say my name! */ -static char *program_name = "inotail"; - -static int dump_remainder(const char *filename, int fd, ssize_t n_bytes) -{ - ssize_t written = 0; - - dprintf("==> dump_remainder()\n"); +static short verbose = 0; - if (n_bytes > SSIZE_MAX) - n_bytes = SSIZE_MAX; - - return written; -} - -static char *pretty_name(const struct file_struct *f) +static void usage(void) { - return ((strcmp(f->name, "-") == 0) ? "standard input" : f->name); + fprintf(stderr, "usage: simpletail [-f] [-n ] \n"); + exit(EXIT_FAILURE); } -static void write_header(const struct file_struct *f) +static void write_header(const char *filename) { static unsigned short first_file = 1; - fprintf (stdout, "%s==> %s <==\n", (first_file ? "" : "\n"), pretty_name(f)); + fprintf (stdout, "%s==> %s <==\n", (first_file ? "" : "\n"), filename); first_file = 0; } -static int file_lines(struct file_struct *f, uintmax_t n_lines, off_t start_pos, off_t end_pos) +static off_t lines(int fd, int file_size, unsigned int n_lines) { - char buffer[BUFSIZ]; - size_t bytes_read; - off_t pos = end_pos; + int i; + char buf[BUFFER_SIZE]; + off_t offset = file_size; - dprintf("==> file_lines()\n"); + /* Negative offsets don't make sense here */ + if (offset < 0) + offset = 0; - if (n_lines == 0) - return 1; + n_lines += 1; /* We also count the last \n */ - dprintf(" start_pos: %lld\n", (unsigned long long) start_pos); - dprintf(" end_pos: %lld\n", (unsigned long long) end_pos); + while (offset > 0 && n_lines > 0) { + int rc; + int block_size = BUFFER_SIZE; /* Size of the current block we're reading */ - /* Set `bytes_read' to the size of the last, probably partial, buffer; - * 0 < `bytes_read' <= `BUFSIZ'. - */ - bytes_read = (pos - start_pos) % BUFSIZ; - if (bytes_read == 0) - bytes_read = BUFSIZ; + if (offset < BUFFER_SIZE) + block_size = offset; - dprintf(" bytes_read: %zd\n", bytes_read); + /* Start of current block */ + offset -= block_size; - /* Make `pos' a multiple of `BUFSIZ' (0 if the file is short), so that - * all - * reads will be on block boundaries, which might increase - * efficiency. */ - pos -= bytes_read; + dprintf(" offset: %lu\n", offset); - dprintf(" pos: %lld\n", pos); + lseek(fd, offset, SEEK_SET); - lseek(f->fd, pos, SEEK_SET); - bytes_read = read(f->fd, buffer, bytes_read); - - /* Count the incomplete line on files that don't end with a newline. */ - if (bytes_read && buffer[bytes_read - 1] != '\n') - --n_lines; - - do { - size_t n = bytes_read; - while (n) { - const char *nl; - nl = memrchr(buffer, '\n', n); - if (nl == NULL) - break; - } + rc = read(fd, &buf, block_size); - /* XXX XXX XXX XXX */ + for (i = block_size; i > 0; i--) { + if (buf[i] == '\n') { + dprintf(" Found \\n at position %d\n", i); + n_lines--; - /* Not enough newlines in that buffer. Just print everything */ - if (pos == start_pos) { - lseek(f->fd, start_pos, SEEK_SET); - return 1; + if (n_lines == 0) { + /* We don't want the first \n */ + offset += i + 1; + break; + } + } } - pos -= BUFSIZ; - } while (bytes_read > 0); - - return 1; -} - -static void check_file(struct file_struct *f) -{ - struct stat new_stats; - - dprintf("==> check_file()\n"); + } - dprintf(" checking '%s'\n", f->name); + return offset; } -static int tail_forever(struct file_struct *f, int n_files) +static int tail_file(struct file_struct *f, int n_lines) { - int i_fd, len; - unsigned int i; - struct inotify_event *inev; - char buf[1000]; + int fd; + off_t offset = 0; + char buf[BUFFER_SIZE]; + struct stat finfo; - dprintf("==> tail_forever()\n"); + fd = open(f->name, O_RDONLY); - i_fd = inotify_init(); - if (i_fd < 0) + if (fd < 0) { + perror("open()"); return -1; - - for (i = 0; i < n_files; i++) { - f[i].i_watch = inotify_add_watch(i_fd, f[i].name, IN_ALL_EVENTS | IN_UNMOUNT); - dprintf(" Watch (%d) added to '%s' (%d)\n", f[i].i_watch, f[i].name, i); } - memset(&buf, 0, sizeof(buf)); + if (fstat(fd, &finfo) < 0) { + perror("fstat()"); + return -1; + } - while (1) { - int fd; - ssize_t bytes_tailed = 0; + f->st_size = finfo.st_size; - len = read(i_fd, buf, sizeof(buf)); - inev = (struct inotify_event *) &buf; + offset = lines(fd, f->st_size, n_lines); + dprintf(" offset: %lu.\n", offset); - while (len > 0) { - struct file_struct *fil; + if (verbose) + write_header(f->name); - /* Which file has produced the event? */ - for (i = 0; i < n_files; i++) { - if (!f[i].ignore && f[i].fd >= 0 && f[i].i_watch == inev->wd) { - fil = &f[i]; - break; - } - } - - /* We should at least catch the following - * events: - * - IN_MODIFY, thats what we hopefully get - * most of the time - * - IN_ATTRIB, still readable? - * - IN_MOVE, reopen the file at the new - * position? - * - IN_DELETE_SELF, we need to check if the - * file is still there or is really gone - * - IN_MOVE_SELF, ditto - * - IN_UNMOUNT, die gracefully - */ - if (inev->mask & IN_MODIFY) { - dprintf(" File '%s' modified.\n", fil->name); - check_file(fil); - /* Dump new content */ - } else if (inev->mask & IN_ATTRIB) { - dprintf(" File '%s' attributes changed.\n", fil->name); - check_file(fil); - } else if (inev->mask & IN_MOVE) { - } else if (inev->mask & IN_DELETE_SELF) { - dprintf(" File '%s' possibly deleted.\n", fil->name); - check_file(fil); - } else { - /* Ignore */ - } - - /* Shift one event forward */ - len -= sizeof(struct inotify_event) + inev->len; - inev = (struct inotify_event *) ((char *) inev + sizeof(struct inotify_event) + inev->len); - } + lseek(fd, offset, SEEK_SET); + while (read(fd, &buf, BUFFER_SIZE) != 0) { + write(STDOUT_FILENO, buf, f->st_size - offset); } - /* XXX: Never reached. Catch SIGINT and handle it there? */ - for (i = 0; i < n_files; i++) - inotify_rm_watch(i_fd, f[i].i_watch); + close(fd); return 0; } -static int tail_lines(struct file_struct *f, uintmax_t n_lines) +static int watch_file(struct file_struct *f) { - struct stat stats; - off_t start_pos = -1; - off_t end_pos; + int ifd, watch; + off_t offset; + struct inotify_event *inev; + char buf[BUFFER_SIZE]; - dprintf("==> tail_lines()\n"); + dprintf(">> Watching %s\n", filename); - if (fstat(f->fd, &stats)) { - perror("fstat()"); - exit(EXIT_FAILURE); + ifd = inotify_init(); + if (ifd < 0) { + perror("inotify_init()"); + exit(-2); } - start_pos = lseek(f->fd, 0, SEEK_CUR); - end_pos = lseek(f->fd, 0, SEEK_END); + watch = inotify_add_watch(ifd, f->name, IN_MODIFY|IN_DELETE_SELF|IN_MOVE_SELF|IN_UNMOUNT); - /* Use file_lines only if FD refers to a regular file for - * which lseek(... SEEK_END) worked. - */ - if (S_ISREG(stats.st_mode) && start_pos != -1 && start_pos < end_pos) { - if (end_pos != 0 && !file_lines(f, n_lines, start_pos, end_pos)) - return -1; - } else { - /* Under very unlikely circumstances, it is possible to reach - this point after positioning the file pointer to end of file - via the `lseek (...SEEK_END)' above. In that case, reposition - the file pointer back to start_pos before calling pipe_lines. */ -/* if (start_pos != -1) - xlseek (fd, start_pos, SEEK_SET, pretty_filename); - - return pipe_lines (pretty_filename, fd, n_lines, read_pos); -*/ - } + memset(&buf, 0, sizeof(buf)); - return 0; -} + while (1) { + int len; -static int tail(struct file_struct *f, uintmax_t n_units) -{ - dprintf("==> tail()\n"); + len = read(ifd, buf, sizeof(buf)); + inev = (struct inotify_event *) &buf; - return tail_lines(f, n_units); -} + while (len > 0) { + if (inev->mask & IN_MODIFY) { + int ffd, block_size; + char fbuf[BUFFER_SIZE]; + struct stat finfo; -static int tail_file(struct file_struct *f, uintmax_t n_units) -{ - int ret = 0; + offset = f->st_size; - dprintf("==> tail_file()\n"); + dprintf(" File '%s' modified.\n", filename); + dprintf(" offset: %lu.\n", offset); - if (strcmp(f->name, "-") == 0) { - f->fd = STDIN_FILENO; - } else { - f->fd = open(f->name, O_RDONLY); - } + ffd = open(f->name, O_RDONLY); + if (fstat(ffd, &finfo) < 0) { + perror("fstat()"); + return -1; + } - if (f->fd == -1) { - perror("open()"); - } else { - if (print_headers) - write_header(f); - ret = tail(f, n_units); - } + f->st_size = finfo.st_size; + block_size = f->st_size - offset; - return ret; -} + if (block_size < 0) + block_size = 0; -static void usage(void) -{ - dprintf("==> usage()\n"); + /* XXX: Dirty hack for now to make sure + * block_size doesn't get bigger than + * BUFFER_SIZE + */ + if (block_size > BUFFER_SIZE) + block_size = BUFFER_SIZE; + + lseek(ffd, offset, SEEK_SET); + while (read(ffd, &fbuf, block_size) != 0) { + write(STDOUT_FILENO, fbuf, block_size); + } + + + close(ffd); + } - fprintf(stderr, "Usage: %s [OPTION]... [FILE]...\n", program_name); + if (inev->mask & IN_DELETE_SELF) { + dprintf(" File '%s' deleted.\n", filename); + return -1; + } + if (inev->mask & IN_MOVE_SELF) { + dprintf(" File '%s' moved.\n", filename); + return -1; + } + if (inev->mask & IN_UNMOUNT) { + dprintf(" Device containing file '%s' unmounted.\n", filename); + return -1; + } + + len -= sizeof(struct inotify_event) + inev->len; + inev = (struct inotify_event *) ((char *) inev + sizeof(struct inotify_event) + inev->len); + } + } } -static void parse_options(int argc, char *argv[], int *n_lines) +int main(int argc, char **argv) { - int c; + int i, opt, ret = 0; + int n_files = 0; + int n_lines = DEFAULT_N_LINES; + short forever = 0; + char **filenames; + struct file_struct *files; - dprintf("==> parse_options()\n"); + if (argc < 2) + usage(); - while ((c = getopt_long(argc, argv, "hfn:qvV", long_options, NULL)) != -1) { - switch (c) { - case 'f': + for (opt = 1; (opt < argc) && (argv[opt][0] == '-'); opt++) { + switch (argv[opt][1]) { + case 'f': forever = 1; break; case 'n': - *n_lines = strtol(optarg, NULL, 0); - if (*n_lines < 0) - *n_lines = 0; - break; - case 'q': - print_headers = 0; + n_lines = strtoul(argv[++opt], NULL, 0); + if (n_lines < 0) + n_lines = 0; break; case 'v': - print_headers = 1; + verbose = 1; break; case 'V': - fprintf(stdout, "%s %s by Tobias Klauser \n", - program_name, VERSION); - break; + fprintf(stderr, "simpletail %s\n", VERSION); + return 0; case 'h': - default: + default: usage(); - } - } -} - -int main(int argc, char *argv[]) -{ - int n_files = 0; - int n_lines = DEFAULT_N_LINES; - struct file_struct *files; - char **filenames; - unsigned int i; - - parse_options(argc, argv, &n_lines); + break; + } + } /* Do we have some files to read from? */ - if (optind < argc) { - n_files = argc - optind; - filenames = argv + optind; - } else { /* OK, we read from stdin */ - static char *dummy_stdin = "-"; - - n_files = 1; - filenames = &dummy_stdin; - - /* - * POSIX says that -f is ignored if no file operand is specified - * and standard input is a pipe. - */ - if (forever) { - struct stat stats; - /* stdin might be a socket on some systems */ - if ((fstat(STDIN_FILENO, &stats) == 0) - && (S_ISFIFO(stats.st_mode) || S_ISSOCK(stats.st_mode))) - forever = 0; - } - - fprintf(stderr, "Reading from stdin is currently not supported.\n"); + if (opt < argc) { + n_files = argc - opt; + filenames = argv + opt; + } else { + usage(); + return -1; } files = malloc(n_files * sizeof(struct file_struct)); for (i = 0; i < n_files; i++) { files[i].name = filenames[i]; - tail_file(&files[i], n_lines); + ret &= tail_file(&files[i], n_lines); } if (forever) - tail_forever(files, n_files); + ret = watch_file(&files[0]); free(files); - exit(EXIT_SUCCESS); + return ret; } diff --git a/inotail.h b/inotail.h index 50cf329..226c1ca 100644 --- a/inotail.h +++ b/inotail.h @@ -10,12 +10,12 @@ /* Every tailed file is represented as a file_struct */ struct file_struct { - char *name; /* Name of file (or '-' for stdin) */ - int fd; /* File descriptor (or -1 if file is not open */ - int ignore:1; /* Ignore file? */ + char *name; /* Name of file (or '-' for stdin) */ + int fd; /* File descriptor (or -1 if file is not open */ + int ignore:1; /* Ignore file? */ + off_t st_size; /* File size */ int i_watch; /* Inotify watch associated with file_struct */ - }; struct option const long_options[] = { diff --git a/simpletail.c b/simpletail.c deleted file mode 100644 index 83ce280..0000000 --- a/simpletail.c +++ /dev/null @@ -1,263 +0,0 @@ -/* - * simpletail.c - * A fast implementation of tail which uses the inotify-API present in - * recent Linux Kernels. - * - * Copyright (C) 2005-2006, Tobias Klauser - * - * The idea and some code were taken from turbotail. - * - * This program is free software; you can redistribute it and/or modify it under - * the terms of the GNU General Public License as published by the Free Software - * Foundation; either version 2, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License along with - * this program; if not, write to the Free Software Foundation, Inc., 51 - * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#define _GNU_SOURCE - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "inotify.h" -#include "inotify-syscalls.h" - -#include "inotail.h" - -#define VERSION "0.0" - -#define BUFFER_SIZE 4096 -#define DEFAULT_N_LINES 10 - -/* Print header with filename before tailing the file? */ -static short verbose = 0; - -static void usage(void) -{ - fprintf(stderr, "usage: simpletail [-f] [-n ] \n"); - exit(EXIT_FAILURE); -} - -static void write_header(const char *filename) -{ - static unsigned short first_file = 1; - - fprintf (stdout, "%s==> %s <==\n", (first_file ? "" : "\n"), filename); - first_file = 0; -} - -static off_t lines(int fd, int file_size, unsigned int n_lines) -{ - int i; - char buf[BUFFER_SIZE]; - off_t offset = file_size; - - /* Negative offsets don't make sense here */ - if (offset < 0) - offset = 0; - - n_lines += 1; /* We also count the last \n */ - - while (offset > 0 && n_lines > 0) { - int rc; - int block_size = BUFFER_SIZE; /* Size of the current block we're reading */ - - if (offset < BUFFER_SIZE) - block_size = offset; - - /* Start of current block */ - offset -= block_size; - - dprintf(" offset: %lu\n", offset); - - lseek(fd, offset, SEEK_SET); - - rc = read(fd, &buf, block_size); - - for (i = block_size; i > 0; i--) { - if (buf[i] == '\n') { - dprintf(" Found \\n at position %d\n", i); - n_lines--; - - if (n_lines == 0) { - /* We don't want the first \n */ - offset += i + 1; - break; - } - } - } - } - - return offset; -} - -static int watch_file(const char *filename, off_t offset) -{ - int ifd, watch; - struct inotify_event *inev; - char buf[BUFFER_SIZE]; - - dprintf(">> Watching %s\n", filename); - - ifd = inotify_init(); - if (ifd < 0) { - perror("inotify_init()"); - exit(-2); - } - - watch = inotify_add_watch(ifd, filename, IN_MODIFY|IN_DELETE_SELF|IN_MOVE_SELF|IN_UNMOUNT); - - memset(&buf, 0, sizeof(buf)); - - while (1) { - int len; - - len = read(ifd, buf, sizeof(buf)); - inev = (struct inotify_event *) &buf; - - while (len > 0) { - if (inev->mask & IN_MODIFY) { - int ffd, block_size; - char fbuf[BUFFER_SIZE]; - struct stat finfo; - - dprintf(" File '%s' modified.\n", filename); - dprintf(" offset: %lu.\n", offset); - - ffd = open(filename, O_RDONLY); - if (fstat(ffd, &finfo) < 0) { - perror("fstat()"); - return -1; - } - - block_size = finfo.st_size - offset; - - if (block_size < 0) - block_size = 0; - - /* XXX: Dirty hack for now to make sure - * block_size doesn't get bigger than - * BUFFER_SIZE - */ - if (block_size > BUFFER_SIZE) - block_size = BUFFER_SIZE; - - lseek(ffd, offset, SEEK_SET); - while (read(ffd, &fbuf, block_size) != 0) { - write(STDOUT_FILENO, fbuf, block_size); - } - - offset = finfo.st_size; - - close(ffd); - } - - if (inev->mask & IN_DELETE_SELF) { - dprintf(" File '%s' deleted.\n", filename); - return -1; - } - if (inev->mask & IN_MOVE_SELF) { - dprintf(" File '%s' moved.\n", filename); - return -1; - } - if (inev->mask & IN_UNMOUNT) { - dprintf(" Device containing file '%s' unmounted.\n", filename); - return -1; - } - - len -= sizeof(struct inotify_event) + inev->len; - inev = (struct inotify_event *) ((char *) inev + sizeof(struct inotify_event) + inev->len); - } - } -} - -int main(int argc, char **argv) -{ - int opt, fd, ret = 0; - int n_files = 0; - int n_lines = DEFAULT_N_LINES; - short forever = 0; - char **filenames; - char buf[BUFFER_SIZE]; - struct file_struct *files; - struct stat finfo; - off_t offset = 0; - - if (argc < 2) - usage(); - - for (opt = 1; (opt < argc) && (argv[opt][0] == '-'); opt++) { - switch (argv[opt][1]) { - case 'f': - forever = 1; - break; - case 'n': - n_lines = strtoul(argv[++opt], NULL, 0); - if (n_lines < 0) - n_lines = 0; - break; - case 'v': - verbose = 1; - break; - case 'V': - fprintf(stderr, "simpletail %s\n", VERSION); - return 0; - case 'h': - default: - usage(); - break; - } - } - - /* Do we have some files to read from? */ - if (opt < argc) { - n_files = argc - opt; - filenames = argv + opt; - } else { - usage(); - return -1; - } - - fd = open(*filenames, O_RDONLY); - - if (fd < 0) { - perror("open()"); - return -1; - } - - if (fstat(fd, &finfo) < 0) { - perror("fstat()"); - return -1; - } - - offset = lines(fd, finfo.st_size, n_lines); - dprintf(" offset: %lu.\n", offset); - - if (verbose) - write_header(*filenames); - - lseek(fd, offset, SEEK_SET); - while (read(fd, &buf, BUFFER_SIZE) != 0) { - write(STDOUT_FILENO, buf, finfo.st_size - offset); - } - - close(fd); - - if (forever) - ret = watch_file(*filenames, finfo.st_size); - - return ret; -} -- cgit v1.2.3-54-g00ecf