/* * $Id: routines.c 536 2007-06-02 06:09:00Z elliotth $ * * 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 /* to declare malloc (), realloc () */ #endif #include #include #include #include #include /* to declare tempnam(), and SEEK_SET (hopefully) */ #ifdef HAVE_FCNTL_H # include /* to declar O_RDWR, O_CREAT, O_EXCL */ #endif #ifdef HAVE_UNISTD_H # include /* to declare mkstemp () */ #endif /* To declare "struct stat" and stat (). */ #if defined (HAVE_SYS_TYPES_H) # include #else # if defined (HAVE_TYPES_H) # include # endif #endif #ifdef HAVE_SYS_STAT_H # include #else # ifdef HAVE_STAT_H # include # endif #endif #ifdef HAVE_DOS_H # include /* to declare MAXPATH */ #endif #ifdef HAVE_DIRECT_H # include /* to _getcwd */ #endif #ifdef HAVE_DIR_H # include /* to declare findfirst() and findnext() */ #endif #ifdef HAVE_IO_H # include /* 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 strcpy (cp, slashp + 3); slashp = cp; continue; } else if (slashp [2] == PATH_SEPARATOR || slashp [2] == '\0') { strcpy (slashp, slashp + 2); 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: */