/*-
* 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 <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <libgen.h>
#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);
}
syntax highlighted by Code2HTML, v. 0.9.1