/* * Copyright (c) 2002-2003, Darren Hiebert * * This source code is released for free distribution under the terms of the * GNU General Public License version 2 or (at your option) any later version. * * This module contains a lose assortment of shared functions. */ /* * INCLUDE FILES */ #include "general.h" /* must always come first */ #include /* to declare malloc (), realloc (), mbcs() */ #include #include #include /* to declare tempnam(), and SEEK_SET (hopefully) */ #ifdef HAVE_FCNTL_H # include /* to declare O_RDWR, O_CREAT, O_EXCL */ #endif #ifdef HAVE_UNISTD_H # include /* to declare mkstemp () */ #endif #include /* to declare MB_LEN_MAX */ #ifndef MB_LEN_MAX # define MB_LEN_MAX 6 #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_DIRECT_H # include /* to _getcwd */ #endif #ifdef HAVE_IO_H # include /* to declare open() */ #endif #include "debug.h" #include "routines.h" #include "routines_p.h" #include #include "vstring.h" /* * MACROS */ #ifndef TMPDIR # define TMPDIR "/tmp" #endif /* File type tests. */ #ifndef S_ISREG # if defined (S_IFREG) # define S_ISREG(mode) ((mode) & S_IFREG) # 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 #ifndef S_ISGID # define S_ISGID 0 #endif /* Hack for ridiculous practice of Microsoft Visual C++. */ #if defined (WIN32) # if defined (_MSC_VER) || defined (__MINGW32__) # ifndef stat # define stat _stat # endif # define getcwd _getcwd # define currentdrive() (_getdrive() + 'A' - 1) # else # define currentdrive() 'C' # endif #endif #ifndef PATH_MAX # define PATH_MAX 256 #endif /* * Miscellaneous macros */ /* * DATA DEFINITIONS */ #if defined (MSDOS_STYLE_PATH) 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 (WIN32) # define lstat(fn,buf) stat(fn,buf) #endif static bool isPathSeparator (const int c); static char *strSeparator (const char *s); static char *strRSeparator (const char *s); static void canonicalizePath (char *const path); /* * FUNCTION DEFINITIONS */ extern void freeRoutineResources (void) { if (CurrentDirectory != NULL) eFree (CurrentDirectory); } extern void setExecutableName (const char *const path) { ExecutableProgram = path; ExecutableName = baseFilename (path); } extern const char *getExecutableName (void) { return ExecutableName; } extern const char *getExecutablePath (void) { return ExecutableProgram; } /* * compare file/dirname characters with platform-correct case sensitivity */ static bool fnmChEq (int c1, int c2) { #ifdef WIN32 return tolower( c1 ) == tolower( c2 ); /* case-insensitive */ #else return c1 == c2 ; /* case- sensitive */ #endif } /* * Memory allocation functions */ extern void *eMalloc (const size_t size) { void *buffer = malloc (size); if (buffer == NULL && size != 0) 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 && count != 0 && size != 0) 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 && size != 0) error (FATAL, "out of memory"); } return buffer; } extern void eFree (void *const ptr) { Assert (ptr != NULL); free (ptr); } extern void eFreeNoNullCheck (void *const ptr) { free (ptr); } extern void eFreeIndirect(void **ptr) { if (ptr && *ptr) { eFree (*ptr); *ptr = NULL; } } /* * 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 *p; for (p = str ; *p != '\0' ; ++p) if (strncmp (p, substr, length) == 0) return (char*) p; return NULL; } #endif extern char* strrstr (const char *str, const char *substr) { const size_t length = strlen (substr); const char *p; for (p = str + strlen(str) - length ; p >= str ; --p) if (strncmp (p, substr, length) == 0) return (char*) p; return NULL; } extern char* eStrdup (const char* str) { char* result = xMalloc (strlen (str) + 1, char); strcpy (result, str); return result; } extern char* eStrndup (const char* str, size_t len) { char* result = xMalloc (len + 1, char); strncpy (result, str, len); result [len] = '\0'; 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; } /* Safe wrapper for strtoul * * The conversion result is placed in value and must only be used if the * function returned true. */ extern bool strToULong(const char *const str, int base, unsigned long *value) { char *endptr; errno = 0; *value = strtoul (str, &endptr, base); return *endptr == '\0' && str != endptr && errno == 0; } /* Safe wrapper for strtol/atol * * The conversion result is placed in value and must only be used if the * function returned true. */ extern bool strToLong(const char *const str, int base, long *value) { char *endptr; errno = 0; *value = strtol (str, &endptr, base); return *endptr == '\0' && str != endptr && errno == 0; } extern bool strToUInt(const char *const str, int base, unsigned int *value) { unsigned long ulong_value; if(!strToULong(str, base, &ulong_value) || ulong_value > UINT_MAX) return false; *value = (unsigned int) ulong_value; return true; } extern bool strToInt(const char *const str, int base, int *value) { long long_value; if(!strToLong(str, base, &long_value) || long_value > INT_MAX || long_value < INT_MIN) return false; *value = (int) long_value; return true; } /* * File system functions */ extern void setCurrentDirectory (void) /* TODO */ { char* buf; if (CurrentDirectory == NULL) CurrentDirectory = xMalloc ((size_t) (PATH_MAX + 1), char); buf = getcwd (CurrentDirectory, PATH_MAX); if (buf == NULL) perror (""); if (! isPathSeparator (CurrentDirectory [strlen (CurrentDirectory) - (size_t) 1])) { sprintf (CurrentDirectory + strlen (CurrentDirectory), "%c", OUTPUT_PATH_SEPARATOR); } canonicalizePath (CurrentDirectory); } /* 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 = (bool) S_ISLNK (status.st_mode); if (file.isSymbolicLink && stat (file.name, &status) != 0) file.exists = false; else { file.exists = true; file.isDirectory = (bool) S_ISDIR (status.st_mode); file.isNormalFile = (bool) (S_ISREG (status.st_mode)); file.isExecutable = (bool) ((status.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) != 0); file.isSetuid = (bool) ((status.st_mode & S_ISUID) != 0); file.isSetgid = (bool) ((status.st_mode & S_ISGID) != 0); file.size = status.st_size; file.mtime = status.st_mtime; } } } return &file; } extern void eStatFree (fileStatus *status) { if (status->name != NULL) { eFree (status->name); status->name = NULL; } } extern bool doesFileExist (const char *const fileName) { fileStatus *status = eStat (fileName); return status->exists; } extern bool doesExecutableExist (const char *const fileName) { fileStatus *status = eStat (fileName); return status->exists && status->isExecutable; } extern bool isRecursiveLink (const char* const dirName) { bool result = false; fileStatus *status = eStat (dirName); if (status->isSymbolicLink) { char* const path = absoluteFilename (dirName); while (isPathSeparator (path [strlen (path) - 1])) path [strlen (path) - 1] = '\0'; while (! result && strlen (path) > (size_t) 1) { char *const separator = strRSeparator (path); 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; } /* * Pathname manipulation (O/S dependent!!!) */ static bool isPathSeparator (const int c) { bool result; #if defined (MSDOS_STYLE_PATH) result = (bool) (strchr (PathDelimiters, c) != NULL); #else result = (bool) (c == PATH_SEPARATOR); #endif return result; } static char *strSeparator (const char *s) { #if defined (MSDOS_STYLE_PATH) return strpbrk (s, PathDelimiters); #else return strchr (s, PATH_SEPARATOR); #endif } static char *strRSeparator (const char *s) { #if defined (MSDOS_STYLE_PATH) const char *last = NULL; while (( s = strSeparator (s) )) { last = s; s++; } return (char*) last; #else return strrchr (s, PATH_SEPARATOR); #endif } static void canonicalizePath (char *const path CTAGS_ATTR_UNUSED) { # if defined (MSDOS_STYLE_PATH) char *p; for (p = path ; *p != '\0' ; ++p) if (isPathSeparator (*p) && *p != ':') *p = OUTPUT_PATH_SEPARATOR; # endif } extern bool isSameFile (const char *const name1, const char *const name2) { bool result = false; #if defined (HAVE_STAT_ST_INO) struct stat stat1, stat2; if (stat (name1, &stat1) == 0 && stat (name2, &stat2) == 0) result = (bool) (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 = (bool) (strcasecmp (n1, n2) == 0); # else result = (bool) (strcmp (n1, n2) == 0); # endif eFree (n1); eFree (n2); } #endif return result; } extern const char *baseFilename (const char *const filePath) { #if defined (MSDOS_STYLE_PATH) const char *tail = NULL; unsigned int i; /* Find whichever of the path delimiters is last. */ for (i = 0 ; i < strlen (PathDelimiters) ; ++i) { # ifdef HAVE_MBLEN const char *p; int ml; /* Some DBCS has letter contains 0x5C in trailing byte. * So skip to the trailing byte. */ for (p = filePath ; *p != '\0' ; ++p) { ml = mblen(p, MB_LEN_MAX); if (ml > 1) p += ml - 1; else if (*p == PathDelimiters [i] && p > tail) tail = p; } # else const char *sep = strrchr (filePath, PathDelimiters [i]); if (sep > tail) tail = sep; # endif } #else const char *tail = strRSeparator (filePath); #endif if (tail == NULL) tail = filePath; else ++tail; /* step past last delimiter */ return tail; } extern const char *fileExtension (const char *const fileName) { const char *extension; const char *pDelimiter; const char *const base = baseFilename (fileName); pDelimiter = strrchr (base, '.'); if (pDelimiter == NULL) extension = ""; else extension = pDelimiter + 1; /* skip to first char of extension */ return extension; } extern char* baseFilenameSansExtensionNew (const char *const fileName, const char *const templateExt) { const char *pDelimiter; const char *const base = baseFilename (fileName); char* shorten_base; pDelimiter = strrchr (base, templateExt[0]); if (pDelimiter && (strcmp (pDelimiter, templateExt) == 0)) { shorten_base = eStrndup (base, pDelimiter - base); return shorten_base; } else return NULL; } extern bool isAbsolutePath (const char *const path) { bool result; #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 { result = false; /* 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); } } else result = false; #else result = isPathSeparator (path [0]); #endif return result; } extern char *combinePathAndFile ( const char *const path, const char *const file) { vString *const filePath = vStringNew (); size_t len = strlen (path); if (len) { const int lastChar = path [len - 1]; bool terminated = isPathSeparator (lastChar); vStringCopyS (filePath, path); if (! terminated) vStringPut (filePath, OUTPUT_PATH_SEPARATOR); } vStringCatS (filePath, file); return vStringDeleteUnwrap (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 = strSeparator (res); while (slashp != NULL && slashp [0] != '\0') { if (slashp[1] == '.') { if (slashp [2] == '.' && (isPathSeparator (slashp [3]) || 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 user could say `d:/../NAME'. We silently treat this * as `d:/NAME'. */ else if (!isPathSeparator (cp [0])) cp = slashp; #endif memmove (cp, slashp + 3, strlen (slashp + 3) + 1); slashp = cp; continue; } else if (isPathSeparator (slashp [2]) || slashp [2] == '\0') { memmove (slashp, slashp + 2, strlen (slashp + 2) + 1); continue; } } slashp = strSeparator (slashp + 1); } if (res [0] == '\0') { const char root [] = {OUTPUT_PATH_SEPARATOR, '\0'}; eFree (res); res = eStrdup (root); } else { #ifdef MSDOS_STYLE_PATH /* Canonicalize drive letter case. */ if (res [1] == ':' && islower (res [0])) res [0] = toupper (res [0]); #endif } canonicalizePath (res); 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 = strRSeparator (file); 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 (fnmChEq (*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 (!isPathSeparator (*fp)); /* Build a sequence of "../" strings for the resulting relative file name. */ i = 0; while ((dp = strSeparator (dp + 1)) != NULL) i += 1; res = xMalloc (3 * i + strlen (fp + 1) + 1, char); res [0] = '\0'; while (i-- > 0) { const char parent [] = {'.', '.', OUTPUT_PATH_SEPARATOR, '\0'}; strcat (res, parent); } /* Add the file name relative to the common root of file and dir. */ strcat (res, fp + 1); eFree (absdir); return res; } extern FILE *tempFileFP (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); # ifdef WIN32 tmpdir = getenv ("TMP"); # else if (! file->isSetuid) tmpdir = getenv ("TMPDIR"); # endif 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); # ifdef WIN32 if (fd == -1) { /* mkstemp() sometimes fails with unknown reasons. * Retry a few times. */ int i; for (i = 0; i < 5 && fd == -1; i++) { sprintf (name, "%s%c%s", tmpdir, OUTPUT_PATH_SEPARATOR, pattern); fd = mkstemp (name); } } # endif eStatFree (file); #elif defined(HAVE_TEMPNAM) const char *tmpdir = NULL; # ifdef WIN32 tmpdir = getenv ("TMP"); # endif if (tmpdir == NULL) tmpdir = TMPDIR; 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: %s", name); fp = fdopen (fd, mode); if (fp == NULL) error (FATAL | PERROR, "cannot open temporary file"); Assert (*pName == NULL); *pName = name; return fp; } extern MIO *tempFile (const char *const mode, char **const pName) { FILE *fp = tempFileFP (mode, pName); return mio_new_fp (fp, fclose); }