hush/shell/glob.c
Stephen Heumann 9320c1e704 Set things up so hush can be build with debug code turned on.
This involved breaking things up into more segments in debug mode, since the code is larger. I also had to remove some unused extern definitions, which were causing link errors when debug code was enabled.

To enable debug code, pass "DEBUG=1" to make or build.gs.
2014-11-06 11:42:17 -06:00

397 lines
10 KiB
C

/*
* dietlibc/libshell/glob.c
*
* Copyright 2001 Guillaume Cottenceau <gc@mandrakesoft.com>
*
* This is free software, licensed under the Gnu General Public License.
*
*/
/*
* unsupported: GLOB_BRACE GLOB_ALTDIRFUNC GLOB_MAGCHAR
*/
#include "glob.h"
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <assert.h>
#include <fnmatch.h>
#include <dirent.h>
#include <pwd.h>
/* If i18n, should be using strcoll */
static int cmp_func(const void * a, const void * b)
{
const char *const s1 = *(const char **) a;
const char *const s2 = *(const char **) b;
if (s1 == NULL)
return 1;
if (s2 == NULL)
return -1;
#ifndef __GNO__
return strcoll(s1, s2);
#else
return strcmp(s1, s2);
#endif
}
/* Like `glob', but PATTERN is a final pathname component,
and matches are searched for in DIRECTORY.
The GLOB_NOSORT bit in FLAGS is ignored. No sorting is ever done.
The GLOB_APPEND flag is assumed to be set (always appends).
Prepends DIRECTORY in constructed PGLOB. */
static void close_dir_keep_errno(DIR* dp) {
int save = errno;
if (dp)
closedir (dp);
errno=save;
}
static int add_entry(const char* name,glob_t *pglob,int* nfound) {
pglob->gl_pathv = (char **) realloc(pglob->gl_pathv,
(pglob->gl_pathc + pglob->gl_offs + 2)
* sizeof (char *));
if (pglob->gl_pathv == NULL)
return 1;
pglob->gl_pathv[pglob->gl_offs + pglob->gl_pathc] = strdup(name);
pglob->gl_pathv[pglob->gl_offs + pglob->gl_pathc + 1] = NULL;
pglob->gl_pathc++;
(*nfound)++;
return 0;
}
static void build_fullname(char * fullname, const char * directory, const char * filename) {
char *dest=fullname;
if (directory[0]=='/' && !directory[1]) {
*dest='/'; ++dest;
} else if (directory[0]!='.' || directory[1]) {
strcpy(dest,directory);
dest=strchr(dest,0);
*dest='/'; ++dest;
}
strcpy(dest,filename);
}
static int glob_in_dir(const char *pattern, const char *directory, int flags,
int (*errfunc)(const char * epath, int eerrno),
glob_t *pglob)
{
DIR *dp = opendir(directory);
int nfound = 0;
int i;
char * ptr = NULL;
if (!dp) {
if (errno != ENOTDIR
&& ((errfunc != NULL && (*errfunc) (directory, errno))
|| (flags & GLOB_ERR)))
return GLOB_ABORTED;
} else {
int fnm_flags = ((!(flags & GLOB_PERIOD) ? FNM_PERIOD : 0)
| ((flags & GLOB_NOESCAPE) ? FNM_NOESCAPE : 0));
struct dirent *ep;
while ((ep = readdir(dp))) {
i = strlen(directory) + strlen(ep->d_name) + 2;
ptr = (char *) malloc(i);
if (ptr == NULL)
goto memory_error;
build_fullname(ptr, directory, ep->d_name);
if (flags & GLOB_ONLYDIR) {
struct stat statr;
if (stat(ptr, &statr) || !S_ISDIR(statr.st_mode))
continue;
}
if (fnmatch(pattern, ep->d_name, fnm_flags) == 0)
if (add_entry(ptr,pglob,&nfound))
goto memory_error;
free(ptr);
ptr = NULL;
}
}
close_dir_keep_errno(dp);
if (nfound != 0)
pglob->gl_flags = flags;
else if (flags & GLOB_NOCHECK) {
/* nfound == 0 */
i = strlen(directory) + strlen(pattern) + 2;
ptr = (char *) malloc(i);
if (ptr == NULL)
goto memory_error;
build_fullname(ptr, directory, pattern);
if (add_entry(ptr,pglob,&nfound))
goto memory_error;
free(ptr);
ptr = NULL;
}
return (nfound == 0) ? GLOB_NOMATCH : 0;
memory_error:
/* We're in trouble since we can't free the already allocated memory. [allocated from strdup(filame)]
* Well, after all, when malloc returns NULL we're already in a bad mood, and no doubt the
* program will manage to segfault by itself very soon :-). */
if (ptr)
free(ptr);
close_dir_keep_errno(dp);
return GLOB_NOSPACE;
}
int glob(const char *pattern, int flags, int (*errfunc)(const char * epath, int eerrno), glob_t *pglob)
{
char * pattern_;
char * filename;
char * dirname;
size_t oldcount;
struct stat statr;
size_t i; /* tmp variables are declared here to save a bit of object space */
int j, k; /* */
char * ptr, * ptr2;
int retval = 0;
if (pattern == NULL || pglob == NULL || (flags & ~__GLOB_FLAGS) != 0) {
errno=EINVAL;
return -1;
}
if (!(flags & GLOB_DOOFFS))
pglob->gl_offs = 0;
/* Duplicate pattern so I can make modif to it later (to handle
TILDE stuff replacing old contents, and to null-terminate the
directory) */
pattern_ = malloc(strlen(pattern) + 1);
if (pattern_ == NULL)
return GLOB_NOSPACE;
strcpy(pattern_, pattern);
/* Check for TILDE stuff */
if ((flags & (GLOB_TILDE|GLOB_TILDE_CHECK)) && pattern_[0] == '~') {
char * home_dir = NULL;
if (pattern_[1] == '\0' || pattern_[1] == '/') {
/* She's asking for ~, her homedir */
home_dir = getenv("HOME");
} else {
/* She's asking for another one's homedir */
struct passwd * p;
ptr2 = malloc(strlen(pattern_) + 1);
if (ptr2 == NULL) {
retval = GLOB_NOSPACE;
goto ret;
}
strcpy(ptr2, pattern_ + 1);
ptr = strchr(ptr2, '/');
if (ptr != NULL)
*ptr = '\0';
if (((p = getpwnam(ptr2)) != NULL))
home_dir = p->pw_dir;
free(ptr2);
}
if (home_dir != NULL) {
i = strlen(home_dir) + strlen(pattern_); /* pessimistic (the ~ case) */
ptr = malloc(i);
if (ptr == NULL) {
retval = GLOB_NOSPACE;
goto ret;
}
strncpy(ptr, home_dir, i);
ptr2 = pattern_ + 1;
while (*ptr2 != '/' && *ptr2 != '\0')
ptr2++;
strncat(ptr, ptr2, i);
free(pattern_);
pattern_ = ptr;
} else if (flags & GLOB_TILDE_CHECK) {
retval = GLOB_NOMATCH;
goto ret;
}
}
/* Find the filename */
filename = strrchr(pattern_, '/');
if (filename == NULL) {
/* We have no '/' in the pattern */
filename = pattern_;
dirname = (char*)".";
} else if (filename == pattern_) {
/* "/pattern". */
dirname = (char*)"/";
filename++;
} else {
dirname = pattern_;
filename++;
/* allow dirname to be null terminated */
*(filename-1) = '\0';
if (filename[0] == '\0' && strcmp(pattern_, "/")) {
/* "pattern/". Expand "pattern", appending slashes. */
j = glob(dirname, flags | GLOB_MARK, errfunc, pglob);
if (j == 0)
pglob->gl_flags = ((pglob->gl_flags & ~GLOB_MARK)
| (flags & GLOB_MARK));
retval = j;
goto ret;
}
}
/* Reserve memory for pglob */
if (!(flags & GLOB_APPEND)) {
pglob->gl_pathc = 0;
if (!(flags & GLOB_DOOFFS))
pglob->gl_pathv = NULL;
else {
pglob->gl_pathv = (char **) malloc((pglob->gl_offs + 1) * sizeof (char *));
if (pglob->gl_pathv == NULL) {
retval = GLOB_NOSPACE;
goto ret;
}
for (i = 0; i <= pglob->gl_offs; i++)
pglob->gl_pathv[i] = NULL;
}
}
oldcount = pglob->gl_pathc + pglob->gl_offs;
/* Begin real work */
if (!strcmp(dirname, "/") || !strcmp(dirname, ".")
|| (!strchr(dirname, '*') && !strchr(dirname, '?') && !strchr(dirname, '['))) {
/* Approx of a terminal state, glob directly in dir. */
j = glob_in_dir(filename, dirname, flags, errfunc, pglob);
if (j != 0) {
retval = j;
goto ret;
}
} else {
/* We are not in a terminal state, so we have to glob for
the directory, and then glob for the pattern in each
directory found. */
glob_t dirs;
j = glob(dirname, ((flags & (GLOB_ERR | GLOB_NOCHECK | GLOB_NOESCAPE | GLOB_ALTDIRFUNC))
| GLOB_NOSORT | GLOB_ONLYDIR),
errfunc, &dirs);
if (j != 0) {
retval = j;
goto ret;
}
/* We have successfully globbed the directory name.
For each name we found, call glob_in_dir on it and FILENAME,
appending the results to PGLOB. */
for (i = 0; i < dirs.gl_pathc; i++) {
j = glob_in_dir(filename, dirs.gl_pathv[i], ((flags | GLOB_APPEND) & ~GLOB_NOCHECK),
errfunc, pglob);
if (j == GLOB_NOMATCH)
/* No matches in this directory. Try the next. */
continue;
if (j != 0) {
globfree(&dirs);
globfree(pglob);
retval = j;
goto ret;
}
}
/* We have ignored the GLOB_NOCHECK flag in the `glob_in_dir' calls.
But if we have not found any matching entry and the GLOB_NOCHECK
flag was set we must return the list consisting of the disrectory
names followed by the filename. */
if (pglob->gl_pathc + pglob->gl_offs == oldcount)
{
/* No matches. */
if (flags & GLOB_NOCHECK)
{
for (i = 0; i < dirs.gl_pathc; i++) {
if (stat(dirs.gl_pathv[i], &statr) || !S_ISDIR(statr.st_mode))
continue;
/* stat is okay, we will add the entry, but before let's resize the pathv */
j = pglob->gl_pathc + pglob->gl_offs;
pglob->gl_pathv = (char **) realloc(pglob->gl_pathv, (j + 2) * sizeof (char *));
if (pglob->gl_pathv == NULL) {
globfree (&dirs);
retval = GLOB_NOSPACE;
goto ret;
}
/* okay now we add the new entry */
k = strlen(dirs.gl_pathv[i]) + strlen(filename) + 2;
if ((pglob->gl_pathv[j] = malloc(k)) == NULL) {
globfree(&dirs);
globfree(pglob);
retval = GLOB_NOSPACE;
goto ret;
}
build_fullname(pglob->gl_pathv[j], dirs.gl_pathv[i], filename);
pglob->gl_pathc++;
pglob->gl_pathv[j+1] = NULL;
}
} else {
globfree(&dirs);
retval = GLOB_NOMATCH;
goto ret;
}
}
globfree (&dirs);
}
if (flags & GLOB_MARK) {
for (i = oldcount; i < pglob->gl_pathc + pglob->gl_offs; i++)
if (!stat(pglob->gl_pathv[i], &statr) && S_ISDIR(statr.st_mode)) {
size_t len = strlen(pglob->gl_pathv[i]) + 2;
ptr = realloc(pglob->gl_pathv[i], len);
if (ptr == NULL) {
globfree(pglob);
retval = GLOB_NOSPACE;
goto ret;
}
strcpy(&ptr[len - 2], "/");
pglob->gl_pathv[i] = ptr;
}
}
if (!(flags & GLOB_NOSORT)) {
qsort(&pglob->gl_pathv[oldcount],
pglob->gl_pathc + pglob->gl_offs - oldcount,
sizeof(char *), cmp_func);
}
ret:
free(pattern_);
return retval;
}
/* Free storage allocated in PGLOB by a previous `glob' call. */
void globfree (glob_t * pglob)
{
if (pglob->gl_pathv != NULL) {
size_t i;
for (i = 0; i < pglob->gl_pathc; i++)
if (pglob->gl_pathv[pglob->gl_offs + i] != NULL)
free((void *) pglob->gl_pathv[pglob->gl_offs + i]);
free((void *) pglob->gl_pathv);
}
}