cosmopolitan/third_party/ctags/routines.c

757 lines
18 KiB
C

/*
* $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 "third_party/ctags/general.h"
/* must always come first */
#include "libc/calls/struct/stat.h"
#include "libc/errno.h"
#include "libc/fmt/fmt.h"
#include "libc/log/log.h"
#include "libc/mem/mem.h"
#include "libc/stdio/temp.h"
#include "third_party/ctags/debug.h"
#include "third_party/ctags/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: */