ctags/routines.c

892 lines
19 KiB
C

/*
* $Id$
*
* Copyright (c) 2002-2003, Darren Hiebert
*
* This source code is released for free distribution under the terms of the
* GNU General Public License.
*
* This module contains a lose assortment of shared functions.
*/
/*
* INCLUDE FILES
*/
#include "general.h" /* must always come first */
#ifdef HAVE_STDLIB_H
# include <stdlib.h> /* to declare malloc (), realloc () */
#endif
#include <ctype.h>
#include <string.h>
#include <stdarg.h>
#include <errno.h>
#include <stdio.h> /* to declare tempnam(), and SEEK_SET (hopefully) */
#ifdef HAVE_FCNTL_H
# include <fcntl.h> /* to declar O_RDWR, O_CREAT, O_EXCL */
#endif
#ifdef HAVE_UNISTD_H
# include <unistd.h> /* to declare mkstemp () */
#endif
/* To declare "struct stat" and stat ().
*/
#if defined (HAVE_SYS_TYPES_H)
# include <sys/types.h>
#else
# if defined (HAVE_TYPES_H)
# include <types.h>
# endif
#endif
#ifdef HAVE_SYS_STAT_H
# include <sys/stat.h>
#else
# ifdef HAVE_STAT_H
# include <stat.h>
# endif
#endif
#ifdef HAVE_DOS_H
# include <dos.h> /* to declare MAXPATH */
#endif
#ifdef HAVE_DIRECT_H
# include <direct.h> /* to _getcwd */
#endif
#ifdef HAVE_DIR_H
# include <dir.h> /* to declare findfirst() and findnext() */
#endif
#ifdef HAVE_IO_H
# include <io.h> /* to declare open() */
#endif
#include "debug.h"
#include "routines.h"
/*
* MACROS
*/
#ifndef TMPDIR
# define TMPDIR "/tmp"
#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
#ifndef S_IFMT
# define S_IFMT 0
#endif
#ifndef S_IXUSR
# define S_IXUSR 0
#endif
#ifndef S_IXGRP
# define S_IXGRP 0
#endif
#ifndef S_IXOTH
# define S_IXOTH 0
#endif
#ifndef S_IRUSR
# define S_IRUSR 0400
#endif
#ifndef S_IWUSR
# define S_IWUSR 0200
#endif
#ifndef S_ISUID
# define S_ISUID 0
#endif
/* Hack for rediculous practice of Microsoft Visual C++.
*/
#if defined (WIN32)
# if defined (_MSC_VER)
# define stat _stat
# define getcwd _getcwd
# define currentdrive() (_getdrive() + 'A' - 1)
# define PATH_MAX _MAX_PATH
# elif defined (__BORLANDC__)
# define PATH_MAX MAXPATH
# define currentdrive() (getdisk() + 'A')
# elif defined (DJGPP)
# define currentdrive() (getdisk() + 'A')
# else
# define currentdrive() 'C'
# endif
#endif
#ifndef PATH_MAX
# define PATH_MAX 256
#endif
/*
* Miscellaneous macros
*/
#define selected(var,feature) (((int)(var) & (int)(feature)) == (int)feature)
/*
* DATA DEFINITIONS
*/
#if defined (MSDOS_STYLE_PATH)
const char *const PathDelimiters = ":/\\";
#elif defined (VMS)
const char *const PathDelimiters = ":]>";
#endif
char *CurrentDirectory;
static const char *ExecutableProgram;
static const char *ExecutableName;
/*
* FUNCTION PROTOTYPES
*/
#ifdef NEED_PROTO_STAT
extern int stat (const char *, struct stat *);
#endif
#ifdef NEED_PROTO_LSTAT
extern int lstat (const char *, struct stat *);
#endif
#if defined (MSDOS) || defined (WIN32) || defined (VMS) || defined (__EMX__) || defined (AMIGA)
# define lstat(fn,buf) stat(fn,buf)
#endif
/*
* FUNCTION DEFINITIONS
*/
extern void freeRoutineResources (void)
{
if (CurrentDirectory != NULL)
eFree (CurrentDirectory);
}
extern void setExecutableName (const char *const path)
{
ExecutableProgram = path;
ExecutableName = baseFilename (path);
#ifdef VAXC
{
/* remove filetype from executable name */
char *p = strrchr (ExecutableName, '.');
if (p != NULL)
*p = '\0';
}
#endif
}
extern const char *getExecutableName (void)
{
return ExecutableName;
}
extern const char *getExecutablePath (void)
{
return ExecutableProgram;
}
extern void error (
const errorSelection selection, const char *const format, ...)
{
va_list ap;
va_start (ap, format);
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);
}
/*
* Memory allocation functions
*/
extern void *eMalloc (const size_t size)
{
void *buffer = malloc (size);
if (buffer == NULL)
error (FATAL, "out of memory");
return buffer;
}
extern void *eCalloc (const size_t count, const size_t size)
{
void *buffer = calloc (count, size);
if (buffer == NULL)
error (FATAL, "out of memory");
return buffer;
}
extern void *eRealloc (void *const ptr, const size_t size)
{
void *buffer;
if (ptr == NULL)
buffer = eMalloc (size);
else
{
buffer = realloc (ptr, size);
if (buffer == NULL)
error (FATAL, "out of memory");
}
return buffer;
}
extern void eFree (void *const ptr)
{
Assert (ptr != NULL);
free (ptr);
}
/*
* String manipulation functions
*/
/*
* Compare two strings, ignoring case.
* Return 0 for match, < 0 for smaller, > 0 for bigger
* Make sure case is folded to uppercase in comparison (like for 'sort -f')
* This makes a difference when one of the chars lies between upper and lower
* ie. one of the chars [ \ ] ^ _ ` for ascii. (The '_' in particular !)
*/
extern int struppercmp (const char *s1, const char *s2)
{
int result;
do
{
result = toupper ((int) *s1) - toupper ((int) *s2);
} while (result == 0 && *s1++ != '\0' && *s2++ != '\0');
return result;
}
extern int strnuppercmp (const char *s1, const char *s2, size_t n)
{
int result;
do
{
result = toupper ((int) *s1) - toupper ((int) *s2);
} while (result == 0 && --n > 0 && *s1++ != '\0' && *s2++ != '\0');
return result;
}
#ifndef HAVE_STRSTR
extern char* strstr (const char *str, const char *substr)
{
const size_t length = strlen (substr);
const char *match = NULL;
const char *p;
for (p = str ; *p != '\0' && match == NULL ; ++p)
if (strncmp (p, substr, length) == 0)
match = p;
return (char*) match;
}
#endif
extern char* eStrdup (const char* str)
{
char* result = xMalloc (strlen (str) + 1, char);
strcpy (result, str);
return result;
}
extern void toLowerString (char* str)
{
while (*str != '\0')
{
*str = tolower ((int) *str);
++str;
}
}
extern void toUpperString (char* str)
{
while (*str != '\0')
{
*str = toupper ((int) *str);
++str;
}
}
/* Newly allocated string containing lower case conversion of a string.
*/
extern char* newLowerString (const char* str)
{
char* const result = xMalloc (strlen (str) + 1, char);
int i = 0;
do
result [i] = tolower ((int) str [i]);
while (str [i++] != '\0');
return result;
}
/* Newly allocated string containing upper case conversion of a string.
*/
extern char* newUpperString (const char* str)
{
char* const result = xMalloc (strlen (str) + 1, char);
int i = 0;
do
result [i] = toupper ((int) str [i]);
while (str [i++] != '\0');
return result;
}
/*
* File system functions
*/
extern void setCurrentDirectory (void)
{
#ifndef AMIGA
char* buf;
#endif
if (CurrentDirectory == NULL)
CurrentDirectory = xMalloc ((size_t) (PATH_MAX + 1), char);
#ifdef AMIGA
strcpy (CurrentDirectory, ".");
#else
buf = getcwd (CurrentDirectory, PATH_MAX);
if (buf == NULL)
perror ("");
#endif
if (CurrentDirectory [strlen (CurrentDirectory) - (size_t) 1] !=
PATH_SEPARATOR)
{
sprintf (CurrentDirectory + strlen (CurrentDirectory), "%c",
OUTPUT_PATH_SEPARATOR);
}
}
#ifdef AMIGA
static boolean isAmigaDirectory (const char *const name)
{
boolean result = FALSE;
struct FileInfoBlock *const fib = xMalloc (1, struct FileInfoBlock);
if (fib != NULL)
{
const BPTR flock = Lock ((UBYTE *) name, (long) ACCESS_READ);
if (flock != (BPTR) NULL)
{
if (Examine (flock, fib))
result = ((fib->fib_DirEntryType >= 0) ? TRUE : FALSE);
UnLock (flock);
}
eFree (fib);
}
return result;
}
#endif
/* For caching of stat() calls */
extern fileStatus *eStat (const char *const fileName)
{
struct stat status;
static fileStatus file;
if (file.name == NULL || strcmp (fileName, file.name) != 0)
{
eStatFree (&file);
file.name = eStrdup (fileName);
if (lstat (file.name, &status) != 0)
file.exists = FALSE;
else
{
file.isSymbolicLink = (boolean) S_ISLNK (status.st_mode);
if (file.isSymbolicLink && stat (file.name, &status) != 0)
file.exists = FALSE;
else
{
file.exists = TRUE;
#ifdef AMIGA
file.isDirectory = isAmigaDirectory (file.name);
#else
file.isDirectory = (boolean) S_ISDIR (status.st_mode);
#endif
file.isNormalFile = (boolean) (S_ISREG (status.st_mode));
file.isExecutable = (boolean) ((status.st_mode &
(S_IXUSR | S_IXGRP | S_IXOTH)) != 0);
file.isSetuid = (boolean) ((status.st_mode & S_ISUID) != 0);
file.size = status.st_size;
}
}
}
return &file;
}
extern void eStatFree (fileStatus *status)
{
if (status->name != NULL)
{
eFree (status->name);
status->name = NULL;
}
}
extern boolean doesFileExist (const char *const fileName)
{
fileStatus *status = eStat (fileName);
return status->exists;
}
extern boolean isRecursiveLink (const char* const dirName)
{
boolean result = FALSE;
fileStatus *status = eStat (dirName);
if (status->isSymbolicLink)
{
char* const path = absoluteFilename (dirName);
while (path [strlen (path) - 1] == PATH_SEPARATOR)
path [strlen (path) - 1] = '\0';
while (! result && strlen (path) > (size_t) 1)
{
char *const separator = strrchr (path, PATH_SEPARATOR);
if (separator == NULL)
break;
else if (separator == path) /* backed up to root directory */
*(separator + 1) = '\0';
else
*separator = '\0';
result = isSameFile (path, dirName);
}
eFree (path);
}
return result;
}
#ifndef HAVE_FGETPOS
extern int fgetpos (FILE *stream, fpos_t *pos)
{
int result = 0;
*pos = ftell (stream);
if (*pos == -1L)
result = -1;
return result;
}
extern int fsetpos (FILE *stream, fpos_t const *pos)
{
return fseek (stream, *pos, SEEK_SET);
}
#endif
/*
* Pathname manipulation (O/S dependent!!!)
*/
static boolean isPathSeparator (const int c)
{
boolean result;
#if defined (MSDOS_STYLE_PATH) || defined (VMS)
result = (boolean) (strchr (PathDelimiters, c) != NULL);
#else
result = (boolean) (c == PATH_SEPARATOR);
#endif
return result;
}
#if ! defined (HAVE_STAT_ST_INO)
static void canonicalizePath (char *const path __unused__)
{
#if defined (MSDOS_STYLE_PATH)
char *p;
for (p = path ; *p != '\0' ; ++p)
if (isPathSeparator (*p) && *p != ':')
*p = PATH_SEPARATOR;
#endif
}
#endif
extern boolean isSameFile (const char *const name1, const char *const name2)
{
boolean result = FALSE;
#if defined (HAVE_STAT_ST_INO)
struct stat stat1, stat2;
if (stat (name1, &stat1) == 0 && stat (name2, &stat2) == 0)
result = (boolean) (stat1.st_ino == stat2.st_ino);
#else
{
char *const n1 = absoluteFilename (name1);
char *const n2 = absoluteFilename (name2);
canonicalizePath (n1);
canonicalizePath (n2);
# if defined (CASE_INSENSITIVE_FILENAMES)
result = (boolean) (strcasecmp (n1, n2) == 0);
#else
result = (boolean) (strcmp (n1, n2) == 0);
#endif
free (n1);
free (n2);
}
#endif
return result;
}
extern const char *baseFilename (const char *const filePath)
{
#if defined (MSDOS_STYLE_PATH) || defined (VMS)
const char *tail = NULL;
unsigned int i;
/* Find whichever of the path delimiters is last.
*/
for (i = 0 ; i < strlen (PathDelimiters) ; ++i)
{
const char *sep = strrchr (filePath, PathDelimiters [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 */
#ifdef VAXC
{
/* remove version number from filename */
char *p = strrchr ((char *) tail, ';');
if (p != NULL)
*p = '\0';
}
#endif
return tail;
}
extern const char *fileExtension (const char *const fileName)
{
const char *extension;
const char *pDelimiter = NULL;
const char *const base = baseFilename (fileName);
#ifdef QDOS
pDelimiter = strrchr (base, '_');
#endif
if (pDelimiter == NULL)
pDelimiter = strrchr (base, '.');
if (pDelimiter == NULL)
extension = "";
else
extension = pDelimiter + 1; /* skip to first char of extension */
return extension;
}
extern boolean isAbsolutePath (const char *const path)
{
boolean result = FALSE;
#if defined (MSDOS_STYLE_PATH)
if (isPathSeparator (path [0]))
result = TRUE;
else if (isalpha (path [0]) && path [1] == ':')
{
if (isPathSeparator (path [2]))
result = TRUE;
else
/* We don't support non-absolute file names with a drive
* letter, like `d:NAME' (it's too much hassle).
*/
error (FATAL,
"%s: relative file names with drive letters not supported",
path);
}
#elif defined (VMS)
result = (boolean) (strchr (path, ':') != NULL);
#else
result = isPathSeparator (path [0]);
#endif
return result;
}
extern vString *combinePathAndFile (
const char *const path, const char *const file)
{
vString *const filePath = vStringNew ();
#ifdef VMS
const char *const directoryId = strstr (file, ".DIR;1");
if (directoryId == NULL)
{
const char *const versionId = strchr (file, ';');
vStringCopyS (filePath, path);
if (versionId == NULL)
vStringCatS (filePath, file);
else
vStringNCatS (filePath, file, versionId - file);
vStringCopyToLower (filePath, filePath);
}
else
{
/* File really is a directory; append it to the path.
* Gotcha: doesn't work with logical names.
*/
vStringNCopyS (filePath, path, strlen (path) - 1);
vStringPut (filePath, '.');
vStringNCatS (filePath, file, directoryId - file);
if (strchr (path, '[') != NULL)
vStringPut (filePath, ']');
else
vStringPut (filePath, '>');
vStringTerminate (filePath);
}
#else
const int lastChar = path [strlen (path) - 1];
boolean terminated = isPathSeparator (lastChar);
vStringCopyS (filePath, path);
if (! terminated)
{
vStringPut (filePath, OUTPUT_PATH_SEPARATOR);
vStringTerminate (filePath);
}
vStringCatS (filePath, file);
#endif
return filePath;
}
/* Return a newly-allocated string whose contents concatenate those of
* s1, s2, s3.
* Routine adapted from Gnu etags.
*/
static char* concat (const char *s1, const char *s2, const char *s3)
{
int len1 = strlen (s1), len2 = strlen (s2), len3 = strlen (s3);
char *result = xMalloc (len1 + len2 + len3 + 1, char);
strcpy (result, s1);
strcpy (result + len1, s2);
strcpy (result + len1 + len2, s3);
result [len1 + len2 + len3] = '\0';
return result;
}
/* Return a newly allocated string containing the absolute file name of FILE
* given CWD (which should end with a slash).
* Routine adapted from Gnu etags.
*/
extern char* absoluteFilename (const char *file)
{
char *slashp, *cp;
char *res = NULL;
if (isAbsolutePath (file))
{
#ifdef MSDOS_STYLE_PATH
if (file [1] == ':')
res = eStrdup (file);
else
{
char drive [3];
sprintf (drive, "%c:", currentdrive ());
res = concat (drive, file, "");
}
#else
res = eStrdup (file);
#endif
}
else
res = concat (CurrentDirectory, file, "");
/* Delete the "/dirname/.." and "/." substrings. */
slashp = strchr (res, PATH_SEPARATOR);
while (slashp != NULL && slashp [0] != '\0')
{
if (slashp[1] == '.')
{
if (slashp [2] == '.' &&
(slashp [3] == PATH_SEPARATOR || slashp [3] == '\0'))
{
cp = slashp;
do
cp--;
while (cp >= res && ! isAbsolutePath (cp));
if (cp < res)
cp = slashp;/* the absolute name begins with "/.." */
#ifdef MSDOS_STYLE_PATH
/* Under MSDOS and NT we get `d:/NAME' as absolute file name,
* so the luser could say `d:/../NAME'. We silently treat this
* as `d:/NAME'.
*/
else if (cp [0] != PATH_SEPARATOR)
cp = slashp;
#endif
memmove (cp, slashp + 3, strlen (slashp + 3) + 1);
slashp = cp;
continue;
}
else if (slashp [2] == PATH_SEPARATOR || slashp [2] == '\0')
{
memmove (slashp, slashp + 2, strlen (slashp + 2) + 1);
continue;
}
}
slashp = strchr (slashp + 1, PATH_SEPARATOR);
}
if (res [0] == '\0')
return eStrdup ("/");
else
{
#ifdef MSDOS_STYLE_PATH
/* Canonicalize drive letter case. */
if (res [1] == ':' && islower (res [0]))
res [0] = toupper (res [0]);
#endif
return res;
}
}
/* Return a newly allocated string containing the absolute file name of dir
* where `file' resides given `CurrentDirectory'.
* Routine adapted from Gnu etags.
*/
extern char* absoluteDirname (char *file)
{
char *slashp, *res;
char save;
slashp = strrchr (file, PATH_SEPARATOR);
if (slashp == NULL)
res = eStrdup (CurrentDirectory);
else
{
save = slashp [1];
slashp [1] = '\0';
res = absoluteFilename (file);
slashp [1] = save;
}
return res;
}
/* Return a newly allocated string containing the file name of FILE relative
* to the absolute directory DIR (which should end with a slash).
* Routine adapted from Gnu etags.
*/
extern char* relativeFilename (const char *file, const char *dir)
{
const char *fp, *dp;
char *absdir, *res;
int i;
/* Find the common root of file and dir (with a trailing slash). */
absdir = absoluteFilename (file);
fp = absdir;
dp = dir;
while (*fp++ == *dp++)
continue;
fp--;
dp--; /* back to the first differing char */
do
{ /* look at the equal chars until path sep */
if (fp == absdir)
return absdir; /* first char differs, give up */
fp--;
dp--;
} while (*fp != PATH_SEPARATOR);
/* Build a sequence of "../" strings for the resulting relative file name.
*/
i = 0;
while ((dp = strchr (dp + 1, PATH_SEPARATOR)) != NULL)
i += 1;
res = xMalloc (3 * i + strlen (fp + 1) + 1, char);
res [0] = '\0';
while (i-- > 0)
strcat (res, "../");
/* Add the file name relative to the common root of file and dir. */
strcat (res, fp + 1);
free (absdir);
return res;
}
extern FILE *tempFile (const char *const mode, char **const pName)
{
char *name;
FILE *fp;
int fd;
#if defined(HAVE_MKSTEMP)
const char *const pattern = "tags.XXXXXX";
const char *tmpdir = NULL;
fileStatus *file = eStat (ExecutableProgram);
if (! file->isSetuid)
tmpdir = getenv ("TMPDIR");
if (tmpdir == NULL)
tmpdir = TMPDIR;
name = xMalloc (strlen (tmpdir) + 1 + strlen (pattern) + 1, char);
sprintf (name, "%s%c%s", tmpdir, OUTPUT_PATH_SEPARATOR, pattern);
fd = mkstemp (name);
eStatFree (file);
#elif defined(HAVE_TEMPNAM)
name = tempnam (TMPDIR, "tags");
if (name == NULL)
error (FATAL | PERROR, "cannot allocate temporary file name");
fd = open (name, O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR);
#else
name = xMalloc (L_tmpnam, char);
if (tmpnam (name) != name)
error (FATAL | PERROR, "cannot assign temporary file name");
fd = open (name, O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR);
#endif
if (fd == -1)
error (FATAL | PERROR, "cannot open temporary file");
fp = fdopen (fd, mode);
if (fp == NULL)
error (FATAL | PERROR, "cannot open temporary file");
DebugStatement (
debugPrintf (DEBUG_STATUS, "opened temporary file %s\n", name); )
Assert (*pName == NULL);
*pName = name;
return fp;
}
/* vi:set tabstop=4 shiftwidth=4: */