]> git.ipfire.org Git - thirdparty/util-linux.git/blobdiff - text-utils/tailf.c
tailf: inotify based reimplementation
[thirdparty/util-linux.git] / text-utils / tailf.c
index f4616ccb881ae88e51fbc83ac40686e049223df0..e30e22f8828cdedc4be21b704236fed900a0adf4 100644 (file)
@@ -19,7 +19,7 @@
  * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
  * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
  * OTHER DEALINGS IN THE SOFTWARE.
- * 
+ *
  * less -F and tail -f cause a disk access every five seconds.  This
  * program avoids this problem by waiting for the file size to change.
  * Hence, the file is not accessed, and the access time does not need to be
 #include <unistd.h>
 #include <malloc.h>
 #include <string.h>
+#include <sys/types.h>
 #include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
 #include <ctype.h>
+#include <errno.h>
 #include <err.h>
+#include <sys/inotify.h>
 #include "nls.h"
 
 #define DEFAULT_LINES  10
 
-static size_t filesize(const char *filename)
+static void
+tailf(const char *filename, int lines)
 {
-    struct stat sb;
+       char *buf, *p;
+       int  head = 0;
+       int  tail = 0;
+       FILE *str;
+       int  i;
+
+       if (!(str = fopen(filename, "r")))
+               err(EXIT_FAILURE, _("cannot open \"%s\" for read"), filename);
+
+       buf = malloc(lines * BUFSIZ);
+       p = buf;
+       while (fgets(p, BUFSIZ, str)) {
+               if (++tail >= lines) {
+                       tail = 0;
+                       head = 1;
+               }
+               p = buf + (tail * BUFSIZ);
+       }
+
+       if (head) {
+               for (i = tail; i < lines; i++)
+                       fputs(buf + (i * BUFSIZ), stdout);
+               for (i = 0; i < tail; i++)
+                       fputs(buf + (i * BUFSIZ), stdout);
+       } else {
+               for (i = head; i < tail; i++)
+                       fputs(buf + (i * BUFSIZ), stdout);
+       }
 
-    if (!stat(filename, &sb)) return sb.st_size;
-    return 0;
+       fflush(stdout);
+       free(buf);
+       fclose(str);
 }
 
-static void tailf(const char *filename, int lines)
+static void
+roll_file(const char *filename, off_t *size)
 {
-    char **buffer;
-    int  head = 0;
-    int  tail = 0;
-    FILE *str;
-    int  i;
-
-    if (!(str = fopen(filename, "r")))
-       err(1, _("cannot open \"%s\" for read"), filename);
-
-    buffer = malloc(lines * sizeof(*buffer));
-    for (i = 0; i < lines; i++) buffer[i] = malloc(BUFSIZ + 1);
-
-    while (fgets(buffer[tail], BUFSIZ, str)) {
-       if (++tail >= lines) {
-           tail = 0;
-           head = 1;
+       char buf[BUFSIZ];
+       int fd;
+       struct stat st;
+
+       if (!(fd = open(filename, O_RDONLY)))
+               err(EXIT_FAILURE, _("cannot open \"%s\" for read"), filename);
+
+       if (fstat(fd, &st) == -1)
+               err(EXIT_FAILURE, _("cannot stat \"%s\""), filename);
+
+       if (st.st_size == *size) {
+               close(fd);
+               return;
        }
-    }
 
-    if (head) {
-       for (i = tail; i < lines; i++) fputs(buffer[i], stdout);
-       for (i = 0; i < tail; i++)     fputs(buffer[i], stdout);
-    } else {
-       for (i = head; i < tail; i++)  fputs(buffer[i], stdout);
-    }
-    fflush(stdout);
+       if (lseek(fd, *size, SEEK_SET) != (off_t)-1) {
+               ssize_t rc, wc;
 
-    for (i = 0; i < lines; i++) free(buffer[i]);
-    free(buffer);
+               while ((rc = read(fd, buf, sizeof(buf))) > 0) {
+                       wc = write(STDOUT_FILENO, buf, rc);
+                       if (rc != wc)
+                               warnx(_("incomplete write to \"%s\" (written %ld, expected %ld)\n"),
+                                       filename, wc, rc);
+               }
+               fflush(stdout);
+       }
+       close(fd);
+       *size = st.st_size;
+}
 
-    fclose(str);
+static void
+watch_file(const char *filename, off_t *size)
+{
+       do {
+               roll_file(filename, size);
+               usleep(250000);
+       } while(1);
 }
 
-int main(int argc, char **argv)
+
+#ifdef HAVE_INOTIFY_INIT
+
+#define EVENTS         (IN_MODIFY|IN_DELETE_SELF|IN_MOVE_SELF|IN_UNMOUNT)
+#define NEVENTS                4
+
+static int
+watch_file_inotify(const char *filename, off_t *size)
 {
-    char       buffer[BUFSIZ];
-    size_t     osize, nsize;
-    FILE       *str;
-    const char *filename;
-    int        count, wcount;
-    int        lines = DEFAULT_LINES;
-
-    setlocale(LC_ALL, "");
-    bindtextdomain(PACKAGE, LOCALEDIR);
-    textdomain(PACKAGE);
-
-    argc--;
-    argv++;
-
-    for (; argc > 0 && argv[0][0] == '-'; argc--, argv++) {
-       if (!strcmp(*argv, "-n") || !strcmp(*argv, "--lines")) {
-           argc--;
-           argv++;
-           if (argc > 0 && (lines = atoi(argv[0])) <= 0)
-               errx(EXIT_FAILURE, _("invalid number of lines"));
+       char buf[ NEVENTS * sizeof(struct inotify_event) ];
+       int fd, ffd, e;
+       ssize_t len;
+
+       fd = inotify_init();
+       if (fd == -1)
+               return 0;
+
+       ffd = inotify_add_watch(fd, filename, EVENTS);
+       while (ffd >= 0) {
+               len = read(fd, buf, sizeof(buf));
+               if (len < 0 && (errno == EINTR || errno == EAGAIN))
+                       continue;
+               if (len < 0)
+                       err(EXIT_FAILURE, "%s: cannot read inotify events", filename);
+
+               for (e = 0; e < len; ) {
+                       struct inotify_event *ev = (struct inotify_event *) &buf[e];
+
+                       if (ev->mask & IN_MODIFY)
+                               roll_file(filename, size);
+                       else {
+                               close(ffd);
+                               ffd = -1;
+                               break;
+                       }
+                       e += sizeof(struct inotify_event) + ev->len;
+               }
        }
-       else if (isdigit(argv[0][1])) {
-           if ((lines = atoi(*argv + 1)) <= 0)
-               errx(EXIT_FAILURE, _("invalid number of lines"));
+       close(fd);
+       return 1;
+}
+
+#endif /* HAVE_INOTIFY_INIT */
+
+int main(int argc, char **argv)
+{
+       const char *filename;
+       int lines = DEFAULT_LINES;
+       struct stat st;
+       off_t size = 0;
+
+       setlocale(LC_ALL, "");
+       bindtextdomain(PACKAGE, LOCALEDIR);
+       textdomain(PACKAGE);
+
+       argc--;
+       argv++;
+
+       for (; argc > 0 && argv[0][0] == '-'; argc--, argv++) {
+               if (!strcmp(*argv, "-n") || !strcmp(*argv, "--lines")) {
+                       argc--; argv++;
+                       if (argc > 0 && (lines = atoi(argv[0])) <= 0)
+                               errx(EXIT_FAILURE, _("invalid number of lines"));
+               }
+               else if (isdigit(argv[0][1])) {
+                       if ((lines = atoi(*argv + 1)) <= 0)
+                               errx(EXIT_FAILURE, _("invalid number of lines"));
+               }
+               else
+                       errx(EXIT_FAILURE, _("invalid option"));
        }
-       else
-               errx(EXIT_FAILURE, _("invalid option"));
-    }
 
-    if (argc != 1)
-       errx(EXIT_FAILURE, _("usage: tailf [-n N | -N] logfile"));
+       if (argc != 1)
+               errx(EXIT_FAILURE, _("usage: tailf [-n N | -N] logfile"));
 
-    filename = argv[0];
-    tailf(filename, lines);
+       filename = argv[0];
 
-    for (osize = filesize(filename);;) {
-       nsize = filesize(filename);
-       if (nsize != osize) {
-           if (!(str = fopen(filename, "r")))
-               err(EXIT_FAILURE, _("cannot open \"%s\" for read"), filename);
+       if (stat(filename, &st) != 0)
+               err(EXIT_FAILURE, _("cannot stat \"%s\""), filename);
 
-           if (!fseek(str, osize, SEEK_SET))
-                while ((count = fread(buffer, 1, sizeof(buffer), str)) > 0) {
-                    wcount = fwrite(buffer, 1, count, stdout);
-                    if (wcount != count)
-                       warnx(_("incomplete write to \"%s\" (written %d, expected %d)\n"),
-                               filename, wcount, count);
-               }
-           fflush(stdout);
-           fclose(str);
-           osize = nsize;
-       }
-       usleep(250000);         /* 250mS */
-    }
-    return EXIT_SUCCESS;
+       size = st.st_size;;
+       tailf(filename, lines);
+
+#ifdef HAVE_INOTIFY_INIT
+       if (!watch_file_inotify(filename, &size))
+#endif
+               watch_file(filename, &size);
+
+       return EXIT_SUCCESS;
 }
+