blob: 43d2f671b58ea5dd12809dc83a694376ed172519 [file] [log] [blame]
/* Copyright (C) 1991, 1992, 1993, 1994, 1995 Free Software Foundation, Inc.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public License as
published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with this library; see the file COPYING.LIB. If
not, write to the Free Software Foundation, Inc., 675 Mass Ave,
Cambridge, MA 02139, USA. */
#include <common.h>
#include <errno.h>
#include <fs.h>
#include <linux/stat.h>
#include <malloc.h>
#include <xfuncs.h>
#include <fnmatch.h>
#define _GNU_SOURCE
#include <glob.h>
#ifdef CONFIG_GLOB
extern __ptr_t(*__glob_opendir_hook) __P((const char *directory));
extern void (*__glob_closedir_hook) __P((__ptr_t stream));
extern const char *(*__glob_readdir_hook) __P((__ptr_t stream));
static int glob_in_dir __P((const char *pattern, const char *directory,
int flags,
int (*errfunc) __P((const char *, int)),
glob_t * pglob));
static int prefix_array __P((const char *prefix, char **array, size_t n,
int add_slash));
#ifdef __GLOB64
extern int glob_pattern_p(const char *pattern, int quote);
#else
/* Return nonzero if PATTERN contains any metacharacters.
Metacharacters can be quoted with backslashes if QUOTE is nonzero. */
int glob_pattern_p(const char *pattern, int quote)
{
const char *p;
int open = 0;
for (p = pattern; *p != '\0'; ++p)
switch (*p) {
case '?':
case '*':
return 1;
case '\\':
if (quote && p[1] != '\0')
++p;
break;
case '[':
open = 1;
break;
case ']':
if (open)
return 1;
break;
}
return 0;
}
#endif
#ifdef CONFIG_GLOB_SORT
/* Do a collated comparison of A and B. */
static int collated_compare(a, b)
const __ptr_t a;
const __ptr_t b;
{
const char *const s1 = *(const char *const *)a;
const char *const s2 = *(const char *const *)b;
if (s1 == s2)
return 0;
if (s1 == NULL)
return 1;
if (s2 == NULL)
return -1;
return strcmp(s1, s2);
}
#endif
/* Do glob searching for PATTERN, placing results in PGLOB.
The bits defined above may be set in FLAGS.
If a directory cannot be opened or read and ERRFUNC is not nil,
it is called with the pathname that caused the error, and the
`errno' value from the failing call; if it returns non-zero
`glob' returns GLOB_ABEND; if it returns zero, the error is ignored.
If memory cannot be allocated for PGLOB, GLOB_NOSPACE is returned.
Otherwise, `glob' returns zero. */
int glob(const char *pattern, int flags,
int (*errfunc) __P((const char *, int)), glob_t *pglob)
{
const char *filename;
char *dirname = NULL;
size_t dirlen;
int status;
int oldcount;
if (pattern == NULL || pglob == NULL || (flags & ~__GLOB_FLAGS) != 0) {
errno = EINVAL;
return -1;
}
/* Find the filename. */
filename = strrchr(pattern, '/');
if (filename == NULL) {
filename = pattern;
dirname = strdup(".");
dirlen = 0;
} else if (filename == pattern) {
/* "/pattern". */
dirname = strdup("/");
dirlen = 1;
++filename;
} else {
dirlen = filename - pattern;
dirname = (char *)xmalloc(dirlen + 1);
memcpy(dirname, pattern, dirlen);
dirname[dirlen] = '\0';
++filename;
}
if (filename[0] == '\0' && dirlen > 1) {
/* "pattern/". Expand "pattern", appending slashes. */
int val = glob(dirname, flags | GLOB_MARK, errfunc, pglob);
if (val == 0)
pglob->gl_flags =
(pglob->
gl_flags & ~GLOB_MARK) | (flags & GLOB_MARK);
status = val;
goto out;
}
if (!(flags & GLOB_APPEND)) {
pglob->gl_pathc = 0;
pglob->gl_pathv = NULL;
}
oldcount = pglob->gl_pathc;
if (glob_pattern_p(dirname, !(flags & GLOB_NOESCAPE))) {
/* The directory name contains metacharacters, so we
have to glob for the directory, and then glob for
the pattern in each directory found. */
glob_t dirs;
register int i;
status = glob(dirname,
((flags &
(GLOB_ERR | GLOB_NOCHECK | GLOB_NOESCAPE)) |
GLOB_NOSORT), errfunc, &dirs);
if (status != 0)
goto out;
/* We have successfully globbed the preceding 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) {
int oldcount1;
#ifdef SHELL
{
/* Make globbing interruptible in the bash shell. */
extern int interrupt_state;
if (interrupt_state) {
globfree(&dirs);
globfree(&files);
status = GLOB_ABEND goto out;
}
}
#endif /* SHELL. */
oldcount1 = pglob->gl_pathc;
status = glob_in_dir(filename, dirs.gl_pathv[i],
(flags | GLOB_APPEND) &
~GLOB_NOCHECK, errfunc, pglob);
if (status == GLOB_NOMATCH) {
/* No matches in this directory. Try the next. */
continue;
}
if (status != 0) {
globfree(pglob);
goto out;
}
/* Stick the directory on the front of each name. */
prefix_array(dirs.gl_pathv[i],
&pglob->gl_pathv[oldcount1],
pglob->gl_pathc - oldcount1,
flags & GLOB_MARK);
}
globfree(&dirs);
flags |= GLOB_MAGCHAR;
if (pglob->gl_pathc == oldcount) {
/* No matches. */
#ifdef CONFIG_GLOB_NOCHECK
if (flags & GLOB_NOCHECK) {
size_t len = strlen(pattern) + 1;
char *patcopy = (char *)xmalloc(len);
memcpy(patcopy, pattern, len);
pglob->gl_pathv
= (char **)xrealloc(pglob->gl_pathv,
(pglob->gl_pathc +
((flags & GLOB_DOOFFS) ?
pglob->gl_offs : 0) +
1 + 1) *
sizeof(char *));
if (flags & GLOB_DOOFFS)
while (pglob->gl_pathc < pglob->gl_offs)
pglob->gl_pathv[pglob->
gl_pathc++] =
NULL;
pglob->gl_pathv[pglob->gl_pathc++] = patcopy;
pglob->gl_pathv[pglob->gl_pathc] = NULL;
pglob->gl_flags = flags;
} else
#endif
{
status = GLOB_NOMATCH;
goto out;
}
}
} else {
status = glob_in_dir(filename, dirname, flags, errfunc, pglob);
if (status != 0)
goto out;
if (dirlen > 0) {
/* Stick the directory on the front of each name. */
prefix_array(dirname,
&pglob->gl_pathv[oldcount],
pglob->gl_pathc - oldcount,
flags & GLOB_MARK);
}
}
if (flags & GLOB_MARK) {
/* Append slashes to directory names. glob_in_dir has already
allocated the extra character for us. */
int i;
struct stat st;
for (i = oldcount; i < pglob->gl_pathc; ++i)
if (stat(pglob->gl_pathv[i], &st) == 0 &&
S_ISDIR(st.st_mode))
strcat(pglob->gl_pathv[i], "/");
}
#ifdef CONFIG_GLOB_SORT
if (!(flags & GLOB_NOSORT))
/* Sort the vector. */
qsort((__ptr_t) & pglob->gl_pathv[oldcount],
pglob->gl_pathc - oldcount,
sizeof(char *), (__compar_fn_t) collated_compare);
#endif
status = 0;
out:
free(dirname);
return status;
}
/* Prepend DIRNAME to each of N members of ARRAY, replacing ARRAY's
elements in place. Return nonzero if out of memory, zero if successful.
A slash is inserted between DIRNAME and each elt of ARRAY,
unless DIRNAME is just "/". Each old element of ARRAY is freed.
If ADD_SLASH is non-zero, allocate one character more than
necessary, so that a slash can be appended later. */
static int prefix_array(const char *dirname, char **array, size_t n,
int add_slash)
{
register size_t i;
size_t dirlen = strlen(dirname);
if (dirlen == 1 && dirname[0] == '/')
/* DIRNAME is just "/", so normal prepending would get us "//foo".
We want "/foo" instead, so don't prepend any chars from DIRNAME. */
dirlen = 0;
for (i = 0; i < n; ++i) {
size_t eltlen = strlen(array[i]) + 1;
char *new =
(char *)xmalloc(dirlen + 1 + eltlen + (add_slash ? 1 : 0));
memcpy(new, dirname, dirlen);
new[dirlen] = '/';
memcpy(&new[dirlen + 1], array[i], eltlen);
free((__ptr_t) array[i]);
array[i] = new;
}
return 0;
}
/* 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). */
static int glob_in_dir(const char *pattern, const char *directory,
int flags, int (*errfunc) __P((const char *, int)), glob_t *pglob)
{
__ptr_t stream;
struct globlink {
struct globlink *next;
char *name;
};
struct globlink *names = NULL;
size_t nfound = 0;
int meta;
stream = opendir(directory);
if (stream == NULL) {
if ((errfunc != NULL && (*errfunc) (directory, errno)) ||
(flags & GLOB_ERR))
return GLOB_ABORTED;
}
meta = glob_pattern_p(pattern, !(flags & GLOB_NOESCAPE));
if (meta)
flags |= GLOB_MAGCHAR;
while (1) {
const char *name;
size_t len;
struct dirent *d = readdir((DIR *) stream);
if (d == NULL)
break;
// if (! (d->d_ino != 0))
// continue;
name = d->d_name;
if ((!meta && strcmp(pattern, name) == 0)
|| fnmatch(pattern, name,
(!(flags & GLOB_PERIOD) ? FNM_PERIOD : 0) |
((flags & GLOB_NOESCAPE) ? FNM_NOESCAPE : 0)) == 0) {
struct globlink *new =
(struct globlink *)xmalloc(sizeof(struct globlink));
len = strlen(name);
new->name = xmalloc(len + ((flags & GLOB_MARK) ? 1 : 0) + 1);
memcpy((__ptr_t) new->name, name, len);
new->name[len] = '\0';
new->next = names;
names = new;
++nfound;
if (!meta)
break;
}
}
#ifdef CONFIG_GLOB_NOCHECK
if (nfound == 0 && (flags & GLOB_NOCHECK)) {
size_t len = strlen(pattern);
nfound = 1;
names = (struct globlink *)xmalloc(sizeof(struct globlink));
names->next = NULL;
names->name =
(char *)xmalloc(len + (flags & GLOB_MARK ? 1 : 0) + 1);
memcpy(names->name, pattern, len);
names->name[len] = '\0';
}
#endif
pglob->gl_pathv
= (char **)xrealloc(pglob->gl_pathv,
(pglob->gl_pathc +
((flags & GLOB_DOOFFS) ? pglob->gl_offs : 0) +
nfound + 1) * sizeof(char *));
if (flags & GLOB_DOOFFS)
while (pglob->gl_pathc < pglob->gl_offs)
pglob->gl_pathv[pglob->gl_pathc++] = NULL;
while (names) {
struct globlink *tmp;
pglob->gl_pathv[pglob->gl_pathc++] = names->name;
tmp = names;
names = names->next;
free(tmp);
}
pglob->gl_pathv[pglob->gl_pathc] = NULL;
pglob->gl_flags = flags;
{
int save = errno;
(void)closedir((DIR *) stream);
errno = save;
}
return nfound == 0 ? GLOB_NOMATCH : 0;
}
#endif /* CONFIG_GLOB */
#ifdef CONFIG_FAKE_GLOB
/* Fake version of glob. We simply put the input string into
* the gl_pathv array. Currently we don't need it as hush.c won't
* call us if no glob support is available.
*/
int glob(pattern, flags, errfunc, pglob)
const char *pattern;
int flags;
int (*errfunc) __P((const char *, int));
glob_t *pglob;
{
int elems, i;
if (!(flags & GLOB_APPEND)) {
pglob->gl_pathc = 0;
pglob->gl_pathv = NULL;
}
elems = pglob->gl_pathc + 2;
if (flags & GLOB_DOOFFS)
elems += pglob->gl_offs;
pglob->gl_pathv = xrealloc(pglob->gl_pathv, elems * sizeof(char *));
if (flags & GLOB_DOOFFS)
for (i = 0; i < pglob->gl_offs; i++)
pglob->gl_pathv[i] = NULL;
pglob->gl_pathv[pglob->gl_pathc] = strdup(pattern);
pglob->gl_pathc++;
pglob->gl_pathv[pglob->gl_pathc] = NULL;
return 0;
}
#endif /* CONFIG_FAKE_GLOB */
/* Free storage allocated in PGLOB by a previous `glob' call. */
void globfree(glob_t *pglob)
{
if (pglob->gl_pathv != NULL) {
int i = pglob->gl_flags & GLOB_DOOFFS ? pglob->gl_offs : 0;
for (; i < pglob->gl_pathc; ++i)
if (pglob->gl_pathv[i] != NULL)
free((__ptr_t) pglob->gl_pathv[i]);
free((__ptr_t) pglob->gl_pathv);
}
}