home *** CD-ROM | disk | FTP | other *** search
/ Usenet 1994 October / usenetsourcesnewsgroupsinfomagicoctober1994disk2.iso / misc / volume7 / xtail < prev    next >
Encoding:
Text File  |  1989-07-27  |  36.2 KB  |  1,336 lines

  1. Newsgroups: comp.sources.misc
  2. organization: Dallas Semiconductor
  3. subject: v07i108: xtail - a kind of "tail -f" for multiple files
  4. From: allbery@uunet.UU.NET (Brandon S. Allbery - comp.sources.misc)
  5. Reply-To: chip@vector.Dallas.TX.US.UUCP (Chip Rosenthal)
  6.  
  7. Posting-number: Volume 7, Issue 108
  8. Submitted-by: chip@vector.Dallas.TX.US.UUCP (Chip Rosenthal)
  9. Archive-name: xtail
  10.  
  11. "xtail" watches the growth of files.  It is similar to "tail -f", but may
  12. watch many files at once.  The syntax is:
  13.  
  14.     xtail pathname ...
  15.     
  16. "xtail" will monitor all the specified files and display information added
  17. to them.  If you specify a directory name, "xtail" will watch all the
  18. files in that directory - including those created after "xtail" was
  19. started.  If you give "xtail" a name which doesn't exist, it will watch
  20. for the creation of the named entry.  My favorite usage is:
  21.  
  22.     xtail /usr/spool/uucp/.Log/*
  23.  
  24. --- cut here -----------------------------------------------------------------
  25. #! /bin/sh
  26. # this is a "shar" archive - run through "/bin/sh" to extract 7 files:
  27. #   README xtail.h xtail.c entryfuncs.c miscfuncs.c Makefile xtail.man
  28. # Wrapped by bin@vector on Wed Jul 26 19:18:07 CDT 1989
  29. # Unpacking this archive requires:  sed test wc (possibly mkdir)
  30. # Existing files will not be clobbered unless "-c" is specified on the cmd line.
  31. if test -f README -a "$1" != "-c" ; then
  32.     echo "README: file exists - will not be overwritten"
  33. else
  34.     echo "x - README (file 1 of 7, 1709 chars)"
  35.     sed -e 's/^X//' << 'END_OF_FILE_README' > README
  36. X"xtail" watches the growth of files.  It is similar to "tail -f", but may
  37. Xwatch many files at once.  The syntax is:
  38. X
  39. X    xtail pathname ...
  40. X    
  41. X"xtail" will monitor all the specified files and display information added
  42. Xto them.  If you specify a directory name, "xtail" will watch all the
  43. Xfiles in that directory - including those created after "xtail" was
  44. Xstarted.  If you give "xtail" a name which doesn't exist, it will watch
  45. Xfor the creation of the named entry.  My favorite usage is:
  46. X
  47. X    xtail /usr/spool/uucp/.Log/*
  48. X
  49. X"xtail" is distributed with a configuration for SCO XENIX.  It has also
  50. Xbeen tested on MIPS System V.  I took a shot at BSD portability.  The
  51. Xmain difference is how the "directory" support library is accessed.
  52. X
  53. XTo build "xtail":
  54. X
  55. X    - edit the definitions in "xtail.h"
  56. X    - run a "make"
  57. X
  58. XA version of "xtail" was originally posted in alt.sources a few months
  59. Xback.  There are several improvements between this version and the
  60. Xoriginal:
  61. X
  62. X       - the ability to watch directories
  63. X       - the ability to watch entries which don't exist yet
  64. X       - the recently changed files display (given upon SIGINT)
  65. X       - performance improvements
  66. X       - portability improvements
  67. X
  68. XMany of these changes were suggested by David Dykstra <dwd@cbnewsc.ATT.COM>.
  69. XThe idea of keeping files open and use fstat() rather than stat() was
  70. Xsuggested by changes by another poster (sorry, I lost the article so I
  71. Xcan't provide credit).  However, that version kept *everything* open, and
  72. Xthat just eats too many entries in the file table for me.  You can tweak
  73. Xthe values in "xtail.h" to optimize the response/load characteristics of
  74. X"xtail".
  75. X
  76. XChip Rosenthal
  77. X<chip@vector.Dallas.TX.US>
  78. X
  79. X@(#) README 2.1 89/07/26 19:16:34
  80. END_OF_FILE_README
  81.     size="`wc -c < README`"
  82.     if test 1709 -ne "$size" ; then
  83.     echo "README: extraction error - got $size chars"
  84.     fi
  85. fi
  86. if test -f xtail.h -a "$1" != "-c" ; then
  87.     echo "xtail.h: file exists - will not be overwritten"
  88. else
  89.     echo "x - xtail.h (file 2 of 7, 7187 chars)"
  90.     sed -e 's/^X//' << 'END_OF_FILE_xtail.h' > xtail.h
  91. X/*
  92. X * @(#) xtail.h 2.1 89/07/26 19:16:49
  93. X *
  94. X * Package:    xtail version 2
  95. X * File:    xtail.h
  96. X * Description:    header definitions
  97. X *
  98. X * Mon Jul 10 02:56:22 1989 - Chip Rosenthal <chip@vector.Dallas.TX.US>
  99. X *    Original composition.
  100. X */
  101. X
  102. X
  103. X/*****************************************************************************
  104. X *
  105. X * Start of Site-Specific Customizations
  106. X *
  107. X *****************************************************************************/
  108. X
  109. X/*
  110. X * Define one of the following.  It says how to use your "directory" library.
  111. X */
  112. X#define DIR_XENIX    /* include <sys/ndir.h>, use "struct direct"    */
  113. X/*#define DIR_BSD    /* include <ndir.h>, use "struct direct"    */
  114. X/*#define DIR_SYSV    /* include <dirent.h>, use "struct dirent"    */
  115. X
  116. X/*
  117. X * Define one of the following.  It specifies the return type of "signal()".
  118. X */
  119. X#define SIGTYPE        int    /* declare as "int (*signal)()"        */
  120. X/*#define SIGTYPE    void    /* declare as "void (*signal)()"    */
  121. X
  122. X/*
  123. X * STATUS_ENAB    If defined, a SIGINT causes a summary of the opened files to
  124. X *        be displayed, and a SIGQUIT terminates the program.  If not
  125. X *        defined, these signals act normally.
  126. X */
  127. X#define STATUS_ENAB    /**/
  128. X
  129. X/*
  130. X * SLEEP_TIME    An iteration through the checking loop is performed once
  131. X *        per this many seconds.
  132. X */
  133. X#define SLEEP_TIME    1
  134. X
  135. X/*
  136. X * MAX_OPEN    This number of most recently changed files is kept open, and
  137. X *        they are checked every iteration through the checking loop.
  138. X *        Keeping these files open improves the performance because we
  139. X *        can use "fstat()" rather than "stat()".  Keeping too many
  140. X *        files open may overflow your open file table, and will reduce
  141. X *        performance by checking more files more frequently.
  142. X */
  143. X#define MAX_OPEN    6
  144. X
  145. X/*
  146. X * CHECK_COUNT    Everything besides open files are checked once per this
  147. X *        many iterations through the checking loop.
  148. X */
  149. X#define CHECK_COUNT    5
  150. X
  151. X/*
  152. X * MAX_ENTRIES    The maximum number of entries in any list.  It can be fairly
  153. X *        large -- each unused entry only eats 3*sizeof(char*) bytes.
  154. X */
  155. X#define MAX_ENTRIES    512
  156. X
  157. X
  158. X/*****************************************************************************
  159. X *
  160. X * End of Site-Specific Customizations
  161. X *
  162. X *****************************************************************************/
  163. X
  164. X
  165. X#define TRUE 1
  166. X#define FALSE 0
  167. X
  168. X#define Dprintf        if ( !Debug ) ; else (void) fprintf
  169. X
  170. X
  171. X/*
  172. X * Codes returned by the "stat_entry()" procedure.
  173. X */
  174. X#define ENTRY_ERROR    0    /* stat error or permissions error    */
  175. X#define ENTRY_SPECIAL    1    /* entry is a special file        */
  176. X#define ENTRY_FILE    2    /* entry is a regular file        */
  177. X#define ENTRY_DIR    3    /* entry is a directory            */
  178. X#define ENTRY_ZAP    4    /* specified entry doesn't exist    */
  179. X
  180. X
  181. X/*
  182. X * Diagnostic message codes.
  183. X *   The ordering of codes must correspond to the "mssg_list[]" defined below.
  184. X */
  185. X#define MSSG_NONE    0    /* no message - just reset header    */
  186. X#define MSSG_BANNER    1    /* display banner for file output    */
  187. X#define MSSG_CREATED    2    /* file has been created        */
  188. X#define MSSG_ZAPPED    3    /* file has been deleted        */
  189. X#define MSSG_TRUNC    4    /* file has been truncated        */
  190. X#define MSSG_NOTAFIL    5    /* error - not a regular file or dir    */
  191. X#define MSSG_STAT    6    /* error - stat() failed        */
  192. X#define MSSG_OPEN    7    /* error - open() failed        */
  193. X#define MSSG_SEEK    8    /* error - lseek() failed        */
  194. X#define MSSG_READ    9    /* error - read() failed        */
  195. X#define MSSG_UNKNOWN    10    /* unknown error - must be last in list */
  196. X
  197. X
  198. X#ifdef INTERN
  199. X#   define EXTERN
  200. X#else
  201. X#   define EXTERN extern
  202. X#endif
  203. X
  204. X
  205. X/*
  206. X * Each item we are watching is stored in a (struct entry_descrip).  These
  207. X * entries are placed in lists, which are managed as (struct entry_list).
  208. X *
  209. X * There are three lists maintained:
  210. X *
  211. X * List_file    All of the regular files we are watching.  We will try to
  212. X *        keep the MAX_OPEN most recently modified files open, and
  213. X *        they will be checked more frequently.
  214. X *
  215. X * List_dir    All of the directories we are watching.  If a file is created
  216. X *        in one of these directories, we will add it to "List_file".
  217. X *
  218. X * List_zap    All the entries which don't exist.  When something appears
  219. X *        under one of these names, the entry will be moved to either
  220. X *        "List_file" or "List_dir", as appropriate.
  221. X */
  222. X
  223. Xstruct entry_descrip {
  224. X    char *name;        /* pathname to the entry            */
  225. X    int fd;        /* opened fd, or <= 0 if not opened        */
  226. X    long size;        /* size of entry last time checked        */
  227. X    long mtime;        /* modification time last time checked        */
  228. X};
  229. X
  230. Xstruct entry_list {
  231. X    struct entry_descrip *list[MAX_ENTRIES];
  232. X    int num;
  233. X};
  234. X
  235. X/*
  236. X * The lists of entries being watched.
  237. X */
  238. XEXTERN struct entry_list List_file;    /* regular files        */
  239. XEXTERN struct entry_list List_dir;    /* directories            */
  240. XEXTERN struct entry_list List_zap;    /* nonexistent entries        */
  241. X
  242. X
  243. X/*
  244. X * List sorting status.
  245. X *   This flag indicates that "List_file" is sorted, and the right entries
  246. X *   are open.  Anything which possibly effects this state (e.g. an entry
  247. X *   is added to "List_file", the mtime of a file is changed, etc.) must set
  248. X *   this flag FALSE.  We will periodically check this flag and call the
  249. X *   "fixup_open_files()" procedure to resort and organize the list.
  250. X */
  251. XEXTERN int Sorted;
  252. X
  253. X
  254. X/*
  255. X * Entry status control flag.
  256. X *   The procedures which manipulate entries will reset the status information
  257. X *   if this flag is TRUE.  When initializing the lists we want this FALSE.
  258. X *   For example, consider the file size.  When initializing we want to use
  259. X *   the current file size, otherwise we would dump the file from the beginning.
  260. X *   However, later when we notice things are created we want to reset the
  261. X *   size to zero so that we do dump from the beginning.
  262. X */
  263. XEXTERN int Reset_status;
  264. X
  265. X
  266. X/*
  267. X * Debugging output flag.
  268. X */
  269. XEXTERN int Debug;
  270. X
  271. X
  272. X/*
  273. X * Diagnostic messages produced by the "message()" procedure.
  274. X *   The first "%s" is the entry name.  The second "%s" is the errno descrip.
  275. X */
  276. X#ifdef INTERN
  277. X    char *mssg_list[] = {
  278. X    NULL,                            /*MSSG_NONE   */
  279. X    "\n*** %s ***\n",                    /*MSSG_BANNER */
  280. X    "\n*** '%s' has been created ***\n",            /*MSSG_CREATED*/
  281. X    "\n*** '%s' has been deleted ***\n",            /*MSSG_ZAPPED */
  282. X    "\n*** '%s' has been truncated - rewinding ***\n",    /*MSSG_TRUNC  */
  283. X    "\n*** error - '%s' not a file or dir - removed ***\n",    /*MSSG_NOTAFIL*/
  284. X    "\n*** error - couldn't stat '%s' (%s) - removed ***\n",/*MSSG_STAT   */
  285. X    "\n*** error - couldn't open '%s' (%s) - removed ***\n",/*MSSG_OPEN   */
  286. X    "\n*** error - couldn't seek '%s' (%s) - removed ***\n",/*MSSG_SEEK   */
  287. X    "\n*** error - couldn't read '%s' (%s) - removed ***\n",/*MSSG_READ   */
  288. X    "\n*** error - unknown error on file '%s' ***\n",    /*MSSG_UNKNOWN*/
  289. X    };
  290. X#else
  291. X    extern char *mssg_list[];
  292. X#endif
  293. X
  294. X
  295. X/*
  296. X * Entry managment procedures.
  297. X */
  298. Xstruct entry_descrip *new_entry();    /* create a new entry and add to list */
  299. Xvoid move_entry();            /* move an entry between lists          */
  300. Xvoid rmv_entry();            /* remove an entry from a list          */
  301. Xint stat_entry();            /* get the inode status for an entry  */
  302. Xint open_entry();            /* open an entry              */
  303. X
  304. X/*
  305. X * Miscelaneous procedures.
  306. X */
  307. Xvoid fixup_open_files();        /* manage the open files          */
  308. Xint scan_directory();            /* scan a dir for files not on a list */
  309. Xvoid message();                /* standard message interface          */
  310. Xvoid show_status();            /* display currently opened files     */
  311. X
  312. END_OF_FILE_xtail.h
  313.     size="`wc -c < xtail.h`"
  314.     if test 7187 -ne "$size" ; then
  315.     echo "xtail.h: extraction error - got $size chars"
  316.     fi
  317. fi
  318. if test -f xtail.c -a "$1" != "-c" ; then
  319.     echo "xtail.c: file exists - will not be overwritten"
  320. else
  321.     echo "x - xtail.c (file 3 of 7, 8883 chars)"
  322.     sed -e 's/^X//' << 'END_OF_FILE_xtail.c' > xtail.c
  323. X/*
  324. X * @(#) xtail.c 2.1 89/07/26 19:15:42
  325. X *
  326. X * Package:    xtail version 2
  327. X * File:    xtail.c
  328. X * Description:    main program
  329. X *
  330. X * Mon Jul 10 02:56:22 1989 - Chip Rosenthal <chip@vector.Dallas.TX.US>
  331. X *    Original composition.
  332. X */
  333. X
  334. X#ifndef LINT
  335. Xstatic char SCCSID[] = "@(#) xtail.c 2.1 89/07/26 19:15:42";
  336. X#endif
  337. X
  338. X#include <stdio.h>
  339. X#include <signal.h>
  340. X#include <sys/types.h>
  341. X#include <sys/stat.h>
  342. X#define  INTERN
  343. X#include "xtail.h"
  344. X
  345. X#ifdef M_XENIX
  346. X# undef  NULL
  347. X# define NULL 0
  348. X#endif
  349. X
  350. X
  351. Xint sigcaught = 0;
  352. X
  353. XSIGTYPE sigcatcher(sig)
  354. Xint sig;
  355. X{
  356. X    extern SIGTYPE (*signal)();
  357. X    if ( sig == SIGQUIT )
  358. X    (void) exit(0);
  359. X    sigcaught = sig;
  360. X#ifdef STATUS_ENAB
  361. X    (void) signal(SIGINT,sigcatcher);
  362. X    (void) signal(SIGQUIT,sigcatcher);
  363. X#endif
  364. X}
  365. X
  366. X
  367. Xmain(argc,argv)
  368. Xint argc;
  369. Xchar *argv[];
  370. X{
  371. X    int open_files_only, already_open, iteration, i;
  372. X    struct entry_descrip *entryp;
  373. X    struct stat sbuf;
  374. X
  375. X    /* 
  376. X     * Initialize.
  377. X     */
  378. X    List_file.num = 0;
  379. X    List_dir.num = 0;
  380. X    List_zap.num = 0;
  381. X    Sorted = FALSE;
  382. X    Reset_status = FALSE;
  383. X    Debug = FALSE;
  384. X    sigcatcher(0);
  385. X
  386. X
  387. X    /*
  388. X     * Place all of the entries onto lists.
  389. X     */
  390. X    for ( i = 1 ; i < argc ; ++i )  {
  391. X
  392. X    if ( i == 1 && strcmp(argv[i],"-D") == 0 ) {
  393. X        Debug = TRUE;
  394. X        continue;
  395. X    }
  396. X
  397. X    /*
  398. X     * Temporarily throw this entry onto the end of the zapped list.
  399. X     */
  400. X    entryp = new_entry( &List_zap, argv[i] );
  401. X
  402. X    /*
  403. X     * Stat the file and get it to its proper place.
  404. X     */
  405. X    switch ( stat_entry( &List_zap, List_zap.num-1, &sbuf ) ) {
  406. X
  407. X    case ENTRY_FILE:        /* move entry to file list    */
  408. X        move_entry( &List_file, &List_zap, List_zap.num-1 );
  409. X        entryp->size = sbuf.st_size;
  410. X        entryp->mtime = sbuf.st_mtime;
  411. X        break;
  412. X
  413. X    case ENTRY_DIR:            /* move entry to dir list    */
  414. X        move_entry( &List_dir, &List_zap, List_zap.num-1 );
  415. X        entryp->size = sbuf.st_size;
  416. X        entryp->mtime = sbuf.st_mtime;
  417. X        if ( scan_directory( entryp->name ) != 0 ) {
  418. X        message( MSSG_OPEN, entryp );
  419. X        rmv_entry( &List_dir, List_dir.num-1 );
  420. X        }
  421. X        break;
  422. X
  423. X    case ENTRY_ZAP:            /* keep entry on zap list    */
  424. X        break;
  425. X
  426. X    case ENTRY_SPECIAL:        /* entry is a special file    */
  427. X        message( MSSG_NOTAFIL, entryp );
  428. X        rmv_entry( &List_zap, List_zap.num-1 );
  429. X        break;
  430. X
  431. X    default:            /* stat error            */
  432. X        message( MSSG_STAT, entryp );
  433. X        rmv_entry( &List_zap, List_zap.num-1 );
  434. X        break;
  435. X
  436. X    }
  437. X
  438. X    }
  439. X
  440. X    /*
  441. X     * Make sure we are watching something reasonable.
  442. X     */
  443. X    if ( List_file.num == 0 ) {
  444. X    if ( List_dir.num == 0 && List_zap.num == 0 ) {
  445. X        (void) fprintf(stderr, "%s: no valid entries specified\n", argv[0]);
  446. X        (void) exit(1);
  447. X    }
  448. X    (void) puts("\n*** warning - no files are being watched ***");
  449. X    }
  450. X
  451. X
  452. X    /*
  453. X     * From this point on we want to reset the status of an entry any
  454. X     * time we move it around to another list.
  455. X     */
  456. X    Reset_status = TRUE;
  457. X
  458. X
  459. X    /*
  460. X     * Force a check of everything first time through the loop.
  461. X     */
  462. X    iteration = CHECK_COUNT;
  463. X
  464. X
  465. X    /* 
  466. X     * Loop forever.
  467. X     */
  468. X    for (;;) {
  469. X
  470. X    /*
  471. X     * Once every CHECK_COUNT iterations check everything.
  472. X     * All other times only look at the opened files.
  473. X     */
  474. X    open_files_only = ( ++iteration < CHECK_COUNT );
  475. X    if ( !open_files_only )
  476. X        iteration = 0;
  477. X
  478. X
  479. X    /*
  480. X     * Make sure that the most recently modified files are open.
  481. X     */
  482. X    if ( !Sorted )
  483. X        fixup_open_files();
  484. X
  485. X
  486. X    /*
  487. X     * Display what we are watching if a SIGINT was caught.
  488. X     */
  489. X    if ( sigcaught ) {
  490. X        show_status();
  491. X        sigcatcher(0);
  492. X    }
  493. X
  494. X
  495. X    /*
  496. X     * Go through all of the files looking for changes.
  497. X     */
  498. X    Dprintf(stderr, ">>> checking files list (%s)\n",
  499. X        ( open_files_only ? "open files only" : "all files" ));
  500. X    for ( i = 0 ; i < List_file.num ; ++i ) {
  501. X
  502. X        entryp = List_file.list[i];
  503. X        already_open = ( entryp->fd > 0 ) ;
  504. X
  505. X        /*
  506. X         * Ignore closed files except every CHECK_COUNT iterations.
  507. X         */
  508. X        if ( !already_open && open_files_only )
  509. X        continue;
  510. X
  511. X        /*
  512. X         * Get the status of this file.
  513. X         */
  514. X        switch ( stat_entry( &List_file, i, &sbuf ) ) {
  515. X        case ENTRY_FILE:        /* got status OK        */
  516. X        break;
  517. X        case ENTRY_DIR:        /* huh??? it's now a dir    */
  518. X        move_entry( &List_dir, &List_file, i-- );
  519. X        continue;
  520. X        case ENTRY_ZAP:        /* entry has been deleted    */
  521. X        message( MSSG_ZAPPED, entryp );
  522. X        move_entry( &List_zap, &List_file, i-- );
  523. X        continue;
  524. X        case ENTRY_SPECIAL:        /* entry is a special file    */
  525. X        message( MSSG_NOTAFIL, entryp );
  526. X        rmv_entry( &List_file, i-- );
  527. X        continue;
  528. X        default:            /* stat error            */
  529. X        message( MSSG_STAT, entryp );
  530. X        rmv_entry( &List_file, i-- );
  531. X        continue;
  532. X        }
  533. X
  534. X
  535. X        /*
  536. X         * See if an opened file has been deleted.
  537. X         */
  538. X        if ( already_open && sbuf.st_nlink == 0 ) {
  539. X        message( MSSG_ZAPPED, entryp );
  540. X        move_entry( &List_zap, &List_file, i-- );
  541. X        continue;
  542. X        }
  543. X
  544. X        /*
  545. X         * If nothing has changed then continue on.
  546. X         */
  547. X        if ( entryp->size==sbuf.st_size && entryp->mtime==sbuf.st_mtime )
  548. X        continue;
  549. X
  550. X        /*
  551. X         * If the file isn't already open, then do so.
  552. X         *   Note -- it is important that we call "fixup_open_files()"
  553. X         *   at the end of the loop to make sure too many files don't
  554. X         *   stay opened.
  555. X         */
  556. X        if ( !already_open && open_entry( &List_file, i ) != 0 ) {
  557. X        --i;
  558. X        continue;
  559. X        }
  560. X
  561. X        /*
  562. X         * See if the file has been truncated.
  563. X         */
  564. X        if ( sbuf.st_size < entryp->size ) {
  565. X        message( MSSG_TRUNC, entryp );
  566. X        entryp->size = 0;
  567. X        }
  568. X
  569. X        /*
  570. X         * Seek to where the changes begin.
  571. X         */
  572. X        {
  573. X        extern long lseek();
  574. X        if ( lseek( entryp->fd, entryp->size, 0 ) < 0 ) {
  575. X            message( MSSG_SEEK, entryp );
  576. X            rmv_entry( &List_file, i-- );
  577. X            continue;
  578. X        }
  579. X        }
  580. X
  581. X        /*
  582. X         * Dump the recently added info.
  583. X         */
  584. X        {
  585. X        int nb;
  586. X            static char buf[BUFSIZ];
  587. X        message( MSSG_BANNER, entryp );
  588. X        while ( ( nb = read( entryp->fd, buf, sizeof(buf) ) ) > 0 ) {
  589. X            (void) fwrite( buf, sizeof(char), (unsigned) nb, stdout );
  590. X            entryp->size += nb;
  591. X        }
  592. X        if ( nb < 0 ) {
  593. X            message( MSSG_READ, entryp );
  594. X            rmv_entry( &List_file, i-- );
  595. X            continue;
  596. X        }
  597. X        }
  598. X
  599. X        /*
  600. X         * Update the modification time.
  601. X         */
  602. X        entryp->mtime = sbuf.st_mtime;
  603. X
  604. X        /*
  605. X         * Since we've changed the mtime, the list might no longer be
  606. X         * sorted.  However if this entry is already at the top of the
  607. X         * list then it's OK.
  608. X         */
  609. X        if ( i != 0 )
  610. X        Sorted = FALSE;
  611. X
  612. X        /*
  613. X         * If we've just opened the file then force a resort now to
  614. X         * prevent too many files from being opened.
  615. X         */
  616. X        if ( !already_open )
  617. X        fixup_open_files();
  618. X
  619. X    }
  620. X
  621. X
  622. X    /*
  623. X     * Go through list of nonexistent entries to see if any have appeared.
  624. X     *   This is done only once every CHECK_COUNT iterations.
  625. X     */
  626. X    if ( !open_files_only ) {
  627. X        Dprintf(stderr, ">>> checking zapped list\n");
  628. X        for ( i = 0 ; i < List_zap.num ; ++i ) {
  629. X        entryp = List_zap.list[i];
  630. X        switch ( stat_entry( &List_zap, i, &sbuf ) ) {
  631. X        case ENTRY_FILE:    /* entry has appeared as a file    */
  632. X            message( MSSG_CREATED, entryp );
  633. X            move_entry( &List_file, &List_zap, i-- );
  634. X            break;
  635. X        case ENTRY_DIR:        /* entry has appeared as a dir    */
  636. X            message( MSSG_CREATED, entryp );
  637. X            move_entry( &List_dir, &List_zap, i-- );
  638. X            break;
  639. X        case ENTRY_ZAP:        /* entry still doesn't exist    */
  640. X            break;
  641. X        case ENTRY_SPECIAL:    /* entry is a special file    */
  642. X            message( MSSG_NOTAFIL, entryp );
  643. X                rmv_entry( &List_zap, i-- );
  644. X            break;
  645. X        default:        /* error - entry removed    */
  646. X                message( MSSG_STAT, entryp );
  647. X                rmv_entry( &List_zap, i-- );
  648. X            break;
  649. X        }
  650. X        }
  651. X    }
  652. X
  653. X
  654. X    /*
  655. X     * Go through the list of dirs to see if any new files were created.
  656. X     *   This is done only once every CHECK_COUNT iterations.
  657. X     */
  658. X    if ( !open_files_only ) {
  659. X        Dprintf(stderr, ">>> checking directory list\n");
  660. X        for ( i = 0 ; !open_files_only && i < List_dir.num ; ++i ) {
  661. X        entryp = List_dir.list[i];
  662. X        switch ( stat_entry( &List_dir, i, &sbuf ) ) {
  663. X        case ENTRY_DIR:        /* got status OK        */
  664. X            break;
  665. X        case ENTRY_FILE:    /* huh??? it's now a reg file    */
  666. X            move_entry( &List_file, &List_dir, i-- );
  667. X            continue;
  668. X        case ENTRY_ZAP:        /* entry has been deleted    */
  669. X                message( MSSG_ZAPPED, entryp );
  670. X                move_entry( &List_zap, &List_dir, i-- );
  671. X            continue;
  672. X        case ENTRY_SPECIAL:    /* entry is a special file    */
  673. X            message( MSSG_NOTAFIL, entryp );
  674. X            rmv_entry( &List_dir, i-- );
  675. X            continue;
  676. X        default:        /* stat error            */
  677. X            message( MSSG_STAT, entryp );
  678. X            rmv_entry( &List_dir, i-- );
  679. X            continue;
  680. X        }
  681. X        if ( entryp->mtime == sbuf.st_mtime )
  682. X            continue;
  683. X        if ( scan_directory( entryp->name ) != 0 ) {
  684. X            message( MSSG_OPEN, entryp );
  685. X            rmv_entry( &List_dir, i-- );
  686. X        }
  687. X        entryp->mtime = sbuf.st_mtime;
  688. X        }
  689. X    }
  690. X
  691. X
  692. X    /*
  693. X     * End of checking loop.
  694. X     */
  695. X    {
  696. X        extern unsigned sleep();
  697. X        (void) fflush(stdout);
  698. X        (void) sleep(SLEEP_TIME);
  699. X    }
  700. X
  701. X    }
  702. X
  703. X    /*NOTREACHED*/
  704. X
  705. X}
  706. X
  707. END_OF_FILE_xtail.c
  708.     size="`wc -c < xtail.c`"
  709.     if test 8883 -ne "$size" ; then
  710.     echo "xtail.c: extraction error - got $size chars"
  711.     fi
  712. fi
  713. if test -f entryfuncs.c -a "$1" != "-c" ; then
  714.     echo "entryfuncs.c: file exists - will not be overwritten"
  715. else
  716.     echo "x - entryfuncs.c (file 4 of 7, 5152 chars)"
  717.     sed -e 's/^X//' << 'END_OF_FILE_entryfuncs.c' > entryfuncs.c
  718. X/*
  719. X * @(#) entryfuncs.c 2.1 89/07/26 19:16:49
  720. X *
  721. X * Package:    xtail version 2
  722. X * File:    entryfuncs.c
  723. X * Description:    procedures to manage individual entries
  724. X *
  725. X * Mon Jul 10 02:56:22 1989 - Chip Rosenthal <chip@vector.Dallas.TX.US>
  726. X *    Original composition.
  727. X */
  728. X
  729. X#ifndef LINT
  730. Xstatic char SCCSID[] = "@(#) entryfuncs.c 2.1 89/07/26 19:16:49";
  731. X#endif
  732. X
  733. X#include <stdio.h>
  734. X#include <fcntl.h>
  735. X#include <sys/types.h>
  736. X#include <sys/stat.h>
  737. X#include <sys/errno.h>
  738. X#include "xtail.h"
  739. X
  740. X#ifdef M_XENIX
  741. X# undef  NULL
  742. X# define NULL 0
  743. X#endif
  744. X
  745. Xextern int errno;
  746. X
  747. X
  748. Xstatic struct entry_descrip *E_append(listp,entryp)
  749. Xstruct entry_list *listp;
  750. Xstruct entry_descrip *entryp;
  751. X{
  752. X    if ( listp->num >= MAX_ENTRIES ) {
  753. X    (void) fprintf(stderr,"%s: too many entries (%d max)\n",
  754. X        entryp->name, MAX_ENTRIES);
  755. X    (void) exit(2);
  756. X    }
  757. X    listp->list[listp->num++] = entryp;
  758. X    Sorted = FALSE;
  759. X    return entryp;
  760. X}
  761. X
  762. X
  763. Xstatic void E_remove(listp,entryno)
  764. Xstruct entry_list *listp;
  765. Xint entryno;
  766. X{
  767. X    while ( ++entryno < listp->num )
  768. X    listp->list[entryno-1] = listp->list[entryno];
  769. X    --listp->num;
  770. X    Sorted = FALSE;
  771. X}
  772. X
  773. X
  774. Xstatic char *list_name(listp)        /* for debug output only */
  775. Xstruct entry_list *listp;
  776. X{
  777. X    if ( listp == &List_file )    return "<file>";
  778. X    if ( listp == &List_dir )    return "<dir>";
  779. X    if ( listp == &List_zap )    return "<zap>";
  780. X    return "?unknown?";
  781. X}
  782. X
  783. X
  784. X/*
  785. X * Create a new entry description and append it to a list.
  786. X */
  787. Xstruct entry_descrip *new_entry(listp,name)
  788. Xstruct entry_list *listp;
  789. Xchar *name;
  790. X{
  791. X    struct entry_descrip *entryp;
  792. X    static char malloc_error[] = "malloc: out of space\n";
  793. X    extern char *strcpy(), *malloc();
  794. X
  795. X    Dprintf(stderr, ">>> creating entry '%s' on %s list\n",
  796. X    name, list_name(listp));
  797. X
  798. X    entryp = (struct entry_descrip *) malloc( sizeof(struct entry_descrip) );
  799. X    if ( entryp == NULL ) {
  800. X    (void) fputs(malloc_error,stderr);
  801. X    (void) exit(2);
  802. X    }
  803. X
  804. X    entryp->name = malloc( (unsigned) strlen(name) + 1 );
  805. X    if ( entryp->name == NULL ) {
  806. X    (void) fputs(malloc_error,stderr);
  807. X    (void) exit(2);
  808. X    }
  809. X    (void) strcpy(entryp->name,name);
  810. X
  811. X    entryp->fd = 0;
  812. X    entryp->size =  0;
  813. X    entryp->mtime = 0;
  814. X
  815. X    return E_append(listp,entryp);
  816. X}
  817. X
  818. X
  819. X/*
  820. X * Remove an entry from a list and free up its space.
  821. X */
  822. Xvoid rmv_entry(listp,entryno)
  823. Xstruct entry_list *listp;
  824. Xint entryno;
  825. X{
  826. X    struct entry_descrip *entryp = listp->list[entryno];
  827. X    extern void free();
  828. X
  829. X    Dprintf(stderr, ">>> removing entry '%s' from %s list\n",
  830. X    listp->list[entryno]->name, list_name(listp));
  831. X    E_remove(listp,entryno);
  832. X    if ( entryp->fd > 0 )
  833. X    (void) close(entryp->fd);
  834. X    free( entryp->name );
  835. X    free( (char *) entryp );
  836. X}
  837. X
  838. X
  839. X/*
  840. X * Move an entry from one list to another.
  841. X *    In addition we close up the entry if appropriate.
  842. X */
  843. Xvoid move_entry(dst_listp,src_listp,src_entryno)
  844. Xstruct entry_list *dst_listp;
  845. Xstruct entry_list *src_listp;
  846. Xint src_entryno;
  847. X{
  848. X    struct entry_descrip *entryp = src_listp->list[src_entryno];
  849. X
  850. X    Dprintf(stderr, ">>> moving entry '%s' from %s list to %s list\n",
  851. X    src_listp->list[src_entryno]->name,
  852. X    list_name(src_listp), list_name(dst_listp));
  853. X    if ( entryp->fd > 0 ) {
  854. X    (void) close(entryp->fd);
  855. X    entryp->fd = 0;
  856. X    }
  857. X    E_remove(src_listp,src_entryno);
  858. X    (void) E_append(dst_listp,entryp);
  859. X    if ( Reset_status ) {
  860. X    entryp->size = 0;
  861. X    entryp->mtime = 0;
  862. X    }
  863. X}
  864. X
  865. X
  866. X/*
  867. X * Get the inode status for an entry.
  868. X *    Returns code describing the status of the entry.
  869. X */
  870. Xint stat_entry(listp,entryno,sbuf)
  871. Xstruct entry_list *listp;
  872. Xint entryno;
  873. Xregister struct stat *sbuf;
  874. X{
  875. X    register int status;
  876. X    register struct entry_descrip *entryp = listp->list[entryno];
  877. X    static int my_gid = -1;
  878. X    static int my_uid = -1;
  879. X
  880. X    if ( my_gid < 0 ) {
  881. X    my_gid = getegid();
  882. X    my_uid = geteuid();
  883. X    }
  884. X
  885. X    status = 
  886. X    ( entryp->fd > 0 ? fstat(entryp->fd,sbuf) : stat(entryp->name,sbuf) );
  887. X
  888. X    if ( status != 0 )
  889. X    return ( errno == ENOENT ? ENTRY_ZAP : ENTRY_ERROR );
  890. X
  891. X    if (
  892. X    ( ( sbuf->st_mode & 0004 ) == 0 ) &&
  893. X    ( ( sbuf->st_mode & 0040 ) == 0 || sbuf->st_gid != my_gid ) &&
  894. X    ( ( sbuf->st_mode & 0400 ) == 0 || sbuf->st_uid != my_uid )
  895. X    ) {
  896. X    errno = EACCES;
  897. X    return ENTRY_ERROR;
  898. X    }
  899. X
  900. X    switch ( sbuf->st_mode & S_IFMT ) {
  901. X    case S_IFREG:    return ENTRY_FILE;
  902. X    case S_IFDIR:    return ENTRY_DIR;
  903. X    default:    return ENTRY_SPECIAL;
  904. X    }
  905. X
  906. X    /*NOTREACHED*/
  907. X}
  908. X
  909. X
  910. X/*
  911. X * Open an entry.
  912. X *    Returns 0 if the open is successful, else returns errno.  In the case
  913. X *    of an error, an appropriate diagnostic will be printed, and the entry
  914. X *    will be moved or deleted as required.  If the entry is already opened,
  915. X *    then no action will occur and 0 will be returned.
  916. X */
  917. Xint open_entry(listp,entryno)
  918. Xstruct entry_list *listp;
  919. Xint entryno;
  920. X{
  921. X    struct entry_descrip *entryp = listp->list[entryno];
  922. X
  923. X    if ( entryp->fd > 0 )
  924. X    return 0;
  925. X
  926. X    Dprintf(stderr, ">>> opening entry '%s' on %s list\n",
  927. X    listp->list[entryno]->name, list_name(listp));
  928. X    if ( (entryp->fd=open(entryp->name,O_RDONLY)) > 0 )
  929. X    return 0;
  930. X
  931. X    if ( errno == ENOENT ) {
  932. X    message( MSSG_ZAPPED, entryp );
  933. X    move_entry( &List_zap, listp, entryno );
  934. X    } else {
  935. X    message( MSSG_OPEN, entryp );
  936. X    rmv_entry( listp, entryno );
  937. X    }
  938. X    return -1;
  939. X}
  940. X
  941. X
  942. END_OF_FILE_entryfuncs.c
  943.     size="`wc -c < entryfuncs.c`"
  944.     if test 5152 -ne "$size" ; then
  945.     echo "entryfuncs.c: extraction error - got $size chars"
  946.     fi
  947. fi
  948. if test -f miscfuncs.c -a "$1" != "-c" ; then
  949.     echo "miscfuncs.c: file exists - will not be overwritten"
  950. else
  951.     echo "x - miscfuncs.c (file 5 of 7, 5423 chars)"
  952.     sed -e 's/^X//' << 'END_OF_FILE_miscfuncs.c' > miscfuncs.c
  953. X/*
  954. X * @(#) miscfuncs.c 2.1 89/07/26 19:16:50
  955. X *
  956. X * Package:    xtail version 2
  957. X * File:    miscfuncs.c
  958. X * Description:    miscelaneous support procedures
  959. X *
  960. X * Mon Jul 10 02:56:22 1989 - Chip Rosenthal <chip@vector.Dallas.TX.US>
  961. X *    Original composition.
  962. X */
  963. X
  964. X#ifndef LINT
  965. Xstatic char SCCSID[] = "@(#) miscfuncs.c 2.1 89/07/26 19:16:50";
  966. X#endif
  967. X
  968. X#include <stdio.h>
  969. X#include <fcntl.h>
  970. X#include <time.h>
  971. X#include <sys/types.h>
  972. X#include <sys/stat.h>
  973. X#include "xtail.h"
  974. X
  975. X#ifdef M_XENIX
  976. X# undef  NULL
  977. X# define NULL 0
  978. X#endif
  979. X
  980. X/*
  981. X * How come the portable directory routines are so !$*&@# unportable?
  982. X */
  983. X#ifdef DIR_XENIX
  984. X#   include <sys/ndir.h>
  985. X    typedef struct direct DIRENT;
  986. X#endif
  987. X#ifdef DIR_BSD
  988. X#   include <ndir.h>
  989. X    typedef struct direct DIRENT;
  990. X#endif
  991. X#ifdef DIR_SYSV
  992. X#   include <dirent.h>
  993. X    typedef struct dirent DIRENT;
  994. X#endif
  995. X
  996. Xextern int errno;
  997. Xextern char *sys_errlist[];
  998. X
  999. X
  1000. X/*
  1001. X * Scan a directory for files not currently on a list.
  1002. X */
  1003. Xint scan_directory(dirname)
  1004. Xchar *dirname;
  1005. X{
  1006. X    register int i;
  1007. X    register DIRENT *dp;
  1008. X    register struct entry_descrip **elist, *entryp;
  1009. X    char *basename;
  1010. X    struct stat sbuf;
  1011. X    DIR *dirp;
  1012. X    static char pathname[MAXNAMLEN];
  1013. X    extern char *strcpy(), *strcat();
  1014. X
  1015. X    Dprintf(stderr, ">>> scanning directory '%s'\n", dirname);
  1016. X    if ( (dirp=opendir(dirname)) == NULL )
  1017. X    return -1;
  1018. X
  1019. X    (void) strcat( strcpy(pathname,dirname), "/" );
  1020. X    basename = pathname + strlen(pathname);
  1021. X
  1022. X#define SKIP_DIR(D) \
  1023. X    ( D[0] == '.' && ( D[1] == '\0' || ( D[1] == '.' && D[2] == '\0' ) ) )
  1024. X
  1025. X    while ( (dp=readdir(dirp)) != NULL ) {
  1026. X
  1027. X    if ( SKIP_DIR(dp->d_name) )
  1028. X        continue;
  1029. X    (void) strcpy( basename, dp->d_name );
  1030. X    if ( stat(pathname,&sbuf) != 0 )
  1031. X        continue;
  1032. X    if ( (sbuf.st_mode&S_IFMT) != S_IFREG )
  1033. X        continue;
  1034. X
  1035. X    for ( i=List_file.num, elist=List_file.list ; i > 0 ; --i, ++elist ) {
  1036. X        if ( strcmp( (*elist)->name, pathname ) == 0 )
  1037. X        break;
  1038. X    }
  1039. X    if ( i > 0 )
  1040. X        continue;
  1041. X
  1042. X    for ( i=List_zap.num, elist=List_zap.list ; i > 0 ; --i, ++elist ) {
  1043. X        if ( strcmp( (*elist)->name, pathname ) == 0 )
  1044. X        break;
  1045. X    }
  1046. X    if ( i > 0 )
  1047. X        continue;
  1048. X
  1049. X    entryp = new_entry( &List_file, pathname );
  1050. X    if ( Reset_status ) {
  1051. X        message( MSSG_CREATED, entryp );
  1052. X    } else {
  1053. X        entryp->mtime = sbuf.st_mtime;
  1054. X        entryp->size = sbuf.st_size;
  1055. X    }
  1056. X
  1057. X    }
  1058. X
  1059. X    (void) closedir(dirp);
  1060. X    return 0;
  1061. X
  1062. X}
  1063. X
  1064. X
  1065. X/*
  1066. X * Compare mtime of two entries.  Used by the "qsort()" in "fixup_open_files()".
  1067. X */
  1068. Xstatic int ecmp(ep1,ep2)
  1069. Xregister struct entry_descrip **ep1, **ep2;
  1070. X{
  1071. X    return ( (*ep2)->mtime - (*ep1)->mtime );
  1072. X}
  1073. X
  1074. X/*
  1075. X * Manage the open files.
  1076. X *   A small number of entries in "List_file" are kept open to minimize
  1077. X *   the overhead in checking for changes.  The strategy is to make sure
  1078. X *   the MAX_OPEN most recently modified files are all open.
  1079. X */
  1080. Xvoid fixup_open_files()
  1081. X{
  1082. X    register int i;
  1083. X    register struct entry_descrip **elist;
  1084. X    extern void qsort();
  1085. X
  1086. X    Dprintf(stderr, ">>> resorting file list\n");
  1087. X    (void) qsort(
  1088. X    (char *) List_file.list,
  1089. X    List_file.num,
  1090. X    sizeof(struct entry_descrip *),
  1091. X    ecmp
  1092. X    );
  1093. X    Sorted = TRUE;
  1094. X
  1095. X    /*
  1096. X     * Start at the end of the list.
  1097. X     */
  1098. X    i = List_file.num - 1;
  1099. X    elist = &List_file.list[i];
  1100. X
  1101. X    /*
  1102. X     * All the files at the end of the list should be closed.
  1103. X     */
  1104. X    for ( ; i >= MAX_OPEN ; --i, --elist ) {
  1105. X    if ( (*elist)->fd > 0 ) {
  1106. X        (void) close( (*elist)->fd );
  1107. X        (*elist)->fd = 0;
  1108. X    }
  1109. X    }
  1110. X
  1111. X    /*
  1112. X     * The first MAX_OPEN files in the list should be open.
  1113. X     */
  1114. X    for ( ; i >= 0 ; --i, --elist ) {
  1115. X    if ( (*elist)->fd <= 0 )
  1116. X        (void) open_entry( &List_file, i );
  1117. X    }
  1118. X
  1119. X}
  1120. X
  1121. X
  1122. X/*
  1123. X * Standard message interface.
  1124. X *   There are two reasons for this message interface.  First, it provides
  1125. X *   consistent diagnostics for all the messages.  Second, it manages the
  1126. X *   filename banner display whenever we switch to a different file.
  1127. X *   Warning - "errno" is used in some of the messages, so care must be
  1128. X *   taken not to step on it before message() can be called.
  1129. X */
  1130. Xvoid message(sel,e)
  1131. Xint sel;
  1132. Xstruct entry_descrip *e;
  1133. X{
  1134. X    static char *ofile = NULL;
  1135. X
  1136. X    /*
  1137. X     * Don't display the file banner if the file hasn't changed since last time.
  1138. X     */
  1139. X    if ( sel == MSSG_BANNER && ofile != NULL && strcmp(ofile,e->name) == 0 )
  1140. X    return;
  1141. X
  1142. X    /*
  1143. X     * Make sure the message selector is within range.
  1144. X     */
  1145. X    if ( sel < 0 || sel > MSSG_UNKNOWN )
  1146. X    sel = MSSG_UNKNOWN;
  1147. X
  1148. X    /*
  1149. X     * Display the message.
  1150. X     */
  1151. X    if ( mssg_list[sel] != NULL )
  1152. X    (void) printf(mssg_list[sel], e->name, sys_errlist[errno]);
  1153. X
  1154. X    ofile = ( sel == MSSG_BANNER ? e->name : NULL );
  1155. X}
  1156. X
  1157. X
  1158. X/*
  1159. X * Display currently opened files.
  1160. X */
  1161. Xvoid show_status()
  1162. X{
  1163. X    int i, n;
  1164. X    struct tm *tp;
  1165. X    static char *monname[] = {
  1166. X    "Jan", "Feb", "Mar", "Apr", "May", "Jun",
  1167. X    "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
  1168. X    };
  1169. X    extern struct tm *localtime();
  1170. X
  1171. X    (void) printf("\n*** recently changed files ***\n");
  1172. X    for ( i = 0, n = 0 ; i < List_file.num ; ++i ) {
  1173. X    if ( List_file.list[i]->fd > 0 ) {
  1174. X        tp = localtime(&List_file.list[i]->mtime);
  1175. X        (void) printf("%4d  %2d-%3s-%02d %02d:%02d:%02d  %s\n",
  1176. X        ++n,
  1177. X        tp->tm_mday, monname[tp->tm_mon], tp->tm_year,
  1178. X        tp->tm_hour, tp->tm_min, tp->tm_sec,
  1179. X        List_file.list[i]->name
  1180. X        );
  1181. X    }
  1182. X    }
  1183. X
  1184. X    (void) printf( 
  1185. X    "currently watching:  %d files  %d dirs  %d unknown entries\n",
  1186. X    List_file.num, List_dir.num, List_zap.num);
  1187. X
  1188. X    message( MSSG_NONE, (struct entry_descrip *) NULL  );
  1189. X
  1190. X}
  1191. X
  1192. END_OF_FILE_miscfuncs.c
  1193.     size="`wc -c < miscfuncs.c`"
  1194.     if test 5423 -ne "$size" ; then
  1195.     echo "miscfuncs.c: extraction error - got $size chars"
  1196.     fi
  1197. fi
  1198. if test -f Makefile -a "$1" != "-c" ; then
  1199.     echo "Makefile: file exists - will not be overwritten"
  1200. else
  1201.     echo "x - Makefile (file 6 of 7, 2046 chars)"
  1202.     sed -e 's/^X//' << 'END_OF_FILE_Makefile' > Makefile
  1203. X
  1204. X# @(#) Makefile 2.1 89/07/26 19:15:39
  1205. X# Makefile for "xtail" (generated by /local/bin/makemake version 1.00.07)
  1206. X# Created by bin@vector on Wed Jul 26 17:36:37 CDT 1989
  1207. X
  1208. XSHELL = /bin/sh
  1209. XCC = cc
  1210. XDEFS = 
  1211. XCOPTS = -O
  1212. XLOPTS = 
  1213. XLIBS = -lx
  1214. XDEBUG = -g -DDEBUG 
  1215. XLINTFLAGS = -DLINT
  1216. X
  1217. XTARG = xtail
  1218. XOTHERS = 
  1219. X
  1220. XSRCS = xtail.c entryfuncs.c miscfuncs.c
  1221. X
  1222. XOBJS = xtail.o entryfuncs.o miscfuncs.o
  1223. X
  1224. X# Any edits below this line will be lost if "makemake" is rerun!
  1225. X# Commands may be inserted after the '#%custom' line at the end of this file.
  1226. X
  1227. XCFLAGS = $(COPTS) $(DEFS) # $(DEBUG)
  1228. XLFLAGS = $(LOPTS) # $(DEBUG)
  1229. X
  1230. Xall:        $(TARG) $(OTHERS)
  1231. Xinstall:    all        ; inst Install
  1232. Xclean:                ; rm -f $(TARG) $(OBJS) a.out core $(TARG).lint
  1233. Xclobber:    clean        ; inst -u Install
  1234. Xlint:        $(TARG).lint
  1235. X
  1236. X$(TARG):        $(OBJS)
  1237. X        $(CC) $(LFLAGS) -o $@ $(OBJS) $(LIBS)
  1238. X
  1239. X$(TARG).lint:    $(TARG)
  1240. X        lint $(LINTFLAGS) $(DEFS) $(SRCS) $(LIBS) > $@
  1241. X
  1242. Xxtail.o: /usr/include/signal.h /usr/include/stdio.h /usr/include/sys/signal.h \
  1243. X        /usr/include/sys/stat.h /usr/include/sys/types.h xtail.c \
  1244. X        xtail.h
  1245. Xentryfuncs.o: /usr/include/fcntl.h /usr/include/stdio.h \
  1246. X        /usr/include/sys/errno.h /usr/include/sys/fcntl.h \
  1247. X        /usr/include/sys/lockcmn.h /usr/include/sys/stat.h \
  1248. X        /usr/include/sys/types.h entryfuncs.c xtail.h
  1249. Xmiscfuncs.o: /usr/include/fcntl.h /usr/include/stdio.h \
  1250. X        /usr/include/sys/fcntl.h /usr/include/sys/lockcmn.h \
  1251. X        /usr/include/sys/ndir.h /usr/include/sys/stat.h \
  1252. X        /usr/include/sys/types.h /usr/include/time.h miscfuncs.c \
  1253. X        xtail.h
  1254. X
  1255. Xmake:        ;
  1256. X        /local/bin/makemake -i -v1.00.07 -aMakefile \
  1257. X            -DSHELL='$(SHELL)' -DCC='$(CC)' -DDEFS='$(DEFS)' \
  1258. X            -DCOPTS='$(COPTS)' -DLOPTS='$(LOPTS)' -DLIBS='$(LIBS)' \
  1259. X            -DDEBUG='$(DEBUG)' -DLINTFLAGS='$(LINTFLAGS)' \
  1260. X            -DOTHERS='$(OTHERS)' $(TARG) $(SRCS)
  1261. X
  1262. X#%custom - commands below this line will be maintained if 'makemake' is rerun
  1263. X
  1264. XARLIST = README xtail.h xtail.c entryfuncs.c miscfuncs.c Makefile xtail.man
  1265. X
  1266. Xshar:        xtail.shar
  1267. Xxtail.shar:    $(ARLIST)    ; shar $(ARLIST) > xtail.shar
  1268. X
  1269. END_OF_FILE_Makefile
  1270.     size="`wc -c < Makefile`"
  1271.     if test 2046 -ne "$size" ; then
  1272.     echo "Makefile: extraction error - got $size chars"
  1273.     fi
  1274. fi
  1275. if test -f xtail.man -a "$1" != "-c" ; then
  1276.     echo "xtail.man: file exists - will not be overwritten"
  1277. else
  1278.     echo "x - xtail.man (file 7 of 7, 1300 chars)"
  1279.     sed -e 's/^X//' << 'END_OF_FILE_xtail.man' > xtail.man
  1280. X''' @(#) xtail.man 2.1 89/07/26 19:15:44
  1281. X.TH XTAIL 1L
  1282. X.SH NAME
  1283. Xxtail - Watch the growth of files.
  1284. X.SH SYNTAX
  1285. X.B xtail
  1286. Xentry ...
  1287. X.SH DESCRIPTION
  1288. X.I Xtail
  1289. Xmonitors one or more files, and displays all data written to a file
  1290. Xsince command invocation.  It is very useful for monitoring multiple
  1291. Xlogfiles simultaneously.
  1292. X.P
  1293. XIf an
  1294. X.I entry
  1295. Xgiven on the command line is a directory, all files in that directory
  1296. Xwill be monitored, including those created after the
  1297. X.I xtail
  1298. Xinvocation.  If an
  1299. X.I entry
  1300. Xgiven on the command line doesn't exist,
  1301. X.I xtail
  1302. Xwill watch for it and monitor it once created.  When switching files in
  1303. Xthe display, a banner showing the pathname of the file is printed.
  1304. X.P
  1305. XAn interrupt character (usually CTRL/C or DEL) will display a list of the
  1306. Xmost recently modified files being watched.  Send a quit signal
  1307. X(usually CTRL/backslash) to stop
  1308. X.IR xtail .
  1309. X.SH SEE ALSO
  1310. Xtail(1)
  1311. X.SH NOTES
  1312. X.I Xtail
  1313. Xmay be easily confused.  For example, if a file is renamed,
  1314. X.I xtail
  1315. Xmay or may not continue to monitor it.  If you ask it to monitor a file
  1316. Xmultiple times, it probably will.  If you misspell a filename,
  1317. X.I xtail
  1318. Xwill treat it as a nonexistent entry and happily wait for its creation.
  1319. X.P
  1320. XMy favorite use is "xtail /usr/spool/uucp/.Log/*".
  1321. X.SH AUTHOR
  1322. XChip Rosenthal <chip@vector.Dallas.TX.US>
  1323. END_OF_FILE_xtail.man
  1324.     size="`wc -c < xtail.man`"
  1325.     if test 1300 -ne "$size" ; then
  1326.     echo "xtail.man: extraction error - got $size chars"
  1327.     fi
  1328. fi
  1329. echo "done - 7 files extracted"
  1330. exit 0
  1331. --- cut here -----------------------------------------------------------------
  1332. -- 
  1333. Chip Rosenthal / chip@vector.Dallas.TX.US / Dallas Semiconductor / 214-450-5337
  1334. "I wish you'd put that starvation box down and go to bed" - Albert Collins' Mom
  1335.  
  1336.