/*
* Copyright (c) 2000, 2001, 2002, 2003, 2004 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,
* 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.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* If file-system access is to be excluded, this module has no function,
* so all of its code should be excluded.
*/
#ifndef WITHOUT_FILE_SYSTEM
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "freelist.h"
#include "direader.h"
#include "pathutil.h"
#include "homedir.h"
#include "stringrp.h"
#include "libtecla.h"
#include "ioutil.h"
#include "expand.h"
#include "errmsg.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.
*/
/*
* A list of directory iterators is maintained using nodes of the
* following form.
*/
struct DirNode {
};
typedef struct {
} DirCache;
/*
* Specify how many directory cache nodes to allocate at a time.
*/
/*
* Set the maximum length allowed for usernames.
*/
/*
* Set the maximum length allowed for environment variable names.
*/
/*
* Set the default number of spaces place between columns when listing
* a set of expansions.
*/
struct ExpandFile {
/* matching filenames are stored. */
/* expanding a path. */
};
int remove_escapes);
int remove_escapes);
/*
* Encapsulate the formatting information needed to layout a
* multi-column listing of expansions.
*/
typedef struct {
} EfListFormat;
/*
* Given the current terminal width, and a list of file expansions,
* determine how to best use the terminal width to display a multi-column
* listing of expansions.
*/
EfListFormat *fmt);
/*
* Display a given line of a multi-column list of file-expansions.
*/
/*.......................................................................
* Create the resources needed to expand filenames.
*
* Output:
* return ExpandFile * The new object, or NULL on error.
*/
{
/*
* Allocate the container.
*/
if(!ef) {
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().
*/
/*
* Allocate a place to record error messages.
*/
return del_ExpandFile(ef);
/*
* Allocate a list of string segments for storing filenames.
*/
return del_ExpandFile(ef);
/*
* Allocate a freelist for allocating directory cache nodes.
*/
return del_ExpandFile(ef);
/*
* Allocate a pathname buffer.
*/
return del_ExpandFile(ef);
/*
* Allocate an object for looking up home-directories.
*/
return del_ExpandFile(ef);
/*
* Allocate an array for files. This will be extended later if needed.
*/
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).
*/
{
if(ef) {
/*
* Delete the string segments.
*/
/*
* Delete the cached directory readers.
*/
/*
* Delete the memory from which the DirNode list was allocated, thus
* deleting the list at the same time.
*/
/*
* Delete the pathname buffer.
*/
/*
* Delete the home-directory lookup object.
*/
/*
* Delete the array of pointers to files.
*/
};
/*
* Delete the error report buffer.
*/
/*
* Delete the container.
*/
};
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.
*/
{
/*
* Check the arguments.
*/
if(ef) {
};
return NULL;
};
/*
* If the caller specified that the whole of path[] be matched,
* work out the corresponding length.
*/
/*
* Discard previous expansion results.
*/
/*
* 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.
*/
if(!path)
return NULL;
/*
* Clear the pathname buffer.
*/
/*
* Does the pathname contain any wildcards?
*/
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) {
return NULL;
/*
* Does the filename exist?
*/
/*
* Match wildcards against existing files.
*/
} else {
/*
* Only existing files that match the pattern will be returned in the
* cache.
*/
/*
* Treat matching of the root-directory as a special case since it
* isn't contained in a directory.
*/
return NULL;
} else {
/*
* What should the top level directory of the search be?
*/
return NULL;
};
path += FS_ROOT_DIR_LEN;
} else {
};
/*
* Open the top-level directory of the search.
*/
if(!dnode)
return NULL;
/*
* Recursively match successive directory components of the path.
*/
return NULL;
};
/*
* Cleanup.
*/
};
/*
* No files matched?
*/
return NULL;
};
/*
* Sort the pathnames that matched.
*/
};
/*
* Return the result container.
*/
}
/*.......................................................................
* 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.
*/
{
/* of the pattern that is to be matched with files */
/* in the current directory. */
/* function */
/*
* Record the current length of the pathname string recorded in
* ef->pathname[].
*/
/*
* 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.
*/
nextp++)
;
/*
* Read each file from the directory, attempting to match it to the
* current pattern.
*/
/*
* Does the latest file match the pattern up to nextp?
*/
/*
* Append the new directory entry to the current matching pathname.
*/
return 1;
};
/*
* If we have reached the end of the pattern, record the accumulated
* pathname in the list of matching files.
*/
if(*nextp == '\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.
*/
/*
* If the pattern finishes with the directory separator, then
* record the pathame as matching.
*/
return 1;
/*
* Match files within the directory.
*/
} else {
if(subdnode) {
return 1;
};
};
};
};
/*
* Remove the latest filename from the pathname string, so that
* another matching file can be appended.
*/
};
};
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->err will contain a
* description of the error).
*/
int remove_escapes)
{
/*
* Attempt to make a copy of the pathname in the cache.
*/
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(!files) {
"Insufficient memory to record all of the matching filenames",
return 1;
};
};
/*
* Record a pointer to the new match.
*/
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->err.
*/
int remove_escapes)
{
if(!copy)
return copy;
}
/*.......................................................................
* Clear the results of the previous expansion operation, ready for the
* next.
*
* Input:
* ef ExpandFile * The pathname expansion resource object.
*/
{
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->err will
* contain a description of the error.
*/
{
/*
* Get the directory reader cache.
*/
/*
* Extend the cache if there are no free cache nodes.
*/
if(!node) {
return NULL;
};
/*
* Initialize the cache node.
*/
/*
* Allocate a directory reader object.
*/
return NULL;
};
/*
* Append the node to the cache list.
*/
else
};
/*
* Get the first unused node, but don't remove it from the list yet.
*/
/*
* Attempt to open the specified directory.
*/
return NULL;
};
/*
* Now that we have successfully opened the specified directory,
* remove the cache node from the list, and relink the list around it.
*/
else
else
/*
* 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).
*/
{
/*
* Get the directory reader cache.
*/
/*
* Close the directory.
*/
/*
* Return the node to the tail of the cache list.
*/
else
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.
*/
{
/*
* Match each character of the pattern in turn.
*/
/*
* 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.
*/
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.
*/
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.
*/
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 '[':
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:
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.
*/
{
/*
* 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 == '-') {
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 == ']') {
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.
*/
/*
* Is this a range of characters signaled by the two end characters
* separated by a hyphen?
*/
if(*pptr == '-') {
matched = 1;
pptr += 2;
};
/*
* A normal character to be compared directly.
*/
} else if(*pptr++ == c) {
matched = 1;
};
};
/*
* Find the terminating ']'.
*/
pptr++;
/*
* Did we find a terminating ']'?
*/
if(*pptr == ']') {
};
/*
* If the pattern didn't end with a ']' then it doesn't match, regardless
* of the value of the required sense of the match.
*/
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
*/
{
}
/*.......................................................................
* 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->err.
*/
{
/* to be copied from path[] to the output pathname. */
int i;
/*
* Clear the pathname buffer.
*/
/*
* 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;
if(escaped) {
escaped = 0;
} else if(c == '\\') {
escaped = 1;
} else if(c == '$') {
/*
* Record the preceding unrecorded part of the pathname.
*/
== NULL) {
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.
*/
/*
* 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.
*/
return NULL;
};
/*
* Terminate the environment variable name.
*/
/*
* Lookup the value of the environment variable.
*/
if(!value) {
return NULL;
};
/*
* Copy the value of the environment variable into the output pathname.
*/
return NULL;
};
/*
* Record the start of the uncopied tail of the input pathname.
*/
};
};
/*
* Record the uncopied tail of the pathname.
*/
== NULL) {
return NULL;
};
/*
* If the first character of the resulting pathname is a tilde,
* then attempt to substitute the home directory of the specified user.
*/
/*
* Get the current length of the output path.
*/
/*
* Skip the tilde.
*/
pptr++;
/*
* Copy the optional username that follows the tilde into ef->usrnam[].
*/
/*
* 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.
*/
return NULL;
};
/*
* Terminate the username string.
*/
/*
* Lookup the home directory of the user.
*/
if(!homedir) {
return NULL;
};
/*
* ~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 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.
*/
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.
*/
/*
* Write the home directory at the beginning of the string.
*/
for(i=0; i<homelen; i++)
};
/*
* Copy the result into the cache, and return a pointer to the copy.
*/
}
/*.......................................................................
* 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.
*/
{
}
/*.......................................................................
* 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.
*/
{
}
/*.......................................................................
* Print out an array of matching files via a callback.
*
* Input:
* result FileExpansion * The container of the sorted array of
* expansions.
* write_fn GlWriteFn * The function to call to write the
* expansions or 0 to discard the output.
* data void * Anonymous data to pass to write_fn().
* term_width int The width of the terminal.
* Output:
* return int 0 - OK.
* 1 - Error.
*/
void *data, int term_width)
{
/*
* Not enough space to list anything?
*/
if(term_width < 1)
return 0;
/*
* Do we have a callback to write via, and any expansions to be listed?
*/
/*
* Work out how to arrange the listing into fixed sized columns.
*/
/*
* Print the listing to the specified stream.
*/
return 1;
};
};
return 0;
}
/*.......................................................................
* Work out how to arrange a given array of completions into a listing
* of one or more fixed size columns.
*
* Input:
* result FileExpansion * The set of completions to be listed.
* term_width int The width of the terminal. A lower limit of
* zero is quietly enforced.
* fmt EfListFormat * The formatting information will be assigned
* to the members of *fmt.
*/
{
int i;
/*
* Ensure that term_width >= 0.
*/
if(term_width < 0)
term_width = 0;
/*
* Start by assuming the worst case, that either nothing will fit
* on the screen, or that there are no matches to be listed.
*/
fmt->column_width = 0;
/*
* Work out the maximum length of the matching strings.
*/
maxlen = 0;
};
/*
* Nothing to list?
*/
if(maxlen == 0)
return;
/*
* Split the available terminal width into columns of
* maxlen + EF_COL_SEP characters.
*/
/*
* If the column width is greater than the terminal width, zero columns
* will have been selected. Set a lower limit of one column. Leave it
* up to the caller how to deal with completions who's widths exceed
* the available terminal width.
*/
/*
* How many lines of output will be needed?
*/
return;
}
/*.......................................................................
* Render one line of a multi-column listing of completions, using a
* callback function to pass the output to an arbitrary destination.
*
* Input:
* result FileExpansion * The container of the sorted array of
* completions.
* fmt EfListFormat * Formatting information.
* lnum int The index of the line to print, starting
* from 0, and incrementing until the return
* value indicates that there is nothing more
* to be printed.
* write_fn GlWriteFn * The function to call to write the line, or
* 0 to discard the output.
* data void * Anonymous data to pass to write_fn().
* Output:
* return int 0 - Line printed ok.
* 1 - Nothing to print.
*/
{
/*
* If the line index is out of bounds, there is nothing to be written.
*/
return 1;
/*
* If no output function has been provided, return as though the line
* had been printed.
*/
if(!write_fn)
return 0;
/*
* Print the matches in 'ncol' columns, sorted in line order within each
* column.
*/
/*
* Is there another match to be written? Note that in general
* the last line of a listing will have fewer filled columns
* than the initial lines.
*/
/*
* How long are the completion and type-suffix strings?
*/
/*
* Write the completion string.
*/
return 1;
/*
* If another column follows the current one, pad to its start with spaces.
*/
/*
* The following constant string of spaces is used to pad the output.
*/
/*
* Pad to the next column, using as few sub-strings of the spaces[]
* array as possible.
*/
while(npad>0) {
return 1;
npad -= n;
};
};
};
};
/*
* Start a new line.
*/
{
char s[] = "\r\n";
int n = strlen(s);
return 1;
};
return 0;
}
#endif /* ifndef WITHOUT_FILE_SYSTEM */