summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--Makefile27
-rwxr-xr-xgenerate_text.pl12
-rw-r--r--inotail.c399
-rw-r--r--inotail.h37
-rw-r--r--inotify-syscalls.h61
-rw-r--r--inotify-watchdir.c89
-rw-r--r--inotify.h64
-rw-r--r--simpletail.c178
9 files changed, 868 insertions, 0 deletions
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 <tklauser@access.unizh.ch>
+ *
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdarg.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <inttypes.h>
+#include <string.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/syscall.h>
+
+#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 <tklauser@access.unizh.ch>\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 <limits.h>
+#include <getopt.h>
+#include <stdlib.h>
+
+/* 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 <sys/syscall.h>
+
+#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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/syscall.h>
+
+#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 <path>\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 <linux/types.h>
+
+/*
+ * 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 <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+
+#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 <nr-lines> <file> <forever?>\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;
+}