diff options
Diffstat (limited to 'filenames.c')
-rw-r--r-- | filenames.c | 603 |
1 files changed, 603 insertions, 0 deletions
diff --git a/filenames.c b/filenames.c new file mode 100644 index 0000000..9ccd4ad --- /dev/null +++ b/filenames.c @@ -0,0 +1,603 @@ +/* filenames.c -- file & directory name manipulations + Copyright (C) 1986, 1995 Greg McGary + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to the + Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "config.h" +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <stdio.h> +#include <sys/param.h> +#include <sys/types.h> +#include <sys/stat.h> +#include "strxtra.h" +#include "filenames.h" +#include "misc.h" + +/* relative_file_name takes two arguments: + * 1) an absolute path name for a directory. + * (This name MUST have a trailing /). + * 2) an absolute path name for a file. + * + * It looks for common components at the front of the file and + * directory names and generates a relative path name for the file + * (relative to the specified directory). + * + * This may result in a huge number of ../s if the names + * have no components in common. + * + * The output from this concatenated with the input directory name + * and run through span_file_name should result in the original input + * absolute path name of the file. + * + * Examples: + * dir arg return value + * /x/y/z/ /x/y/q/file -> ../q/file + * /x/y/z/ /q/t/p/file -> ../../../q/t/p/file + * /x/y/z/ /x/y/z/file -> file + */ +char const * +relative_file_name (char const *dir, char const *arg) +{ + char const *a; + char const *d; + char const *lasta; + char const *lastd; + static char file_name_buffer[BUFSIZ]; + char *buf = file_name_buffer; + + lasta = a = arg; + lastd = d = dir; + while (*a == *d) + { + if (*a == '/') + { + lasta = a; + lastd = d; + } + ++a; + ++d; + } + /* lasta and lastd now point to the last / in each + * file name where the leading file components were + * identical. + */ + ++lasta; + ++lastd; + /* copy a ../ into the buffer for each component of + * the directory that remains. + */ + + while (*lastd != '\0') + { + if (*lastd == '/') + { + strcpy (buf, "../"); + buf += 3; + } + ++lastd; + } + /* now tack on remainder of arg */ + strcpy (buf, lasta); + return file_name_buffer; +} + +/* span_file_name accepts a directory name and a file name and returns + a cannonical form of the full file name within that directory. It + gets rid of ./ and things like that. If the file is an absolute + name then the directory is ignored. */ +char const * +span_file_name (char const *dir, char const *arg) +{ + char *argptr; + static char file_name_buffer[BUFSIZ]; + + /* reduce directory to cannonical form */ + strcpy (file_name_buffer, dir); + cannoname (file_name_buffer); + /* tack the obilgatory / on the end */ + strcat (file_name_buffer, "/"); + /* stick file name in buffer after directory */ + argptr = file_name_buffer + strlen (file_name_buffer); + strcpy (argptr, arg); + /* and reduce it to cannonical form also */ + cannoname (argptr); + /* If it is an absolute name, just return it */ + if (*argptr == '/') + return argptr; + /* otherwise, combine the names to cannonical form */ + cannoname (file_name_buffer); + return file_name_buffer; +} + +/* root_name returns the base name of the file with any leading + * directory information or trailing suffix stripped off. Examples: + * + * /usr/include/stdio.h -> stdio + * fred -> fred + * barney.c -> barney + * bill/bob -> bob + * / -> < null string > + */ +char const * +root_name (char const *path) +{ + static char file_name_buffer[BUFSIZ]; + char const *root; + char const *dot; + + root = strrchr (path, '/'); + if (root == NULL) + root = path; + else + root++; + + dot = strrchr (root, '.'); + if (dot == NULL) + strcpy (file_name_buffer, root); + else + { + strncpy (file_name_buffer, root, dot - root); + file_name_buffer[dot - root] = '\0'; + } + return file_name_buffer; +} + +/* suff_name returns the suffix (including the dot) or a null string + * if there is no suffix. Examples: + * + * /usr/include/stdio.h -> .h + * fred -> < null string > + * barney.c -> .c + * bill/bob -> < null string > + * / -> < null string > + */ +char const * +suff_name (char const *path) +{ + char const *dot; + + dot = strrchr (path, '.'); + if (dot == NULL) + return ""; + return dot; +} + +int +can_crunch (char const *path1, char const *path2) +{ + char const *slash1; + char const *slash2; + + slash1 = strrchr (path1, '/'); + slash2 = strrchr (path2, '/'); + + if (slash1 == NULL && slash2 == NULL) + return strequ (suff_name (path1), suff_name (path2)); + if ((slash1 - path1) != (slash2 - path2)) + return 0; + if (!strnequ (path1, path2, slash1 - path1)) + return 0; + return strequ (suff_name (slash1), suff_name (slash2)); +} + +/* look_up adds ../s to the beginning of a file name until it finds + * the one that really exists. Returns NULL if it gets all the way + * to / and never finds it. + * + * If the file name starts with /, just return it as is. + * + * This routine is used to locate the ID database file. + */ +char const * +look_up (char const *arg) +{ + static char file_name_buffer[BUFSIZ]; + char *buf = file_name_buffer; + struct stat rootb; + struct stat statb; + + /* if we got absolute name, just use it. */ + if (arg[0] == '/') + return arg; + /* if the name we were give exists, don't bother searching */ + if (stat (arg, &statb) == 0) + return arg; + /* search up the tree until we find a directory where this + * relative file name is visible. + * (or we run out of tree to search by hitting root). + */ + + if (stat ("/", &rootb) != 0) + return NULL; + do + { + strcpy (buf, "../"); + buf += 3; + strcpy (buf, arg); + if (stat (file_name_buffer, &statb) == 0) + return file_name_buffer; + *buf = '\0'; + if (stat (file_name_buffer, &statb) != 0) + return NULL; + } + while (!((statb.st_ino == rootb.st_ino) || + (statb.st_dev == rootb.st_dev))); + return NULL; +} + +/* define special name components */ + +static char slash[] = "/"; +static char dot[] = "."; +static char dotdot[] = ".."; + +/* nextc points to the next character to look at in the string or is + * null if the end of string was reached. + * + * namep points to buffer that holds the components. + */ +static char const *nextc = NULL; +static char *namep; + +/* lexname - Return next name component. Uses global variables initialized + * by cannoname to figure out what it is scanning. + */ +static char const * +lexname (void) +{ + char c; + char const *d; + + if (nextc) + { + c = *nextc++; + if (c == '\0') + { + nextc = NULL; + return NULL; + } + if (c == '/') + { + return &slash[0]; + } + if (c == '.') + { + if ((*nextc == '/') || (*nextc == '\0')) + return &dot[0]; + if (*nextc == '.' && (*(nextc + 1) == '/' || *(nextc + 1) == '\0')) + { + ++nextc; + return &dotdot[0]; + } + } + d = namep; + *namep++ = c; + while ((c = *nextc) != '/') + { + *namep++ = c; + if (c == '\0') + { + nextc = NULL; + return d; + } + ++nextc; + } + *namep++ = '\0'; + return d; + } + else + { + return NULL; + } +} + +/* cannoname - Put a file name in cannonical form. Looks for all the + * whacky wonderful things a demented *ni* programmer might put + * in a file name and reduces the name to cannonical form. + */ +void +cannoname (char *n) +{ + char const *components[1024]; + char const **cap = &components[0]; + char const **cad; + char const *cp; + char namebuf[2048]; + char const *s; + + /* initialize scanner */ + nextc = n; + namep = &namebuf[0]; + + /* break the file name into individual components */ + while ((cp = lexname ())) + { + *cap++ = cp; + } + + /* If name is empty, leave it that way */ + if (cap == &components[0]) + return; + + /* flag end of component list */ + *cap = NULL; + + /* remove all trailing slashes and dots */ + while ((--cap != &components[0]) && + ((*cap == &slash[0]) || (*cap == &dot[0]))) + *cap = NULL; + + /* squeeze out all . / component sequences */ + cap = &components[0]; + cad = cap; + while (*cap) + { + if ((*cap == &dot[0]) && (*(cap + 1) == &slash[0])) + { + cap += 2; + } + else + { + *cad++ = *cap++; + } + } + *cad++ = NULL; + + /* find multiple // and use last slash as root, except on apollo which + * apparently actually uses // in real file names (don't ask me why). + */ +#ifndef apollo + s = NULL; + cap = &components[0]; + cad = cap; + while (*cap) + { + if ((s == &slash[0]) && (*cap == &slash[0])) + { + cad = &components[0]; + } + s = *cap++; + *cad++ = s; + } + *cad = NULL; +#endif + + /* if this is absolute name get rid of any /.. at beginning */ + if ((components[0] == &slash[0]) && (components[1] == &dotdot[0])) + { + cap = &components[1]; + cad = cap; + while (*cap == &dotdot[0]) + { + ++cap; + if (*cap == NULL) + break; + if (*cap == &slash[0]) + ++cap; + } + while (*cap) + *cad++ = *cap++; + *cad = NULL; + } + + /* squeeze out any name/.. sequences (but leave leading ../..) */ + cap = &components[0]; + cad = cap; + while (*cap) + { + if ((*cap == &dotdot[0]) && + ((cad - 2) >= &components[0]) && + ((*(cad - 2)) != &dotdot[0])) + { + cad -= 2; + ++cap; + if (*cap) + ++cap; + } + else + { + *cad++ = *cap++; + } + } + /* squeezing out a trailing /.. can leave unsightly trailing /s */ + if ((cad >= &components[2]) && ((*(cad - 1)) == &slash[0])) + --cad; + *cad = NULL; + /* if it was just name/.. it now becomes . */ + if (components[0] == NULL) + { + components[0] = &dot[0]; + components[1] = NULL; + } + + /* re-assemble components */ + cap = &components[0]; + while ((s = *cap++)) + { + while (*s) + *n++ = *s++; + } + *n++ = '\0'; +} + +/* kshgetwd is a routine that acts just like getwd, but is optimized + * for ksh users, taking advantage of the fact that ksh maintains + * an environment variable named PWD holding path name of the + * current working directory. + * + * The primary motivation for this is not really that it is algorithmically + * simpler, but that it is much less likely to bother NFS if we can just + * guess the name of the current working directory using the hint that + * ksh maintains. Anything that avoids NFS gettar failed messages is + * worth doing. + */ +char const * +kshgetwd (char *pathname) +{ + struct stat kshstat, dotstat; + char kshname[MAXPATHLEN]; + char const *kshp; + + kshp = getenv ("PWD"); + if (kshp) + { + /* OK, there was a PWD environment variable */ + strcpy (kshname, kshp); + if (unsymlink (kshname) + /* And we could resolve the symbolic links through it */ + && kshname[0] == '/' + /* And the name we have is an absolute path name */ + && stat (kshname, &kshstat) == 0 + /* And we can stat the name */ + && stat (".", &dotstat) == 0 + /* And we can stat "." */ + && (kshstat.st_dev == dotstat.st_dev) + && (kshstat.st_ino == dotstat.st_ino)) + /* By golly, that name is the same file as "." ! */ + return strcpy (pathname, kshname); + } + /* Oh well, something did not work out right, do it the hard way */ + return getwd (pathname); +} + +/* unsymlink is a routine that resolves all symbolic links in + * a file name, transforming a name to the "actual" file name + * instead of the name in terms of symbolic links. + * + * If it can resolve all links and discover an actual file + * it returns a pointer to its argument string and transforms + * the argument in place to the actual name. + * + * If no such actual file exists, or for some reason the links + * cannot be resolved, it returns a NULL pointer and leaves the + * name alone. + */ +char const * +unsymlink (char *n) +{ + char newname[MAXPATHLEN]; + char partname[MAXPATHLEN]; + char linkname[MAXPATHLEN]; + char const *s; + char *d; + char *lastcomp; + int linksize; + struct stat statb; + + /* Just stat the file to automagically do all the symbolic + * link verification checks and make sure we have access to + * directories, etc. + */ + if (stat (n, &statb) != 0) + return NULL; + strcpy (newname, n); + /* Now loop, lstating each component to see if it is a symbolic + * link. For symbolic link components, use readlink() to get + * the real name, put the read link name in place of the + * last component, and start again. + */ + cannoname (newname); + s = &newname[0]; + d = &partname[0]; + if (*s == '/') + *d++ = *s++; + lastcomp = d; + for (;;) + { + if ((*s == '/') || (*s == '\0')) + { + /* we have a complete component name in partname, check it out */ + *d = '\0'; + if (lstat (partname, &statb) != 0) + return NULL; + if ((statb.st_mode & S_IFMT) == S_IFLNK) + { + /* This much of name is a symbolic link, do a readlink + * and tack the bits and pieces together + */ + linksize = readlink (partname, linkname, MAXPATHLEN); + if (linksize < 0) + return NULL; + linkname[linksize] = '\0'; + strcpy (lastcomp, linkname); + lastcomp += linksize; + strcpy (lastcomp, s); + strcpy (newname, partname); + cannoname (newname); + s = &newname[0]; + d = &partname[0]; + if (*s == '/') + { + *d++ = *s++; + } + lastcomp = d; + } + else + { + /* Not a symlink, just keep scanning to next component */ + if (*s == '\0') + break; + *d++ = *s++; + lastcomp = d; + } + } + else + { + *d++ = *s++; + } + } + strcpy (n, newname); + return n; +} + +long input_chars = 0; + +FILE * +open_source_FILE (char *file_name, char const *filter) +{ + struct stat stat_buf; + FILE *source_FILE; + + if (stat (file_name, &stat_buf) < 0) + { + filerr ("open", file_name); + return NULL; + } + input_chars += stat_buf.st_size; + + if (filter) + { + char command[1024]; + sprintf (command, filter, file_name); + source_FILE = popen (command, "r"); + } + else + source_FILE = fopen (file_name, "r"); + if (source_FILE == NULL) + filerr ("open", file_name); + return source_FILE; +} + +void +close_source_FILE (FILE *fp, char const *filter) +{ + if (filter) + pclose (fp); + else + fclose (fp); +} |