sources-request@mirror.UUCP (08/01/86)
Submitted by: ihnp4!poseidon!brent Mod.sources: Volume 6, Issue 84 Archive-name: watch [ This program does not use the portable directory access routines, and references a <values.h> file. I didn't notice any "non-generic" curses calls (e.g., curses/terminfo, PD curses, etc.), but I'm not sure that I would. I will happily publish a set of context diffs if someone ports this to 4.[23]BSD. --r$ ] I've found this program pretty useful in testing an application with lots of asynchronous processes creating spool files and writing logs. Essentially it allows you to do multiple "tail -f"s with each one in a separate window on the screen. Watch also allows you to "tail" a directory and see files as soon as they appear in the directory. Each window can have an wakeup alarm set to go off on any output to the window - useful if you want to look at something more interesting. Have fun! Made in New Zealand --> Brent Callaghan AT&T Information Systems, Lincroft, NJ {ihnp4|mtuxo|pegasus}!poseidon!brent (201) 576-3475 -- cut here -- cut here -- cut here -- cut here -- cut here -- cut here #!/bin/sh # This is a shar archive. # The rest of this file is a shell script which will extract: # watch.1 watch.c # Archive created: Fri Jul 11 14:21:28 EDT 1986 echo x - watch.1 sed 's/^X//' > watch.1 << '~FUNKY STUFF~' X.TH WATCH 1 "" X.SH NAME watch \- watch files or directories X.SH SYNOPSIS X.B watch [ X.I flags ] X.I file ... X.SH DESCRIPTION X.B Watch is a program for monitoring files or directories. It accepts a list of files as arguments and allocates a window on the screen to display each file. X.P The top line of each window is reserved for a label containing the name of the file. If the file is a directory a slash "/" is appended to the name. The X.B -l flag dispenses with the labels and gives you an extra line per window (but you'll quickly forget what you were watching). X.P For an X.I ordinary file it functions like X.B "tail -f". The window displays the lines at the end of the file and includes new characters as they are appended. X.P If the file is a X.I directory it functions like X.B "ls -lrt". The window displays the directory entries sorted by modification time with the most recently added files at the bottom. The display is updated if a new file is added to the directory or if the modification time of an existing file is updated. For each entry the modification time, owner, file size and file name are displayed. X.P X.B Watch allocates equal window space to each file argument. You can control the allocation by giving the window size after the file name. X.P X.RS watch iqueue OPTRACE 15 oqueue X.RE X.P assigns a 15 line window to OPTRACE. The other two files get what's left, on a 24 line screen one would get 4 lines and the other 5 lines. X.P Once watch is running, you can refresh the screen with ctrl-L or quit by typing a "q" ctrl-d or "del" (interrupt). X.P You can set alarms in any or all of the windows to wake you up when something happens. To set an alarm just hit a digit corresponding to the window. For the first (top) window just hit "1". You'll see a star appear just to the right of the label to remind you that the alarm is set. If the screen gets some output the terminal bell will go off to wake you up and the star will disappear. You can set alarms in all windows by hitting "0". X.SH BUGS Can't handle all-numeric file names. ~FUNKY STUFF~ ls -l watch.1 echo x - watch.c sed 's/^X//' > watch.c << '~FUNKY STUFF~' #include <curses.h> #include <ctype.h> #include <fcntl.h> #include <pwd.h> #include <values.h> #include <errno.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/dir.h> #include <signal.h> #define BUFFSZ 256 #define MAXLINES 70 #define MAXFILES 16 #define MAX(x,y) ((x) > (y) ? (x) : (y)) struct fl { char *name ; /* name of file or dir */ int fd ; /* file descriptor */ int isdir ; /* TRUE if directory */ int lines ; /* screen lines */ WINDOW *win ; /* window descriptor */ long tstamp ; /* timestamp for dir */ int bell ; /* ring bell on output */ int indpos ; /* posn of bell ind */ int lc ; /* last char in last buff */ } file [MAXFILES] ; struct fileinfo { char name [DIRSIZ+1] ; time_t mtime ; off_t fsize ; int uid ; } ; int nf ; int interval = 1 ; int label = 1 ; void indicate(f, c) struct fl *f ; int c ; { int x, y ; if (c == ' ') beep() ; if (label) { getyx(f->win, y, x) ; mvwaddch(f->win, 0, f->indpos+1, c) ; wmove(f->win, y, x) ; wrefresh(f->win) ; } f->bell = c ; } int compare (a, b) /* Compare file creation times */ struct fileinfo *a, *b ; { if (a->mtime == b->mtime) return strcmp(a->name, b->name) ; return a->mtime < b->mtime ? -1 : 1 ; } int dodirect (d) struct fl *d ; /* * Check a directory for new files. * First scan requires a sort to * get files into time order. */ { register int i ; int rflag = FALSE ; int n = 0 ; int slot ; time_t stamp, oldest ; static char fname [DIRSIZ+1] ; static char sname [128] ; static char tbuf [26] ; struct passwd *pwd, *getpwuid() ; struct direct dir ; struct stat fs ; struct fileinfo finfo [MAXLINES] ; if (d->tstamp == 0L) { for (i = 0 ; i < d->lines ; i++) /* initialize */ finfo[i].mtime = 0L ; while (read(d->fd, &dir, sizeof(struct direct)) == sizeof(struct direct)) if (dir.d_ino && dir.d_name[0] != '.') { /* non-empty */ strncpy(fname, dir.d_name, DIRSIZ) ; sprintf(sname, "%s/%s", d->name, fname) ; stat(sname, &fs) ; slot = -1 ; if (n < d->lines) slot = n++ ; else for (oldest = MAXLONG, i = 0 ; i < d->lines ; i++) if (finfo[i].mtime < oldest) { oldest = finfo[i].mtime ; slot = i ; } if (slot >= 0) { strcpy(finfo[slot].name, fname) ; finfo[slot].mtime = fs.st_mtime ; finfo[slot].fsize = fs.st_size ; finfo[slot].uid = fs.st_uid ; } } if (n > 1) qsort((char *)finfo, n, sizeof(struct fileinfo), compare) ; for (i = 0 ; i < n ; i++) { strncpy(tbuf, ctime(&finfo[i].mtime), 24) ; if ((pwd = getpwuid(finfo[i].uid)) == NULL) sprintf(sname, "%4d", finfo[i].uid) ; else strcpy(sname, pwd->pw_name) ; if (i > 0) waddch(d->win, '\n') ; wprintw(d->win, "%s %10s %8ld %s", tbuf, sname, finfo[i].fsize, finfo[i].name) ; stamp = finfo[i].mtime ; } rflag = TRUE ; } else { /* look for new files */ lseek(d->fd, 0L, 0) ; stamp = d->tstamp ; while (read(d->fd, &dir, sizeof(struct direct)) == sizeof(struct direct)) if (dir.d_ino && dir.d_name[0] != '.') { /* non-empty */ strncpy(fname, dir.d_name, DIRSIZ) ; sprintf(sname, "%s/%s", d->name, fname) ; stat(sname, &fs) ; if (fs.st_mtime > d->tstamp) { stamp = MAX(stamp, fs.st_mtime) ; strncpy(tbuf, ctime(&fs.st_mtime), 24) ; if ((pwd = getpwuid(fs.st_uid)) == NULL) sprintf(sname, "%4d", fs.st_uid) ; else strcpy(sname, pwd->pw_name) ; if (d->tstamp > 0L) waddch(d->win, '\n') ; wprintw(d->win, "%s %10s %8ld %s", tbuf, sname, fs.st_size, fname) ; rflag = TRUE ; } } } d->tstamp = stamp ; return rflag ; } int dofile (f) struct fl *f ; /* * Display stuff added to the file. */ { static char buff [BUFFSZ+1] ; int n ; int rflag = FALSE ; while ((n = read(f->fd, buff, BUFFSZ)) > 0) { if (f->lc == '\n') waddch(f->win, '\n') ; if ((f->lc = buff[n-1]) == '\n') buff[n-1] = '\0' ; /* remove final nl */ waddstr(f->win, buff) ; rflag = TRUE ; } return rflag || f->lc == 0 ; } void quit() /* clean up curses stuff */ { alarm(0) ; signal(SIGINT, SIG_IGN) ; /* Avoid interrupts */ signal(SIGQUIT, SIG_IGN) ; /* while */ signal(SIGTERM, SIG_IGN) ; /* cleaning up */ endwin() ; putchar('\n') ; exit(0) ; } long line2byte (fd, lines) int fd, lines ; /* * Convert an EOF relative line number * offset to a byte offset for lseek. */ { int lc=0, cc=0, n, off ; register char *p ; long fsize ; static char buff [BUFFSZ] ; struct stat fs ; fstat(fd, &fs) ; fsize = fs.st_size ; /* file size in bytes */ for (off = BUFFSZ ; ; off += BUFFSZ) { if (off >= fsize) { lseek(fd, 0L, 0) ; off = fsize ; } else lseek(fd, -(long)off, 2) ; n = read(fd, buff, BUFFSZ) ; if (lc == 0 && buff[n-1] == '\n') lc = -1 ; /* don't count nl at EOF */ for (p = buff+(n-1) ; p >= buff ; p--) if (*p == '\n') { if ((lc += cc / COLS + 1) >= lines) return off - (p-buff) - ((lc-lines) * COLS + 1) ; cc = 0 ; } else cc++ ; if (off >= fsize) return fsize ; } } void wakeup () { register int i ; int upd = 0 ; for (i = 0 ; i < nf ; i++) if ( (file[i].isdir ? dodirect : dofile) (&file[i]) ) { if (file[i].bell != ' ') indicate(&file[i], ' ') ; wnoutrefresh(file[i].win) ; upd = 1 ; } if (upd) doupdate() ; signal(SIGALRM, wakeup) ; alarm(interval) ; } int num (p) char *p ; /* test if p all digits */ { for (; *p ; p++) if (!isdigit(*p)) return FALSE ; return TRUE ; } main (argc, argv) int argc ; char *argv[] ; { register int i, c ; char *p ; int curline, tlines=0, defs ; int bad = FALSE ; int dummy ; struct stat fs ; WINDOW *w ; extern int optind ; extern char *optarg ; while ((i = getopt(argc, argv, "li:")) != EOF) switch(i) { case 'l': label = 0 ; break ; case 'i': interval = atoi(optarg) ; break ; case '?': /* invalid flag */ bad = TRUE ; break ; } if (bad || optind == argc) { fprintf(stderr, "\nUsage: %s [-lt] [-i n] file [lines] ...\n", argv[0]) ; fprintf(stderr, "\nflags:\tl - Omit window labels\n") ; fprintf(stderr, "\ti - Set sleep interval to n secs (default 1)\n") ; exit(1) ; } for (nf = 0, i = optind ; i < argc ; i++) { p = argv[i] ; if (num(p) && nf > 0) file[nf-1].lines = atoi(p) ; /* screen lines */ else { if (nf >= MAXFILES) { fprintf(stderr, "Too many files (max %d)\n", MAXFILES) ; exit (1) ; } if ((file[nf].fd = open(file[nf].name = p, O_RDONLY)) < 0) { fprintf(stderr, "Couldn't open %s\n", p) ; bad = TRUE ; } else { /* dir or regular file ? */ fstat(file[nf].fd, &fs) ; if (fs.st_mode & S_IFDIR) file[nf].isdir = TRUE ; else if (!fs.st_mode & S_IFREG) { fprintf(stderr, "Cannot seek on %s\n", p) ; bad = TRUE ; } } nf++ ; } } if (bad) exit(1) ; if (getenv("TERM") == NULL) { fprintf(stderr, "set TERM and try again\n") ; exit(1) ; } /* Check allocations of screen lines and */ /* compute leftover to be allocated to */ /* unspecified allocations. */ for (i = 0 ; i < nf ; i++) { tlines += file[i].lines ; if (file[i].lines == 0) defs++ ; } initscr() ; for (i = 0 ; i < nf ; i++) /* allocate screen space */ if (file[i].lines == 0) { file[i].lines = MAX((LINES-tlines) / defs, label+1) ; tlines += file[i].lines ; defs-- ; } if (tlines > LINES) { fprintf(stderr, "Need more than %d lines (%d)\n", LINES, tlines) ; quit() ; } /* Open and initialize windows */ signal(SIGINT , quit) ; /* set up */ signal(SIGQUIT, quit) ; /* signals for */ signal(SIGTERM, quit) ; /* graceful death */ for (curline = 0, i = 0 ; i < nf ; i++) { w = file[i].win = newwin(file[i].lines, 0, curline, 0) ; curline += file[i].lines ; wsetscrreg(w, label, file[i].lines-label) ; if (label) { wmove(w, 0, 0) ; if (nf > 1) wprintw(w, "%d ", i+1) ; wstandout(w) ; waddstr(w, file[i].name) ; if (file[i].isdir && *(file[i].name + (strlen(file[i].name)-1)) != '/') waddch(file[i].win, '/') ; getyx(w, dummy, file[i].indpos) ; waddch(file[i].win, '\n') ; wstandend(w) ; file[i].lines-- ; } file[i].bell = ' ' ; idlok(w, TRUE) ; scrollok(w, TRUE) ; leaveok(w, TRUE) ; } /* Seek to end of files */ for (i = 0 ; i < nf ; i++) if (!file[i].isdir) lseek(file[i].fd, -line2byte(file[i].fd, file[i].lines), 2) ; /* Set up modes for input */ crmode() ; nonl() ; cbreak() ; noecho() ; clear() ; wakeup() ; for (;;) { while ((c = getchar()) == -1 && errno == EINTR) ; switch (c) { case '\f': /* refresh */ alarm(0) ; clearok(curscr, TRUE) ; wrefresh(curscr) ; alarm(interval) ; break ; case '0': for (i = 0 ; i < nf ; i++) indicate(&file[i], '*') ; break ; case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': c -= '0' ; if (c > nf) flash() ; else indicate(&file[c-1], '*') ; break ; case 'q' : /* quit */ case '\4': quit() ; } } /*NOTREACHED*/ } ~FUNKY STUFF~ ls -l watch.c sed 's/^X//' > Makefile << '~FUNKY STUFF~' XCFLAGS=-O Xwatch: watch.c X cc $(CFLAGS) watch.c -o watch -lcurses ~FUNKY STUFF~ # The following exit is to ensure that extra garbage # after the end of the shar file will be ignored. exit 0