diff options
Diffstat (limited to 'pseudo_util.c')
-rw-r--r-- | pseudo_util.c | 499 |
1 files changed, 499 insertions, 0 deletions
diff --git a/pseudo_util.c b/pseudo_util.c new file mode 100644 index 0000000..2e21824 --- /dev/null +++ b/pseudo_util.c @@ -0,0 +1,499 @@ +/* + * pseudo_util.c, miscellaneous utility functions + * + * Copyright (c) 2008-2010 Wind River Systems, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the Lesser GNU General Public License version 2.1 as + * published by the Free Software Foundation. + * + * This program 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 Lesser GNU General Public License for more details. + * + * You should have received a copy of the Lesser GNU General Public License + * version 2.1 along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#include <stdarg.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <sys/stat.h> +#include <time.h> +#include <unistd.h> +#include <limits.h> + +#include "pseudo.h" +#include "pseudo_ipc.h" +#include "pseudo_db.h" + +/* 3 = detailed protocol analysis + * 2 = higher-level protocol analysis + * 1 = stuff that might go wrong + * 0 = fire and arterial bleeding + */ +static int max_debug_level = 0; +int pseudo_util_debug_fd = 2; +static int debugged_newline = 1; +static char pid_text[32]; +static size_t pid_len; +static int pseudo_append_element(char **newpath, size_t *allocated, char **current, const char *element, size_t elen, int leave_last); +static int pseudo_append_elements(char **newpath, size_t *allocated, char **current, const char *elements, size_t elen, int leave_last); +extern char **environ; + +char *pseudo_version = PSEUDO_VERSION; + +void +pseudo_debug_terse(void) { + if (max_debug_level > 0) + --max_debug_level; +} + +void +pseudo_debug_verbose(void) { + ++max_debug_level; +} + +int +pseudo_diag(char *fmt, ...) { + va_list ap; + char debuff[8192]; + int len; + /* gcc on Ubuntu 8.10 requires that you examine the return from + * write(), and won't let you cast it to void. Of course, if you + * can't print error messages, there's nothing to do. + */ + int wrote = 0; + + va_start(ap, fmt); + len = vsnprintf(debuff, 8192, fmt, ap); + va_end(ap); + + if (len > 8192) + len = 8192; + + if (debugged_newline && max_debug_level > 1) { + wrote += write(pseudo_util_debug_fd, pid_text, pid_len); + } + debugged_newline = (debuff[len - 1] == '\n'); + + wrote += write(pseudo_util_debug_fd, "pseudo: ", 8); + wrote += write(pseudo_util_debug_fd, debuff, len); + return wrote; +} + +int +pseudo_debug_real(int level, char *fmt, ...) { + va_list ap; + char debuff[8192]; + int len; + int wrote = 0; + + if (max_debug_level < level) + return 0; + + va_start(ap, fmt); + len = vsnprintf(debuff, 8192, fmt, ap); + va_end(ap); + + if (len > 8192) + len = 8192; + + if (debugged_newline && max_debug_level > 1) { + wrote += write(pseudo_util_debug_fd, pid_text, pid_len); + } + debugged_newline = (debuff[len - 1] == '\n'); + + wrote += write(pseudo_util_debug_fd, debuff, len); + return wrote; +} + +/* given n, pick a multiple of block enough bigger than n + * to give us some breathing room. + */ +static inline size_t +round_up(size_t n, size_t block) { + return block * (((n + block / 4) / block) + 1); +} + +/* store pid in text form for prepending to messages */ +void +pseudo_new_pid() { + pid_len = snprintf(pid_text, 32, "%d: ", getpid()); + pseudo_debug(2, "new pid.\n"); +} + +/* helper function for pseudo_fix_path + * adds "element" to "newpath" at location current, if it can, then + * checks whether this now points to a symlink. If it does, expand + * the symlink, appending each element in turn the same way. + */ +static int +pseudo_append_element(char **pnewpath, size_t *pallocated, char **pcurrent, const char *element, size_t elen, int leave_this) { + static int link_recursion = 0; + size_t curlen, allocated; + char *newpath, *current; + struct stat64 buf; + if (!pnewpath || !*pnewpath || !pallocated || !pcurrent || !*pcurrent || !element) { + pseudo_diag("pseudo_append_element: invalid args.\n"); + return -1; + } + newpath = *pnewpath; + allocated = *pallocated; + current = *pcurrent; + /* sanity-check: ignore // or /./ */ + if (elen == 0 || (elen == 1 && *element == '.')) { + return 1; + } + /* backtrack for .. */ + if (elen == 2 && element[0] == '.' && element[1] == '.') { + /* if newpath's whole contents are '/', do nothing */ + if (current <= newpath + 1) + return 1; + /* backtrack to the character before the / */ + current -= 2; + /* now find the previous slash */ + while (current > newpath && *current != '/') { + --current; + } + /* and point to the nul just past it */ + *(++current) = '\0'; + *pcurrent = current; + return 1; + } + curlen = current - newpath; + /* current length, plus / <element> / \0 */ + /* => curlen + elen + 3 */ + if (curlen + elen + 3 > allocated) { + char *bigger; + size_t big = round_up(allocated + elen, 256); + bigger = malloc(big); + if (!bigger) { + pseudo_diag("pseudo_append_element: couldn't allocate space (wanted %lu bytes).\n", (unsigned long) big); + return -1; + } + memcpy(bigger, newpath, curlen); + current = bigger + curlen; + free(newpath); + newpath = bigger; + allocated = big; + *pnewpath = newpath; + *pcurrent = current; + *pallocated = allocated; + } + memcpy(current, element, elen); + current += elen; + /* nul-terminate, and we now point to the nul after the slash */ + *current = '\0'; + /* now, the moment of truth... is that a symlink? */ + /* if lstat fails, that's fine -- nonexistent files aren't symlinks */ + if (!leave_this) { + int is_link; + is_link = (lstat64(newpath, &buf) != -1) && S_ISLNK(buf.st_mode); + if (link_recursion >= PSEUDO_MAX_LINK_RECURSION && is_link) { + pseudo_diag("link recursion too deep, not expanding path '%s'.\n", newpath); + is_link = 0; + } + if (is_link) { + char linkbuf[PATH_MAX + 1]; + ssize_t linklen; + int retval; + + linklen = readlink(newpath, linkbuf, PATH_MAX); + if (linklen == -1) { + pseudo_diag("uh-oh! '%s' seems to be a symlink, but I can't read it. Ignoring.", newpath); + return 0; + } + /* null-terminate buffer */ + linkbuf[linklen] = '\0'; + /* absolute symlink means start over! */ + if (*linkbuf == '/') { + current = newpath + 1; + } else { + /* point back at the end of the previous path... */ + current -= elen; + } + /* null terminate at the new pointer */ + *current = '\0'; + /* append all the elements in series */ + *pcurrent = current; + ++link_recursion; + retval = pseudo_append_elements(pnewpath, pallocated, pcurrent, linkbuf, linklen, 0); + --link_recursion; + return retval; + } + } + /* okay, not a symlink, go ahead and append a slash */ + *(current++) = '/'; + *current = '\0'; + *pcurrent = current; + return 1; +} + +static int +pseudo_append_elements(char **newpath, size_t *allocated, char **current, const char *element, size_t elen, int leave_last) { + int retval = 1; + const char * start = element; + if (!newpath || !current || !element || !*newpath || !*current) { + pseudo_diag("pseudo_append_elements: invalid arguments."); + return -1; + } + while (element < (start + elen) && *element) { + size_t this_elen; + int leave_this = 0; + char *next = strchr(element, '/'); + if (!next) { + next = strchr(element, '\0'); + leave_this = leave_last; + } + this_elen = next - element; + switch (this_elen) { + case 0: /* path => '/' */ + break; + case 1: /* path => '?/' */ + if (*element != '.') { + if (pseudo_append_element(newpath, allocated, current, element, this_elen, leave_this) == -1) { + retval = -1; + } + } + break; + default: + if (pseudo_append_element(newpath, allocated, current, element, this_elen, leave_this) == -1) { + retval = -1; + } + break; + } + /* and now move past the separator */ + element += this_elen + 1; + } + return retval; +} + +/* Canonicalize path. "base", if present, is an already-canonicalized + * path of baselen characters, presumed not to end in a /. path is + * the new path to be canonicalized. The tricky part is that path may + * contain symlinks, which must be resolved. + * if "path" starts with a /, then it is an absolute path, and + * we ignore base. + */ +char * +pseudo_fix_path(char *base, const char *path, size_t baselen, size_t *lenp, int leave_last) { + size_t newpathlen, pathlen; + char *newpath; + char *current; + + if (!path) { + pseudo_diag("can't fix empty path.\n"); + return 0; + } + pathlen = strlen(path); + /* allow a bit of slush. overallocating a bit won't + * hurt. rounding to 256's in the hopes that it makes life + * easier for the library. + */ + if (path[0] == '/') { + newpathlen = round_up(pathlen + 1, 256); + newpath = malloc(newpathlen); + if (!newpath) { + pseudo_diag("allocation failed seeking memory for path (%s).\n", path); + return 0; + } + current = newpath; + ++path; + } else { + newpathlen = round_up(baselen + pathlen + 2, 256); + newpath = malloc(newpathlen); + memcpy(newpath, base, baselen); + current = newpath + baselen; + } + *current++ = '/'; + *current = '\0'; + /* at any given point: + * current points to just after the last / of newpath + * path points to the next path element of path + * newpathlen is the total allocated length of newpath + * (current - newpathlen) is the used length of newpath + * oldpath is the starting point of path + * (path - oldpath) is how far into path we are + */ + if (pseudo_append_elements(&newpath, &newpathlen, ¤t, path, pathlen, leave_last) != -1) { + --current; + if (*current == '/') { + *current = '\0'; + } + pseudo_debug(3, "%s + %s => <%s>\n", + base ? base : "<nil>", + path ? path : "<nil>", + newpath ? newpath : "<nil>"); + if (lenp) { + *lenp = current - newpath; + } + return newpath; + } else { + free(newpath); + return 0; + } +} + +/* remove the pseudo stuff from the environment (leaving other preloads + * alone). + */ +void +pseudo_dropenv(void) { + char *ld_env = getenv("LD_PRELOAD"); + + /* why do this two ways? Because calling setenv(), then execing, + * in bash seems to result in the variables still being set in the + * new environment. + * we remove the entire LD_PRELOAD, because our use case would have + * fakechroot and fakepasswd in it too -- and we don't want that. + * we don't touch LD_LIBRARY_PATH because it might be being used for + * other system libraries... + */ + if (ld_env) { + unsetenv("LD_PRELOAD"); + } +} + +/* add pseudo stuff to the environment. + */ +void +pseudo_setupenv(char *opts) { + char *ld_env; + char *newenv; + size_t len; + char debugvalue[64]; + + newenv = "libpseudo.so"; + setenv("LD_PRELOAD", newenv, 1); + + ld_env = getenv("LD_LIBRARY_PATH"); + if (ld_env) { + char *prefix = pseudo_prefix_path(NULL); + if (!strstr(ld_env, prefix)) { + char *e1, *e2; + e1 = pseudo_prefix_path("lib"); + e2 = pseudo_prefix_path("lib64"); + len = strlen(ld_env) + strlen(e1) + strlen(e2) + 3; + newenv = malloc(len); + snprintf(newenv, len, "%s:%s:%s", ld_env, e1, e2); + free(e1); + free(e2); + setenv("LD_LIBRARY_PATH", newenv, 1); + free(newenv); + } + free(prefix); + } else { + char *e1, *e2; + e1 = pseudo_prefix_path("lib"); + e2 = pseudo_prefix_path("lib64"); + len = strlen(e1) + strlen(e2) + 2; + newenv = malloc(len); + snprintf(newenv, len, "%s:%s", e1, e2); + setenv("LD_LIBRARY_PATH", newenv, 1); + free(newenv); + } + + if (max_debug_level) { + sprintf(debugvalue, "%d", max_debug_level); + setenv("PSEUDO_DEBUG", debugvalue, 1); + } else { + unsetenv("PSEUDO_DEBUG"); + } + + if (opts) { + setenv("PSEUDO_OPTS", opts, 1); + } else { + unsetenv("PSEUDO_OPTS"); + } +} + +/* get the full path to a file under $PSEUDO_PREFIX. Other ways of + * setting the prefix all set it in the environment. + */ +char * +pseudo_prefix_path(char *s) { + static char *prefix = NULL; + static size_t prefix_len; + char *path; + + if (!prefix) { + prefix = getenv("PSEUDO_PREFIX"); + if (!prefix) { + pseudo_diag("You must set the PSEUDO_PREFIX environment variable to run pseudo.\n"); + exit(1); + } + prefix_len = strlen(prefix); + while ((prefix[prefix_len - 1] == '/') && (prefix_len > 0)) { + prefix[--prefix_len] = '\0'; + } + } + if (!s) { + return strdup(prefix); + } else { + size_t len = prefix_len + strlen(s) + 2; + path = malloc(len); + if (path) { + snprintf(path, len, "%s/%s", prefix, s); + } + return path; + } +} + +char * +pseudo_get_prefix(char *pathname) { + char *s; + s = getenv("PSEUDO_PREFIX"); + if (!s) { + char mypath[PATH_MAX]; + char *dir; + char *tmp_path; + + if (pathname[0] == '/') { + snprintf(mypath, PATH_MAX, "%s", pathname); + s = mypath + strlen(mypath); + } else { + if (!getcwd(mypath, PATH_MAX)) { + mypath[0] = '\0'; + } + s = mypath + strlen(mypath); + s += snprintf(s, PATH_MAX - (s - mypath), "/%s", + pathname); + } + tmp_path = pseudo_fix_path(NULL, mypath, 0, 0, 0); + /* point s to the end of the fixed path */ + if (strlen(tmp_path) >= PATH_MAX) { + pseudo_diag("Can't expand path '%s' -- expansion exceeds PATH_MAX.\n", + mypath); + free(tmp_path); + } else { + s = mypath + snprintf(mypath, PATH_MAX, "%s", tmp_path); + free(tmp_path); + } + + while (s > (mypath + 1) && *s != '/') + --s; + *s = '\0'; + dir = s - 1; + while (dir > mypath && *dir != '/') { + --dir; + } + /* strip bin directory, if any */ + if (!strncmp(dir, "/bin", 4)) { + *dir = '\0'; + } + /* degenerate case: /bin/pseudo should yield a pseudo_prefix "/" */ + if (*mypath == '\0') { + strcpy(mypath, "/"); + } + + pseudo_diag("Warning: PSEUDO_PREFIX unset, defaulting to %s.\n", + mypath); + setenv("PSEUDO_PREFIX", mypath, 1); + s = getenv("PSEUDO_PREFIX"); + } + return s; +} |