Print this page
3484 enhance and document tail follow support
Reviewed by: Joshua M. Clulow <jmc@joyent.com>

@@ -29,13 +29,11 @@
  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  * SUCH DAMAGE.
  */
 
 /*
- * Solaris porting notes:  the original FreeBSD version made use of the
- *                         BSD kqueue event notification framework; this
- *                         was changed to use usleep()
+ * Copyright (c) 2013, Joyent, Inc. All rights reserved.
  */
 
 #include <sys/param.h>
 #include <sys/mount.h>
 #include <sys/types.h>

@@ -51,10 +49,11 @@
 #include <fcntl.h>
 #include <limits.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <strings.h>
 #include <unistd.h>
 
 #include "extern.h"
 
 static void rlines(FILE *, const char *fn, off_t, struct stat *);

@@ -61,13 +60,15 @@
 static int show(file_info_t *);
 static void set_events(file_info_t *files);
 
 /* defines for inner loop actions */
 #define USE_SLEEP       0
+#define USE_PORT        1
 #define ADD_EVENTS      2
 
-int action = USE_SLEEP;
+int port;
+int action = USE_PORT;
 
 static const file_info_t *last;
 
 /*
  * forward -- display the file, from an offset, forward.

@@ -259,10 +260,69 @@
         clearerr(file->fp);
         return (1);
 }
 
 static void
+associate(file_info_t *file, boolean_t assoc)
+{
+        char buf[64];
+
+        if (action != USE_PORT || file->fp == NULL)
+                return;
+
+        if (!S_ISREG(file->st.st_mode)) {
+                /*
+                 * For FIFOs, we use PORT_SOURCE_FD as our port event source.
+                 */
+                if (assoc) {
+                        (void) port_associate(port, PORT_SOURCE_FD,
+                            fileno(file->fp), POLLIN, file);
+                } else {
+                        (void) port_dissociate(port, PORT_SOURCE_FD,
+                            fileno(file->fp));
+                }
+
+                return;
+        }
+
+        bzero(&file->fobj, sizeof (file->fobj));
+
+        /*
+         * We pull a bit of a stunt here.  PORT_SOURCE_FILE only allows us to
+         * specify a file name -- not a file descriptor.  If we were to specify
+         * the name of the file to port_associate() and that file were moved
+         * aside, we would not be able to reassociate an event because we would
+         * not know a name that would resolve to the new file (indeed, there
+         * might not be such a name -- the file may have been unlinked).  But
+         * there _is_ a name that we know maps to the file and doesn't change:
+         * the name of the representation of the open file descriptor in /proc.
+         * We therefore associate with this name (and the underlying file),
+         * not the name of the file as specified at the command line.
+         */
+        (void) snprintf(buf,
+            sizeof (buf), "/proc/self/fd/%d", fileno(file->fp));
+
+        /*
+         * Note that portfs uses the address of the specified file_obj_t to
+         * tag an association; if one creates a different association with a
+         * (different) file_ob_t that happens to be at the same address,
+         * the first association will be implicitly removed.  To assure that
+         * each file has a disjoint file_obj_t, we allocate the memory for it
+         * in the file_info, not on the stack.
+         */
+        file->fobj.fo_name = buf;
+
+        if (assoc) {
+                (void) port_associate(port, PORT_SOURCE_FILE,
+                    (uintptr_t)&file->fobj, FILE_MODIFIED | FILE_TRUNC, file);
+        } else {
+                (void) port_dissociate(port, PORT_SOURCE_FILE,
+                    (uintptr_t)&file->fobj);
+        }
+}
+
+static void
 set_events(file_info_t *files)
 {
         int i;
         file_info_t *file;
 

@@ -269,10 +329,12 @@
         for (i = 0, file = files; i < no_files; i++, file++) {
                 if (! file->fp)
                         continue;
 
                 (void) fstat(fileno(file->fp), &file->st);
+
+                associate(file, B_TRUE);
         }
 }
 
 /*
  * follow -- display the file, from an offset, forward.

@@ -282,10 +344,12 @@
 follow(file_info_t *files, enum STYLE style, off_t off)
 {
         int active, ev_change, i, n = -1;
         struct stat sb2;
         file_info_t *file;
+        struct timespec ts;
+        port_event_t ev;
 
         /* Position each of the files */
 
         file = files;
         active = 0;

@@ -305,10 +369,16 @@
         }
         if (!Fflag && !active)
                 return;
 
         last = --file;
+
+        if (action == USE_PORT &&
+            (stat("/proc/self/fd", &sb2) == -1 || !S_ISDIR(sb2.st_mode) ||
+            (port = port_create()) == -1))
+                action = USE_SLEEP;
+
         set_events(files);
 
         for (;;) {
                 ev_change = 0;
                 if (Fflag) {

@@ -339,16 +409,17 @@
 
                                 if (sb2.st_ino != file->st.st_ino ||
                                     sb2.st_dev != file->st.st_dev ||
                                     sb2.st_nlink == 0) {
                                         (void) show(file);
+                                        associate(file, B_FALSE);
                                         file->fp = freopen(file->file_name, "r",
                                             file->fp);
-                                        if (file->fp != NULL)
+                                        if (file->fp != NULL) {
                                                 (void) memcpy(&file->st, &sb2,
                                                     sizeof (struct stat));
-                                        else if (errno != ENOENT)
+                                        } else if (errno != ENOENT)
                                                 ierr(file->file_name);
                                         ev_change++;
                                 }
                         }
                 }

@@ -359,10 +430,30 @@
 
                 if (ev_change)
                         set_events(files);
 
                 switch (action) {
+                case USE_PORT:
+                        ts.tv_sec = 1;
+                        ts.tv_nsec = 0;
+
+                        /*
+                         * In the -F case we set a timeout to ensure that
+                         * we re-stat the file at least once every second.
+                         */
+                        n = port_get(port, &ev, Fflag ? &ts : NULL);
+
+                        if (n == 0) {
+                                file = (file_info_t *)ev.portev_user;
+                                associate(file, B_TRUE);
+
+                                if (ev.portev_events & FILE_TRUNC)
+                                        (void) fseek(file->fp, 0, SEEK_SET);
+                        }
+
+                        break;
+
                 case USE_SLEEP:
                         (void) usleep(250000);
                         break;
                 }
         }