home *** CD-ROM | disk | FTP | other *** search
/ Usenet 1994 October / usenetsourcesnewsgroupsinfomagicoctober1994disk2.iso / unix / volume6 / watch < prev    next >
Text File  |  1986-11-30  |  14KB  |  570 lines

  1. Subject: v06i084: A multiple "tail -f" program (watch)
  2. Newsgroups: mod.sources
  3. Approved: rs@mirror.UUCP
  4.  
  5. Submitted by: ihnp4!poseidon!brent
  6. Mod.sources: Volume 6, Issue 84
  7. Archive-name: watch
  8.  
  9. [  This program does not use the portable directory access routines,
  10.    and references a <values.h> file.  I didn't notice any "non-generic"
  11.    curses calls (e.g., curses/terminfo, PD curses, etc.), but I'm
  12.    not sure that I would.  I will happily publish a set of context
  13.    diffs if someone ports this to 4.[23]BSD.  --r$ ]
  14.  
  15. I've found this program pretty useful in testing an application with
  16. lots of asynchronous processes creating spool files and writing logs.
  17.  
  18. Essentially it allows you to do multiple "tail -f"s with each one in a
  19. separate window on the screen.  Watch also allows you to "tail" a
  20. directory and see files as soon as they appear in the directory.
  21.  
  22. Each window can have an wakeup alarm set to go off on any output to the
  23. window - useful if you want to look at something more interesting.
  24.  
  25. Have fun!
  26.  
  27. Made in New Zealand -->        Brent Callaghan
  28.                 AT&T Information Systems, Lincroft, NJ
  29.                 {ihnp4|mtuxo|pegasus}!poseidon!brent
  30.                 (201) 576-3475
  31.  
  32. -- cut here -- cut here -- cut here -- cut here -- cut here -- cut here 
  33. #!/bin/sh
  34. # This is a shar archive.
  35. # The rest of this file is a shell script which will extract:
  36. # watch.1 watch.c
  37. # Archive created: Fri Jul 11 14:21:28 EDT 1986
  38. echo x - watch.1
  39. sed 's/^X//' > watch.1 << '~FUNKY STUFF~'
  40. X.TH WATCH 1 ""
  41. X.SH NAME
  42. watch \- watch files or directories
  43.  
  44. X.SH SYNOPSIS
  45. X.B watch 
  46. [
  47. X.I flags
  48. ]
  49. X.I file ...
  50.  
  51. X.SH DESCRIPTION
  52. X.B Watch
  53. is a program for monitoring files or directories.
  54. It accepts a list of files as arguments
  55. and allocates a window on the screen to display
  56. each file.
  57. X.P
  58. The top line of each window is reserved for a label
  59. containing the name of the file.
  60. If the file is a directory a slash "/" is appended to the name.
  61. The 
  62. X.B -l
  63. flag dispenses with the labels and gives you an extra line
  64. per window (but you'll quickly forget what you were watching).
  65. X.P
  66. For an 
  67. X.I ordinary file
  68. it functions like
  69. X.B
  70. "tail -f".
  71. The window displays the lines at the end of the file and includes
  72. new characters as they are appended.
  73. X.P
  74. If the file is a
  75. X.I directory
  76. it functions like 
  77. X.B
  78. "ls -lrt".
  79. The window displays the directory entries sorted by
  80. modification time with the most recently added files at the
  81. bottom.
  82. The display is updated if a new file is added to the directory
  83. or if the modification time of an existing file is updated.
  84. For each entry the modification time, owner, file size
  85. and file name are displayed.
  86. X.P
  87. X.B Watch
  88. allocates equal window space to each file argument.
  89. You can control the allocation by giving the window
  90. size after the file name.
  91. X.P
  92. X.RS
  93. watch iqueue OPTRACE 15 oqueue
  94. X.RE
  95. X.P
  96. assigns a 15 line window to OPTRACE.
  97. The other two files get what's left, on a 24 line screen
  98. one would get 4 lines and the other 5 lines.
  99. X.P
  100. Once watch is running, you can refresh the screen with
  101. ctrl-L or quit by typing a "q" ctrl-d or "del" (interrupt).
  102. X.P
  103. You can set alarms in any or all of the windows to wake
  104. you up when something happens.
  105. To set an alarm just hit a digit corresponding to the window.
  106. For the first (top) window just hit "1".
  107. You'll see a star appear just to the right of the label to
  108. remind you that the alarm is set. If the screen gets some
  109. output the terminal bell will go off to wake you up and
  110. the star will disappear.
  111. You can set alarms in all windows by hitting "0".
  112.  
  113. X.SH BUGS
  114. Can't handle all-numeric file names.
  115. ~FUNKY STUFF~
  116. ls -l watch.1
  117. echo x - watch.c
  118. sed 's/^X//' > watch.c << '~FUNKY STUFF~'
  119. #include <curses.h>
  120. #include <ctype.h>
  121. #include <fcntl.h>
  122. #include <pwd.h>
  123. #include <values.h>
  124. #include <errno.h>
  125. #include <sys/types.h>
  126. #include <sys/stat.h>
  127. #include <sys/dir.h>
  128. #include <signal.h>
  129.  
  130. #define BUFFSZ 256
  131. #define MAXLINES 70
  132. #define MAXFILES 16
  133. #define MAX(x,y) ((x) > (y) ? (x) : (y))
  134.  
  135. struct fl {
  136.    char *name ;  /* name of file or dir */
  137.    int fd ;      /* file descriptor     */
  138.    int isdir ;   /* TRUE if directory   */
  139.    int lines ;   /* screen lines        */
  140.    WINDOW *win ; /* window descriptor   */
  141.    long tstamp ; /* timestamp for dir   */
  142.    int bell ;    /* ring bell on output */
  143.    int indpos ;  /* posn of bell ind    */
  144.    int lc ;      /* last char in last buff */
  145.    } file [MAXFILES] ;
  146.  
  147. struct fileinfo {
  148.    char name [DIRSIZ+1] ;
  149.    time_t mtime ;
  150.    off_t fsize ;
  151.    int uid ;
  152.    } ;
  153.  
  154. int nf ;
  155. int interval = 1 ;
  156. int label = 1 ;
  157.  
  158. void indicate(f, c)
  159.    struct fl *f ; int c ;
  160.    {
  161.    int x, y ;
  162.  
  163.    if (c == ' ')
  164.       beep() ;
  165.    if (label) {
  166.       getyx(f->win, y, x) ;
  167.       mvwaddch(f->win, 0, f->indpos+1, c) ;
  168.       wmove(f->win, y, x) ;
  169.       wrefresh(f->win) ;
  170.       }
  171.    f->bell = c ;
  172.    }
  173.  
  174. int compare (a, b)
  175.    /* Compare file creation times */
  176.    struct fileinfo *a, *b ;
  177.    {
  178.    if (a->mtime == b->mtime)
  179.       return strcmp(a->name, b->name) ;
  180.    return a->mtime < b->mtime ? -1 : 1 ;
  181.    }
  182.  
  183. int dodirect (d)
  184.    struct fl *d ;
  185.    /*
  186.     * Check a directory for new files.
  187.     * First scan requires a sort to
  188.     * get files into time order.
  189.     */
  190.    {
  191.    register int i ;
  192.    int rflag = FALSE ;
  193.    int n = 0 ;
  194.    int slot ;
  195.    time_t stamp, oldest ;
  196.    static char fname [DIRSIZ+1] ;
  197.    static char sname [128] ;
  198.    static char tbuf [26] ;
  199.    struct passwd *pwd, *getpwuid() ;
  200.    struct direct dir ;
  201.    struct stat fs ;
  202.  
  203.    struct fileinfo finfo [MAXLINES] ;
  204.  
  205.    if (d->tstamp == 0L) {
  206.       for (i = 0 ; i < d->lines ; i++) /* initialize */
  207.      finfo[i].mtime = 0L ;
  208.  
  209.       while (read(d->fd, &dir, sizeof(struct direct)) ==
  210.      sizeof(struct direct))
  211.      if (dir.d_ino && dir.d_name[0] != '.') { /* non-empty */
  212.         strncpy(fname, dir.d_name, DIRSIZ) ;
  213.         sprintf(sname, "%s/%s", d->name, fname) ;
  214.         stat(sname, &fs) ;
  215.         slot = -1 ;
  216.         if (n < d->lines)
  217.            slot = n++ ;
  218.         else
  219.            for (oldest = MAXLONG, i = 0 ; i < d->lines ; i++)
  220.           if (finfo[i].mtime < oldest) {
  221.              oldest = finfo[i].mtime ;
  222.              slot = i ;
  223.              }
  224.         if (slot >= 0) {
  225.            strcpy(finfo[slot].name, fname) ;
  226.            finfo[slot].mtime = fs.st_mtime ;
  227.            finfo[slot].fsize = fs.st_size ;
  228.            finfo[slot].uid = fs.st_uid ;
  229.            }
  230.         }
  231.  
  232.       if (n > 1)
  233.      qsort((char *)finfo, n, sizeof(struct fileinfo), compare) ;
  234.  
  235.       for (i = 0 ; i < n ; i++) {
  236.      strncpy(tbuf, ctime(&finfo[i].mtime), 24) ;
  237.      if ((pwd = getpwuid(finfo[i].uid)) == NULL)
  238.         sprintf(sname, "%4d", finfo[i].uid) ;
  239.      else
  240.         strcpy(sname, pwd->pw_name) ;
  241.  
  242.      if (i > 0)
  243.         waddch(d->win, '\n') ;
  244.      wprintw(d->win, "%s %10s %8ld  %s",
  245.      tbuf, sname, finfo[i].fsize, finfo[i].name) ;
  246.      stamp = finfo[i].mtime ;
  247.      }
  248.       rflag = TRUE ;
  249.       }
  250.  
  251.    else {                    /* look for new files */
  252.       lseek(d->fd, 0L, 0) ;
  253.       stamp = d->tstamp ;
  254.  
  255.       while (read(d->fd, &dir, sizeof(struct direct)) ==
  256.      sizeof(struct direct))
  257.      if (dir.d_ino && dir.d_name[0] != '.') { /* non-empty */
  258.         strncpy(fname, dir.d_name, DIRSIZ) ;
  259.         sprintf(sname, "%s/%s", d->name, fname) ;
  260.         stat(sname, &fs) ;
  261.         if (fs.st_mtime > d->tstamp) {
  262.            stamp = MAX(stamp, fs.st_mtime) ;
  263.            strncpy(tbuf, ctime(&fs.st_mtime), 24) ;
  264.            if ((pwd = getpwuid(fs.st_uid)) == NULL)
  265.           sprintf(sname, "%4d", fs.st_uid) ;
  266.            else
  267.           strcpy(sname, pwd->pw_name) ;
  268.            if (d->tstamp > 0L)
  269.           waddch(d->win, '\n') ;
  270.            wprintw(d->win, "%s %10s %8ld  %s",
  271.            tbuf, sname, fs.st_size, fname) ;
  272.            rflag = TRUE ;
  273.            }
  274.         }
  275.      }
  276.    d->tstamp = stamp ;
  277.    return rflag ;
  278.    }
  279.  
  280. int dofile (f)
  281.    struct fl *f ;
  282.    /*
  283.     * Display stuff added to the file.
  284.     */
  285.    {
  286.    static char buff [BUFFSZ+1] ;
  287.    int n ;
  288.    int rflag = FALSE ;
  289.  
  290.    while ((n = read(f->fd, buff, BUFFSZ)) > 0) {
  291.       if (f->lc == '\n')
  292.      waddch(f->win, '\n') ;
  293.       if ((f->lc = buff[n-1]) == '\n')
  294.      buff[n-1] = '\0' ; /* remove final nl */
  295.       waddstr(f->win, buff) ;
  296.       rflag = TRUE ;
  297.       }
  298.    return rflag || f->lc == 0 ;
  299.    }
  300.  
  301. void quit()
  302.    /* clean up curses stuff */
  303.    {
  304.    alarm(0) ;
  305.    signal(SIGINT,  SIG_IGN) ; /* Avoid interrupts */
  306.    signal(SIGQUIT, SIG_IGN) ; /*      while       */
  307.    signal(SIGTERM, SIG_IGN) ; /*   cleaning up    */
  308.    endwin() ;
  309.    putchar('\n') ;
  310.    exit(0) ;
  311.    }
  312.  
  313. long line2byte (fd, lines)
  314.    int fd, lines ;
  315.    /*
  316.     * Convert an EOF relative line number
  317.     * offset to a byte offset for lseek.
  318.     */
  319.    {
  320.    int lc=0, cc=0, n, off ;
  321.    register char *p ;
  322.    long fsize ;
  323.    static char buff [BUFFSZ] ;
  324.    struct stat fs ;
  325.  
  326.    fstat(fd, &fs) ;
  327.    fsize = fs.st_size ; /* file size in bytes */
  328.  
  329.    for (off = BUFFSZ ; ; off += BUFFSZ) {
  330.       if (off >= fsize) {
  331.      lseek(fd, 0L, 0) ;
  332.      off = fsize ;
  333.      }
  334.       else
  335.      lseek(fd, -(long)off, 2) ;
  336.  
  337.       n = read(fd, buff, BUFFSZ) ;
  338.       if (lc == 0 && buff[n-1] == '\n')
  339.      lc = -1 ; /* don't count nl at EOF */
  340.  
  341.       for (p = buff+(n-1) ; p >= buff ; p--)
  342.      if (*p == '\n') {
  343.         if ((lc += cc / COLS + 1) >= lines)
  344.            return off - (p-buff) - ((lc-lines) * COLS + 1) ;
  345.         cc = 0 ;
  346.         }
  347.      else
  348.         cc++ ;
  349.  
  350.       if (off >= fsize)
  351.      return fsize ;
  352.       }
  353.    }
  354.  
  355. void wakeup ()
  356.    {
  357.    register int i ;
  358.    int upd = 0 ;
  359.  
  360.    for (i = 0 ; i < nf ; i++)
  361.       if ( (file[i].isdir ? dodirect : dofile) (&file[i]) ) {
  362.      if (file[i].bell != ' ')
  363.         indicate(&file[i], ' ') ;
  364.      wnoutrefresh(file[i].win) ;
  365.      upd = 1 ;
  366.      }
  367.    
  368.    if (upd)
  369.       doupdate() ;
  370.    signal(SIGALRM, wakeup) ;
  371.    alarm(interval) ;
  372.    }
  373.     
  374. int num (p)
  375.    char *p ;
  376.    /* test if p all digits */
  377.    {
  378.    for (; *p ; p++)
  379.       if (!isdigit(*p))
  380.      return FALSE ;
  381.    return TRUE ;
  382.    }
  383.  
  384. main (argc, argv)
  385.    int argc ; char *argv[] ;
  386.    {
  387.    register int i, c ;
  388.    char *p ;
  389.    int curline, tlines=0, defs ;
  390.    int bad = FALSE ;
  391.    int dummy ;
  392.    struct stat fs ;
  393.    WINDOW *w ;
  394.    extern int optind ;
  395.    extern char *optarg ;
  396.  
  397.    while ((i = getopt(argc, argv, "li:")) != EOF)
  398.       switch(i) {
  399.      case 'l':
  400.         label = 0 ;
  401.         break ;
  402.      case 'i':
  403.         interval = atoi(optarg) ;
  404.         break ;
  405.      case '?': /* invalid flag */
  406.         bad = TRUE ;
  407.         break ;
  408.         }
  409.  
  410.    if (bad || optind == argc) {
  411.       fprintf(stderr, "\nUsage: %s [-lt] [-i n] file [lines] ...\n", argv[0]) ;
  412.       fprintf(stderr, "\nflags:\tl - Omit window labels\n") ;
  413.       fprintf(stderr, "\ti - Set sleep interval to n secs (default 1)\n") ;
  414.       exit(1) ;
  415.       }
  416.  
  417.    for (nf = 0, i = optind ; i < argc ; i++) {
  418.       p = argv[i] ;
  419.       if (num(p) && nf > 0)
  420.      file[nf-1].lines = atoi(p) ;  /* screen lines */
  421.       else {
  422.      if (nf >= MAXFILES) {
  423.         fprintf(stderr, "Too many files (max %d)\n", MAXFILES) ;
  424.         exit (1) ;
  425.         }
  426.      if ((file[nf].fd = open(file[nf].name = p, O_RDONLY)) < 0) {
  427.         fprintf(stderr, "Couldn't open %s\n", p) ;
  428.         bad = TRUE ;
  429.         }
  430.      else {                       /* dir or regular file ? */
  431.         fstat(file[nf].fd, &fs) ;
  432.         if (fs.st_mode & S_IFDIR)
  433.            file[nf].isdir = TRUE ;
  434.         else if (!fs.st_mode & S_IFREG) {
  435.            fprintf(stderr, "Cannot seek on %s\n", p) ;
  436.            bad = TRUE ;
  437.            }
  438.         }
  439.      nf++ ;
  440.      }
  441.       }
  442.    if (bad)
  443.       exit(1) ;
  444.  
  445.    if (getenv("TERM") == NULL) {
  446.       fprintf(stderr, "set TERM and try again\n") ;
  447.       exit(1) ;
  448.       }
  449.  
  450.    /* Check allocations of screen lines and */
  451.    /* compute leftover to be allocated to   */
  452.    /* unspecified allocations.              */
  453.  
  454.    for (i = 0 ; i < nf ; i++) {
  455.       tlines += file[i].lines ;
  456.       if (file[i].lines == 0)
  457.      defs++ ;
  458.       }
  459.  
  460.    initscr() ;
  461.  
  462.    for (i = 0 ; i < nf ; i++)  /* allocate screen space */
  463.       if (file[i].lines == 0) {
  464.      file[i].lines = MAX((LINES-tlines) / defs, label+1) ;
  465.      tlines += file[i].lines ;
  466.      defs-- ;
  467.      }
  468.  
  469.    if (tlines > LINES) {
  470.       fprintf(stderr, "Need more than %d lines (%d)\n", LINES, tlines) ;
  471.       quit() ;
  472.       }
  473.  
  474.    /* Open and initialize windows */
  475.  
  476.    signal(SIGINT , quit) ; /* set up             */
  477.    signal(SIGQUIT, quit) ; /*   signals for      */
  478.    signal(SIGTERM, quit) ; /*     graceful death */
  479.  
  480.    for (curline = 0, i = 0 ; i < nf ; i++) {
  481.       w = file[i].win = newwin(file[i].lines, 0, curline, 0) ;
  482.       curline += file[i].lines ;
  483.       wsetscrreg(w, label, file[i].lines-label) ;
  484.       if (label) {
  485.      wmove(w, 0, 0) ;
  486.      if (nf > 1)
  487.         wprintw(w, "%d ", i+1) ;
  488.      wstandout(w) ;
  489.      waddstr(w, file[i].name) ;
  490.      if (file[i].isdir && 
  491.         *(file[i].name + (strlen(file[i].name)-1)) != '/')
  492.         waddch(file[i].win, '/') ;
  493.      getyx(w, dummy, file[i].indpos) ;
  494.      waddch(file[i].win, '\n') ;
  495.      wstandend(w) ;
  496.      file[i].lines-- ;
  497.      }
  498.       file[i].bell = ' ' ;
  499.       idlok(w, TRUE) ;
  500.       scrollok(w, TRUE) ;
  501.       leaveok(w, TRUE) ;
  502.       }
  503.  
  504.    /* Seek to end of files */
  505.  
  506.    for (i = 0 ; i < nf ; i++)
  507.       if (!file[i].isdir)
  508.      lseek(file[i].fd, -line2byte(file[i].fd, file[i].lines), 2) ;
  509.  
  510.    /* Set up modes for input */
  511.    
  512.    crmode() ;
  513.    nonl() ;
  514.    cbreak() ;
  515.    noecho() ;
  516.    clear() ;
  517.  
  518.    wakeup() ;
  519.  
  520.    for (;;) {
  521.       while ((c = getchar()) == -1 && errno == EINTR)
  522.          ;
  523.       switch (c) {
  524.  
  525.      case '\f':  /* refresh */
  526.         alarm(0) ;
  527.         clearok(curscr, TRUE) ;
  528.         wrefresh(curscr) ;
  529.         alarm(interval) ;
  530.         break ;
  531.  
  532.      case '0':
  533.         for (i = 0 ; i < nf ; i++)
  534.            indicate(&file[i], '*') ;
  535.         break ;
  536.  
  537.      case '1':
  538.      case '2':
  539.      case '3':
  540.      case '4':
  541.      case '5':
  542.      case '6':
  543.      case '7':
  544.      case '8':
  545.      case '9':
  546.         c -= '0' ;
  547.         if (c > nf)
  548.            flash() ;
  549.         else
  550.            indicate(&file[c-1], '*') ;
  551.         break ;
  552.  
  553.      case 'q' :   /* quit */
  554.      case '\4':
  555.         quit() ;
  556.      }
  557.       }
  558.    /*NOTREACHED*/
  559.    }
  560. ~FUNKY STUFF~
  561. ls -l watch.c
  562. sed 's/^X//' > Makefile << '~FUNKY STUFF~'
  563. XCFLAGS=-O
  564. Xwatch:    watch.c
  565. X    cc $(CFLAGS) watch.c -o watch -lcurses
  566. ~FUNKY STUFF~
  567. # The following exit is to ensure that extra garbage 
  568. # after the end of the shar file will be ignored.
  569. exit 0
  570.