/*- * Copyright (c) 2001 Peter Pentchev * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include "penv.h" #include "pe_cmd.h" #include "pe_err.h" #include "pe_log.h" #include "pe_var.h" #include "pathnames.h" __RINGID("$Ringlet: c/misc/penv/penv.c,v 1.27 2004/01/06 16:38:10 roam Exp $"); unsigned verbose, quiet; char pe_verstr[64] = "penv"; char *pe_cenvdir; int pe_direx; mode_t pe_filemode; static char pe_conffile[NAME_MAX] = PE_CONFFILE; static char pe_basedir[PATH_MAX] = PE_BASEDIR; static char pe_envdir[PATH_MAX] = PE_ENVDIR; static char pe_modestr[4] = PE_FILEMODE; static unsigned pe_depth = PE_DEPTH; static struct pe_var pe_vars[] = { {"basedir", PE_VT_STRING, sizeof(pe_basedir), pe_basedir, 0}, {"conffile", PE_VT_STRING, sizeof(pe_conffile), pe_conffile, 0}, {"depth", PE_VT_UINT, 0, &pe_depth, 0}, {"envdir_p", PE_VT_STRING, sizeof(pe_envdir), pe_envdir, 0}, {"filemode", PE_VT_STRING, sizeof(pe_modestr), pe_modestr, 0}, {NULL, PE_VT_NULL, 0, NULL, 0} }; static const char *pe_envvars[][2] = { {"PENV_BASEDIR", "basedir"}, {"PENV_CONFFILE", "conffile"}, {"PENV_DEPTH", "depth"}, {"PENV_ENVDIR_P", "envdir_p"}, {NULL, NULL} }; extern char **environ; static int pe_confreq; static int donada; static int pe_clean; static pe_err_t pe_init(int, char *[]); static pe_err_t pe_close(void); static pe_err_t pe_doit(int, char *[]); static pe_err_t pe_getdirname(const char *base, unsigned depth, char **penv, int *pdirex); static pe_err_t pe_dirsfx(char **result, unsigned depth, char *path); static pe_err_t pe_basename(char **result, const char *path); static pe_err_t pe_dirname(char **result, const char *path); static pe_err_t pe_mkdir(const char *path); static pe_err_t pe_buildargv(int *pnargc, char **pnargv[], int argc, char *argv[], const char *envdir); static pe_err_t pe_userconf_name(char *name, size_t sz); static pe_err_t version(void); /* * Function: * pe_makeversion() - generate version string * Inputs: * none * Returns: * pe_err_t * Modifies: * pe_verstr */ static pe_err_t pe_makeversion(void) { snprintf(pe_verstr, sizeof(pe_verstr), "penv v%d.%d" #if PE_VER_PRE "-pre%d" #endif /* PE_VER_PRE */ #if PE_VER_PATCH "p%d" #endif /* PE_VER_PATCH */ , PE_VER_MAJ, PE_VER_MIN #if PE_VER_PRE , PE_VER_PRE #endif /* PE_VER_PRE */ #if PE_VER_PATCH , PE_VER_PATCH #endif /* PE_VER_PATCH */ ); return (PE_ERR_NONE); } /* * Function: * pe_init - general-purpose init function * Inputs: * argc, argv - main() args for option processing * Returns: * pe_err_t * CMDLINE * Modifies: * verbose, quiet */ static pe_err_t pe_init(int argc, char *argv[]) { int ch, helpq, versq; unsigned i; const char *val; pe_err_t r; helpq = versq = 0; pe_makeversion(); /* Process cmdline options */ opterr = 0; while (ch = getopt(argc, argv, PE_OPTSTR), ch != EOF) switch (ch) { case 'c': if (r = pe_cmd_set(optarg, '\0'), r) return (r); break; case 'C': pe_clean = 1; break; case 'D': if (r = pe_var_set(pe_vars, "depth", optarg), r) return (r); break; case 'd': if (r = pe_var_set(pe_vars, "basedir", optarg), r) return (r); break; case 'f': if (r = pe_var_set(pe_vars, "conffile", optarg), r) return (r); pe_confreq = 1; break; case 'h': helpq = 1; break; case 'm': if (r = pe_var_set(pe_vars, "filemode", optarg), r) return (r); break; case 'n': donada = 1; break; case 'o': if (r = pe_var_parseline(pe_vars, optarg, strlen(optarg)), r) return (r); break; case 'V': versq = 1; break; case 'q': quiet++; break; case 'v': verbose++; break; default: r = pe_cmd_set(NULL, ch); if (r == PE_ERR_NONE) break; else if (r != PE_ERR_NOCMD) return (r); case '?': usage(); /* NOTREACHED */ } argc -= optind; argv += optind; if (versq) version(); if (helpq) usage(); if (versq) /* no need for "|| helpq": usage() never returns */ exit(PE_ERR_NONE); /* Process the environment variables */ for (i = 0; pe_envvars[i][0] != NULL; i++) if (val = getenv(pe_envvars[i][0]), val != NULL) if (r = pe_var_set(pe_vars, pe_envvars[i][1], val), r) return (r); return (PE_ERR_NONE); } /* * Function: * pe_close - general-purpose shutdown function * Inputs: * none * Returns: * pe_err_t * nothing for the present * Modifies: * nothing for the present */ static pe_err_t pe_close(void) { return (PE_ERR_NONE); } /* * Function: * usage - startup help info * Inputs: * none * Returns: * nothing * Modifies: * nothing, writes to stdout and exits */ void usage(void) { static const char *msg[] = { "penv [-c cmd] [-D depth] [-d basedir] [-f config] [-m mode]" " [-o option]", "\t[-ChnqVv] [command [args..]]", "penv -L [-D depth] [-d basedir] [-f config] [-o option] [-nqv]", "penv -p [-D depth] [-d basedir] [-f config] [-o option] [-nqv]", "penv -S [-D depth] [-d basedir] [-f config] [-m mode]" " [-o option] var=val..", "penv -R [-D depth] [-d basedir] [-f config] [-o option] var..", "\t-c\tperform a specified action,", "\t\tpenv -c help lists the available actions;", "\t-C\tclean the environment;", "\t-D\tspecify the number of path components to be taken for", "\t\tdetermining the environment directory;", "\t-d\tspecify the base dir for locating environment directories", "\t\t(default " PE_BASEDIR ");", "\t-f\tspecify the config file name", "\t\t(default " PE_CONFFILE ");", "\t-h\tprint this help text and exit;", "\t-L\tdo not execute a program, only list the environment;", "\t-n\tdo not execute any programs, just print out what would", "\t\thave been executed;", "\t-o\tspecify an option to be parsed as if read from the start", "\t\tof the configuration file;", "\t-p\tdo not execute a program, only print the environment" " directory;", "\t-q\tquiet operation; multiple -q's make it even more quiet;", "\t-R\treset the specified variables", "\t\t(remove files from the environment directory);", "\t-S\tset the specified variables to the specified values;", "\t-V\tprint version information and exit;", "\t-v\tverbose operation; multiple -v's increase verbosity level.", NULL }; unsigned i; for (i = 0; msg[i] != NULL; i++) fprintf(stderr, "%s\n", msg[i]); exit(PE_ERR_CMDLINE); } /* * function: * pe_version - output version info * Inputs: * none * Returns: * PE_ERR_NONE * Modifies: * nothing, writes to stdout */ pe_err_t version(void) { printf("%s\n", pe_verstr); pe_verbose(1, "%s\n", "Built on " __DATE__ ", " __TIME__); #ifdef __GNUC__ pe_verbose(2, "%s\n", "Compiler: GNU C " __VERSION__); pe_verbose(2, "%s\n", "Platform: " PE_OS " " PE_OSREL ": " PE_OSHOST); #endif /* __GNUC__ */ return (PE_ERR_NONE); } /* * Function: * pe_userconf_name - build the name of a per-user * configuration file * Inputs: * name - the array to store the filename in * sz - max length of the name * Returns: * pe_err_t * nothing so far * Modifies: * on success, stores the per-user configuration file name into 'name' */ static pe_err_t pe_userconf_name(char *name, size_t sz) { const char *val; if (val = getenv("HOME"), val != NULL) snprintf(name, sz, "%s/%s", val, PE_USERCONF); else if (val = getenv("LOGNAME"), val != NULL) snprintf(name, sz, "/home/%s/%s", val, PE_USERCONF); else if (val = getenv("USER"), val != NULL) snprintf(name, sz, "/home/%s/%s", val, PE_USERCONF); else snprintf(name, sz, "~/%s", PE_USERCONF); return (PE_ERR_NONE); } /* * Function: * pe_doit - perform the actual work * Inputs: * argc, argv - main() args * Returns: * pe_err_t * EXEC - execvp() failed * error code from pe_readconf(), pe_getdirname(), pe_buildargv() * Modifies: * nothing by itself; writes args to stdout */ static pe_err_t pe_doit(int argc, char *argv[]) { char userconf[PATH_MAX], *next; pe_err_t r; pe_cenvdir = NULL; r = PE_ERR_NONE; do { /* fake loop so I can break out */ if (r = pe_userconf_name(userconf, sizeof(userconf)), r) break; if (r = pe_var_readfile(pe_vars, userconf, 0), r) break; if (r = pe_var_readfile(pe_vars, pe_conffile, pe_confreq), r) break; /* Convert the file mode to an umask */ pe_filemode = (mode_t)strtoul(pe_modestr, &next, 8); if (*pe_modestr == '\0' || *next != '\0') return (PE_ERR_CMDLINE); if (r = pe_getdirname(pe_basedir, pe_depth, &pe_cenvdir, &pe_direx), r) break; if (r = pe_cmd_exec(argc, argv), r) break; } while (0); free(pe_cenvdir); pe_cenvdir = NULL; return (r); } pe_err_t pe_c_printpath(int argc, char *argv[] __unused) { if (argc > 0) usage(); printf("%s\n", (pe_cenvdir == NULL)? "": pe_cenvdir); return (PE_ERR_NONE); } pe_err_t pe_c_exec(int argc, char *argv[]) { int nargc, i; char **nargv; char *cleanenv[] = { NULL }; pe_err_t r; if (argc < 1) usage(); nargc = 0; nargv = NULL; r = PE_ERR_NONE; do { /* fake loop so I can break out */ if (pe_cenvdir != NULL) pe_verbose(2, "Using envdir %s\n", pe_cenvdir); else pe_verbose(2, "No envdir\n"); r = pe_buildargv(&nargc, &nargv, argc, argv, pe_cenvdir); if (r != PE_ERR_NONE) break; if (verbose || donada) { for (i = 0; i < nargc-1; i++) pe_verbose(0, "%s%s", nargv[i], (i < nargc-2? " ": "\n")); } if (!donada) { /* So let's do it! */ if (pe_clean) environ = cleanenv; execvp(nargv[0], nargv); r = PE_ERR_EXEC; } } while (0); return (r); } pe_err_t pe_c_mkdir(int argc, char *argv[] __unused) { if (argc > 0) usage(); return (pe_mkdir(pe_cenvdir)); } /* * Function: * pe_mkdir - create a directory and higher-level dirs * Inputs: * path - directory to create * Returns: * pe_err_t * MKDIR - mkdir(2) failed * INT - mkdir(2) returned ENOENT on a TLD * Modifies: * nothing, creates directories */ static pe_err_t pe_mkdir(const char *path) { char *base; pe_err_t r; base = NULL; if ((mkdir(path, 0755) == 0) || (errno == EEXIST)) return (PE_ERR_NONE); if (errno != ENOENT) return (PE_ERR_MKDIR); if (r = pe_dirname(&base, path), r) return (r); /* Sanity check: did we fail to create a top-level dir? */ if (!strcmp(base, ".") || !strcmp(base, "/")) return (PE_ERR_INT); /* FIXME: this should be OSERR */ r = pe_mkdir(base); free(base); if (r != PE_ERR_NONE) return (r); if (mkdir(path, 0755) == -1) return (PE_ERR_MKDIR); return (PE_ERR_NONE); } /* FIXME: document everything from here onwards */ static pe_err_t pe_getdirname(const char *base, unsigned depth, char **penv, int *pdirex) { char *envdir, *cwd, *dirsfx; int found, fd, direx; struct stat sbuf; pe_err_t r; envdir = cwd = dirsfx = NULL; found = direx = 0; do { if (cwd = getcwd(NULL, PATH_MAX), cwd == NULL) return (PE_ERR_GETCWD); if (r = pe_dirsfx(&dirsfx, depth, cwd), r) break; if (dirsfx != NULL) pe_verbose(2, "Using dir suffix %s\n", dirsfx); else pe_verbose(2, "No dir suffix\n"); /* Okay, we found us a pathname! */ asprintf(&envdir, "%s/%s", base, dirsfx); if (envdir == NULL) return (PE_ERR_NOMEM); /* Now let's just check if this thing really exists.. */ if (fd = open(envdir, O_RDONLY), fd == -1) break; if (fstat(fd, &sbuf) == -1) { r = PE_ERR_FSTAT; break; } close(fd); if (!S_ISDIR(sbuf.st_mode)) break; direx = 1; } while (0); free(dirsfx); if (r == PE_ERR_NONE) { *penv = envdir; *pdirex = direx; } return (PE_ERR_NONE); } static pe_err_t pe_dirsfx(char **result, unsigned depth, char *path) { unsigned d; char *comp; char *cpath; char *res, *nres; int exhausted; pe_err_t r; exhausted = 0; cpath = path; res = NULL; r = PE_ERR_NONE; for (d = 0; d < depth; d++) { /* Find the last component.. */ if (r = pe_basename(&comp, cpath), r) break; if (!strcmp(comp, ".") || !strcmp(comp, "/")) { free(res); res = NULL; break; } /* ..add it to the newly built path.. */ if (d == 0) nres = strdup(comp); else asprintf(&nres, "%s/%s", comp, res); if (nres == NULL) { r = PE_ERR_NOMEM; break; } free(res); res = nres; /* ..and remove it from the path. */ if (d < depth - 1) { if (r = pe_dirname(&cpath, cpath), r) break; if (!strcmp(cpath, ".")) { free(res); res = NULL; break; } } } free(comp); if (r == PE_ERR_NONE) *result = res; return (r); } static pe_err_t pe_basename(char **result, const char *path) { char *base, *b; if (base = basename(path), base == NULL) return (PE_ERR_BASENAME); if (b = strdup(base), b == NULL) return (PE_ERR_NOMEM); *result = b; return (PE_ERR_NONE); } static pe_err_t pe_dirname(char **result, const char *path) { char *dir, *b; if (dir = dirname(path), dir == NULL) return (PE_ERR_DIRNAME); if (b = strdup(dir), b == NULL) return (PE_ERR_NOMEM); *result = b; return (PE_ERR_NONE); } static pe_err_t pe_buildargv(int *pnargc, char **pnargv[], int argc, char *argv[], const char *envdir) { int nargc, add; char **nargv; if (pe_direx) add = 2; else add = 0; nargc = argc + 1 + add; if (nargv = calloc(nargc, sizeof(*nargv)), nargv == NULL) return (PE_ERR_NOMEM); memcpy(nargv + add, argv, argc * sizeof(*nargv)); nargv[nargc-1] = NULL; if (pe_direx) { if (nargv[0] = strdup(pe_envdir), nargv[0] == NULL) return (PE_ERR_NOMEM); if (nargv[1] = strdup(envdir), nargv[1] == NULL) return (PE_ERR_NOMEM); } *pnargc = nargc; *pnargv = nargv; return (PE_ERR_NONE); } /* * M A I N F U N C T I O N */ int main(int argc, char *argv[]) { pe_err_t r, cr; if (r = pe_init(argc, argv), r) return (pe_prerror("init", r)); argc -= optind; argv += optind; if (r = pe_doit(argc, argv), r) pe_prerror("doit", r); if (cr = pe_close(), cr) pe_prerror("close", cr); return (r? r: cr); }