/* Copyright (C) 2010-2012 Ryan Kavanagh 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: loreti@padova.infn.it | | 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 ) 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 /* Standard library */ #include #include #include #include #include #include /* Unix proper */ #include #include #include /* 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); }