/*
 Copyright (C) 2010-2012 Ryan Kavanagh <[email protected]>
 Copyright (C) 1996–2002 Maurizio Loreti:
     *------------------------------------------------------*
     | Author: Maurizio Loreti, aka MLO or (HAM) I3NOO      |
     | Work:   University of Padova - Department of Physics |
     |         Via F. Marzolo, 8 - 35131 PADOVA - Italy     |
     | Phone:  ++39(49) 827-7216     FAX: ++39(49) 827-7102 |
     | EMail:  [email protected]                        |
     | WWW:    http://wwwcdf.pd.infn.it/~loreti/mlo.html    |
     *------------------------------------------------------*

 This program is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or
 (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License along
 with this program; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

 The lintex website now resides at:
   http://github.com/ryanakca/lintex

 Description: the command "lintex [-i] [-r] [dir1 [dir2 ...]]" scans
   the directories given as parameters (the default is the current
   directory), looking for TeX-related files no more needed and to be
   removed.  With the option -i the user is asked before actually
   removing any file; with the option -r, the given directories are
   scanned recursively.

 Environment: the program has been developed under Solaris 2; but
   should run on every system supporting opendir/readdir/closedir and
   stat.  The file names in the struct dirent (defined in <dirent.h>)
   are assumed to be null terminated (this is guaranteed under Solaris
   2).

 History:
   1.00 - 1996-07-05 , first release
   1.01 - 1996-07-25 , improved (modify time in nodes)
   1.02 - 1996-10-16 , list garbage files w/out .tex
   1.03 - 1997-05-21 , call to basename; uploaded to CTAN, where lives
                       in /tex-archive/support/lintex .  Added a man
                       page and a Makefile.
   1.04 - 1998-06-22 , multiple directories in the command line; -r
                       command option; -I and -R accepted, in addition
                       to -i and -r; more extensions in protoTree; code
                       cleanup.
   1.05 - 2001-12-02 , linked list structure optimized.
   1.06 - 2002-09-25 , added .pdf extension.
   1.07 - 2010-08-11 , don't delete read only files; delete .bbl BibTeX
                       files; add -k to keep final product; added
                       extensions for files generated by Beamer class
   1.08 - 2010-10-01 , Add support for different verbosity levels, be
                       relatively quiet by default; dropped support for
                       the DEBUG and FULLDEBUG compiler flags. It's all
                       taken care of with command line options now;
                       added a -p (pretend) flag that shows what we would
                       have removed, but doesn't do anything.
   1.09 - 2010-11-30 , Add support for removing files older than their
                       source; remove duplicate entry in usage; update
                       usage to not be wider than 72 characters.
   1.10 - 2011-01-30 , Also remove .thm (generated by ntheorem), .out
                       (generated by hyperref), .toc.old (memoir?); list
                       removal extensions in manpage.
   1.11 - 2011-11-07 , Also remove .synctex.gz files.
   1.12 - 2012-08-30 , Also remove xypic's .xyc files.
   1.13 - 2012-09-11 , Add support for configuration file; updated
                       documentation.

 ---------------------------------------------------------------------*/

/**
| Included files
**/

#include <stdio.h>              /* Standard library */
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <ctype.h>
#include <unistd.h>

#include <sys/types.h>          /* Unix proper */
#include <sys/stat.h>
#include <dirent.h>

#include <libconfig.h>          /* Configuration file support */

/**
| Definitions:
| - LONG_ENOUGH: length of the buffer used to read the answer from the
|   user, when the -i command option is specified;
| - MAX_B_EXT: maximum length of the extension for backup files (including
|   the leading dot and the trailing '\0').
| - TRUE, FALSE: guess what?
| - VERSION: lintex version
| - QUIET, WHISPER, VERBOSE and DEBUG:
|    * QUIET: no output
|    * WHISPER: only print actions that are taken (ie. list files that are
|      removed, default starting as of 1.08)
|    * VERBOSE: list files on which actions are taken as well as those which
|      aren't (default up to / including 1.07)
|    * DEDUG: print debug information (originally FULLDEBUG compiler flag)
|      means print everything you can for those who want to debug.
|   Errors will be sent to stderr regardless of the output level.
**/

#define LONG_ENOUGH 48
#define MAX_B_EXT    8
#define TRUE         1
#define FALSE        0
#define VERSION    "1.13 (2012-09-11)"
#define QUIET        0
#define WHISPER      1
#define VERBOSE      2
#define DEBUG        3

/**
| Type definitions:
| - Froot: the root of a linked list structure, where file names having a
|     given extension (pointed to by Froot.extension) will be stored;
|     these linked lists are also used to store directory names (with fake
|     extension strings).
| - Fnode: an entry in the linked list of the file names; contains the
|     file modification time, the file name and a pointer to the next node.
|     As a side note, the so called 'struct hack', here used to store the
|     file name, is not guaranteed to work by the current C ANSI standard;
|     but no environment/compiler where it does not work is currently
|     known.
**/

typedef struct sFroot {
 char *extension;
 struct sFnode *firstNode;
 struct sFnode *lastNode;
} Froot;

typedef struct sFnode {
 time_t mTime;
 struct sFnode *next;
 int write;
 char name[1];
} Fnode;

/**
| Global variables:
| - confirm: will be 0 or 1 according to the -i command option;
| - recurse: will be 0 or 1 according to the -r command option;
| - keep: will be 0 or 1 according to the -k command option;
| - output_level: See the definitions above for more details;
| - pretend: will be 0 or 1 according to -p command option;
| - older: will be 0 or 1 according to -o command option;
| - bExt: the extension for backup files: defaults to "~" (the emacs
|   convention);
| - n_bExt: the length of the previous string;
| - programName: the name of the executable;
| - protoTree: Froot's of the file names having extensions relevant to
|   TeX.  ".tex" extensions are assumed to be pointed to by protoTree[0].
| - keep_exts: Array containing extensions to keep.
| - protoTreeSize, keep_exts_size: number of elements in protoTree and
|   keep_exts.
**/

Froot *protoTree;
char **keep_exts;
int protoTreeSize;

static int     confirm         = FALSE;
static int     recurse         = FALSE;
static int     keep            = FALSE;
static int     output_level    = WHISPER;
static int     pretend         = FALSE;
static int     older           = FALSE;
static char    bExt[MAX_B_EXT] = "~";
static size_t  n_bExt;
static char   *programName;

char *remove_exts[] = {
 ".tex",               /* Must be first */
 ".aux",
 ".bbl",
 ".blg",
 ".dvi",
 ".idx",
 ".ilg",
 ".ind",
 ".lof",
 ".log",
 ".lot",
 ".nav",
 ".out",
 ".pdf",
 ".ps",
 ".snm",
 ".synctex.gz",
 ".thm",
 ".toc",
 ".toc.old",
 ".xyc"
};

char *keep_exts_defaults[] = {
 ".pdf",
 ".ps",
 ".dvi"
};
int keep_exts_size = 3;

/**
| Procedure prototypes (in alphabetical order)
**/

static char  *baseName(char *);
static Froot *buildTree(char *, Froot *);
static void   clean(char *);
static void   examineTree(Froot *, char *);
static void   insertNode(char *, size_t, time_t, int, Froot *);
static void   noMemory(void);
static void   nuke(char *);
static void   putsMessage(char *, int);
static void   printTree(Froot *);
static void   releaseTree(Froot *);
static void   setupTrees(void);
static void   syntax(void);

/*---------------------------*
| And now, our main program |
*---------------------------*/

int main(
 int argc,
 char *argv[]
){
 Froot *dirNames;              /* To hold the directories to be scanned */
 Fnode *pFN;                   /* Running pointer over directory names  */
 int    to_bExt  = FALSE;      /* Flag "next parameter to bExt"         */

 /**
  | Scans the arguments appropriately; the required directories are stored
  | in the linked list starting at "dirNames".
 **/

 programName = baseName(argv[0]);

 if ((dirNames = calloc(2, sizeof(Froot))) == 0) {
   noMemory();
 }
 dirNames->extension = "argv";

 while (--argc) {
   if ((*++argv)[0] == '-') {
     switch ( (*argv)[1] ) {
       case 'i':   case 'I':
         confirm = TRUE;
         break;

       case 'r':   case 'R':
         recurse = TRUE;
         break;

       case 'k':   case 'K':
         keep = TRUE;
         break;

       case 'b':   case 'B':
         to_bExt = TRUE;
         break;

       case 'q':   case 'Q':
         output_level = QUIET;
         break;

       case 'v':   case 'V':
         output_level = VERBOSE;
         break;

       case 'd':   case 'D':
         output_level = DEBUG;
         break;

       case 'p':   case 'P':
         pretend = TRUE;
         break;

       case 'o':   case 'O':
         older = TRUE;
         break;

       default:
         syntax();
     }

   } else {
     if (to_bExt) {
       strcpy(bExt, *argv);
       to_bExt = FALSE;
     } else {
       insertNode(*argv, 0, 0, 0, dirNames);
     }
   }
 }

 if (to_bExt) {
   syntax();
 }
 n_bExt = strlen(bExt);

 setupTrees();

 /**
  | If no parameter has been given, clean the current directory
 **/

 if ((pFN = dirNames->firstNode) == 0) {
   clean(".");
 } else {
   while (pFN != 0) {
     clean(pFN->name);
     pFN = pFN->next;
   }
 }
 releaseTree(dirNames);

 return EXIT_SUCCESS;
}

/*------------------------------------------*
| The called procedures (in logical order) |
*------------------------------------------*/

static void setupTrees(void)
{
 /**
  | Initialise protoTree, keep_exts
 **/
 config_t cfg;                 /* To hold our config                    */
 config_setting_t *setting;    /* To hold a setting                     */
 int i;                        /* Iterator                              */
 int needed;                   /* number of characters in filename      */
 char *extension, *cfg_file;

 if (output_level >= DEBUG)
   printf("Initialising protoTree, keep_exts.\n");

 protoTreeSize = sizeof(remove_exts) / sizeof(char *) + 1;
 if ((protoTree = malloc(sizeof(Froot) * protoTreeSize)) == 0) {
     noMemory();
 }
 /* remove_exts has (protoTreeSize - 1) elements */
 if (output_level >= DEBUG)
   printf("Created protoTree of size %d\n", protoTreeSize);
 for (i=0; i < protoTreeSize - 1; i++) {
   protoTree[i].extension = remove_exts[i];
   protoTree[i].firstNode = 0;
   protoTree[i].lastNode = 0;
   if (output_level >= DEBUG)
     printf("Added %d-th extension %s to protoTree of size %d at pos %d\n",
             i, remove_exts[i], protoTreeSize, i);
 }

 /* Add the sentinel */
 protoTree[protoTreeSize - 1].extension = 0;
 protoTree[protoTreeSize - 1].firstNode = 0;
 protoTree[protoTreeSize - 1].lastNode = 0;
 if (output_level >= DEBUG)
   printf("Added sentinal to protoTree at pos %d.\n", protoTreeSize - 1);

 keep_exts = keep_exts_defaults;
 /**
  | Initialise and read the config file
 **/

 config_init(&cfg);

 needed = strlen(getenv("HOME")) + strlen("/.lintexrc") + 1;
 cfg_file = (char *) malloc(sizeof(char) * needed);
 if (getenv("HOME")) {
   strcpy(cfg_file, getenv("HOME"));
   strcat(cfg_file, "/.lintexrc");
   if (output_level >= DEBUG)
     printf("Using config file %s.\n", cfg_file);
 }

 if (! access(cfg_file, R_OK)) {
   /* We have read access to the config file */
   if (! config_read_file(&cfg, cfg_file)) {
     fprintf(stderr, "%s:%d - %s\n", config_error_file(&cfg),
             config_error_line(&cfg), config_error_text(&cfg));
     config_destroy(&cfg);
     exit(EXIT_FAILURE);
   }

   /* Lets extract our settings */
   /* Extensions to remove */
   setting = config_lookup(&cfg, "remove-exts");
   if (setting != NULL) {
     unsigned int count = config_setting_length(setting);

     Froot *protoTreeTemp;
     if ((protoTreeTemp = (Froot *) malloc(sizeof(Froot) * (count + protoTreeSize))) == 0) {
       noMemory();
     }
     memcpy(protoTreeTemp, protoTree, sizeof(Froot) * protoTreeSize);
     if (output_level >= DEBUG)
       printf("Copied protoTree to larger location to add config exts.\n");

     for (i = 0; i < count; i++) {
       extension = (char *) config_setting_get_string_elem(setting, i);
       protoTreeTemp[protoTreeSize - 1 + i].extension = extension;
       protoTreeTemp[protoTreeSize - 1 + i].firstNode = 0;
       protoTreeTemp[protoTreeSize - 1 + i].lastNode = 0;
       if (output_level >= DEBUG)
         printf("Added %d-th config extension %s to protoTree of size %d at pos %d\n",
               i, extension, protoTreeSize + count, protoTreeSize - 1 + i);
     }

     /* Add the sentinel */
     protoTreeTemp[protoTreeSize - 1 + count].extension = 0;
     protoTreeTemp[protoTreeSize - 1 + count].firstNode = 0;
     protoTreeTemp[protoTreeSize - 1 + count].lastNode = 0;
     protoTree = protoTreeTemp;
     if (output_level >= DEBUG)
       printf("Added sentinel to protoTree at position %d\n", protoTreeSize - 1 + count);

     protoTreeSize += count;
   }

   /* Extensions to keep */
   setting = config_lookup(&cfg, "keep-exts");
   if (setting != NULL) {
     int i;
     keep_exts_size = config_setting_length(setting);

     if ((keep_exts = (char **) malloc(sizeof(char *) * keep_exts_size)) == 0) {
       noMemory();
     }

     for (i = 0; i < keep_exts_size; i++) {
       keep_exts[i] = (char *) config_setting_get_string_elem(setting, i);
       if (output_level >= DEBUG)
         printf("Added %d-th config extension %s to keep_exts of size %d at pos %d\n",
               i, keep_exts[i], keep_exts_size, i);
     }
   }
 } else {
   if (! access(cfg_file, F_OK)) {
     /* File exists */
     fprintf(stderr,
       "Warning: Insufficient permissions to read config file $HOME/.lintexrc");
   }
 }
}

static void insertNode(
 char   *name,
 size_t  lName,
 time_t  mTime,
 int write,
 Froot  *root
){

 /**
  | Creates a new Fnode, to be inserted at the _end_ of the linked
  | list pointed to by root->firstNode (i.e., the list is organized
  | as a "queue", a.k.a. "FIFO" list): if a new node cannot be created,
  | an error message is printed and the program aborted.
  | If "lName" is bigger than zero, the file name is represented by the
  | first lName characters of "name"; otherwise by the whole string in
  | "name".
 **/

 Fnode  *pFN;                  /* The new node created by insertNode */
 size_t  sSize;                /* Structure size                     */

 sSize = sizeof(Fnode) + (lName == 0 ? strlen(name) : lName);

 if ((pFN = malloc(sSize)) == 0) {
   noMemory();
 }
 pFN->mTime = mTime;
 pFN->write = write;
 pFN->next  = 0;

 if (lName == 0) {
   strcpy(pFN->name, name);
 } else {
   strncpy(pFN->name, name, lName);
   pFN->name[lName] = '\0';
 }

 if (root->lastNode == 0) {
   root->firstNode = pFN;
 } else {
   root->lastNode->next = pFN;
 }
 root->lastNode = pFN;
}

static void noMemory(void)
{
 fprintf(stderr, "%s: couldn't obtain heap memory\n", programName);
 exit(EXIT_FAILURE);
}

static void clean(
 char *dirName
){

 /**
  | Does the job for the directory "dirName".
  |
  | Builds a structure holding the TeX-related files, and does the
  | required cleanup; finally, removes the file structure.
  | If the list appended to "dirs" has been filled, recurse over the
  | tree of subdirectories.
 **/

 Froot *teXTree;               /* Root node of the TeX-related files  */
 Froot *dirs;                  /* Subdirectories in this directory    */
 Fnode *pFN;                   /* Running pointer over subdirectories */

 if ((dirs = calloc(2, sizeof(Froot))) == 0) {
   noMemory();
 }
 dirs->extension = "subs";

 if ((teXTree = buildTree(dirName, dirs)) != 0) {

   if (output_level >= DEBUG) {
     printTree(teXTree);
   }

   examineTree(teXTree, dirName);
   releaseTree(teXTree);
 }

 for (pFN = dirs->firstNode;   pFN != 0;   pFN = pFN->next) {
   clean(pFN->name);
 }
 releaseTree(dirs);
}

static Froot *buildTree(
 char  *dirName,
 Froot *subDirs
){

 /**
  | - Opens the required directory;
  | - allocates a structure to hold the names of the TeX-related files,
  |   initialized from the global structure "protoTree";
  | - starts a loop over all the files of the given directory.
 **/

 DIR           *pDir;         /* Pointer returned from opendir()    */
 struct dirent *pDe;          /* Pointer returned from readdir()    */
 Froot         *teXTree;      /* Root node of the TeX-related files */

 if (output_level >= DEBUG) {
   printf("* Scanning directory \"%s\" - confirm = %c, recurse = %c, ",
        dirName, (confirm ? 'Y' : 'N'), (recurse ? 'Y' : 'N')),
   printf("keep = %c\n", (keep ? 'Y' : 'N'));
   printf("* Editor trailer: \"%s\"\n", bExt);
   puts("------------------------------Phase 1: directory scan");
 }

 if ((pDir = opendir(dirName)) == 0) {
   fprintf(stderr,
           "%s: \"%s\" cannot be opened (or is not a directory)\n",
           programName, dirName);
   return 0;
 }

 if ((teXTree = malloc(protoTreeSize * sizeof(Froot))) == 0) {
   noMemory();
 }
 memcpy(teXTree, protoTree, protoTreeSize * sizeof(Froot));

 while ((pDe = readdir(pDir)) != 0) {
   char    tName[FILENAME_MAX];         /* Fully qualified file name       */
   struct  stat sStat;                  /* To be filled by stat(2)         */
   size_t  len;                         /* Lenght of the current file name */
   size_t  last;                        /* Index of its last character     */
   char   *pFe;                         /* Pointer to file extension       */

   /**
    | - Tests for empty inodes (already removed files);
    | - skips the . and .. (current and previous directory);
    | - tests the trailing part of the file name against the extension of
    |   the backup files, to be always deleted.
   **/

   if (pDe->d_ino == 0)                continue;
   if (strcmp(pDe->d_name, ".")  == 0) continue;
   if (strcmp(pDe->d_name, "..") == 0) continue;

   sprintf(tName, "%s/%s", dirName, pDe->d_name);

   len  = strlen(pDe->d_name);
   last = len - 1;

   if (n_bExt != 0) {                  /* If 0, no backup files to delete */
     int crit;                         /* What exceeds backup extensions  */

     crit = len - n_bExt;
     if (crit > 0   &&   strcmp(pDe->d_name + crit, bExt) == 0) {
       nuke(tName);
       continue;
     }
   }

   /**
    | If the file is a directory and the -r option has been given, stores
    | the directory name in the linked list pointed to by "subDirs", for
    | recursive calls.
    |
    | N.B.: if stat(2) fails, the file is skipped.
   **/

   if (stat(tName, &sStat) != 0) {
     fprintf(stderr, "File \"%s", tName);
     perror("\"");
     continue;
   }

   if (S_ISDIR(sStat.st_mode) != 0) {

     if (output_level >= DEBUG) {
       printf("File %s - is a directory\n", pDe->d_name);
     }

     if (recurse) {
       insertNode(tName, 0, 0, 0, subDirs);
     }
     continue;
   }

   /**
    | If the file has an extension (the rightmost dot followed by at
    | least one character), and if that extension matches one of the
    | entries in teXTree[i].extension: stores the file name (with the
    | extension stripped) in the appropriate linked list, together with
    | its modification time.
   **/

   if ((pFe = strrchr(pDe->d_name, '.')) != 0) {
     size_t nameLen;

     nameLen = pFe - pDe->d_name;
     if (nameLen < last) {
       Froot *pTT;

       if (output_level >= DEBUG) {
         printf("File %s - extension %s", pDe->d_name, pFe);
       }

       /**
        | Loop on recognized TeX-related file extensions
       **/

       for (pTT = teXTree;   pTT->extension != 0;   pTT++) {
         if (strcmp(pFe, pTT->extension) == 0) {
           int i;
           if (keep) {
             i = 0;
           } else {
             i = keep_exts_size;
           }
           for (i = 0; i < keep_exts_size; i++) {
             if (strcmp(pFe, keep_exts[i]) == 0) {
               /**
                | The current file's extension is in keep_exts, we want to keep
                | it.
               **/
               i = -1;
               break;
             }
           }

           if ((!keep) | (i != -1)) {
             /**
              | Only add the file if we didn't find its extension in keep_exts
             **/
             insertNode(pDe->d_name, nameLen, sStat.st_mtime, access(tName, W_OK), pTT);
             if (output_level >= DEBUG) {
               printf(" - inserted in tree");
             }
           } else if (keep) {
             if (output_level >= DEBUG) {
               printf(" - not inserted in tree (extension in keep-exts)");
             } else if (output_level >= VERBOSE) {
               printf("*** %s not removed; keep activated ***\n", pDe->d_name);
             }
           }
           break;
         }
       } /* loop on known extensions */

       if (output_level >= DEBUG) {
         puts("");
       }

     } else {
       if (output_level >= VERBOSE) {
         printf("File %s - empty extension\n", pDe->d_name);
       }
     }

   } else {
     if (output_level >= DEBUG) {
       printf("File %s - without extension\n", pDe->d_name);
     }
   }
 }             /* while (readdir) ... */

 if (closedir(pDir) != 0) {
   fprintf(stderr, "Directory \"%s", dirName);
   perror("\"");
 }

 return teXTree;
}

static void printTree(
 Froot *teXTree
){

 /**
  | Prints all the file names archived in the linked lists (for
  | debugging purposes).
 **/

 if (output_level >= DEBUG) {
   Froot *pTT;           /* Running pointer over teXTree elements */

   puts("------------------------------Phase 2: tree printout");;
   for (pTT = teXTree;   pTT->extension != 0;   pTT++) {
     Fnode *pTeX;              /* Running pointer over TeX-related files */
     int    nNodes = 0;        /* Counter */

     for (pTeX = pTT->firstNode;   pTeX != 0;   pTeX = pTeX->next) {
       ++nNodes;
       printf("%s%s\n", pTeX->name, pTT->extension);
     }
     printf("  --> %d file%s with extension %s\n", nNodes,
            (nNodes == 1 ? "" : "s"), pTT->extension);
   }
 }
}

static void examineTree(
 Froot *teXTree,
 char  *dirName
){

 /**
  | Examines the linked lists for this directory doing the effective
  | cleanup.
 **/

 Froot *pTT;           /* Pointer over linked list trees      */
 Fnode *pTeX;          /* Running pointer over the .tex files */

 /**
  | Looks, for all the .tex files, if a corresponding entry with the same
  | name exists (with a different extension) in the other lists; if so,
  | and if its modification time is later than the one of the related
  | .tex file, removes it from the file system.
 **/
 putsMessage("------------------------------Phase 3: effective cleanup",
             DEBUG);

 for (pTeX = teXTree->firstNode;   pTeX != 0;   pTeX = pTeX->next) {
   char tName[FILENAME_MAX];

   sprintf(tName, "%s/%s.tex", dirName, pTeX->name);
   pTT = teXTree;

   if (output_level >= DEBUG) {
     printf("    Finding files related to %s:\n", tName);
   }

   for (pTT++;   pTT->extension != 0;   pTT++) {
     Fnode *pComp;

     for (pComp = pTT->firstNode;   pComp != 0;   pComp = pComp->next) {
       char cName[FILENAME_MAX];

       if (strcmp(pTeX->name, pComp->name) == 0) {
         sprintf(cName, "%s/%s%s", dirName, pTeX->name, pTT->extension);
         pComp->name[0] = '\0';

         /**
          | Remove generated file if more recent than source (default) or if
          | we permit the removal of files older than source
         **/
         if (difftime(pComp->mTime, pTeX->mTime) > 0.0 || older) {
           if (pComp->write == 0) {
             nuke(cName);
           } else {
             if (output_level >= DEBUG) {
               printf("*** %s readonly; perms are %d***\n", cName,
                      pComp->write);
             }
             if (output_level >= VERBOSE) {
               printf("*** %s not removed; it is read only ***\n", cName);
             }
           }
         } else {
           if (output_level >= VERBOSE) {
             printf("*** %s not removed; %s is newer ***\n", cName, tName);
           }
         }
         break;
       }
     }
   }
 }

 /**
  | If some garbage file has not been deleted, list it
 **/

 putsMessage("------------------------------Phase 4: left garbage files",
             DEBUG);

 pTT = teXTree;
 for (pTT++;  pTT->extension != 0;  pTT++) {
   Fnode *pComp;

   for (pComp = pTT->firstNode;   pComp != 0;   pComp = pComp->next) {
     if (pComp->name[0] != '\0') {
       char cName[FILENAME_MAX];

       sprintf(cName, "%s/%s%s", dirName, pComp->name, pTT->extension);
       if (output_level >= VERBOSE) {
         printf("*** %s not removed; no .tex file found ***\n", cName);
       }
     }
   }
 }
}

static void releaseTree(
 Froot *teXTree
){

 /**
  | Cleanup of the file name storage structures: an _array_ of Froot's,
  | terminated by a NULL extension pointer as a sentinel, is assumed.
  | Every linked list nodes is freed; then releaseTree frees also the
  | root structure.
 **/

 Froot *pFR;

 putsMessage("------------------------------Phase 5: tree cleanup", DEBUG);

 for (pFR = teXTree;   pFR->extension != 0;   pFR++) {
   Fnode *pFN, *p;

   int nNodes = 0;
   if (output_level >= DEBUG) {
     printf("Dealing with extensions %s ...", pFR->extension);
   }

   for (pFN = pFR->firstNode;   pFN != 0;   pFN = p) {
     p = pFN->next;
     free(pFN);

     nNodes++;
   }

   if (output_level >= DEBUG) {
     printf("   %d nodes freed\n", nNodes);
   }
 }

 free(teXTree);
}

static void nuke(
 char *name
){

 /**
  | Removes "name" (the fully qualified file name) from the file system
 **/

 if ((output_level >= DEBUG) || pretend) {
   printf("*** File \"%s\" would have been removed ***\n", name);
 }

 if (pretend) {
   /* We don't need to continue if we aren't going to remove the file */
   return;
 }

 if (confirm) {
   char yn[LONG_ENOUGH], c;

   do {
     printf("Remove %s (y|n) ? ", name);
     if (fgets(yn, LONG_ENOUGH, stdin) == 0) return;
     if (yn[0] == '\0' || (c = tolower((unsigned char) yn[0])) == 'n') {
       return;
     }
   } while (c != 'y');
 }

 if (remove(name) != 0) {
   fprintf(stderr, "File \"%s", name);
   perror("\"");
 } else {
   if (output_level >= WHISPER) {
     printf("%s has been removed\n", name);
   }
 }

}

static char *baseName(
 char *pc
){
 char *p;

/**
| Strips the (eventual) path information from the string pointed
| to by 'pc'; if no file name is given, returns an empty string.
**/

 p = strrchr(pc, '/');
 if (p == 0) return pc;
 return ++p;
}

static void putsMessage(
 char *message,
 int  message_level
){
 if (message_level <= output_level) {
   puts(message);
 }
}

static void syntax()
{
 printf("lintex version %s\n", VERSION);
 puts("Usage:");
 printf("  %s [OPTIONS] [DIR [DIR ...]]\n", programName);
 puts("Purpose:");
 puts("  Removes unneeded TeX auxiliary files and editor backup files from"
      " the");
 puts("  given directories (default: the current directory); the TeX files"
      " are");
 puts("  actually removed only if their modification time is more recent"
      " than");
 puts("  the one of the related TeX source and if they aren't readonly.");
 puts("  Please see the manpage for a list of extensions that get removed.");
 puts("Options:");
 puts("  -i     : asks the user before removing any file;");
 puts("  -r     : scans recursively the subdirectories of the given");
 puts("           directories;");
 puts("  -b ext : \"ext\" is the trailing string identifying editor backup"
      " files");
 puts("           (defaults to \"~\").  -b \"\" avoids any cleanup of special");
 puts("           files;");
 puts("  -p     : pretend, show what files would be removed but don't actually");
 puts("           remove them;");
 puts("  -k     : keeps final document (.pdf, .ps, .dvi);");
 puts("  -o     : permit removal of files older than their sources;");
 puts("  -q     : quiet, only print error messages;");
 puts("  -v     : verbose, prints which files were removed and which weren't;");
 puts("  -d     : debug output, prints the answers to all of life's questions.");

 exit(EXIT_SUCCESS);
}