/* * Copyright (c) 2000, 2001 by Martin C. Shepherd. * * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, and/or sell copies of the Software, and to permit persons * to whom the Software is furnished to do so, provided that the above * copyright notice(s) and this permission notice appear in all copies of * the Software and that both the above copyright notice(s) and this * permission notice appear in supporting documentation. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT * OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR * HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL * INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * Except as contained in this notice, the name of a copyright holder * shall not be used in advertising or otherwise to promote the sale, use * or other dealings in this Software without prior written authorization * of the copyright holder. */ #include #include #include #include #include "freelist.h" #include "direader.h" #include "pathutil.h" #include "homedir.h" #include "stringrp.h" #include "libtecla.h" /* * Specify the number of elements to extend the files[] array by * when it proves to be too small. This also sets the initial size * of the array. */ #define MATCH_BLK_FACT 256 /* * A list of directory iterators is maintained using nodes of the * following form. */ typedef struct DirNode DirNode; struct DirNode { DirNode *next; /* The next directory in the list */ DirNode *prev; /* The node that precedes this node in the list */ DirReader *dr; /* The directory reader object */ }; typedef struct { FreeList *mem; /* Memory for DirNode list nodes */ DirNode *head; /* The head of the list of used and unused cache nodes */ DirNode *next; /* The next unused node between head and tail */ DirNode *tail; /* The tail of the list of unused cache nodes */ } DirCache; /* * Specify how many directory cache nodes to allocate at a time. */ #define DIR_CACHE_BLK 20 /* * Set the maximum length allowed for usernames. */ #define USR_LEN 100 /* * Set the maximum length allowed for environment variable names. */ #define ENV_LEN 100 /* * Set the max length of the error-reporting string. There is no point * in this being longer than the width of a typical terminal window. * In composing error messages, I have assumed that this number is * at least 80, so you don't decrease it below this number. */ #define ERRLEN 200 struct ExpandFile { StringGroup *sg; /* A list of string segments in which */ /* matching filenames are stored. */ DirCache cache; /* The cache of directory reader objects */ PathName *path; /* The pathname being matched */ HomeDir *home; /* Home-directory lookup object */ int files_dim; /* The allocated dimension of result.files[] */ char usrnam[USR_LEN+1]; /* A user name */ char envnam[ENV_LEN+1]; /* An environment variable name */ char errmsg[ERRLEN+1]; /* Error-report buffer */ FileExpansion result; /* The container used to return the results of */ /* expanding a path. */ }; static int ef_record_pathname(ExpandFile *ef, const char *pathname, int remove_escapes); static char *ef_cache_pathname(ExpandFile *ef, const char *pathname, int remove_escapes); static void ef_clear_files(ExpandFile *ef); static DirNode *ef_open_dir(ExpandFile *ef, const char *pathname); static DirNode *ef_close_dir(ExpandFile *ef, DirNode *node); static char *ef_expand_special(ExpandFile *ef, const char *path, int pathlen); static int ef_match_relative_pathname(ExpandFile *ef, DirReader *dr, const char *pattern, int separate); static int ef_matches_range(int c, const char *pattern, const char **endp); static int ef_string_matches_pattern(const char *file, const char *pattern, int xplicit, const char *nextp); static int ef_cmp_strings(const void *v1, const void *v2); /*....................................................................... * Create the resources needed to expand filenames. * * Output: * return ExpandFile * The new object, or NULL on error. */ ExpandFile *new_ExpandFile(void) { ExpandFile *ef; /* The object to be returned */ /* * Allocate the container. */ ef = (ExpandFile *) malloc(sizeof(ExpandFile)); if(!ef) { fprintf(stderr, "new_ExpandFile: Insufficient memory.\n"); return NULL; }; /* * Before attempting any operation that might fail, initialize the * container at least up to the point at which it can safely be passed * to del_ExpandFile(). */ ef->sg = NULL; ef->cache.mem = NULL; ef->cache.head = NULL; ef->cache.next = NULL; ef->cache.tail = NULL; ef->path = NULL; ef->home = NULL; ef->result.files = NULL; ef->result.nfile = 0; ef->usrnam[0] = '\0'; ef->envnam[0] = '\0'; ef->errmsg[0] = '\0'; /* * Allocate a list of string segments for storing filenames. */ ef->sg = _new_StringGroup(_pu_pathname_dim()); if(!ef->sg) return del_ExpandFile(ef); /* * Allocate a freelist for allocating directory cache nodes. */ ef->cache.mem = _new_FreeList("new_ExpandFile", sizeof(DirNode), DIR_CACHE_BLK); if(!ef->cache.mem) return del_ExpandFile(ef); /* * Allocate a pathname buffer. */ ef->path = _new_PathName(); if(!ef->path) return del_ExpandFile(ef); /* * Allocate an object for looking up home-directories. */ ef->home = _new_HomeDir(); if(!ef->home) return del_ExpandFile(ef); /* * Allocate an array for files. This will be extended later if needed. */ ef->files_dim = MATCH_BLK_FACT; ef->result.files = (char **) malloc(sizeof(ef->result.files[0]) * ef->files_dim); if(!ef->result.files) { fprintf(stderr, "new_ExpandFile: Insufficient memory to allocate array of files.\n"); return del_ExpandFile(ef); }; return ef; } /*....................................................................... * Delete a ExpandFile object. * * Input: * ef ExpandFile * The object to be deleted. * Output: * return ExpandFile * The deleted object (always NULL). */ ExpandFile *del_ExpandFile(ExpandFile *ef) { if(ef) { DirNode *dnode; /* * Delete the string segments. */ ef->sg = _del_StringGroup(ef->sg); /* * Delete the cached directory readers. */ for(dnode=ef->cache.head; dnode; dnode=dnode->next) dnode->dr = _del_DirReader(dnode->dr); /* * Delete the memory from which the DirNode list was allocated, thus * deleting the list at the same time. */ ef->cache.mem = _del_FreeList("del_ExpandFile", ef->cache.mem, 1); ef->cache.head = ef->cache.tail = ef->cache.next = NULL; /* * Delete the pathname buffer. */ ef->path = _del_PathName(ef->path); /* * Delete the home-directory lookup object. */ ef->home = _del_HomeDir(ef->home); /* * Delete the array of pointers to files. */ if(ef->result.files) { free(ef->result.files); ef->result.files = NULL; }; /* * Delete the container. */ free(ef); }; return NULL; } /*....................................................................... * Expand a pathname, converting ~user/ and ~/ patterns at the start * of the pathname to the corresponding home directories, replacing * $envvar with the value of the corresponding environment variable, * and then, if there are any wildcards, matching these against existing * filenames. * * If no errors occur, a container is returned containing the array of * files that resulted from the expansion. If there were no wildcards * in the input pathname, this will contain just the original pathname * after expansion of ~ and $ expressions. If there were any wildcards, * then the array will contain the files that matched them. Note that * if there were any wildcards but no existing files match them, this * is counted as an error and NULL is returned. * * The supported wildcards and their meanings are: * * - Match any sequence of zero or more characters. * ? - Match any single character. * [chars] - Match any single character that appears in 'chars'. * If 'chars' contains an expression of the form a-b, * then any character between a and b, including a and b, * matches. The '-' character looses its special meaning * as a range specifier when it appears at the start * of the sequence of characters. * [^chars] - The same as [chars] except that it matches any single * character that doesn't appear in 'chars'. * * Wildcard expressions are applied to individual filename components. * They don't match across directory separators. A '.' character at * the beginning of a filename component must also be matched * explicitly by a '.' character in the input pathname, since these * are UNIX's hidden files. * * Input: * ef ExpandFile * The pathname expansion resource object. * path char * The path name to be expanded. * pathlen int The length of the suffix of path[] that * constitutes the filename to be expanded, * or -1 to specify that the whole of the * path string should be used. Note that * regardless of the value of this argument, * path[] must contain a '\0' terminated * string, since this function checks that * pathlen isn't mistakenly too long. * Output: * return FileExpansion * A pointer to a container within the given * ExpandFile object. This contains an array * of the pathnames that resulted from expanding * ~ and $ expressions and from matching any * wildcards, sorted into lexical order. * This container and its contents will be * recycled on subsequent calls, so if you need * to keep the results of two successive runs, * you will either have to allocate a private * copy of the array, or use two ExpandFile * objects. * * On error NULL is returned. A description * of the error can be acquired by calling the * ef_last_error() function. */ FileExpansion *ef_expand_file(ExpandFile *ef, const char *path, int pathlen) { DirNode *dnode; /* A directory-reader cache node */ const char *dirname; /* The name of the top level directory of the search */ const char *pptr; /* A pointer into path[] */ int wild; /* True if the path contains any wildcards */ /* * Check the arguments. */ if(!ef || !path) { if(ef) strcpy(ef->errmsg, "ef_expand_file: NULL path argument"); else fprintf(stderr, "ef_expand_file: NULL argument(s).\n"); return NULL; }; /* * If the caller specified that the whole of path[] be matched, * work out the corresponding length. */ if(pathlen < 0 || pathlen > strlen(path)) pathlen = strlen(path); /* * Discard previous expansion results. */ ef_clear_files(ef); /* * Preprocess the path, expanding ~/, ~user/ and $envvar references, * using ef->path as a work directory and returning a pointer to * a copy of the resulting pattern in the cache. */ path = ef_expand_special(ef, path, pathlen); if(!path) return NULL; /* * Clear the pathname buffer. */ _pn_clear_path(ef->path); /* * Does the pathname contain any wildcards? */ for(wild=0,pptr=path; !wild && *pptr; pptr++) { switch(*pptr) { case '\\': /* Skip escaped characters */ if(pptr[1]) pptr++; break; case '*': case '?': case '[': /* A wildcard character? */ wild = 1; break; }; }; /* * If there are no wildcards to match, copy the current expanded * path into the output array, removing backslash escapes while doing so. */ if(!wild) { if(ef_record_pathname(ef, path, 1)) return NULL; /* * Does the filename exist? */ ef->result.exists = _pu_file_exists(ef->result.files[0]); /* * Match wildcards against existing files. */ } else { /* * Only existing files that match the pattern will be returned in the * cache. */ ef->result.exists = 1; /* * Treat matching of the root-directory as a special case since it * isn't contained in a directory. */ if(strcmp(path, FS_ROOT_DIR) == 0) { if(ef_record_pathname(ef, FS_ROOT_DIR, 0)) return NULL; } else { /* * What should the top level directory of the search be? */ if(strncmp(path, FS_ROOT_DIR, FS_ROOT_DIR_LEN) == 0) { dirname = FS_ROOT_DIR; if(!_pn_append_to_path(ef->path, FS_ROOT_DIR, -1, 0)) { strcpy(ef->errmsg, "Insufficient memory to record path"); return NULL; }; path += FS_ROOT_DIR_LEN; } else { dirname = FS_PWD; }; /* * Open the top-level directory of the search. */ dnode = ef_open_dir(ef, dirname); if(!dnode) return NULL; /* * Recursively match successive directory components of the path. */ if(ef_match_relative_pathname(ef, dnode->dr, path, 0)) { dnode = ef_close_dir(ef, dnode); return NULL; }; /* * Cleanup. */ dnode = ef_close_dir(ef, dnode); }; /* * No files matched? */ if(ef->result.nfile < 1) { strcpy(ef->errmsg, "No files match"); return NULL; }; /* * Sort the pathnames that matched. */ qsort(ef->result.files, ef->result.nfile, sizeof(ef->result.files[0]), ef_cmp_strings); }; /* * Return the result container. */ return &ef->result; } /*....................................................................... * Attempt to recursively match the given pattern with the contents of * the current directory, descending sub-directories as needed. * * Input: * ef ExpandFile * The pathname expansion resource object. * dr DirReader * The directory reader object of the directory * to be searched. * pattern const char * The pattern to match with files in the current * directory. * separate int When appending a filename from the specified * directory to ef->pathname, insert a directory * separator between the existing pathname and * the filename, unless separate is zero. * Output: * return int 0 - OK. * 1 - Error. */ static int ef_match_relative_pathname(ExpandFile *ef, DirReader *dr, const char *pattern, int separate) { const char *nextp; /* The pointer to the character that follows the part */ /* of the pattern that is to be matched with files */ /* in the current directory. */ char *file; /* The name of the file being matched */ int pathlen; /* The length of ef->pathname[] on entry to this */ /* function */ /* * Record the current length of the pathname string recorded in * ef->pathname[]. */ pathlen = strlen(ef->path->name); /* * Get a pointer to the character that follows the end of the part of * the pattern that should be matched to files within the current directory. * This will either point to a directory separator, or to the '\0' terminator * of the pattern string. */ for(nextp=pattern; *nextp && strncmp(nextp, FS_DIR_SEP, FS_DIR_SEP_LEN) != 0; nextp++) ; /* * Read each file from the directory, attempting to match it to the * current pattern. */ while((file=_dr_next_file(dr)) != NULL) { /* * Does the latest file match the pattern up to nextp? */ if(ef_string_matches_pattern(file, pattern, file[0]=='.', nextp)) { /* * Append the new directory entry to the current matching pathname. */ if((separate && _pn_append_to_path(ef->path, FS_DIR_SEP, -1, 0)==NULL) || _pn_append_to_path(ef->path, file, -1, 0)==NULL) { strcpy(ef->errmsg, "Insufficient memory to record path"); return 1; }; /* * If we have reached the end of the pattern, record the accumulated * pathname in the list of matching files. */ if(*nextp == '\0') { if(ef_record_pathname(ef, ef->path->name, 0)) return 1; /* * If the matching directory entry is a subdirectory, and the * next character of the pattern is a directory separator, * recursively call the current function to scan the sub-directory * for matches. */ } else if(_pu_path_is_dir(ef->path->name) && strncmp(nextp, FS_DIR_SEP, FS_DIR_SEP_LEN) == 0) { /* * If the pattern finishes with the directory separator, then * record the pathame as matching. */ if(nextp[FS_DIR_SEP_LEN] == '\0') { if(ef_record_pathname(ef, ef->path->name, 0)) return 1; /* * Match files within the directory. */ } else { DirNode *subdnode = ef_open_dir(ef, ef->path->name); if(subdnode) { if(ef_match_relative_pathname(ef, subdnode->dr, nextp+FS_DIR_SEP_LEN, 1)) { subdnode = ef_close_dir(ef, subdnode); return 1; }; subdnode = ef_close_dir(ef, subdnode); }; }; }; /* * Remove the latest filename from the pathname string, so that * another matching file can be appended. */ ef->path->name[pathlen] = '\0'; }; }; return 0; } /*....................................................................... * Record a new matching filename. * * Input: * ef ExpandFile * The filename-match resource object. * pathname const char * The pathname to record. * remove_escapes int If true, remove backslash escapes in the * recorded copy of the pathname. * Output: * return int 0 - OK. * 1 - Error (ef->errmsg will contain a * description of the error). */ static int ef_record_pathname(ExpandFile *ef, const char *pathname, int remove_escapes) { char *copy; /* The recorded copy of pathname[] */ /* * Attempt to make a copy of the pathname in the cache. */ copy = ef_cache_pathname(ef, pathname, remove_escapes); if(!copy) return 1; /* * If there isn't room to record a pointer to the recorded pathname in the * array of files, attempt to extend the array. */ if(ef->result.nfile + 1 > ef->files_dim) { int files_dim = ef->files_dim + MATCH_BLK_FACT; char **files = (char **) realloc(ef->result.files, files_dim * sizeof(files[0])); if(!files) { sprintf(ef->errmsg, "Insufficient memory to record all of the matching filenames"); return 1; }; ef->result.files = files; ef->files_dim = files_dim; }; /* * Record a pointer to the new match. */ ef->result.files[ef->result.nfile++] = copy; return 0; } /*....................................................................... * Record a pathname in the cache. * * Input: * ef ExpandFile * The filename-match resource object. * pathname char * The pathname to record. * remove_escapes int If true, remove backslash escapes in the * copy of the pathname. * Output: * return char * The pointer to the copy of the pathname. * On error NULL is returned and a description * of the error is left in ef->errmsg[]. */ static char *ef_cache_pathname(ExpandFile *ef, const char *pathname, int remove_escapes) { char *copy = _sg_store_string(ef->sg, pathname, remove_escapes); if(!copy) strcpy(ef->errmsg, "Insufficient memory to store pathname"); return copy; } /*....................................................................... * Clear the results of the previous expansion operation, ready for the * next. * * Input: * ef ExpandFile * The pathname expansion resource object. */ static void ef_clear_files(ExpandFile *ef) { _clr_StringGroup(ef->sg); _pn_clear_path(ef->path); ef->result.exists = 0; ef->result.nfile = 0; ef->errmsg[0] = '\0'; return; } /*....................................................................... * Get a new directory reader object from the cache. * * Input: * ef ExpandFile * The pathname expansion resource object. * pathname const char * The pathname of the directory. * Output: * return DirNode * The cache entry of the new directory reader, * or NULL on error. On error, ef->errmsg will * contain a description of the error. */ static DirNode *ef_open_dir(ExpandFile *ef, const char *pathname) { char *errmsg = NULL; /* An error message from a called function */ DirNode *node; /* The cache node used */ /* * Get the directory reader cache. */ DirCache *cache = &ef->cache; /* * Extend the cache if there are no free cache nodes. */ if(!cache->next) { node = (DirNode *) _new_FreeListNode(cache->mem); if(!node) { sprintf(ef->errmsg, "Insufficient memory to open a new directory"); return NULL; }; /* * Initialize the cache node. */ node->next = NULL; node->prev = NULL; node->dr = NULL; /* * Allocate a directory reader object. */ node->dr = _new_DirReader(); if(!node->dr) { sprintf(ef->errmsg, "Insufficient memory to open a new directory"); node = (DirNode *) _del_FreeListNode(cache->mem, node); return NULL; }; /* * Append the node to the cache list. */ node->prev = cache->tail; if(cache->tail) cache->tail->next = node; else cache->head = node; cache->next = cache->tail = node; }; /* * Get the first unused node, but don't remove it from the list yet. */ node = cache->next; /* * Attempt to open the specified directory. */ if(_dr_open_dir(node->dr, pathname, &errmsg)) { strncpy(ef->errmsg, errmsg, ERRLEN); ef->errmsg[ERRLEN] = '\0'; return NULL; }; /* * Now that we have successfully opened the specified directory, * remove the cache node from the list, and relink the list around it. */ cache->next = node->next; if(node->prev) node->prev->next = node->next; else cache->head = node->next; if(node->next) node->next->prev = node->prev; else cache->tail = node->prev; node->next = node->prev = NULL; /* * Return the successfully initialized cache node to the caller. */ return node; } /*....................................................................... * Return a directory reader object to the cache, after first closing * the directory that it was managing. * * Input: * ef ExpandFile * The pathname expansion resource object. * node DirNode * The cache entry of the directory reader, as returned * by ef_open_dir(). * Output: * return DirNode * The deleted DirNode (ie. allways NULL). */ static DirNode *ef_close_dir(ExpandFile *ef, DirNode *node) { /* * Get the directory reader cache. */ DirCache *cache = &ef->cache; /* * Close the directory. */ _dr_close_dir(node->dr); /* * Return the node to the tail of the cache list. */ node->next = NULL; node->prev = cache->tail; if(cache->tail) cache->tail->next = node; else cache->head = cache->tail = node; if(!cache->next) cache->next = node; return NULL; } /*....................................................................... * Return non-zero if the specified file name matches a given glob * pattern. * * Input: * file const char * The file-name component to be matched to the pattern. * pattern const char * The start of the pattern to match against file[]. * xplicit int If non-zero, the first character must be matched * explicitly (ie. not with a wildcard). * nextp const char * The pointer to the the character following the * end of the pattern in pattern[]. * Output: * return int 0 - Doesn't match. * 1 - The file-name string matches the pattern. */ static int ef_string_matches_pattern(const char *file, const char *pattern, int xplicit, const char *nextp) { const char *pptr = pattern; /* The pointer used to scan the pattern */ const char *fptr = file; /* The pointer used to scan the filename string */ /* * Match each character of the pattern in turn. */ while(pptr < nextp) { /* * Handle the next character of the pattern. */ switch(*pptr) { /* * A match zero-or-more characters wildcard operator. */ case '*': /* * Skip the '*' character in the pattern. */ pptr++; /* * If wildcards aren't allowed, the pattern doesn't match. */ if(xplicit) return 0; /* * If the pattern ends with a the '*' wildcard, then the * rest of the filename matches this. */ if(pptr >= nextp) return 1; /* * Using the wildcard to match successively longer sections of * the remaining characters of the filename, attempt to match * the tail of the filename against the tail of the pattern. */ for( ; *fptr; fptr++) { if(ef_string_matches_pattern(fptr, pptr, 0, nextp)) return 1; }; return 0; /* The pattern following the '*' didn't match */ break; /* * A match-one-character wildcard operator. */ case '?': /* * If there is a character to be matched, skip it and advance the * pattern pointer. */ if(!xplicit && *fptr) { fptr++; pptr++; /* * If we hit the end of the filename string, there is no character * matching the operator, so the string doesn't match. */ } else { return 0; }; break; /* * A character range operator, with the character ranges enclosed * in matching square brackets. */ case '[': if(xplicit || !ef_matches_range(*fptr++, ++pptr, &pptr)) return 0; break; /* * A backslash in the pattern prevents the following character as * being seen as a special character. */ case '\\': pptr++; /* Note fallthrough to default */ /* * A normal character to be matched explicitly. */ default: if(*fptr == *pptr) { fptr++; pptr++; } else { return 0; }; break; }; /* * After passing the first character, turn off the explicit match * requirement. */ xplicit = 0; }; /* * To get here the pattern must have been exhausted. If the filename * string matched, then the filename string must also have been * exhausted. */ return *fptr == '\0'; } /*....................................................................... * Match a character range expression terminated by an unescaped close * square bracket. * * Input: * c int The character to be matched with the range * pattern. * pattern const char * The range pattern to be matched (ie. after the * initiating '[' character). * endp const char ** On output a pointer to the character following the * range expression will be assigned to *endp. * Output: * return int 0 - Doesn't match. * 1 - The character matched. */ static int ef_matches_range(int c, const char *pattern, const char **endp) { const char *pptr = pattern; /* The pointer used to scan the pattern */ int invert = 0; /* True to invert the sense of the match */ int matched = 0; /* True if the character matched the pattern */ /* * If the first character is a caret, the sense of the match is * inverted and only if the character isn't one of those in the * range, do we say that it matches. */ if(*pptr == '^') { pptr++; invert = 1; }; /* * The hyphen is only a special character when it follows the first * character of the range (not including the caret). */ if(*pptr == '-') { pptr++; if(c == '-') { *endp = pptr; matched = 1; }; /* * Skip other leading '-' characters since they make no sense. */ while(*pptr == '-') pptr++; }; /* * The hyphen is only a special character when it follows the first * character of the range (not including the caret or a hyphen). */ if(*pptr == ']') { pptr++; if(c == ']') { *endp = pptr; matched = 1; }; }; /* * Having dealt with the characters that have special meanings at * the beginning of a character range expression, see if the * character matches any of the remaining characters of the range, * up until a terminating ']' character is seen. */ while(!matched && *pptr && *pptr != ']') { /* * Is this a range of characters signaled by the two end characters * separated by a hyphen? */ if(*pptr == '-') { if(pptr[1] != ']') { if(c >= pptr[-1] && c <= pptr[1]) matched = 1; pptr += 2; }; /* * A normal character to be compared directly. */ } else if(*pptr++ == c) { matched = 1; }; }; /* * Find the terminating ']'. */ while(*pptr && *pptr != ']') pptr++; /* * Did we find a terminating ']'? */ if(*pptr == ']') { *endp = pptr + 1; return matched ? !invert : invert; }; /* * If the pattern didn't end with a ']' then it doesn't match, regardless * of the value of the required sense of the match. */ *endp = pptr; return 0; } /*....................................................................... * This is a qsort() comparison function used to sort strings. * * Input: * v1, v2 void * Pointers to the two strings to be compared. * Output: * return int -1 -> v1 < v2. * 0 -> v1 == v2 * 1 -> v1 > v2 */ static int ef_cmp_strings(const void *v1, const void *v2) { char * const *s1 = (char * const *) v1; char * const *s2 = (char * const *) v2; return strcmp(*s1, *s2); } /*....................................................................... * Preprocess a path, expanding ~/, ~user/ and $envvar references, using * ef->path as a work buffer, then copy the result into a cache entry, * and return a pointer to this copy. * * Input: * ef ExpandFile * The resource object of the file matcher. * pathlen int The length of the prefix of path[] to be expanded. * Output: * return char * A pointer to a copy of the output path in the * cache. On error NULL is returned, and a description * of the error is left in ef->errmsg[]. */ static char *ef_expand_special(ExpandFile *ef, const char *path, int pathlen) { int spos; /* The index of the start of the path segment that needs */ /* to be copied from path[] to the output pathname. */ int ppos; /* The index of a character in path[] */ char *pptr; /* A pointer into the output path */ int escaped; /* True if the previous character was a '\' */ int i; /* * Clear the pathname buffer. */ _pn_clear_path(ef->path); /* * We need to perform two passes, one to expand environment variables * and a second to do tilde expansion. This caters for the case * where an initial dollar expansion yields a tilde expression. */ escaped = 0; for(spos=ppos=0; ppos < pathlen; ppos++) { int c = path[ppos]; if(escaped) { escaped = 0; } else if(c == '\\') { escaped = 1; } else if(c == '$') { int envlen; /* The length of the environment variable */ char *value; /* The value of the environment variable */ /* * Record the preceding unrecorded part of the pathname. */ if(spos < ppos && _pn_append_to_path(ef->path, path + spos, ppos-spos, 0) == NULL) { strcpy(ef->errmsg, "Insufficient memory to expand path"); return NULL; }; /* * Skip the dollar. */ ppos++; /* * Copy the environment variable name that follows the dollar into * ef->envnam[], stopping if a directory separator or end of string * is seen. */ for(envlen=0; envlenenvnam[envlen] = path[ppos++]; /* * If the username overflowed the buffer, treat it as invalid (note that * on most unix systems only 8 characters are allowed in a username, * whereas our ENV_LEN is much bigger than that. */ if(envlen >= ENV_LEN) { strcpy(ef->errmsg, "Environment variable name too long"); return NULL; }; /* * Terminate the environment variable name. */ ef->envnam[envlen] = '\0'; /* * Lookup the value of the environment variable. */ value = getenv(ef->envnam); if(!value) { const char *fmt = "No expansion found for: $%.*s"; sprintf(ef->errmsg, fmt, ERRLEN - strlen(fmt), ef->envnam); return NULL; }; /* * Copy the value of the environment variable into the output pathname. */ if(_pn_append_to_path(ef->path, value, -1, 0) == NULL) { strcpy(ef->errmsg, "Insufficient memory to expand path"); return NULL; }; /* * Record the start of the uncopied tail of the input pathname. */ spos = ppos; }; }; /* * Record the uncopied tail of the pathname. */ if(spos < ppos && _pn_append_to_path(ef->path, path + spos, ppos-spos, 0) == NULL) { strcpy(ef->errmsg, "Insufficient memory to expand path"); return NULL; }; /* * If the first character of the resulting pathname is a tilde, * then attempt to substitute the home directory of the specified user. */ pptr = ef->path->name; if(*pptr == '~' && path[0] != '\\') { int usrlen; /* The length of the username following the tilde */ const char *homedir; /* The home directory of the user */ int homelen; /* The length of the home directory string */ int plen; /* The current length of the path */ int skip=0; /* The number of characters to skip after the ~user */ /* * Get the current length of the output path. */ plen = strlen(ef->path->name); /* * Skip the tilde. */ pptr++; /* * Copy the optional username that follows the tilde into ef->usrnam[]. */ for(usrlen=0; usrlenusrnam[usrlen] = *pptr++; /* * If the username overflowed the buffer, treat it as invalid (note that * on most unix systems only 8 characters are allowed in a username, * whereas our USR_LEN is much bigger than that. */ if(usrlen >= USR_LEN) { strcpy(ef->errmsg, "Username too long"); return NULL; }; /* * Terminate the username string. */ ef->usrnam[usrlen] = '\0'; /* * Lookup the home directory of the user. */ homedir = _hd_lookup_home_dir(ef->home, ef->usrnam); if(!homedir) { strncpy(ef->errmsg, _hd_last_home_dir_error(ef->home), ERRLEN); ef->errmsg[ERRLEN] = '\0'; return NULL; }; homelen = strlen(homedir); /* * ~user and ~ are usually followed by a directory separator to * separate them from the file contained in the home directory. * If the home directory is the root directory, then we don't want * to follow the home directory by a directory separator, so we must * erase it. */ if(strcmp(homedir, FS_ROOT_DIR) == 0 && strncmp(pptr, FS_DIR_SEP, FS_DIR_SEP_LEN) == 0) { skip = FS_DIR_SEP_LEN; }; /* * If needed, increase the size of the pathname buffer to allow it * to accomodate the home directory instead of the tilde expression. * Note that pptr may not be valid after this call. */ if(_pn_resize_path(ef->path, plen - usrlen - 1 - skip + homelen)==NULL) { strcpy(ef->errmsg, "Insufficient memory to expand filename"); return NULL; }; /* * Move the part of the pathname that follows the tilde expression to * the end of where the home directory will need to be inserted. */ memmove(ef->path->name + homelen, ef->path->name + 1 + usrlen + skip, plen - usrlen - 1 - skip+1); /* * Write the home directory at the beginning of the string. */ for(i=0; ipath->name[i] = homedir[i]; }; /* * Copy the result into the cache, and return a pointer to the copy. */ return ef_cache_pathname(ef, ef->path->name, 0); } /*....................................................................... * Return a description of the last path-expansion error that occurred. * * Input: * ef ExpandFile * The path-expansion resource object. * Output: * return char * The description of the last error. */ const char *ef_last_error(ExpandFile *ef) { return ef ? ef->errmsg : "NULL ExpandFile argument"; } /*....................................................................... * Print out an array of matching files. * * Input: * result FileExpansion * The container of the sorted array of * expansions. * fp FILE * The output stream to write to. * term_width int The width of the terminal. * Output: * return int 0 - OK. * 1 - Error. */ int ef_list_expansions(FileExpansion *result, FILE *fp, int term_width) { int maxlen; /* The length of the longest matching string */ int width; /* The width of a column */ int ncol; /* The number of columns to list */ int nrow; /* The number of rows needed to list all of the expansions */ int row,col; /* The row and column being written to */ int i; /* * Check the arguments. */ if(!result || !fp) { fprintf(stderr, "ef_list_expansions: NULL argument(s).\n"); return 1; }; /* * Not enough space to list anything? */ if(term_width < 1) return 0; /* * Work out the maximum length of the matching filenames. */ maxlen = 0; for(i=0; infile; i++) { int len = strlen(result->files[i]); if(len > maxlen) maxlen = len; }; /* * Nothing to list? */ if(maxlen == 0) return 0; /* * Split the available terminal width into columns of maxlen + 2 characters. */ width = maxlen + 2; ncol = term_width / width; /* * If the column width is greater than the terminal width, the matches will * just have to overlap onto the next line. */ if(ncol < 1) ncol = 1; /* * How many rows will be needed? */ nrow = (result->nfile + ncol - 1) / ncol; /* * Print the expansions out in ncol columns, sorted in row order within each * column. */ for(row=0; row < nrow; row++) { for(col=0; col < ncol; col++) { int m = col*nrow + row; if(m < result->nfile) { const char *filename = result->files[m]; if(fprintf(fp, "%s%-*s%s", filename, (int) (ncol > 1 ? maxlen - strlen(filename):0), "", col