www.pudn.com > vim53src.zip > main.c, change:1998-08-30,size:36623b


/***************************************************************************** 
*   $Id: main.c,v 6.23 1998/08/18 04:26:50 darren Exp $ 
* 
*   Copyright (c) 1996-1998, Darren Hiebert 
* 
*   Author: Darren Hiebert ,  
*           http://darren.hiebert.com 
* 
*   This source code is released for free distribution under the terms of the 
*   GNU General Public License. It is provided on an as-is basis and no 
*   responsibility is accepted for its failure to perform as expected. 
* 
*   This is a reimplementation of the ctags(1) program. It is an attempt to 
*   provide a fully featured ctags program which is free of the limitations 
*   which most (all?) others are subject to. 
* 
*   It is derived from and inspired by the ctags program by Steve Kirkendall 
*   (kirkenda@cs.pdx.edu) that comes with the Elvis vi clone (though almost 
*   none of the original code remains). This, too, was freely available. 
* 
*   This program provides the following features: 
* 
*   Support for both K&R style and new ANSI style function definitions. 
* 
*   Generates tags for the following objects: 
*	- macro definitions 
*	- enumeration values 
*	- function definitions (and C++ methods) 
*	- function declarations (optional) 
*	- enum, struct and union tags and C++ class names 
*	- typedefs 
*	- variables 
* 
*   This module contains top level start-up and portability functions. 
*****************************************************************************/ 
 
/*============================================================================ 
=   Include files 
============================================================================*/ 
#ifdef HAVE_CONFIG_H 
# include  
#endif 
 
#if defined(__STDC__) || defined(MSDOS) || defined(WIN32) || defined(OS2) 
# define ENABLE_STDARG 
#endif 
 
#if defined(MSDOS) || defined(WIN32) 
# define HAVE_DOS_H 
# define HAVE_IO_H 
# define HAVE_TIME_H 
# define HAVE_CLOCK 
# define HAVE_CHSIZE 
# define HAVE_STRERROR 
# define HAVE_FINDNEXT 
# ifdef __BORLANDC__ 
#  define HAVE_DIR_H 
#  define HAVE_DIRENT_H 
#  define HAVE_FINDFIRST 
# else 
#  ifdef _MSC_VER 
#   define HAVE__FINDFIRST 
#  endif 
# endif 
#endif 
 
#ifdef DJGPP 
# define HAVE_DIR_H 
# define HAVE_UNISTD_H 
# define HAVE_FINDFIRST 
# define HAVE_TRUNCATE 
#endif 
 
#if defined(OS2) 
# define HAVE_DIRENT_H 
# define HAVE_TIME_H 
# define HAVE_IO_H 
# define HAVE_CLOCK 
# define HAVE_CHSIZE 
# define HAVE_OPENDIR 
# define HAVE_STRERROR 
#endif 
 
#ifdef AMIGA 
# define HAVE_TIME_H 
# define HAVE_SYS_STAT_H 
# define HAVE_CLOCK 
# define HAVE_STRERROR 
# define PATH_MAX 255 
# include 	/* for struct AnchorPath */ 
# include 	/* function prototypes */ 
# define ANCHOR_BUF_SIZE (512) 
# define ANCHOR_SIZE (sizeof(struct AnchorPath) + ANCHOR_BUF_SIZE) 
#endif 
 
#if defined(HAVE_UNISTD_H) 
# include 	/* to declare close(), ftruncate(), truncate() */ 
#endif 
 
#include  
 
#ifdef ENABLE_STDARG 
# include  
#else 
# include  
#endif 
 
/*  To declare "struct stat" and stat(). 
 */ 
#if defined(__MWERKS__) && defined(__MACINTOSH__) 
# include 		/* there is no sys directory on the Mac */ 
#else 
# include  
# include  
#endif 
 
#ifdef HAVE_DIRENT_H 
# ifdef __BORLANDC__ 
#  define boolean BORLAND_boolean 
# endif 
# include  
# undef boolean 
#endif 
 
/*  These header files provide for the functions necessary to do file 
 *  truncation. 
 */ 
#include  
#ifdef HAVE_IO_H 
# include  
#endif 
#ifdef HAVE_DOS_H 
# include  
#endif 
#ifdef HAVE_DIR_H 
# include  
#endif 
 
/*  To define the maximum path length 
 */ 
#include 			/* to define PATH_MAX (hopefully) */ 
#ifndef PATH_MAX 
# ifdef MAXNAMLEN 
#  define PATH_MAX MAXNAMLEN 
# endif 
#endif 
#if !defined(PATH_MAX) && defined(MAXPATH)	/* found in Borland C  */ 
# define PATH_MAX MAXPATH 
#endif 
#ifndef PATH_MAX 
# define PATH_MAX 127			/* ultimate fall-back */ 
#endif 
 
/*  To provide timings features if available. 
 */ 
#ifdef HAVE_CLOCK 
# ifdef HAVE_TIME_H 
#  include  
# endif 
#else 
# ifdef HAVE_TIMES 
#  ifdef HAVE_SYS_TIMES_H 
#   include  
#  endif 
# endif 
#endif 
 
#ifdef DEBUG 
# include  
#endif 
 
#include "ctags.h" 
 
/*============================================================================ 
=   Defines 
============================================================================*/ 
 
/*---------------------------------------------------------------------------- 
 *  Portability defines 
 *--------------------------------------------------------------------------*/ 
#if !defined(HAVE_TRUNCATE) && !defined(HAVE_FTRUNCATE) && !defined(HAVE_CHSIZE) 
# define USE_REPLACEMENT_TRUNCATE 
#endif 
 
#if defined(MSDOS) || defined(WIN32) || defined(OS2) 
# define PATH_SEPARATOR	'\\' 
#else 
# define PATH_SEPARATOR	'/' 
#endif 
 
/*  File type tests. 
 */ 
#ifndef S_ISREG 
# if defined(S_IFREG) && !defined(AMIGA) 
#  define S_ISREG(mode)	    ((mode) & S_IFREG) 
# else 
#  define S_ISREG(mode)	    TRUE	/* assume regular file */ 
# endif 
#endif 
 
#ifndef S_ISLNK 
# ifdef S_IFLNK 
#  define S_ISLNK(mode)	    (((mode) & S_IFMT) == S_IFLNK) 
# else 
#  define S_ISLNK(mode)	    FALSE	/* assume no soft links */ 
# endif 
#endif 
 
#ifndef S_ISDIR 
# ifdef S_IFDIR 
#  define S_ISDIR(mode)	    (((mode) & S_IFMT) == S_IFDIR) 
# else 
#  define S_ISDIR(mode)	    FALSE	/* assume no soft links */ 
# endif 
#endif 
 
/*  Hack for rediculous practice of Microsoft Visual C++. 
 */ 
#if defined(WIN32) && defined(_MSC_VER) 
# define chsize		_chsize 
# define open		_open 
# define close		_close 
# define stat		_stat 
# define O_RDWR 	_O_RDWR 
#endif 
 
/*---------------------------------------------------------------------------- 
 *  Miscellaneous defines 
 *--------------------------------------------------------------------------*/ 
#ifndef ETAGS 
# define ETAGS	"etags"		/* name which causes default use of to -e */ 
#endif 
 
#define selected(var,feature)	(((int)(var) & (int)(feature)) == (int)feature) 
 
#define plural(value)		(((unsigned long)(value) == 1L) ? "" : "s") 
 
/*============================================================================ 
=   Data definitions 
============================================================================*/ 
#if defined(MSDOS) || defined(WIN32) || defined(OS2) 
static const char DosPathDelimiters[] = ":/\\"; 
#endif 
 
static const char *ExecutableName = NULL; 
 
static struct { long files, lines, bytes; } Totals = { 0, 0, 0 }; 
 
tagFile TagFile = { 
    NULL,		/* file name */ 
    NULL,		/* file pointer */ 
    { 0, 0 },		/* numTags */ 
    { 0, 0, 0 },	/* max */ 
    { "", NULL },	/* etags */ 
    { 0, NULL }		/* line */ 
}; 
 
memberInfo NoClass = { MEMBER_NONE, VIS_UNDEFINED, FALSE, "" }; 
 
/*============================================================================ 
=   Function prototypes 
============================================================================*/ 
#ifdef NEED_PROTO_STAT 
extern int stat __ARGS((const char *, struct stat *)); 
#endif 
 
#ifdef NEED_PROTO_TRUNCATE 
extern int truncate __ARGS((const char *path, off_t length)); 
#endif 
 
#ifdef NEED_PROTO_FTRUNCATE 
extern int ftruncate __ARGS((int fd, off_t length)); 
#endif 
 
static const char *baseFilename __ARGS((const char *const path)); 
static boolean isAbsolutePath __ARGS((const char *const path)); 
static void combinePathAndFile __ARGS((char *const filePath, const char *const path, const char *const file)); 
 
static const char *findExtension __ARGS((const char *const fileName)); 
static boolean isFileHeader __ARGS((const char *const FileName)); 
static boolean isExtensionInList __ARGS((const char *const extension, const char *const *const list)); 
static langType getExtensionLanguage __ARGS((const char *const extension)); 
static langType getFileLanguage __ARGS((const char *const FileName)); 
 
static boolean isValidTagAddress __ARGS((const char *const tag)); 
static boolean isCtagsLine __ARGS((const char *const tag)); 
static boolean isEtagsLine __ARGS((const char *const tag)); 
static boolean isTagFile __ARGS((const char *const filename)); 
static void openTagFile __ARGS((const boolean toStdout)); 
static void closeTagFile __ARGS((const boolean resize)); 
 
static void beginEtagsFile __ARGS((void)); 
static void endEtagsFile __ARGS((const char *const name)); 
static boolean createTagsForFile __ARGS((const char *const filePath, const langType language)); 
static const char *getNextListFile __ARGS((FILE *const fp)); 
static const char *sourceFilePath __ARGS((const char *const file)); 
static boolean createTagsWithFallback __ARGS((const char *const fileName, const langType language)); 
static boolean createTagsForDirectory __ARGS((const char *const dirName)); 
static boolean createTagsForEntry __ARGS((const char *const entryName)); 
static boolean createTagsForList __ARGS((const char *const listFile)); 
static boolean createTagsForArgs __ARGS((const char *const *const argList)); 
static void printTotals __ARGS((const clock_t *const timeStamps)); 
static void makeTags __ARGS((const char *const *const argList)); 
 
static void setExecutableName __ARGS((const char *const path)); 
static void setDefaultTagFileName __ARGS((void)); 
static void setOptionDefaults __ARGS((void)); 
static void testEtagsInvocation __ARGS((void)); 
 
extern int main __ARGS((int argc, char **argv)); 
 
/*============================================================================ 
=   Function definitions 
============================================================================*/ 
 
#ifdef ENABLE_STDARG 
extern void error( const errorSelection selection, 
		   const char *const format, ... ) 
#else 
extern void error( va_alist ) 
    va_dcl 
#endif 
{ 
    va_list ap; 
 
#ifdef ENABLE_STDARG 
    va_start(ap, format); 
#else 
    const char *format; 
    errorSelection selection; 
 
    va_start(ap); 
    selection = va_arg(ap, errorSelection); 
    format = va_arg(ap, char *); 
#endif 
    fprintf(errout, "%s: %s", getExecutableName(), 
	    selected(selection, WARNING) ? "warning: " : ""); 
    vfprintf(errout, format, ap); 
    if (selected(selection, PERROR)) 
#ifdef HAVE_STRERROR 
	fprintf(errout, " : %s", strerror(errno)); 
#else 
	perror(" "); 
#endif 
    fputs("\n", errout); 
    va_end(ap); 
    if (selected(selection, FATAL)) 
	exit(1); 
} 
 
extern unsigned long getFileSize( name ) 
    const char *const __unused__ name; 
{ 
    struct stat fileStatus; 
    unsigned long size = 0; 
 
    if (stat(name, &fileStatus) == 0) 
	size = fileStatus.st_size; 
 
    return size; 
} 
 
extern boolean isNormalFile( name ) 
    const char *const name; 
{ 
    struct stat fileStatus; 
    boolean isNormal = FALSE; 
 
    if (stat(name, &fileStatus) == 0) 
	isNormal = (boolean)(S_ISLNK(fileStatus.st_mode) || 
			     S_ISREG(fileStatus.st_mode)  ); 
 
    return isNormal; 
} 
 
extern boolean isDirectory( name ) 
    const char *const name; 
{ 
    boolean isDir = FALSE; 
#ifdef AMIGA 
    struct FileInfoBlock *const fib = (struct FileInfoBlock *) 
					malloc(sizeof(struct FileInfoBlock)); 
 
    if (fib != NULL) 
    { 
	const BPTR flock = Lock((UBYTE *)name, (long)ACCESS_READ); 
 
	if (flock != (BPTR)NULL) 
	{ 
	    if (Examine(flock, fib)) 
		isDir = ((fib->fib_DirEntryType >= 0) ? TRUE : FALSE); 
	    UnLock(flock); 
	} 
	free(fib); 
    } 
#else 
    struct stat fileStatus; 
 
    if (stat(name, &fileStatus) == 0) 
	isDir = (boolean)S_ISDIR(fileStatus.st_mode); 
#endif 
    return isDir; 
} 
 
extern boolean doesFileExist( fileName ) 
    const char *const fileName; 
{ 
    struct stat fileStatus; 
 
    return (boolean)(stat(fileName, &fileStatus) == 0); 
} 
 
extern void addTotals( files, lines, bytes ) 
    const unsigned int files; 
    const unsigned long lines; 
    const unsigned long bytes; 
{ 
    Totals.files += files; 
    Totals.lines += lines; 
    Totals.bytes += bytes; 
} 
 
/*---------------------------------------------------------------------------- 
 *  Pathname manipulation (O/S dependent!!!) 
 *--------------------------------------------------------------------------*/ 
 
static const char *baseFilename( filePath ) 
    const char *const filePath; 
{ 
#if defined(MSDOS) || defined(WIN32) || defined(OS2) 
    const char *tail = NULL; 
    unsigned int i; 
 
    /*  Find whichever of the path delimiters is last. 
     */ 
    for (i = 0  ;  i < strlen(DosPathDelimiters)  ;  ++i) 
    { 
	const char *sep = strrchr(filePath, DosPathDelimiters[i]); 
 
	if (sep > tail) 
	    tail = sep; 
    } 
#else 
    const char *tail = strrchr(filePath, PATH_SEPARATOR); 
#endif 
    if (tail == NULL) 
	tail = filePath; 
    else 
	++tail;			/* step past last delimiter */ 
 
    return tail; 
} 
 
static boolean isAbsolutePath( path ) 
    const char *const path; 
{ 
#if defined(MSDOS) || defined(WIN32) || defined(OS2) 
    return (strchr(DosPathDelimiters, path[0]) != NULL); 
#else 
    return (boolean)(path[0] == PATH_SEPARATOR); 
#endif 
} 
 
static void combinePathAndFile( filePath, path, file ) 
    char *const filePath; 
    const char *const path; 
    const char *const file; 
{ 
#if defined(MSDOS) || defined(WIN32) || defined(OS2) 
    if (strchr(DosPathDelimiters, path[strlen(path)-1]) != NULL) 
	sprintf(filePath, "%s%s", path, file); 
    else 
	sprintf(filePath, "%s%c%s", path, PATH_SEPARATOR, file); 
#else 
    if ((path[strlen(path) - 1]) == PATH_SEPARATOR) 
	sprintf(filePath, "%s%s", path, file); 
    else 
	sprintf(filePath, "%s%c%s", path, PATH_SEPARATOR, file); 
#endif 
} 
 
/*---------------------------------------------------------------------------- 
 *  File extension and language handling 
 *--------------------------------------------------------------------------*/ 
 
static const char *findExtension( fileName ) 
    const char *const fileName; 
{ 
    const char *extension; 
    const char *const start = strrchr(fileName, '.');	/* find last '.' */ 
 
    if (start == NULL) 
	extension = ""; 
    else 
	extension = start + 1;		/* skip to character after '.' */ 
 
    return extension; 
} 
 
/*  Determines whether the specified file name is considered to be a header 
 *  file for the purposes of determining whether enclosed tags are global or 
 *  static. 
 */ 
static boolean isFileHeader( fileName ) 
    const char *const fileName; 
{ 
    const char *const extension = findExtension(fileName); 
    boolean header = FALSE;		    /* default unless match found */ 
    int i; 
 
    for (i = 0 ; Option.headerExt[i] != NULL ; ++i) 
    { 
	if (strcmp(Option.headerExt[i], extension) == 0) 
	{ 
	    header = TRUE;		    /* found in list */ 
	    break; 
	} 
    } 
    return header; 
} 
 
static boolean isExtensionInList( extension, list ) 
    const char *const extension; 
    const char *const *const list; 
{ 
    boolean isKnown = FALSE; 
 
    if (list != NULL) 
    { 
	const char *const *pExtension = list; 
 
	while (! isKnown  &&  *pExtension != NULL) 
	{ 
#if defined(MSDOS) || defined(WIN32) || defined(OS2) 
	    if (strequiv(extension, *pExtension)) 
#else 
	    if (strcmp(extension, *pExtension) == 0) 
#endif 
		isKnown = TRUE; 
	    ++pExtension; 
	} 
    } 
    return isKnown; 
} 
 
static langType getExtensionLanguage( extension ) 
    const char *const extension; 
{ 
    unsigned int i; 
    langType language = LANG_IGNORE; 
 
    for (i = 0  ;  i < (int)LANG_COUNT  ;  ++i) 
    { 
	if (isExtensionInList(extension, Option.langMap[i])) 
	{ 
	    language = (langType)i; 
	    break; 
	} 
    } 
    return language; 
} 
 
static langType getFileLanguage( fileName ) 
    const char *const fileName; 
{ 
    const char *const extension = findExtension(fileName); 
    langType language; 
 
    if (Option.language != LANG_AUTO) 
	language = Option.language; 
    else if (isFileHeader(fileName)) 
	language = LANG_CPP; 
    else if (extension[0] == '\0') 
	language = LANG_IGNORE;		/* ignore files with no extension */ 
    else 
	language = getExtensionLanguage(extension); 
 
    return language; 
} 
 
/*---------------------------------------------------------------------------- 
 *  Tag file management 
 *--------------------------------------------------------------------------*/ 
 
static boolean isValidTagAddress( excmd ) 
    const char *const excmd; 
{ 
    boolean isValid = FALSE; 
 
    if (strchr("/?", excmd[0]) != NULL) 
	isValid = TRUE; 
    else 
    { 
	char *address = (char *)malloc(strlen(excmd) + 1); 
 
	if (address != NULL) 
	{ 
	    if (sscanf(excmd, "%[^;\n]", address) == 1  && 
		strspn(address,"0123456789") == strlen(address)) 
		    isValid = TRUE; 
	    free(address); 
	} 
    } 
    return isValid; 
} 
 
static boolean isCtagsLine( line ) 
    const char *const line; 
{ 
    enum fieldList { TAG, TAB1, SRC_FILE, TAB2, EXCMD, NUM_FIELDS }; 
    boolean ok = FALSE;			/* we assume not unless confirmed */ 
    const size_t fieldLength = strlen(line) + 1; 
    char *const fields = (char *)malloc((size_t)NUM_FIELDS * fieldLength); 
 
    if (fields == NULL) 
	error(FATAL, "Cannot analyze tag file"); 
    else 
    { 
#define field(x)	(fields + ((size_t)(x) * fieldLength)) 
 
	const int numFields = sscanf(line, "%[^\t]%[\t]%[^\t]%[\t]%[^\n]", 
				     field(TAG), field(TAB1), field(SRC_FILE), 
				     field(TAB2), field(EXCMD)); 
 
	/*  There must be exactly five fields: two tab fields containing 
	 *  exactly one tab each, the tag must not begin with "#", and the 
	 *  file name should not end with ";", and the excmd must be 
	 *  accceptable. 
	 * 
	 *  These conditions will reject tag-looking lines like: 
	 *      int	a;	 
	 *      #define	LABEL	 
	 */ 
	if (numFields == NUM_FIELDS   && 
	    strlen(field(TAB1)) == 1  && 
	    strlen(field(TAB2)) == 1  && 
	    field(TAG)[0] != '#'      && 
	    field(SRC_FILE)[strlen(field(SRC_FILE)) - 1] != ';'  && 
	    isValidTagAddress(field(EXCMD))) 
		ok = TRUE; 
 
	free(fields); 
    } 
    return ok; 
} 
 
static boolean isEtagsLine( line ) 
    const char *const line; 
{ 
    const char *const magic = "\f\n"; 
 
    return (boolean)(strncmp(line, magic, strlen(magic)) == 0); 
} 
 
static boolean isTagFile( filename ) 
    const char *const filename; 
{ 
    boolean ok = FALSE;			/* we assume not unless confirmed */ 
    FILE *const fp = fopen(filename, "r"); 
 
    if (fp == NULL  &&  errno == ENOENT) 
	ok = TRUE; 
    else if (fp != NULL) 
    { 
	const char *line = readLine(&TagFile.line, fp); 
 
	if (line == NULL) 
	    ok = TRUE; 
	else if (Option.etags)		/* check for etags magic number */ 
	    ok = isEtagsLine(line); 
	else 
	    ok = isCtagsLine(line); 
	fclose(fp); 
    } 
    return ok; 
} 
 
static void openTagFile( toStdout ) 
    const boolean toStdout; 
{ 
    static char tempName[L_tmpnam]; 
 
    /*  Open the tags File. 
     */ 
    if (toStdout) 
    { 
	TagFile.name = tmpnam(tempName); 
	TagFile.fp = fopen(TagFile.name, "w"); 
    } 
    else 
    { 
	const char *const fname  = Option.tagFileName; 
	const boolean fileExists = doesFileExist(fname); 
 
	TagFile.name = fname; 
	if (fileExists  &&  ! isTagFile(fname)) 
	    error(FATAL, 
	      "\"%s\" doesn't look like a tag file; I refuse to overwrite it.", 
		  fname); 
	if (Option.append  &&  fileExists) 
	{ 
	    TagFile.fp = fopen(fname, "r+"); 
	    if (TagFile.fp != NULL) 
	    { 
		TagFile.numTags.prev = updatePseudoTags(); 
		fseek(TagFile.fp, 0L, SEEK_END); 
	    } 
	} 
	else 
	{ 
	    TagFile.fp = fopen(fname, "w"); 
	    if (TagFile.fp != NULL) 
		addPseudoTags(); 
	} 
    } 
    if (TagFile.fp == NULL) 
    { 
	error(FATAL | PERROR, "cannot open tag file"); 
	exit(1); 
    } 
} 
 
#ifdef USE_REPLACEMENT_TRUNCATE 
 
static int copyChars __ARGS((FILE *const fpFrom, FILE *const fpTo, const off_t size)); 
static int copyFile __ARGS((const char *const from, const char *const to, const off_t size)); 
static int replacementTruncate __ARGS((const char *const name, const off_t size)); 
 
static int copyChars( fpFrom, fpTo, size ) 
    FILE *const fpFrom; 
    FILE *const fpTo; 
    const off_t size; 
{ 
    off_t count; 
    int result = -1; 
 
    for (count = 0  ;  count < size  ;  ++count) 
    { 
	const int c = getc(fpFrom); 
 
	if (c == EOF  ||  putc(c, fpTo) == EOF) 
	    break; 
    } 
    if (count == size) 
	result = 0; 
    return result; 
} 
 
static int copyFile( from, to, size ) 
    const char *const from; 
    const char *const to; 
    const off_t size; 
{ 
    int result = -1; 
    FILE *const fpFrom = fopen(from, "r"); 
 
    if (fpFrom != NULL) 
    { 
	FILE *const fpTo = fopen(to, "w"); 
 
	if (fpFrom != NULL) 
	{ 
	    result = copyChars(fpFrom, fpTo, size); 
	    if (result == 0) 
		result = fclose(fpTo); 
	} 
	if (result == 0) 
	    result = fclose(fpFrom); 
    } 
    return result; 
} 
 
/*  Replacement for missing library function. 
 */ 
static int replacementTruncate( name, size ) 
    const char *const name; 
    const off_t size; 
{ 
    char tempName[L_tmpnam]; 
    int result = -1; 
 
    if (tmpnam(tempName) != NULL) 
    { 
	result = copyFile(name, tempName, size); 
	if (result == 0) 
	    result = copyFile(tempName, name, size); 
	if (result == 0) 
	    result = remove(tempName); 
    } 
    return result; 
} 
 
#endif 
 
static void closeTagFile( resize ) 
    const boolean resize; 
{ 
    const long __unused__ size = ftell(TagFile.fp); 
 
    fclose(TagFile.fp); 
    if (resize) 
    { 
	int __unused__ result = 0; 
 
#ifdef USE_REPLACEMENT_TRUNCATE 
	result = replacementTruncate(TagFile.name, (off_t)size); 
#else 
# ifdef HAVE_TRUNCATE 
	result = truncate(TagFile.name, (off_t)size); 
# else 
	const int fd = open(TagFile.name, O_RDWR); 
 
	if (fd != -1) 
	{ 
#  ifdef HAVE_FTRUNCATE 
	    result = ftruncate(fd, (off_t)size); 
#  else 
#   ifdef HAVE_CHSIZE 
	    result = chsize(fd, size); 
#   endif 
#  endif 
	    close(fd); 
	} 
# endif 
#endif 
	if (result == -1) 
	    fprintf(errout, "Cannot shorten tag file: errno = %d\n", errno); 
    } 
} 
 
/*---------------------------------------------------------------------------- 
 *	Create tags 
 *--------------------------------------------------------------------------*/ 
 
static void beginEtagsFile() 
{ 
    tmpnam(TagFile.etags.name); 
    TagFile.etags.fp = fopen(TagFile.etags.name, "w+b"); 
    if (TagFile.etags.fp == NULL) 
	error(FATAL | PERROR, "cannot open \"%s\"", TagFile.etags.name); 
    TagFile.etags.byteCount = 0; 
} 
 
static void endEtagsFile( name ) 
    const char *const name; 
{ 
    const char *line; 
 
    fprintf(TagFile.fp, "\f\n%s,%ld\n", name, (long)TagFile.etags.byteCount); 
    if (TagFile.etags.fp != NULL) 
    { 
	rewind(TagFile.etags.fp); 
	while ((line = readLine(&TagFile.line, TagFile.etags.fp)) != NULL) 
	    fputs(line, TagFile.fp); 
	fclose(TagFile.etags.fp); 
	remove(TagFile.etags.name); 
    } 
    TagFile.etags.name[0] = '\0'; 
} 
 
static boolean createTagsForFile( filePath, language ) 
    const char *const filePath; 
    const langType language; 
{ 
    boolean ok; 
 
    if (filePath == NULL) 
	ok = FALSE; 
    else 
    { 
	const boolean isHeader = isFileHeader(filePath); 
 
	ok = TRUE; 
	if (Option.etags) 
	    beginEtagsFile(); 
	if (cppOpen(filePath, language, isHeader)) 
	{ 
	    tagInfo tag; 
 
	    DebugStatement( clearString(tag.name, MaxNameLength); ) 
	    strcpy(tag.name, baseFilename(filePath)); 
	    tag.location   = 0; 
	    tag.lineNumber = 1; 
 
	    makeTag(&tag, &NoClass, SCOPE_GLOBAL, TAG_SOURCE_FILE); 
	    ok = createTags(0, NULL); 
	    cppClose(); 
	} 
	if (Option.etags) 
	    endEtagsFile(filePath); 
    } 
    return ok; 
} 
 
static const char *getNextListFile( fp ) 
    FILE *const fp; 
{ 
    static char fileName[PATH_MAX + 1]; 
    const char *const buf = fgets(fileName, PATH_MAX + 1, fp); 
 
    if (buf != NULL) 
    { 
	char *const newline = strchr(fileName, '\n'); 
 
	if (newline == NULL) 
	    fileName[PATH_MAX] = '\0'; 
	else 
	    *newline = '\0'; 
    } 
    return buf; 
} 
 
static const char *sourceFilePath( file ) 
    const char *const file; 
{ 
    const char *result = NULL; 
 
    if (Option.path == NULL  ||  isAbsolutePath(file)) 
	result = file; 
    else if (strlen(Option.path) + strlen(file) < (size_t)PATH_MAX) 
    { 
	static char filePath[PATH_MAX + 1]; 
 
	combinePathAndFile(filePath, Option.path, file); 
	result = filePath; 
    } 
    return result; 
} 
 
static boolean createTagsWithFallback( fileName, language ) 
    const char *const fileName; 
    const langType language; 
{ 
    const char *const filePath	= sourceFilePath(fileName); 
    const unsigned long numTags	= TagFile.numTags.added; 
    const long tagFilePosition	= ftell(TagFile.fp); 
    boolean resize = FALSE; 
 
    if (! createTagsForFile(filePath, language)) 
    { 
	/*  Restore prior state of tag file. 
	 */ 
	fseek(TagFile.fp, tagFilePosition, SEEK_SET); 
	TagFile.numTags.added = numTags; 
	DebugStatement( debugPrintf(DEBUG_STATUS, 
				"%s: formatting error; retrying\n", fileName); ) 
 
	Option.braceFormat = TRUE; 
	    createTagsForFile(filePath, language); 
	Option.braceFormat = FALSE; 
	resize = TRUE; 
    } 
    return resize; 
} 
 
# if defined(MSDOS) || defined(WIN32) 
 
static boolean createTagsForMatchingEntries __ARGS((char *const pattern)); 
 
static boolean createTagsForMatchingEntries( pattern ) 
    char *const pattern; 
{ 
    boolean resize = FALSE; 
    const size_t tailIndex = baseFilename(pattern) - (const char *)pattern; 
    char *const tail = pattern + tailIndex; 
#ifdef HAVE_FINDFIRST 
    struct ffblk fileInfo; 
    int result = findfirst(pattern, &fileInfo, FA_DIREC); 
 
    while (result == 0) 
    { 
	const char *const entryName = fileInfo.ff_name; 
 
	/*  We must not recurse into the directories "." or "..". 
	 */ 
	if (strcmp(entryName, ".") != 0  &&  strcmp(entryName, "..") != 0) 
	{ 
	    strcpy(tail, entryName); 
	    resize |= createTagsForEntry(pattern); 
	} 
	result = findnext(&fileInfo); 
    } 
#else 
# ifdef HAVE__FINDFIRST 
    struct _finddata_t fileInfo; 
    long hFile = _findfirst(pattern, &fileInfo); 
 
    if (hFile != -1L) 
    { 
	do 
	{ 
	    const char *const entryName = fileInfo.name; 
 
	    /*  We must not recurse into the directories "." or "..". 
	     */ 
	    if (strcmp(entryName, ".") != 0  &&  strcmp(entryName, "..") != 0) 
	    { 
		strcpy(tail, entryName); 
		resize |= createTagsForEntry(pattern); 
	    } 
	} while (_findnext(hFile, &fileInfo) == 0); 
	_findclose(hFile); 
    } 
# endif		/* HAVE__FINDFIRST */ 
#endif		/* HAVE_FINDFIRST */ 
    return resize; 
} 
 
#else 
# ifdef AMIGA 
 
static boolean createTagsForMatchingEntries __ARGS((char *const pattern)); 
 
static boolean createTagsForMatchingEntries( pattern ) 
    char *const pattern; 
{ 
    boolean resize = FALSE; 
    struct AnchorPath *const anchor = (struct AnchorPath *) 
					calloc((size_t)1, (size_t)ANCHOR_SIZE); 
 
    if (anchor != NULL) 
    { 
	LONG result; 
 
	anchor->ap_Strlen = ANCHOR_BUF_SIZE; /* ap_Length no longer supported */ 
 
	/*  Allow '.' for current directory. 
	 */ 
#ifdef APF_DODOT 
	anchor->ap_Flags = APF_DODOT | APF_DOWILD; 
#else 
	anchor->ap_Flags = APF_DoDot | APF_DoWild; 
#endif 
 
	result = MatchFirst((UBYTE *)pattern, anchor); 
	while (result == 0) 
	{ 
	    resize |= createTagsForEntry((char *)anchor->ap_Buf); 
	    result = MatchNext(anchor); 
	} 
	MatchEnd(anchor); 
	free(anchor); 
    } 
    return resize; 
} 
 
# endif	/* AMIGA */ 
#endif	/* MSDOS || WIN32 */ 
 
static boolean createTagsForDirectory( dirName ) 
    const char *const dirName; 
{ 
#ifdef HAVE_OPENDIR 
    boolean resize = FALSE; 
    DIR *const dir = opendir(dirName); 
 
    if (dir == NULL) 
	error(WARNING, ""); 
    else 
    { 
	struct dirent *entry; 
 
	DebugStatement( debugPrintf(DEBUG_STATUS, "RECURSING into %s%c\n", 
				    dirName, PATH_SEPARATOR); ) 
	while ((entry = readdir(dir)) != NULL) 
	{ 
	    const char *const entryName = entry->d_name; 
 
	    if (strcmp(entryName, ".")    != 0  && 
		strcmp(entryName, "..")   != 0  && 
		strcmp(entryName, "SCCS") != 0   ) 
	    { 
		char filePath[PATH_MAX + 1]; 
 
		combinePathAndFile(filePath, dirName, entryName); 
		resize |= createTagsForEntry(filePath); 
	    } 
	} 
	closedir(dir); 
    } 
    return resize; 
#else 
# if defined(MSDOS) || defined(WIN32) 
    char pattern[PATH_MAX + 1]; 
    boolean resize; 
 
    DebugStatement( debugPrintf(DEBUG_STATUS, "RECURSING into %s%c\n", 
				dirName, PATH_SEPARATOR); ) 
    strcpy(pattern, dirName); 
    strcat(pattern, "\\*.*"); 
    resize = createTagsForMatchingEntries(pattern); 
 
    return resize; 
# else 
#  ifdef AMIGA 
    char pattern[PATH_MAX + 1]; 
    boolean resize = FALSE; 
 
    DebugStatement( debugPrintf(DEBUG_STATUS, "RECURSING into %s%c\n", 
				dirName, PATH_SEPARATOR); ) 
    strcpy(pattern, dirName); 
    if (pattern[0] != '\0') 
	strcat(pattern, "/");     /* "/#?" searches one directory upwards */ 
    strcat(pattern, "#?"); 
    resize = createTagsForMatchingEntries(pattern); 
 
    return resize; 
#  else /* !AMIGA */ 
    return FALSE; 
#  endif 
# endif 
#endif	/* HAVE_OPENDIR */ 
} 
 
static boolean createTagsForEntry( entryName ) 
    const char *const entryName; 
{ 
    boolean resize = FALSE; 
    langType language; 
 
    if (! doesFileExist(entryName)) 
	error(WARNING | PERROR, "cannot open \"%s\"", entryName); 
    else if (isDirectory(entryName)) 
    { 
	if (Option.recurse) 
	    resize = createTagsForDirectory(entryName); 
	else 
	{ 
	    DebugStatement( debugPrintf(DEBUG_STATUS,"  no recurse into %s%c\n", 
					entryName, PATH_SEPARATOR) ); 
	    resize = FALSE; 
	} 
    } 
    else if (! isNormalFile(entryName)) 
    { 
	DebugStatement( if (debug(DEBUG_STATUS)) 
			printf("  ignoring %s (special file)\n", entryName) ); 
    } 
    else if ((language = getFileLanguage(entryName)) == LANG_IGNORE) 
	DebugStatement( debugOpen(entryName, FALSE, language) ); 
    else 
    { 
	resize = createTagsWithFallback(entryName, language); 
	addTotals(1, 0L, 0L); 
    } 
    return resize; 
} 
 
static boolean createTagsForArgs( argList ) 
    const char *const *const argList; 
{ 
    boolean resize = FALSE; 
    const char *const *pArg; 
 
    /*  Generate tags for each argument on the command line. 
     */ 
    for (pArg = argList  ;  *pArg != NULL  ;  ++pArg) 
    { 
	const char *arg = *pArg; 
 
#if defined(MSDOS) || defined(WIN32) 
	char pattern[PATH_MAX + 1]; 
 
	strcpy(pattern, arg); 
 
	/*  We must transform the "." and ".." forms into something that can 
	 *  be expanded by the MSDOS/Windows functions. 
	 */ 
	if (Option.recurse  && 
	    (strcmp(pattern, ".") == 0  ||  strcmp(pattern, "..") == 0)) 
	{ 
	    strcat(pattern, "\\*.*"); 
	} 
	resize |= createTagsForMatchingEntries(pattern); 
#else 
	resize |= createTagsForEntry(arg); 
#endif 
    } 
 
    return resize; 
} 
 
/*  Create tags for the source files listed in the file specified by 
 *  "listFile". 
 */ 
static boolean createTagsForList( listFile ) 
    const char *const listFile; 
{ 
    boolean resize = FALSE; 
    FILE *const fp = (strcmp(listFile,"-") == 0) ? stdin : fopen(listFile, "r"); 
 
    if (fp == NULL) 
	error(FATAL | PERROR, "cannot open \"%s\"", listFile); 
    else 
    { 
	const char *fileName = getNextListFile(fp); 
 
	while (fileName != NULL  &&  fileName[0] != '\0') 
	{ 
	    resize |= createTagsForEntry(fileName); 
	    fileName = getNextListFile(fp); 
	} 
	if (fp != stdin) 
	    fclose(fp); 
    } 
    return resize; 
} 
 
#ifdef HAVE_CLOCK 
# define CLOCK_AVAILABLE 
# ifndef CLOCKS_PER_SEC 
#  define CLOCKS_PER_SEC	1000000 
# endif 
#else 
# ifdef HAVE_TIMES 
#  define CLOCK_AVAILABLE 
#  define CLOCKS_PER_SEC	60 
static clock_t clock __ARGS((void)); 
static clock_t clock() 
{ 
    struct tms buf; 
 
    times(&buf); 
    return (buf.tms_utime + buf.tms_stime); 
} 
# else 
#  define clock()  (clock_t)0 
# endif 
#endif 
 
static void printTotals( timeStamps ) 
    const clock_t *const timeStamps; 
{ 
    const unsigned long totalTags = TagFile.numTags.added + 
				    TagFile.numTags.prev; 
 
    fprintf(errout, "%ld file%s, %ld line%s (%ld kB) scanned", 
	    Totals.files, plural(Totals.files), 
	    Totals.lines, plural(Totals.lines), 
	    Totals.bytes/1024L); 
#ifdef CLOCK_AVAILABLE 
    { 
	const double interval = ((double)(timeStamps[1] - timeStamps[0])) / 
				CLOCKS_PER_SEC; 
 
	fprintf(errout, " in %.01f seconds", interval); 
	if (interval != (double)0.0) 
	    fprintf(errout, " (%lu kB/s)", 
		    (unsigned long)(Totals.bytes / interval) / 1024L); 
    } 
#endif 
    fputc('\n', errout); 
 
    fprintf(errout, "%lu tag%s added to tag file", 
	    TagFile.numTags.added, plural(TagFile.numTags.added)); 
    if (Option.append) 
	fprintf(errout, " (now %lu tags)", totalTags); 
    fputc('\n', errout); 
 
    if (totalTags > 0  &&  Option.sorted) 
    { 
	fprintf(errout, "%lu tag%s sorted", totalTags, plural(totalTags)); 
#ifdef CLOCK_AVAILABLE 
	fprintf(errout, " in %.02f seconds", 
		((double)(timeStamps[3] - timeStamps[2])) / CLOCKS_PER_SEC); 
#endif 
	fputc('\n', errout); 
    } 
 
#ifdef DEBUG 
    fprintf(errout, "longest tag line = %lu\n", 
	    (unsigned long)TagFile.max.line); 
#endif 
} 
 
static void makeTags( argList ) 
    const char *const *const argList; 
{ 
    boolean toStdout = FALSE; 
    boolean resize = FALSE; 
    clock_t timeStamps[4]; 
 
    if (Option.xref  ||  strcmp(Option.tagFileName, "-") == 0 
#if !defined(MSDOS) && !defined(WIN32) && !defined(OS2) 
	|| strcmp(Option.tagFileName, "/dev/stdout") == 0 
#endif 
	) 
	toStdout = TRUE; 
 
    openTagFile(toStdout); 
 
    if (Option.printTotals) timeStamps[0] = clock(); 
 
    if (Option.fileList != NULL) 
	resize = createTagsForList(Option.fileList); 
    resize = (boolean)(createTagsForArgs(argList) || resize); 
 
    if (Option.printTotals) timeStamps[1] = clock(); 
 
    closeTagFile(resize); 
    freeIgnoreList(); 
 
    if (TagFile.numTags.added + TagFile.numTags.prev > 0L) 
    { 
	if (Option.sorted) 
	{ 
	    if (Option.printTotals) timeStamps[2] = clock(); 
#ifdef EXTERNAL_SORT 
	    externalSortTags(toStdout); 
#else 
	    internalSortTags(toStdout); 
#endif 
	    if (Option.printTotals) timeStamps[3] = clock(); 
	} 
	else if (toStdout) 
	    catFile(TagFile.name); 
    } 
    if (toStdout) 
	remove(TagFile.name);		/* remove temporary file */ 
 
    if (Option.printTotals) 
	printTotals(timeStamps); 
} 
 
/*---------------------------------------------------------------------------- 
 *	Start up code 
 *--------------------------------------------------------------------------*/ 
 
static void setExecutableName( path ) 
    const char *const path; 
{ 
    ExecutableName = baseFilename(path); 
} 
 
extern const char *getExecutableName() 
{ 
    return ExecutableName; 
} 
 
static void setDefaultTagFileName() 
{ 
    if (Option.tagFileName != NULL) 
	;		/* accept given name */ 
    else if (Option.etags) 
	Option.tagFileName = ETAGS_FILE; 
    else 
	Option.tagFileName = CTAGS_FILE; 
} 
 
static void setOptionDefaults() 
{ 
    if (Option.xref) 
	Option.include.sourceFiles = FALSE; 
 
    setDefaultTagFileName(); 
} 
 
static void testEtagsInvocation() 
{ 
    if (strncmp(getExecutableName(), ETAGS, strlen(ETAGS)) == 0) 
    { 
	Option.startedAsEtags	= TRUE; 
	Option.etags		= TRUE; 
	Option.sorted		= FALSE; 
    } 
} 
 
extern int main( argc, argv ) 
    int __unused__ argc; 
    char **argv; 
{ 
    char **envArgList; 
    const char *const *fileList; 
 
#ifdef __EMX__ 
    _wildcard(&argc, &argv);		/* expand wildcards in argument list */ 
#endif 
 
    setExecutableName(*argv++); 
    testEtagsInvocation(); 
 
    /*  Parse options. 
     */ 
    envArgList = parseEnvironmentOptions(); 
    fileList = (const char *const *)parseOptions(argv); 
    setOptionDefaults(); 
    buildKeywordHash(); 
 
    /*	Generate tags if there is at least one source file or a file list. 
     */ 
    if (*fileList == NULL  &&  Option.fileList == NULL) 
    { 
	if (! Option.recurse) 
	    error(FATAL, "No files specified. Try \"%s --help\".", 
		getExecutableName()); 
	else 
	{ 
	    static const char *defaultRecursionDir[] = { ".", NULL }; 
	    fileList = defaultRecursionDir; 
	} 
    } 
    makeTags(fileList); 
 
    /*  Post tag generation clean-up. 
     */ 
    freeLineBuffer(&TagFile.line); 
    if (envArgList != NULL) 
	free(envArgList); 
    exit(0); 
    return 0; 
} 
 
/* vi:set tabstop=8 shiftwidth=4: */