summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTobias Klauser <tklauser@xenon.tklauser.home>2006-05-11 15:31:33 +0200
committerTobias Klauser <tklauser@xenon.tklauser.home>2006-05-11 15:31:33 +0200
commit818bf75711938460f43815cfcca8dab16a93ac97 (patch)
tree8ead1222ba3f11eeda6f44dea46de6d6783f4ff6
parent1ad0b7abeb5ac5cd310f5c9ad32d3e1af42bdc78 (diff)
simpletail is the new inotail
-rw-r--r--Makefile10
-rw-r--r--inotail-old.c389
-rw-r--r--inotail.c428
-rw-r--r--inotail.h8
-rw-r--r--simpletail.c263
5 files changed, 560 insertions, 538 deletions
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 <tklauser@distanz.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";
+
+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 <tklauser@distanz.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);
+
+ /* 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 <tklauser@distanz.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
@@ -27,363 +23,261 @@
#define _GNU_SOURCE
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
#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"
+#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 <nr-lines>] <file>\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 <tklauser@distanz.ch>\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 <tklauser@distanz.ch>
- *
- * 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 <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 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 <nr-lines>] <file>\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;
-}