From 135b557f6d14c3ce4d60419602d8ca92316f9a71 Mon Sep 17 00:00:00 2001 From: Tobias Klauser Date: Mon, 13 Mar 2006 18:16:41 +0100 Subject: Initial import --- .gitignore | 1 + Makefile | 27 ++++ generate_text.pl | 12 ++ inotail.c | 399 +++++++++++++++++++++++++++++++++++++++++++++++++++++ inotail.h | 37 +++++ inotify-syscalls.h | 61 ++++++++ inotify-watchdir.c | 89 ++++++++++++ inotify.h | 64 +++++++++ simpletail.c | 178 ++++++++++++++++++++++++ 9 files changed, 868 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100755 generate_text.pl create mode 100644 inotail.c create mode 100644 inotail.h create mode 100644 inotify-syscalls.h create mode 100644 inotify-watchdir.c create mode 100644 inotify.h create mode 100644 simpletail.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c7ec943 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +test.log diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..74d549b --- /dev/null +++ b/Makefile @@ -0,0 +1,27 @@ +CC := gcc +CFLAGS := -Wall -D_USE_SOURCE + +DEBUG = false + +ifeq ($(strip $(DEBUG)),true) + CFLAGS += -g -DDEBUG +endif + +PROGRAMS := inotail inotail-simple inotify-watchdir simpletail + +all: $(PROGRAMS) + +inotail: inotail.o + +inotail-simple: inotail-simple.o + +inotify-watchdir: inotify-watchdir.o + +simpletail: simpletail.o + +%.o: %.c + $(CC) $(CFLAGS) -c $< -o $@ + +clean: + rm -f *.o + rm -f $(PROGRAMS) diff --git a/generate_text.pl b/generate_text.pl new file mode 100755 index 0000000..845fb44 --- /dev/null +++ b/generate_text.pl @@ -0,0 +1,12 @@ +#!/usr/bin/perl + +$lines = $ARGV[0]; +$chars = $ARGV[1]; + +die unless $lines; +die unless $chars; + +while ($lines--) { + print int(rand(9)) x $chars; + print "\n"; +} diff --git a/inotail.c b/inotail.c new file mode 100644 index 0000000..2d8c00e --- /dev/null +++ b/inotail.c @@ -0,0 +1,399 @@ +/* + * 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"; + +void error(char *message, ...) +{ + va_list args; + + va_start(args, message); + fprintf(stderr, "%s: ", program_name); + vfprintf(stderr, message, args); + va_end(args); + fprintf(stderr, " (errno: %d=%s)\n", errno, strerror(errno)); + + exit(EXIT_FAILURE); +} + +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 void usage(void) +{ + fprintf(stderr, "Usage: %s [OPTION]... [FILE]...\n", program_name); +} + +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, ret = -1; + unsigned int i; + int last; + char buf[1000]; + struct inotify_event *inev; + + dprintf("==> tail_forever()\n"); + + i_fd = inotify_init(); + if (i_fd < 0) + return ret; + + 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)); + + last = n_files - 1; + + 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); + } 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 */ + } + + 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++) + ret = inotify_rm_watch(i_fd, f[i].i_watch); + + return ret; +} + +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)) { + error("fstat '%s' failed!\n", f->name); + return -1; + } + + 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) { + error(" Failed to open file '%s'\n", f->name); + } else { + if (print_headers) + write_header(f); + ret = tail(f, n_units); + } + + return ret; +} + +static void parse_options(int argc, char *argv[], int *n_lines) +{ + int c; + + 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); + + dprintf("n_lines: %d\n", n_lines); + return -1; + + /* 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; + } + + printf(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.h b/inotail.h new file mode 100644 index 0000000..50cf329 --- /dev/null +++ b/inotail.h @@ -0,0 +1,37 @@ +#ifndef _INOTAIL_H +#define _INOTAIL_H + +#include +#include +#include + +/* Number of items to tail. */ +#define DEFAULT_N_LINES 10 + +/* 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? */ + + int i_watch; /* Inotify watch associated with file_struct */ + +}; + +struct option const long_options[] = { + {"lines", required_argument, NULL, 'n'}, + {"quiet", no_argument, NULL, 'q'}, + {"silent", no_argument, NULL, 'q'}, + {"verbose", no_argument, NULL, 'v'}, + {"help", no_argument, NULL, 'h'}, + {"version", no_argument, NULL, 'V'}, + {NULL, 0, NULL, 0} +}; + +#ifdef DEBUG +#define dprintf(fmt, args...) fprintf(stderr, fmt, ##args) +#else +#define dprintf(fmt, args...) +#endif /* DEBUG */ + +#endif /* _INOTAIL_H */ diff --git a/inotify-syscalls.h b/inotify-syscalls.h new file mode 100644 index 0000000..1431d46 --- /dev/null +++ b/inotify-syscalls.h @@ -0,0 +1,61 @@ +#ifndef _LINUX_INOTIFY_SYSCALLS_H +#define _LINUX_INOTIFY_SYSCALLS_H + +#include + +#if defined(__i386__) +# define __NR_inotify_init 291 +# define __NR_inotify_add_watch 292 +# define __NR_inotify_rm_watch 293 +#elif defined(__x86_64__) +# define __NR_inotify_init 253 +# define __NR_inotify_add_watch 254 +# define __NR_inotify_rm_watch 255 +#elif defined(__powerpc__) || defined(__powerpc64__) +# define __NR_inotify_init 275 +# define __NR_inotify_add_watch 276 +# define __NR_inotify_rm_watch 277 +#elif defined (__ia64__) +# define __NR_inotify_init 1277 +# define __NR_inotify_add_watch 1278 +# define __NR_inotify_rm_watch 1279 +#elif defined (__s390__) +# define __NR_inotify_init 284 +# define __NR_inotify_add_watch 285 +# define __NR_inotify_rm_watch 286 +#elif defined (__alpha__) +# define __NR_inotify_init 444 +# define __NR_inotify_add_watch 445 +# define __NR_inotify_rm_watch 446 +#elif defined (__sparc__) || defined (__sparc64__) +# define __NR_inotify_init 151 +# define __NR_inotify_add_watch 152 +# define __NR_inotify_rm_watch 156 +#elif defined (__arm__) +# define __NR_inotify_init 316 +# define __NR_inotify_add_watch 317 +# define __NR_inotify_rm_watch 318 +#elif defined (__sh__) +# define __NR_inotify_init 290 +# define __NR_inotify_add_watch 291 +# define __NR_inotify_rm_watch 292 +#else +# error "Unsupported architecture!" +#endif + +static inline int inotify_init (void) +{ + return syscall (__NR_inotify_init); +} + +static inline int inotify_add_watch (int fd, const char *name, __u32 mask) +{ + return syscall (__NR_inotify_add_watch, fd, name, mask); +} + +static inline int inotify_rm_watch (int fd, __u32 wd) +{ + return syscall (__NR_inotify_rm_watch, fd, wd); +} + +#endif /* _LINUX_INOTIFY_SYSCALLS_H */ diff --git a/inotify-watchdir.c b/inotify-watchdir.c new file mode 100644 index 0000000..f34e03a --- /dev/null +++ b/inotify-watchdir.c @@ -0,0 +1,89 @@ +/* + * initofy-watchdir.c + * + */ + +#include +#include +#include +#include +#include + +#include "inotify.h" +#include "inotify-syscalls.h" + +static void print_event(int mask) +{ + if (mask & IN_ISDIR) + printf("(dir) "); + else + printf("(file) "); + + if (mask & IN_ACCESS) + printf("ACCESS "); + if (mask & IN_MODIFY) + printf("MODIFY "); + if (mask & IN_ATTRIB) + printf("ATTRIB "); + if (mask & IN_CLOSE) + printf("CLOSE "); + if (mask & IN_OPEN) + printf("OPEN "); + if (mask & IN_MOVED_FROM) + printf("MOVED_FROM "); + if (mask & IN_MOVED_TO) + printf("MOVED_TO "); + if (mask & IN_MOVE_SELF) + printf("MOVE_SELF "); + if (mask & IN_DELETE) + printf("DELETE "); + if (mask & IN_CREATE) + printf("CREATE "); + if (mask & IN_DELETE_SELF) + printf("DELETE_SELF "); + if (mask & IN_UNMOUNT) + printf("UNMOUNT "); + if (mask & IN_Q_OVERFLOW) + printf("Q_OVERFLOW "); + if (mask & IN_IGNORED) + printf("IGNORED " ); + + printf("(0x%08x), ", mask); +} + +int main(int argc, char *argv[]) +{ + int fd, watch, len, ret; + struct inotify_event *inev; + char buf[1000]; + + if (argc != 2) { + printf("Usage: %s \n", argv[0]); + exit(-1); + } + + fd = inotify_init(); + if (fd < 0) + exit(-2); + + watch = inotify_add_watch(fd, argv[1], IN_ALL_EVENTS|IN_UNMOUNT); + + memset(&buf, 0, sizeof(buf)); + + while (1) { + len = read(fd, buf, sizeof(buf)); + inev = (struct inotify_event *) &buf; + while (len > 0) { + printf("wd=%04x, ", inev->wd); + print_event(inev->mask); + printf("cookie=%04x, len=%04x, name=\"%s\"\n", inev->cookie, inev->len, inev->name); + + len -= sizeof(struct inotify_event) + inev->len; + inev = (struct inotify_event *) ((char *) inev + sizeof(struct inotify_event) + inev->len); + } + } + + ret = inotify_rm_watch(fd, watch); + + return ret; +} diff --git a/inotify.h b/inotify.h new file mode 100644 index 0000000..e176971 --- /dev/null +++ b/inotify.h @@ -0,0 +1,64 @@ +/* + * Inode based directory notification for Linux + * + * Copyright (C) 2005 John McCutchan + */ + +#ifndef _LINUX_INOTIFY_H +#define _LINUX_INOTIFY_H + +#include + +/* + * struct inotify_event - structure read from the inotify device for each event + * + * When you are watching a directory, you will receive the filename for events + * such as IN_CREATE, IN_DELETE, IN_OPEN, IN_CLOSE, ..., relative to the wd. + */ +struct inotify_event { + __s32 wd; /* watch descriptor */ + __u32 mask; /* watch mask */ + __u32 cookie; /* cookie to synchronize two events */ + __u32 len; /* length (including nulls) of name */ + char name[0]; /* stub for possible name */ +}; + +/* the following are legal, implemented events that user-space can watch for */ +#define IN_ACCESS 0x00000001 /* File was accessed */ +#define IN_MODIFY 0x00000002 /* File was modified */ +#define IN_ATTRIB 0x00000004 /* Metadata changed */ +#define IN_CLOSE_WRITE 0x00000008 /* Writtable file was closed */ +#define IN_CLOSE_NOWRITE 0x00000010 /* Unwrittable file closed */ +#define IN_OPEN 0x00000020 /* File was opened */ +#define IN_MOVED_FROM 0x00000040 /* File was moved from X */ +#define IN_MOVED_TO 0x00000080 /* File was moved to Y */ +#define IN_CREATE 0x00000100 /* Subfile was created */ +#define IN_DELETE 0x00000200 /* Subfile was deleted */ +#define IN_DELETE_SELF 0x00000400 /* Self was deleted */ +#define IN_MOVE_SELF 0x00000800 /* Self was moved */ + +/* the following are legal events. they are sent as needed to any watch */ +#define IN_UNMOUNT 0x00002000 /* Backing fs was unmounted */ +#define IN_Q_OVERFLOW 0x00004000 /* Event queued overflowed */ +#define IN_IGNORED 0x00008000 /* File was ignored */ + +/* helper events */ +#define IN_CLOSE (IN_CLOSE_WRITE | IN_CLOSE_NOWRITE) /* close */ +#define IN_MOVE (IN_MOVED_FROM | IN_MOVED_TO) /* moves */ + +/* special flags */ +#define IN_MASK_ADD 0x20000000 /* add to the mask of an already existing watch */ +#define IN_ISDIR 0x40000000 /* event occurred against dir */ +#define IN_ONESHOT 0x80000000 /* only send event once */ + +/* + * All of the events - we build the list by hand so that we can add flags in + * the future and not break backward compatibility. Apps will get only the + * events that they originally wanted. Be sure to add new events here! + */ +#define IN_ALL_EVENTS (IN_ACCESS | IN_MODIFY | IN_ATTRIB | IN_CLOSE_WRITE | \ + IN_CLOSE_NOWRITE | IN_OPEN | IN_MOVED_FROM | \ + IN_MOVED_TO | IN_DELETE | IN_CREATE | IN_DELETE_SELF | \ + IN_MOVE_SELF) + +#endif /* _LINUX_INOTIFY_H */ diff --git a/simpletail.c b/simpletail.c new file mode 100644 index 0000000..b5df235 --- /dev/null +++ b/simpletail.c @@ -0,0 +1,178 @@ +/* + * simpletail.c + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "inotify.h" +#include "inotify-syscalls.h" + +#include "inotail.h" + +#define BUFFER_SIZE 4096 + +off_t lines(int fd, int file_size, unsigned int nr_lines) +{ + int i; + char buf[BUFFER_SIZE]; + off_t offset = file_size; + + /* Negative offsets don't make sense here */ + if (offset < 0) + offset = 0; + + while (offset > 0 && nr_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); + nr_lines--; + + if (nr_lines == 0) + break; + } + } + } + + if (nr_lines == 0) + offset += i + 1; /* We don't want the first \n */ + + return offset; +} + +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; + } + + /* XXX: block_size could be bigger than BUFFER_SIZE */ + block_size = finfo.st_size - offset; + if (block_size < 0) + block_size = 0; + + lseek(ffd, offset, SEEK_SET); + while (read(ffd, &fbuf, BUFFER_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 fd; + int nr_lines = 0; + int ret = 0; + struct stat finfo; + char buf[BUFFER_SIZE]; + off_t offset = 0; + + if (argc < 3) { + fprintf(stderr, "%s \n", argv[0]); + return -1; + } + + nr_lines = strtol(argv[1], NULL, 0) + 1; + fd = open(argv[2], O_RDONLY); + + if (fd < 0) { + perror("open()"); + return -1; + } + + if (fstat(fd, &finfo) < 0) { + perror("fstat()"); + return -1; + } + + offset = lines(fd, finfo.st_size, nr_lines); + dprintf(" offset: %lu.\n", offset); + + lseek(fd, offset, SEEK_SET); + while (read(fd, &buf, BUFFER_SIZE) != 0) { + write(STDOUT_FILENO, buf, finfo.st_size - offset); + } + + close(fd); + + if (argv[3]) + ret = watch_file(argv[2], finfo.st_size); + + return ret; +} -- cgit v1.2.3-54-g00ecf