home *** CD-ROM | disk | FTP | other *** search
- From decwrl!ucbvax!tut.cis.ohio-state.edu!brutus.cs.uiuc.edu!wuarchive!wugate!uunet!allbery Thu Aug 3 08:51:46 PDT 1989
- Article 1000 of comp.sources.misc:
- Path: decwrl!ucbvax!tut.cis.ohio-state.edu!brutus.cs.uiuc.edu!wuarchive!wugate!uunet!allbery
- From: allbery@uunet.UU.NET (Brandon S. Allbery - comp.sources.misc)
- Newsgroups: comp.sources.misc
- Subject: v07i108: xtail - a kind of "tail -f" for multiple files
- Message-ID: <61723@uunet.UU.NET>
- Date: 28 Jul 89 01:24:35 GMT
- Sender: allbery@uunet.UU.NET
- Reply-To: chip@vector.Dallas.TX.US.UUCP (Chip Rosenthal)
- Organization: Dallas Semiconductor
- Lines: 1328
- Approved: allbery@uunet.UU.NET (Brandon S. Allbery - comp.sources.misc)
-
- Posting-number: Volume 7, Issue 108
- Submitted-by: chip@vector.Dallas.TX.US.UUCP (Chip Rosenthal)
- Archive-name: xtail
-
- "xtail" watches the growth of files. It is similar to "tail -f", but may
- watch many files at once. The syntax is:
-
- xtail pathname ...
-
- "xtail" will monitor all the specified files and display information added
- to them. If you specify a directory name, "xtail" will watch all the
- files in that directory - including those created after "xtail" was
- started. If you give "xtail" a name which doesn't exist, it will watch
- for the creation of the named entry. My favorite usage is:
-
- xtail /usr/spool/uucp/.Log/*
-
- --- cut here -----------------------------------------------------------------
- #! /bin/sh
- # this is a "shar" archive - run through "/bin/sh" to extract 7 files:
- # README xtail.h xtail.c entryfuncs.c miscfuncs.c Makefile xtail.man
- # Wrapped by bin@vector on Wed Jul 26 19:18:07 CDT 1989
- # Unpacking this archive requires: sed test wc (possibly mkdir)
- # Existing files will not be clobbered unless "-c" is specified on the cmd line.
- if test -f README -a "$1" != "-c" ; then
- echo "README: file exists - will not be overwritten"
- else
- echo "x - README (file 1 of 7, 1709 chars)"
- sed -e 's/^X//' << 'END_OF_FILE_README' > README
- X"xtail" watches the growth of files. It is similar to "tail -f", but may
- Xwatch many files at once. The syntax is:
- X
- X xtail pathname ...
- X
- X"xtail" will monitor all the specified files and display information added
- Xto them. If you specify a directory name, "xtail" will watch all the
- Xfiles in that directory - including those created after "xtail" was
- Xstarted. If you give "xtail" a name which doesn't exist, it will watch
- Xfor the creation of the named entry. My favorite usage is:
- X
- X xtail /usr/spool/uucp/.Log/*
- X
- X"xtail" is distributed with a configuration for SCO XENIX. It has also
- Xbeen tested on MIPS System V. I took a shot at BSD portability. The
- Xmain difference is how the "directory" support library is accessed.
- X
- XTo build "xtail":
- X
- X - edit the definitions in "xtail.h"
- X - run a "make"
- X
- XA version of "xtail" was originally posted in alt.sources a few months
- Xback. There are several improvements between this version and the
- Xoriginal:
- X
- X - the ability to watch directories
- X - the ability to watch entries which don't exist yet
- X - the recently changed files display (given upon SIGINT)
- X - performance improvements
- X - portability improvements
- X
- XMany of these changes were suggested by David Dykstra <dwd@cbnewsc.ATT.COM>.
- XThe idea of keeping files open and use fstat() rather than stat() was
- Xsuggested by changes by another poster (sorry, I lost the article so I
- Xcan't provide credit). However, that version kept *everything* open, and
- Xthat just eats too many entries in the file table for me. You can tweak
- Xthe values in "xtail.h" to optimize the response/load characteristics of
- X"xtail".
- X
- XChip Rosenthal
- X<chip@vector.Dallas.TX.US>
- X
- X@(#) README 2.1 89/07/26 19:16:34
- END_OF_FILE_README
- size="`wc -c < README`"
- if test 1709 -ne "$size" ; then
- echo "README: extraction error - got $size chars"
- fi
- fi
- if test -f xtail.h -a "$1" != "-c" ; then
- echo "xtail.h: file exists - will not be overwritten"
- else
- echo "x - xtail.h (file 2 of 7, 7187 chars)"
- sed -e 's/^X//' << 'END_OF_FILE_xtail.h' > xtail.h
- X/*
- X * @(#) xtail.h 2.1 89/07/26 19:16:49
- X *
- X * Package: xtail version 2
- X * File: xtail.h
- X * Description: header definitions
- X *
- X * Mon Jul 10 02:56:22 1989 - Chip Rosenthal <chip@vector.Dallas.TX.US>
- X * Original composition.
- X */
- X
- X
- X/*****************************************************************************
- X *
- X * Start of Site-Specific Customizations
- X *
- X *****************************************************************************/
- X
- X/*
- X * Define one of the following. It says how to use your "directory" library.
- X */
- X#define DIR_XENIX /* include <sys/ndir.h>, use "struct direct" */
- X/*#define DIR_BSD /* include <ndir.h>, use "struct direct" */
- X/*#define DIR_SYSV /* include <dirent.h>, use "struct dirent" */
- X
- X/*
- X * Define one of the following. It specifies the return type of "signal()".
- X */
- X#define SIGTYPE int /* declare as "int (*signal)()" */
- X/*#define SIGTYPE void /* declare as "void (*signal)()" */
- X
- X/*
- X * STATUS_ENAB If defined, a SIGINT causes a summary of the opened files to
- X * be displayed, and a SIGQUIT terminates the program. If not
- X * defined, these signals act normally.
- X */
- X#define STATUS_ENAB /**/
- X
- X/*
- X * SLEEP_TIME An iteration through the checking loop is performed once
- X * per this many seconds.
- X */
- X#define SLEEP_TIME 1
- X
- X/*
- X * MAX_OPEN This number of most recently changed files is kept open, and
- X * they are checked every iteration through the checking loop.
- X * Keeping these files open improves the performance because we
- X * can use "fstat()" rather than "stat()". Keeping too many
- X * files open may overflow your open file table, and will reduce
- X * performance by checking more files more frequently.
- X */
- X#define MAX_OPEN 6
- X
- X/*
- X * CHECK_COUNT Everything besides open files are checked once per this
- X * many iterations through the checking loop.
- X */
- X#define CHECK_COUNT 5
- X
- X/*
- X * MAX_ENTRIES The maximum number of entries in any list. It can be fairly
- X * large -- each unused entry only eats 3*sizeof(char*) bytes.
- X */
- X#define MAX_ENTRIES 512
- X
- X
- X/*****************************************************************************
- X *
- X * End of Site-Specific Customizations
- X *
- X *****************************************************************************/
- X
- X
- X#define TRUE 1
- X#define FALSE 0
- X
- X#define Dprintf if ( !Debug ) ; else (void) fprintf
- X
- X
- X/*
- X * Codes returned by the "stat_entry()" procedure.
- X */
- X#define ENTRY_ERROR 0 /* stat error or permissions error */
- X#define ENTRY_SPECIAL 1 /* entry is a special file */
- X#define ENTRY_FILE 2 /* entry is a regular file */
- X#define ENTRY_DIR 3 /* entry is a directory */
- X#define ENTRY_ZAP 4 /* specified entry doesn't exist */
- X
- X
- X/*
- X * Diagnostic message codes.
- X * The ordering of codes must correspond to the "mssg_list[]" defined below.
- X */
- X#define MSSG_NONE 0 /* no message - just reset header */
- X#define MSSG_BANNER 1 /* display banner for file output */
- X#define MSSG_CREATED 2 /* file has been created */
- X#define MSSG_ZAPPED 3 /* file has been deleted */
- X#define MSSG_TRUNC 4 /* file has been truncated */
- X#define MSSG_NOTAFIL 5 /* error - not a regular file or dir */
- X#define MSSG_STAT 6 /* error - stat() failed */
- X#define MSSG_OPEN 7 /* error - open() failed */
- X#define MSSG_SEEK 8 /* error - lseek() failed */
- X#define MSSG_READ 9 /* error - read() failed */
- X#define MSSG_UNKNOWN 10 /* unknown error - must be last in list */
- X
- X
- X#ifdef INTERN
- X# define EXTERN
- X#else
- X# define EXTERN extern
- X#endif
- X
- X
- X/*
- X * Each item we are watching is stored in a (struct entry_descrip). These
- X * entries are placed in lists, which are managed as (struct entry_list).
- X *
- X * There are three lists maintained:
- X *
- X * List_file All of the regular files we are watching. We will try to
- X * keep the MAX_OPEN most recently modified files open, and
- X * they will be checked more frequently.
- X *
- X * List_dir All of the directories we are watching. If a file is created
- X * in one of these directories, we will add it to "List_file".
- X *
- X * List_zap All the entries which don't exist. When something appears
- X * under one of these names, the entry will be moved to either
- X * "List_file" or "List_dir", as appropriate.
- X */
- X
- Xstruct entry_descrip {
- X char *name; /* pathname to the entry */
- X int fd; /* opened fd, or <= 0 if not opened */
- X long size; /* size of entry last time checked */
- X long mtime; /* modification time last time checked */
- X};
- X
- Xstruct entry_list {
- X struct entry_descrip *list[MAX_ENTRIES];
- X int num;
- X};
- X
- X/*
- X * The lists of entries being watched.
- X */
- XEXTERN struct entry_list List_file; /* regular files */
- XEXTERN struct entry_list List_dir; /* directories */
- XEXTERN struct entry_list List_zap; /* nonexistent entries */
- X
- X
- X/*
- X * List sorting status.
- X * This flag indicates that "List_file" is sorted, and the right entries
- X * are open. Anything which possibly effects this state (e.g. an entry
- X * is added to "List_file", the mtime of a file is changed, etc.) must set
- X * this flag FALSE. We will periodically check this flag and call the
- X * "fixup_open_files()" procedure to resort and organize the list.
- X */
- XEXTERN int Sorted;
- X
- X
- X/*
- X * Entry status control flag.
- X * The procedures which manipulate entries will reset the status information
- X * if this flag is TRUE. When initializing the lists we want this FALSE.
- X * For example, consider the file size. When initializing we want to use
- X * the current file size, otherwise we would dump the file from the beginning.
- X * However, later when we notice things are created we want to reset the
- X * size to zero so that we do dump from the beginning.
- X */
- XEXTERN int Reset_status;
- X
- X
- X/*
- X * Debugging output flag.
- X */
- XEXTERN int Debug;
- X
- X
- X/*
- X * Diagnostic messages produced by the "message()" procedure.
- X * The first "%s" is the entry name. The second "%s" is the errno descrip.
- X */
- X#ifdef INTERN
- X char *mssg_list[] = {
- X NULL, /*MSSG_NONE */
- X "\n*** %s ***\n", /*MSSG_BANNER */
- X "\n*** '%s' has been created ***\n", /*MSSG_CREATED*/
- X "\n*** '%s' has been deleted ***\n", /*MSSG_ZAPPED */
- X "\n*** '%s' has been truncated - rewinding ***\n", /*MSSG_TRUNC */
- X "\n*** error - '%s' not a file or dir - removed ***\n", /*MSSG_NOTAFIL*/
- X "\n*** error - couldn't stat '%s' (%s) - removed ***\n",/*MSSG_STAT */
- X "\n*** error - couldn't open '%s' (%s) - removed ***\n",/*MSSG_OPEN */
- X "\n*** error - couldn't seek '%s' (%s) - removed ***\n",/*MSSG_SEEK */
- X "\n*** error - couldn't read '%s' (%s) - removed ***\n",/*MSSG_READ */
- X "\n*** error - unknown error on file '%s' ***\n", /*MSSG_UNKNOWN*/
- X };
- X#else
- X extern char *mssg_list[];
- X#endif
- X
- X
- X/*
- X * Entry managment procedures.
- X */
- Xstruct entry_descrip *new_entry(); /* create a new entry and add to list */
- Xvoid move_entry(); /* move an entry between lists */
- Xvoid rmv_entry(); /* remove an entry from a list */
- Xint stat_entry(); /* get the inode status for an entry */
- Xint open_entry(); /* open an entry */
- X
- X/*
- X * Miscelaneous procedures.
- X */
- Xvoid fixup_open_files(); /* manage the open files */
- Xint scan_directory(); /* scan a dir for files not on a list */
- Xvoid message(); /* standard message interface */
- Xvoid show_status(); /* display currently opened files */
- X
- END_OF_FILE_xtail.h
- size="`wc -c < xtail.h`"
- if test 7187 -ne "$size" ; then
- echo "xtail.h: extraction error - got $size chars"
- fi
- fi
- if test -f xtail.c -a "$1" != "-c" ; then
- echo "xtail.c: file exists - will not be overwritten"
- else
- echo "x - xtail.c (file 3 of 7, 8883 chars)"
- sed -e 's/^X//' << 'END_OF_FILE_xtail.c' > xtail.c
- X/*
- X * @(#) xtail.c 2.1 89/07/26 19:15:42
- X *
- X * Package: xtail version 2
- X * File: xtail.c
- X * Description: main program
- X *
- X * Mon Jul 10 02:56:22 1989 - Chip Rosenthal <chip@vector.Dallas.TX.US>
- X * Original composition.
- X */
- X
- X#ifndef LINT
- Xstatic char SCCSID[] = "@(#) xtail.c 2.1 89/07/26 19:15:42";
- X#endif
- X
- X#include <stdio.h>
- X#include <signal.h>
- X#include <sys/types.h>
- X#include <sys/stat.h>
- X#define INTERN
- X#include "xtail.h"
- X
- X#ifdef M_XENIX
- X# undef NULL
- X# define NULL 0
- X#endif
- X
- X
- Xint sigcaught = 0;
- X
- XSIGTYPE sigcatcher(sig)
- Xint sig;
- X{
- X extern SIGTYPE (*signal)();
- X if ( sig == SIGQUIT )
- X (void) exit(0);
- X sigcaught = sig;
- X#ifdef STATUS_ENAB
- X (void) signal(SIGINT,sigcatcher);
- X (void) signal(SIGQUIT,sigcatcher);
- X#endif
- X}
- X
- X
- Xmain(argc,argv)
- Xint argc;
- Xchar *argv[];
- X{
- X int open_files_only, already_open, iteration, i;
- X struct entry_descrip *entryp;
- X struct stat sbuf;
- X
- X /*
- X * Initialize.
- X */
- X List_file.num = 0;
- X List_dir.num = 0;
- X List_zap.num = 0;
- X Sorted = FALSE;
- X Reset_status = FALSE;
- X Debug = FALSE;
- X sigcatcher(0);
- X
- X
- X /*
- X * Place all of the entries onto lists.
- X */
- X for ( i = 1 ; i < argc ; ++i ) {
- X
- X if ( i == 1 && strcmp(argv[i],"-D") == 0 ) {
- X Debug = TRUE;
- X continue;
- X }
- X
- X /*
- X * Temporarily throw this entry onto the end of the zapped list.
- X */
- X entryp = new_entry( &List_zap, argv[i] );
- X
- X /*
- X * Stat the file and get it to its proper place.
- X */
- X switch ( stat_entry( &List_zap, List_zap.num-1, &sbuf ) ) {
- X
- X case ENTRY_FILE: /* move entry to file list */
- X move_entry( &List_file, &List_zap, List_zap.num-1 );
- X entryp->size = sbuf.st_size;
- X entryp->mtime = sbuf.st_mtime;
- X break;
- X
- X case ENTRY_DIR: /* move entry to dir list */
- X move_entry( &List_dir, &List_zap, List_zap.num-1 );
- X entryp->size = sbuf.st_size;
- X entryp->mtime = sbuf.st_mtime;
- X if ( scan_directory( entryp->name ) != 0 ) {
- X message( MSSG_OPEN, entryp );
- X rmv_entry( &List_dir, List_dir.num-1 );
- X }
- X break;
- X
- X case ENTRY_ZAP: /* keep entry on zap list */
- X break;
- X
- X case ENTRY_SPECIAL: /* entry is a special file */
- X message( MSSG_NOTAFIL, entryp );
- X rmv_entry( &List_zap, List_zap.num-1 );
- X break;
- X
- X default: /* stat error */
- X message( MSSG_STAT, entryp );
- X rmv_entry( &List_zap, List_zap.num-1 );
- X break;
- X
- X }
- X
- X }
- X
- X /*
- X * Make sure we are watching something reasonable.
- X */
- X if ( List_file.num == 0 ) {
- X if ( List_dir.num == 0 && List_zap.num == 0 ) {
- X (void) fprintf(stderr, "%s: no valid entries specified\n", argv[0]);
- X (void) exit(1);
- X }
- X (void) puts("\n*** warning - no files are being watched ***");
- X }
- X
- X
- X /*
- X * From this point on we want to reset the status of an entry any
- X * time we move it around to another list.
- X */
- X Reset_status = TRUE;
- X
- X
- X /*
- X * Force a check of everything first time through the loop.
- X */
- X iteration = CHECK_COUNT;
- X
- X
- X /*
- X * Loop forever.
- X */
- X for (;;) {
- X
- X /*
- X * Once every CHECK_COUNT iterations check everything.
- X * All other times only look at the opened files.
- X */
- X open_files_only = ( ++iteration < CHECK_COUNT );
- X if ( !open_files_only )
- X iteration = 0;
- X
- X
- X /*
- X * Make sure that the most recently modified files are open.
- X */
- X if ( !Sorted )
- X fixup_open_files();
- X
- X
- X /*
- X * Display what we are watching if a SIGINT was caught.
- X */
- X if ( sigcaught ) {
- X show_status();
- X sigcatcher(0);
- X }
- X
- X
- X /*
- X * Go through all of the files looking for changes.
- X */
- X Dprintf(stderr, ">>> checking files list (%s)\n",
- X ( open_files_only ? "open files only" : "all files" ));
- X for ( i = 0 ; i < List_file.num ; ++i ) {
- X
- X entryp = List_file.list[i];
- X already_open = ( entryp->fd > 0 ) ;
- X
- X /*
- X * Ignore closed files except every CHECK_COUNT iterations.
- X */
- X if ( !already_open && open_files_only )
- X continue;
- X
- X /*
- X * Get the status of this file.
- X */
- X switch ( stat_entry( &List_file, i, &sbuf ) ) {
- X case ENTRY_FILE: /* got status OK */
- X break;
- X case ENTRY_DIR: /* huh??? it's now a dir */
- X move_entry( &List_dir, &List_file, i-- );
- X continue;
- X case ENTRY_ZAP: /* entry has been deleted */
- X message( MSSG_ZAPPED, entryp );
- X move_entry( &List_zap, &List_file, i-- );
- X continue;
- X case ENTRY_SPECIAL: /* entry is a special file */
- X message( MSSG_NOTAFIL, entryp );
- X rmv_entry( &List_file, i-- );
- X continue;
- X default: /* stat error */
- X message( MSSG_STAT, entryp );
- X rmv_entry( &List_file, i-- );
- X continue;
- X }
- X
- X
- X /*
- X * See if an opened file has been deleted.
- X */
- X if ( already_open && sbuf.st_nlink == 0 ) {
- X message( MSSG_ZAPPED, entryp );
- X move_entry( &List_zap, &List_file, i-- );
- X continue;
- X }
- X
- X /*
- X * If nothing has changed then continue on.
- X */
- X if ( entryp->size==sbuf.st_size && entryp->mtime==sbuf.st_mtime )
- X continue;
- X
- X /*
- X * If the file isn't already open, then do so.
- X * Note -- it is important that we call "fixup_open_files()"
- X * at the end of the loop to make sure too many files don't
- X * stay opened.
- X */
- X if ( !already_open && open_entry( &List_file, i ) != 0 ) {
- X --i;
- X continue;
- X }
- X
- X /*
- X * See if the file has been truncated.
- X */
- X if ( sbuf.st_size < entryp->size ) {
- X message( MSSG_TRUNC, entryp );
- X entryp->size = 0;
- X }
- X
- X /*
- X * Seek to where the changes begin.
- X */
- X {
- X extern long lseek();
- X if ( lseek( entryp->fd, entryp->size, 0 ) < 0 ) {
- X message( MSSG_SEEK, entryp );
- X rmv_entry( &List_file, i-- );
- X continue;
- X }
- X }
- X
- X /*
- X * Dump the recently added info.
- X */
- X {
- X int nb;
- X static char buf[BUFSIZ];
- X message( MSSG_BANNER, entryp );
- X while ( ( nb = read( entryp->fd, buf, sizeof(buf) ) ) > 0 ) {
- X (void) fwrite( buf, sizeof(char), (unsigned) nb, stdout );
- X entryp->size += nb;
- X }
- X if ( nb < 0 ) {
- X message( MSSG_READ, entryp );
- X rmv_entry( &List_file, i-- );
- X continue;
- X }
- X }
- X
- X /*
- X * Update the modification time.
- X */
- X entryp->mtime = sbuf.st_mtime;
- X
- X /*
- X * Since we've changed the mtime, the list might no longer be
- X * sorted. However if this entry is already at the top of the
- X * list then it's OK.
- X */
- X if ( i != 0 )
- X Sorted = FALSE;
- X
- X /*
- X * If we've just opened the file then force a resort now to
- X * prevent too many files from being opened.
- X */
- X if ( !already_open )
- X fixup_open_files();
- X
- X }
- X
- X
- X /*
- X * Go through list of nonexistent entries to see if any have appeared.
- X * This is done only once every CHECK_COUNT iterations.
- X */
- X if ( !open_files_only ) {
- X Dprintf(stderr, ">>> checking zapped list\n");
- X for ( i = 0 ; i < List_zap.num ; ++i ) {
- X entryp = List_zap.list[i];
- X switch ( stat_entry( &List_zap, i, &sbuf ) ) {
- X case ENTRY_FILE: /* entry has appeared as a file */
- X message( MSSG_CREATED, entryp );
- X move_entry( &List_file, &List_zap, i-- );
- X break;
- X case ENTRY_DIR: /* entry has appeared as a dir */
- X message( MSSG_CREATED, entryp );
- X move_entry( &List_dir, &List_zap, i-- );
- X break;
- X case ENTRY_ZAP: /* entry still doesn't exist */
- X break;
- X case ENTRY_SPECIAL: /* entry is a special file */
- X message( MSSG_NOTAFIL, entryp );
- X rmv_entry( &List_zap, i-- );
- X break;
- X default: /* error - entry removed */
- X message( MSSG_STAT, entryp );
- X rmv_entry( &List_zap, i-- );
- X break;
- X }
- X }
- X }
- X
- X
- X /*
- X * Go through the list of dirs to see if any new files were created.
- X * This is done only once every CHECK_COUNT iterations.
- X */
- X if ( !open_files_only ) {
- X Dprintf(stderr, ">>> checking directory list\n");
- X for ( i = 0 ; !open_files_only && i < List_dir.num ; ++i ) {
- X entryp = List_dir.list[i];
- X switch ( stat_entry( &List_dir, i, &sbuf ) ) {
- X case ENTRY_DIR: /* got status OK */
- X break;
- X case ENTRY_FILE: /* huh??? it's now a reg file */
- X move_entry( &List_file, &List_dir, i-- );
- X continue;
- X case ENTRY_ZAP: /* entry has been deleted */
- X message( MSSG_ZAPPED, entryp );
- X move_entry( &List_zap, &List_dir, i-- );
- X continue;
- X case ENTRY_SPECIAL: /* entry is a special file */
- X message( MSSG_NOTAFIL, entryp );
- X rmv_entry( &List_dir, i-- );
- X continue;
- X default: /* stat error */
- X message( MSSG_STAT, entryp );
- X rmv_entry( &List_dir, i-- );
- X continue;
- X }
- X if ( entryp->mtime == sbuf.st_mtime )
- X continue;
- X if ( scan_directory( entryp->name ) != 0 ) {
- X message( MSSG_OPEN, entryp );
- X rmv_entry( &List_dir, i-- );
- X }
- X entryp->mtime = sbuf.st_mtime;
- X }
- X }
- X
- X
- X /*
- X * End of checking loop.
- X */
- X {
- X extern unsigned sleep();
- X (void) fflush(stdout);
- X (void) sleep(SLEEP_TIME);
- X }
- X
- X }
- X
- X /*NOTREACHED*/
- X
- X}
- X
- END_OF_FILE_xtail.c
- size="`wc -c < xtail.c`"
- if test 8883 -ne "$size" ; then
- echo "xtail.c: extraction error - got $size chars"
- fi
- fi
- if test -f entryfuncs.c -a "$1" != "-c" ; then
- echo "entryfuncs.c: file exists - will not be overwritten"
- else
- echo "x - entryfuncs.c (file 4 of 7, 5152 chars)"
- sed -e 's/^X//' << 'END_OF_FILE_entryfuncs.c' > entryfuncs.c
- X/*
- X * @(#) entryfuncs.c 2.1 89/07/26 19:16:49
- X *
- X * Package: xtail version 2
- X * File: entryfuncs.c
- X * Description: procedures to manage individual entries
- X *
- X * Mon Jul 10 02:56:22 1989 - Chip Rosenthal <chip@vector.Dallas.TX.US>
- X * Original composition.
- X */
- X
- X#ifndef LINT
- Xstatic char SCCSID[] = "@(#) entryfuncs.c 2.1 89/07/26 19:16:49";
- X#endif
- X
- X#include <stdio.h>
- X#include <fcntl.h>
- X#include <sys/types.h>
- X#include <sys/stat.h>
- X#include <sys/errno.h>
- X#include "xtail.h"
- X
- X#ifdef M_XENIX
- X# undef NULL
- X# define NULL 0
- X#endif
- X
- Xextern int errno;
- X
- X
- Xstatic struct entry_descrip *E_append(listp,entryp)
- Xstruct entry_list *listp;
- Xstruct entry_descrip *entryp;
- X{
- X if ( listp->num >= MAX_ENTRIES ) {
- X (void) fprintf(stderr,"%s: too many entries (%d max)\n",
- X entryp->name, MAX_ENTRIES);
- X (void) exit(2);
- X }
- X listp->list[listp->num++] = entryp;
- X Sorted = FALSE;
- X return entryp;
- X}
- X
- X
- Xstatic void E_remove(listp,entryno)
- Xstruct entry_list *listp;
- Xint entryno;
- X{
- X while ( ++entryno < listp->num )
- X listp->list[entryno-1] = listp->list[entryno];
- X --listp->num;
- X Sorted = FALSE;
- X}
- X
- X
- Xstatic char *list_name(listp) /* for debug output only */
- Xstruct entry_list *listp;
- X{
- X if ( listp == &List_file ) return "<file>";
- X if ( listp == &List_dir ) return "<dir>";
- X if ( listp == &List_zap ) return "<zap>";
- X return "?unknown?";
- X}
- X
- X
- X/*
- X * Create a new entry description and append it to a list.
- X */
- Xstruct entry_descrip *new_entry(listp,name)
- Xstruct entry_list *listp;
- Xchar *name;
- X{
- X struct entry_descrip *entryp;
- X static char malloc_error[] = "malloc: out of space\n";
- X extern char *strcpy(), *malloc();
- X
- X Dprintf(stderr, ">>> creating entry '%s' on %s list\n",
- X name, list_name(listp));
- X
- X entryp = (struct entry_descrip *) malloc( sizeof(struct entry_descrip) );
- X if ( entryp == NULL ) {
- X (void) fputs(malloc_error,stderr);
- X (void) exit(2);
- X }
- X
- X entryp->name = malloc( (unsigned) strlen(name) + 1 );
- X if ( entryp->name == NULL ) {
- X (void) fputs(malloc_error,stderr);
- X (void) exit(2);
- X }
- X (void) strcpy(entryp->name,name);
- X
- X entryp->fd = 0;
- X entryp->size = 0;
- X entryp->mtime = 0;
- X
- X return E_append(listp,entryp);
- X}
- X
- X
- X/*
- X * Remove an entry from a list and free up its space.
- X */
- Xvoid rmv_entry(listp,entryno)
- Xstruct entry_list *listp;
- Xint entryno;
- X{
- X struct entry_descrip *entryp = listp->list[entryno];
- X extern void free();
- X
- X Dprintf(stderr, ">>> removing entry '%s' from %s list\n",
- X listp->list[entryno]->name, list_name(listp));
- X E_remove(listp,entryno);
- X if ( entryp->fd > 0 )
- X (void) close(entryp->fd);
- X free( entryp->name );
- X free( (char *) entryp );
- X}
- X
- X
- X/*
- X * Move an entry from one list to another.
- X * In addition we close up the entry if appropriate.
- X */
- Xvoid move_entry(dst_listp,src_listp,src_entryno)
- Xstruct entry_list *dst_listp;
- Xstruct entry_list *src_listp;
- Xint src_entryno;
- X{
- X struct entry_descrip *entryp = src_listp->list[src_entryno];
- X
- X Dprintf(stderr, ">>> moving entry '%s' from %s list to %s list\n",
- X src_listp->list[src_entryno]->name,
- X list_name(src_listp), list_name(dst_listp));
- X if ( entryp->fd > 0 ) {
- X (void) close(entryp->fd);
- X entryp->fd = 0;
- X }
- X E_remove(src_listp,src_entryno);
- X (void) E_append(dst_listp,entryp);
- X if ( Reset_status ) {
- X entryp->size = 0;
- X entryp->mtime = 0;
- X }
- X}
- X
- X
- X/*
- X * Get the inode status for an entry.
- X * Returns code describing the status of the entry.
- X */
- Xint stat_entry(listp,entryno,sbuf)
- Xstruct entry_list *listp;
- Xint entryno;
- Xregister struct stat *sbuf;
- X{
- X register int status;
- X register struct entry_descrip *entryp = listp->list[entryno];
- X static int my_gid = -1;
- X static int my_uid = -1;
- X
- X if ( my_gid < 0 ) {
- X my_gid = getegid();
- X my_uid = geteuid();
- X }
- X
- X status =
- X ( entryp->fd > 0 ? fstat(entryp->fd,sbuf) : stat(entryp->name,sbuf) );
- X
- X if ( status != 0 )
- X return ( errno == ENOENT ? ENTRY_ZAP : ENTRY_ERROR );
- X
- X if (
- X ( ( sbuf->st_mode & 0004 ) == 0 ) &&
- X ( ( sbuf->st_mode & 0040 ) == 0 || sbuf->st_gid != my_gid ) &&
- X ( ( sbuf->st_mode & 0400 ) == 0 || sbuf->st_uid != my_uid )
- X ) {
- X errno = EACCES;
- X return ENTRY_ERROR;
- X }
- X
- X switch ( sbuf->st_mode & S_IFMT ) {
- X case S_IFREG: return ENTRY_FILE;
- X case S_IFDIR: return ENTRY_DIR;
- X default: return ENTRY_SPECIAL;
- X }
- X
- X /*NOTREACHED*/
- X}
- X
- X
- X/*
- X * Open an entry.
- X * Returns 0 if the open is successful, else returns errno. In the case
- X * of an error, an appropriate diagnostic will be printed, and the entry
- X * will be moved or deleted as required. If the entry is already opened,
- X * then no action will occur and 0 will be returned.
- X */
- Xint open_entry(listp,entryno)
- Xstruct entry_list *listp;
- Xint entryno;
- X{
- X struct entry_descrip *entryp = listp->list[entryno];
- X
- X if ( entryp->fd > 0 )
- X return 0;
- X
- X Dprintf(stderr, ">>> opening entry '%s' on %s list\n",
- X listp->list[entryno]->name, list_name(listp));
- X if ( (entryp->fd=open(entryp->name,O_RDONLY)) > 0 )
- X return 0;
- X
- X if ( errno == ENOENT ) {
- X message( MSSG_ZAPPED, entryp );
- X move_entry( &List_zap, listp, entryno );
- X } else {
- X message( MSSG_OPEN, entryp );
- X rmv_entry( listp, entryno );
- X }
- X return -1;
- X}
- X
- X
- END_OF_FILE_entryfuncs.c
- size="`wc -c < entryfuncs.c`"
- if test 5152 -ne "$size" ; then
- echo "entryfuncs.c: extraction error - got $size chars"
- fi
- fi
- if test -f miscfuncs.c -a "$1" != "-c" ; then
- echo "miscfuncs.c: file exists - will not be overwritten"
- else
- echo "x - miscfuncs.c (file 5 of 7, 5423 chars)"
- sed -e 's/^X//' << 'END_OF_FILE_miscfuncs.c' > miscfuncs.c
- X/*
- X * @(#) miscfuncs.c 2.1 89/07/26 19:16:50
- X *
- X * Package: xtail version 2
- X * File: miscfuncs.c
- X * Description: miscelaneous support procedures
- X *
- X * Mon Jul 10 02:56:22 1989 - Chip Rosenthal <chip@vector.Dallas.TX.US>
- X * Original composition.
- X */
- X
- X#ifndef LINT
- Xstatic char SCCSID[] = "@(#) miscfuncs.c 2.1 89/07/26 19:16:50";
- X#endif
- X
- X#include <stdio.h>
- X#include <fcntl.h>
- X#include <time.h>
- X#include <sys/types.h>
- X#include <sys/stat.h>
- X#include "xtail.h"
- X
- X#ifdef M_XENIX
- X# undef NULL
- X# define NULL 0
- X#endif
- X
- X/*
- X * How come the portable directory routines are so !$*&@# unportable?
- X */
- X#ifdef DIR_XENIX
- X# include <sys/ndir.h>
- X typedef struct direct DIRENT;
- X#endif
- X#ifdef DIR_BSD
- X# include <ndir.h>
- X typedef struct direct DIRENT;
- X#endif
- X#ifdef DIR_SYSV
- X# include <dirent.h>
- X typedef struct dirent DIRENT;
- X#endif
- X
- Xextern int errno;
- Xextern char *sys_errlist[];
- X
- X
- X/*
- X * Scan a directory for files not currently on a list.
- X */
- Xint scan_directory(dirname)
- Xchar *dirname;
- X{
- X register int i;
- X register DIRENT *dp;
- X register struct entry_descrip **elist, *entryp;
- X char *basename;
- X struct stat sbuf;
- X DIR *dirp;
- X static char pathname[MAXNAMLEN];
- X extern char *strcpy(), *strcat();
- X
- X Dprintf(stderr, ">>> scanning directory '%s'\n", dirname);
- X if ( (dirp=opendir(dirname)) == NULL )
- X return -1;
- X
- X (void) strcat( strcpy(pathname,dirname), "/" );
- X basename = pathname + strlen(pathname);
- X
- X#define SKIP_DIR(D) \
- X ( D[0] == '.' && ( D[1] == '\0' || ( D[1] == '.' && D[2] == '\0' ) ) )
- X
- X while ( (dp=readdir(dirp)) != NULL ) {
- X
- X if ( SKIP_DIR(dp->d_name) )
- X continue;
- X (void) strcpy( basename, dp->d_name );
- X if ( stat(pathname,&sbuf) != 0 )
- X continue;
- X if ( (sbuf.st_mode&S_IFMT) != S_IFREG )
- X continue;
- X
- X for ( i=List_file.num, elist=List_file.list ; i > 0 ; --i, ++elist ) {
- X if ( strcmp( (*elist)->name, pathname ) == 0 )
- X break;
- X }
- X if ( i > 0 )
- X continue;
- X
- X for ( i=List_zap.num, elist=List_zap.list ; i > 0 ; --i, ++elist ) {
- X if ( strcmp( (*elist)->name, pathname ) == 0 )
- X break;
- X }
- X if ( i > 0 )
- X continue;
- X
- X entryp = new_entry( &List_file, pathname );
- X if ( Reset_status ) {
- X message( MSSG_CREATED, entryp );
- X } else {
- X entryp->mtime = sbuf.st_mtime;
- X entryp->size = sbuf.st_size;
- X }
- X
- X }
- X
- X (void) closedir(dirp);
- X return 0;
- X
- X}
- X
- X
- X/*
- X * Compare mtime of two entries. Used by the "qsort()" in "fixup_open_files()".
- X */
- Xstatic int ecmp(ep1,ep2)
- Xregister struct entry_descrip **ep1, **ep2;
- X{
- X return ( (*ep2)->mtime - (*ep1)->mtime );
- X}
- X
- X/*
- X * Manage the open files.
- X * A small number of entries in "List_file" are kept open to minimize
- X * the overhead in checking for changes. The strategy is to make sure
- X * the MAX_OPEN most recently modified files are all open.
- X */
- Xvoid fixup_open_files()
- X{
- X register int i;
- X register struct entry_descrip **elist;
- X extern void qsort();
- X
- X Dprintf(stderr, ">>> resorting file list\n");
- X (void) qsort(
- X (char *) List_file.list,
- X List_file.num,
- X sizeof(struct entry_descrip *),
- X ecmp
- X );
- X Sorted = TRUE;
- X
- X /*
- X * Start at the end of the list.
- X */
- X i = List_file.num - 1;
- X elist = &List_file.list[i];
- X
- X /*
- X * All the files at the end of the list should be closed.
- X */
- X for ( ; i >= MAX_OPEN ; --i, --elist ) {
- X if ( (*elist)->fd > 0 ) {
- X (void) close( (*elist)->fd );
- X (*elist)->fd = 0;
- X }
- X }
- X
- X /*
- X * The first MAX_OPEN files in the list should be open.
- X */
- X for ( ; i >= 0 ; --i, --elist ) {
- X if ( (*elist)->fd <= 0 )
- X (void) open_entry( &List_file, i );
- X }
- X
- X}
- X
- X
- X/*
- X * Standard message interface.
- X * There are two reasons for this message interface. First, it provides
- X * consistent diagnostics for all the messages. Second, it manages the
- X * filename banner display whenever we switch to a different file.
- X * Warning - "errno" is used in some of the messages, so care must be
- X * taken not to step on it before message() can be called.
- X */
- Xvoid message(sel,e)
- Xint sel;
- Xstruct entry_descrip *e;
- X{
- X static char *ofile = NULL;
- X
- X /*
- X * Don't display the file banner if the file hasn't changed since last time.
- X */
- X if ( sel == MSSG_BANNER && ofile != NULL && strcmp(ofile,e->name) == 0 )
- X return;
- X
- X /*
- X * Make sure the message selector is within range.
- X */
- X if ( sel < 0 || sel > MSSG_UNKNOWN )
- X sel = MSSG_UNKNOWN;
- X
- X /*
- X * Display the message.
- X */
- X if ( mssg_list[sel] != NULL )
- X (void) printf(mssg_list[sel], e->name, sys_errlist[errno]);
- X
- X ofile = ( sel == MSSG_BANNER ? e->name : NULL );
- X}
- X
- X
- X/*
- X * Display currently opened files.
- X */
- Xvoid show_status()
- X{
- X int i, n;
- X struct tm *tp;
- X static char *monname[] = {
- X "Jan", "Feb", "Mar", "Apr", "May", "Jun",
- X "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
- X };
- X extern struct tm *localtime();
- X
- X (void) printf("\n*** recently changed files ***\n");
- X for ( i = 0, n = 0 ; i < List_file.num ; ++i ) {
- X if ( List_file.list[i]->fd > 0 ) {
- X tp = localtime(&List_file.list[i]->mtime);
- X (void) printf("%4d %2d-%3s-%02d %02d:%02d:%02d %s\n",
- X ++n,
- X tp->tm_mday, monname[tp->tm_mon], tp->tm_year,
- X tp->tm_hour, tp->tm_min, tp->tm_sec,
- X List_file.list[i]->name
- X );
- X }
- X }
- X
- X (void) printf(
- X "currently watching: %d files %d dirs %d unknown entries\n",
- X List_file.num, List_dir.num, List_zap.num);
- X
- X message( MSSG_NONE, (struct entry_descrip *) NULL );
- X
- X}
- X
- END_OF_FILE_miscfuncs.c
- size="`wc -c < miscfuncs.c`"
- if test 5423 -ne "$size" ; then
- echo "miscfuncs.c: extraction error - got $size chars"
- fi
- fi
- if test -f Makefile -a "$1" != "-c" ; then
- echo "Makefile: file exists - will not be overwritten"
- else
- echo "x - Makefile (file 6 of 7, 2046 chars)"
- sed -e 's/^X//' << 'END_OF_FILE_Makefile' > Makefile
- X
- X# @(#) Makefile 2.1 89/07/26 19:15:39
- X# Makefile for "xtail" (generated by /local/bin/makemake version 1.00.07)
- X# Created by bin@vector on Wed Jul 26 17:36:37 CDT 1989
- X
- XSHELL = /bin/sh
- XCC = cc
- XDEFS =
- XCOPTS = -O
- XLOPTS =
- XLIBS = -lx
- XDEBUG = -g -DDEBUG
- XLINTFLAGS = -DLINT
- X
- XTARG = xtail
- XOTHERS =
- X
- XSRCS = xtail.c entryfuncs.c miscfuncs.c
- X
- XOBJS = xtail.o entryfuncs.o miscfuncs.o
- X
- X# Any edits below this line will be lost if "makemake" is rerun!
- X# Commands may be inserted after the '#%custom' line at the end of this file.
- X
- XCFLAGS = $(COPTS) $(DEFS) # $(DEBUG)
- XLFLAGS = $(LOPTS) # $(DEBUG)
- X
- Xall: $(TARG) $(OTHERS)
- Xinstall: all ; inst Install
- Xclean: ; rm -f $(TARG) $(OBJS) a.out core $(TARG).lint
- Xclobber: clean ; inst -u Install
- Xlint: $(TARG).lint
- X
- X$(TARG): $(OBJS)
- X $(CC) $(LFLAGS) -o $@ $(OBJS) $(LIBS)
- X
- X$(TARG).lint: $(TARG)
- X lint $(LINTFLAGS) $(DEFS) $(SRCS) $(LIBS) > $@
- X
- Xxtail.o: /usr/include/signal.h /usr/include/stdio.h /usr/include/sys/signal.h \
- X /usr/include/sys/stat.h /usr/include/sys/types.h xtail.c \
- X xtail.h
- Xentryfuncs.o: /usr/include/fcntl.h /usr/include/stdio.h \
- X /usr/include/sys/errno.h /usr/include/sys/fcntl.h \
- X /usr/include/sys/lockcmn.h /usr/include/sys/stat.h \
- X /usr/include/sys/types.h entryfuncs.c xtail.h
- Xmiscfuncs.o: /usr/include/fcntl.h /usr/include/stdio.h \
- X /usr/include/sys/fcntl.h /usr/include/sys/lockcmn.h \
- X /usr/include/sys/ndir.h /usr/include/sys/stat.h \
- X /usr/include/sys/types.h /usr/include/time.h miscfuncs.c \
- X xtail.h
- X
- Xmake: ;
- X /local/bin/makemake -i -v1.00.07 -aMakefile \
- X -DSHELL='$(SHELL)' -DCC='$(CC)' -DDEFS='$(DEFS)' \
- X -DCOPTS='$(COPTS)' -DLOPTS='$(LOPTS)' -DLIBS='$(LIBS)' \
- X -DDEBUG='$(DEBUG)' -DLINTFLAGS='$(LINTFLAGS)' \
- X -DOTHERS='$(OTHERS)' $(TARG) $(SRCS)
- X
- X#%custom - commands below this line will be maintained if 'makemake' is rerun
- X
- XARLIST = README xtail.h xtail.c entryfuncs.c miscfuncs.c Makefile xtail.man
- X
- Xshar: xtail.shar
- Xxtail.shar: $(ARLIST) ; shar $(ARLIST) > xtail.shar
- X
- END_OF_FILE_Makefile
- size="`wc -c < Makefile`"
- if test 2046 -ne "$size" ; then
- echo "Makefile: extraction error - got $size chars"
- fi
- fi
- if test -f xtail.man -a "$1" != "-c" ; then
- echo "xtail.man: file exists - will not be overwritten"
- else
- echo "x - xtail.man (file 7 of 7, 1300 chars)"
- sed -e 's/^X//' << 'END_OF_FILE_xtail.man' > xtail.man
- X''' @(#) xtail.man 2.1 89/07/26 19:15:44
- X.TH XTAIL 1L
- X.SH NAME
- Xxtail - Watch the growth of files.
- X.SH SYNTAX
- X.B xtail
- Xentry ...
- X.SH DESCRIPTION
- X.I Xtail
- Xmonitors one or more files, and displays all data written to a file
- Xsince command invocation. It is very useful for monitoring multiple
- Xlogfiles simultaneously.
- X.P
- XIf an
- X.I entry
- Xgiven on the command line is a directory, all files in that directory
- Xwill be monitored, including those created after the
- X.I xtail
- Xinvocation. If an
- X.I entry
- Xgiven on the command line doesn't exist,
- X.I xtail
- Xwill watch for it and monitor it once created. When switching files in
- Xthe display, a banner showing the pathname of the file is printed.
- X.P
- XAn interrupt character (usually CTRL/C or DEL) will display a list of the
- Xmost recently modified files being watched. Send a quit signal
- X(usually CTRL/backslash) to stop
- X.IR xtail .
- X.SH SEE ALSO
- Xtail(1)
- X.SH NOTES
- X.I Xtail
- Xmay be easily confused. For example, if a file is renamed,
- X.I xtail
- Xmay or may not continue to monitor it. If you ask it to monitor a file
- Xmultiple times, it probably will. If you misspell a filename,
- X.I xtail
- Xwill treat it as a nonexistent entry and happily wait for its creation.
- X.P
- XMy favorite use is "xtail /usr/spool/uucp/.Log/*".
- X.SH AUTHOR
- XChip Rosenthal <chip@vector.Dallas.TX.US>
- END_OF_FILE_xtail.man
- size="`wc -c < xtail.man`"
- if test 1300 -ne "$size" ; then
- echo "xtail.man: extraction error - got $size chars"
- fi
- fi
- echo "done - 7 files extracted"
- exit 0
- --- cut here -----------------------------------------------------------------
- --
- Chip Rosenthal / chip@vector.Dallas.TX.US / Dallas Semiconductor / 214-450-5337
- "I wish you'd put that starvation box down and go to bed" - Albert Collins' Mom
-
-
-