diff options
Diffstat (limited to 'examples/loadables/pathchk.c')
-rw-r--r-- | examples/loadables/pathchk.c | 358 |
1 files changed, 358 insertions, 0 deletions
diff --git a/examples/loadables/pathchk.c b/examples/loadables/pathchk.c new file mode 100644 index 0000000..2e36f8f --- /dev/null +++ b/examples/loadables/pathchk.c @@ -0,0 +1,358 @@ +/* pathchk - check pathnames for validity and portability */ + +/* Usage: pathchk [-p] path ... + + For each PATH, print a message if any of these conditions are false: + * all existing leading directories in PATH have search (execute) permission + * strlen (PATH) <= PATH_MAX + * strlen (each_directory_in_PATH) <= NAME_MAX + + Exit status: + 0 All PATH names passed all of the tests. + 1 An error occurred. + + Options: + -p Instead of performing length checks on the + underlying filesystem, test the length of the + pathname and its components against the POSIX.1 + minimum limits for portability, _POSIX_NAME_MAX + and _POSIX_PATH_MAX in 2.9.2. Also check that + the pathname contains no character not in the + portable filename character set. */ + +/* See Makefile for compilation details. */ + +#include <config.h> + +#include <sys/types.h> +#include "posixstat.h" + +#if defined (HAVE_UNISTD_H) +# include <unistd.h> +#endif + +#if defined (HAVE_LIMITS_H) +# include <limits.h> +#endif + +#include "bashansi.h" + +#include <stdio.h> +#include <errno.h> + +#include "builtins.h" +#include "shell.h" +#include "stdc.h" +#include "bashgetopt.h" +#include "maxpath.h" + +#if !defined (errno) +extern int errno; +#endif + +#if !defined (_POSIX_PATH_MAX) +# define _POSIX_PATH_MAX 255 +#endif +#if !defined (_POSIX_NAME_MAX) +# define _POSIX_NAME_MAX 14 +#endif + +/* How do we get PATH_MAX? */ +#if defined (_POSIX_VERSION) && !defined (PATH_MAX) +# define PATH_MAX_FOR(p) pathconf ((p), _PC_PATH_MAX) +#endif + +/* How do we get NAME_MAX? */ +#if defined (_POSIX_VERSION) && !defined (NAME_MAX) +# define NAME_MAX_FOR(p) pathconf ((p), _PC_NAME_MAX) +#endif + +#if !defined (PATH_MAX_FOR) +# define PATH_MAX_FOR(p) PATH_MAX +#endif + +#if !defined (NAME_MAX_FOR) +# define NAME_MAX_FOR(p) NAME_MAX +#endif + +extern char *strerror (); + +static int validate_path (); + +pathchk_builtin (list) + WORD_LIST *list; +{ + int retval, pflag, opt; + + reset_internal_getopt (); + while ((opt = internal_getopt (list, "p")) != -1) + { + switch (opt) + { + case 'p': + pflag = 1; + break; + default: + builtin_usage (); + return (EX_USAGE); + } + } + list = loptend; + + if (list == 0) + { + builtin_usage (); + return (EX_USAGE); + } + + for (retval = 0; list; list = list->next) + retval |= validate_path (list->word->word, pflag); + + return (retval ? EXECUTION_FAILURE : EXECUTION_SUCCESS); +} + +char *pathchk_doc[] = { + "Check each pathname argument for validity (i.e., it may be used to", + "create or access a file without casuing syntax errors) and portability", + "(i.e., no filename truncation will result). If the `-p' option is", + "supplied, more extensive portability checks are performed.", + (char *)NULL +}; + +/* The standard structure describing a builtin command. bash keeps an array + of these structures. */ +struct builtin pathchk_struct = { + "pathchk", /* builtin name */ + pathchk_builtin, /* function implementing the builtin */ + BUILTIN_ENABLED, /* initial flags for builtin */ + pathchk_doc, /* array of long documentation strings. */ + "pathchk [-p] pathname ...", /* usage synopsis */ + 0 /* reserved for internal use */ +}; + +/* The remainder of this file is stolen shamelessly from `pathchk.c' in + the sh-utils-1.12 distribution, by + + David MacKenzie <djm@gnu.ai.mit.edu> + and Jim Meyering <meyering@cs.utexas.edu> */ + +/* Each element is nonzero if the corresponding ASCII character is + in the POSIX portable character set, and zero if it is not. + In addition, the entry for `/' is nonzero to simplify checking. */ +static char const portable_chars[256] = +{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0-15 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 16-31 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, /* 32-47 */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, /* 48-63 */ + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 64-79 */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, /* 80-95 */ + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 96-111 */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, /* 112-127 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +/* If PATH contains only portable characters, return 1, else 0. */ + +static int +portable_chars_only (path) + const char *path; +{ + const char *p; + + for (p = path; *p; ++p) + if (portable_chars[(const unsigned char) *p] == 0) + { + builtin_error ("path `%s' contains nonportable character `%c'", path, *p); + return 0; + } + return 1; +} + +/* On some systems, stat can return EINTR. */ + +#ifndef EINTR +# define SAFE_STAT(name, buf) stat (name, buf) +#else +# define SAFE_STAT(name, buf) safe_stat (name, buf) +static inline int +safe_stat (name, buf) + const char *name; + struct stat *buf; +{ + int ret; + + do + ret = stat (name, buf); + while (ret < 0 && errno == EINTR); + + return ret; +} +#endif + +/* Return 1 if PATH is a usable leading directory, 0 if not, + 2 if it doesn't exist. */ + +static int +dir_ok (path) + const char *path; +{ + struct stat stats; + + if (SAFE_STAT (path, &stats)) + return 2; + + if (!S_ISDIR (stats.st_mode)) + { + builtin_error ("`%s' is not a directory", path); + return 0; + } + + /* Use access to test for search permission because + testing permission bits of st_mode can lose with new + access control mechanisms. Of course, access loses if you're + running setuid. */ + if (access (path, X_OK) != 0) + { + if (errno == EACCES) + builtin_error ("directory `%s' is not searchable", path); + else + builtin_error ("%s: %s", path, strerror (errno)); + return 0; + } + + return 1; +} + +static char * +xstrdup (s) + char *s; +{ + return (savestring (s)); +} + +/* Make sure that + strlen (PATH) <= PATH_MAX + && strlen (each-existing-directory-in-PATH) <= NAME_MAX + + If PORTABILITY is nonzero, compare against _POSIX_PATH_MAX and + _POSIX_NAME_MAX instead, and make sure that PATH contains no + characters not in the POSIX portable filename character set, which + consists of A-Z, a-z, 0-9, ., _, -. + + Make sure that all leading directories along PATH that exist have + `x' permission. + + Return 0 if all of these tests are successful, 1 if any fail. */ + +static int +validate_path (path, portability) + char *path; + int portability; +{ + int path_max; + int last_elem; /* Nonzero if checking last element of path. */ + int exists; /* 2 if the path element exists. */ + char *slash; + char *parent; /* Last existing leading directory so far. */ + + if (portability && !portable_chars_only (path)) + return 1; + + if (*path == '\0') + return 0; + +#ifdef lint + /* Suppress `used before initialized' warning. */ + exists = 0; +#endif + + /* Figure out the parent of the first element in PATH. */ + parent = xstrdup (*path == '/' ? "/" : "."); + + slash = path; + last_elem = 0; + while (1) + { + int name_max; + int length; /* Length of partial path being checked. */ + char *start; /* Start of path element being checked. */ + + /* Find the end of this element of the path. + Then chop off the rest of the path after this element. */ + while (*slash == '/') + slash++; + start = slash; + slash = strchr (slash, '/'); + if (slash != NULL) + *slash = '\0'; + else + { + last_elem = 1; + slash = strchr (start, '\0'); + } + + if (!last_elem) + { + exists = dir_ok (path); + if (dir_ok == 0) + { + free (parent); + return 1; + } + } + + length = slash - start; + /* Since we know that `parent' is a directory, it's ok to call + pathconf with it as the argument. (If `parent' isn't a directory + or doesn't exist, the behavior of pathconf is undefined.) + But if `parent' is a directory and is on a remote file system, + it's likely that pathconf can't give us a reasonable value + and will return -1. (NFS and tempfs are not POSIX . . .) + In that case, we have no choice but to assume the pessimal + POSIX minimums. */ + name_max = portability ? _POSIX_NAME_MAX : NAME_MAX_FOR (parent); + if (name_max < 0) + name_max = _POSIX_NAME_MAX; + if (length > name_max) + { + builtin_error ("name `%s' has length %d; exceeds limit of %d", + start, length, name_max); + free (parent); + return 1; + } + + if (last_elem) + break; + + if (exists == 1) + { + free (parent); + parent = xstrdup (path); + } + + *slash++ = '/'; + } + + /* `parent' is now the last existing leading directory in the whole path, + so it's ok to call pathconf with it as the argument. */ + path_max = portability ? _POSIX_PATH_MAX : PATH_MAX_FOR (parent); + if (path_max < 0) + path_max = _POSIX_PATH_MAX; + free (parent); + if (strlen (path) > path_max) + { + builtin_error ("path `%s' has length %d; exceeds limit of %d", + path, strlen (path), path_max); + return 1; + } + + return 0; +} |