/* Copyright (C) 2004 Manuel Novoa III * * Licensed under the LGPL v2.1, see the file COPYING.LIB in this tarball. */ /* Jan 1, 2004 * Initial version of a SUSv3 compliant exec*() functions. * Feb 17, 2004 * Sigh... Fall back to alloca() if munmap() is broken on uClinux. */ /* NOTE: Strictly speaking, there could be problems from accessing * __environ in multithreaded programs. The only way around this * that I see is to essentially lock __environ access (modifying * the setenv code), make a copy of the environment table (just the * pointers since the strings themselves are never freed), and then * unlock prior to the execve call. If that fails, then we'd need * to free the storage allocated for the copy. Better ideas anyone? */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <stdarg.h> #include <limits.h> #include <unistd.h> #include <sys/mman.h> /**********************************************************************/ #define EXEC_FUNC_COMMON 0 #define EXEC_FUNC_EXECVP 1 #if defined(__ARCH_USE_MMU__) /* We have an MMU, so use alloca() to grab space for buffers and arg lists. */ # define EXEC_ALLOC_SIZE(VAR) /* nothing to do */ # define EXEC_ALLOC(SIZE,VAR,FUNC) alloca((SIZE)) # define EXEC_FREE(PTR,VAR) ((void)0) #else /* We do not have an MMU, so using alloca() is not an option (as this will * easily overflow the stack in most setups). Less obviously, using malloc() * is not an option either since malloc()ed memory can leak in from a vfork()ed * child into the parent as no one is around after the child calls exec*() to * free() the memory. Therefore, we must use mmap() and unmap() directly, * caching the result as we go. This way we minimize the leak by reusing the * memory with every call to an exec*(). * * To prevent recursive use of the same cached memory, we have to give execvp() * its own cache. Here are the nested exec calls (a/-: alloc/no-alloc): * execve(-) -> calls straight to kernel * execl(a) -> execve(-) * execlp(a) -> execvp(a) !! recursive usage !! * execle(a) -> execve(-) * execv(-) -> execve(-) * execvp(a) -> execve(-) */ # define EXEC_ALLOC_SIZE(VAR) /* nothing to do */ # define EXEC_ALLOC(SIZE,VAR,FUNC) __exec_alloc((SIZE), FUNC) # define EXEC_FREE(PTR,VAR) ((void)0) extern void *__exec_alloc(size_t size, int func) attribute_hidden; # ifdef L___exec_alloc void attribute_hidden *__exec_alloc(size_t size, int func) { static void *common_cache, *execvp_cache; static size_t common_size, execvp_size; void **cache = (func ? &execvp_cache : &common_cache); size_t *cache_size = (func ? &execvp_size : &common_size); if (*cache_size >= size) return *cache; else if (*cache) munmap(*cache, *cache_size); *cache_size = size; return *cache = mmap(0, size, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0); /* We don't actually handle OOM in the exec funcs ... if (*cache != MAP_FAILED) return *cache; else return (*cache = NULL); */ } # endif #endif /**********************************************************************/ #ifdef L_execl int execl(const char *path, const char *arg, ...) { EXEC_ALLOC_SIZE(size) /* Do NOT add a semicolon! */ int n; char **argv; char **p; va_list args; n = 0; va_start(args, arg); do { ++n; } while (va_arg(args, char *)); va_end(args); p = argv = (char **) EXEC_ALLOC((n+1) * sizeof(char *), size, EXEC_FUNC_COMMON); p[0] = (char *)arg; va_start(args, arg); do { *++p = va_arg(args, char *); } while (--n); va_end(args); n = execve(path, (char *const *) argv, __environ); EXEC_FREE(argv, size); return n; } libc_hidden_def(execl) #endif /**********************************************************************/ #ifdef L_execv int execv(__const char *path, char *__const argv[]) { return execve(path, argv, __environ); } libc_hidden_def(execv) #endif /**********************************************************************/ #ifdef L_execle int execle(const char *path, const char *arg, ...) { EXEC_ALLOC_SIZE(size) /* Do NOT add a semicolon! */ int n; char **argv; char **p; char *const *envp; va_list args; n = 0; va_start(args, arg); do { ++n; } while (va_arg(args, char *)); envp = va_arg(args, char *const *); /* Varies from execl and execlp. */ va_end(args); p = argv = (char **) EXEC_ALLOC((n+1) * sizeof(char *), size, EXEC_FUNC_COMMON); p[0] = (char *)arg; va_start(args, arg); do { *++p = va_arg(args, char *); } while (--n); va_end(args); n = execve(path, (char *const *) argv, envp); EXEC_FREE(argv, size); return n; } libc_hidden_def(execle) #endif /**********************************************************************/ #ifdef L_execlp int execlp(const char *file, const char *arg, ...) { EXEC_ALLOC_SIZE(size) /* Do NOT add a semicolon! */ int n; char **argv; char **p; va_list args; n = 0; va_start(args, arg); do { ++n; } while (va_arg(args, char *)); va_end(args); p = argv = (char **) EXEC_ALLOC((n+1) * sizeof(char *), size, EXEC_FUNC_COMMON); p[0] = (char *)arg; va_start(args, arg); do { *++p = va_arg(args, char *); } while (--n); va_end(args); n = execvp(file, (char *const *) argv); EXEC_FREE(argv, size); return n; } libc_hidden_def(execlp) #endif /**********************************************************************/ #ifdef L_execvp /* Use a default path that matches glibc behavior, since SUSv3 says * this is implementation-defined. The default is current working dir, * /bin, and then /usr/bin. */ static const char default_path[] = ":/bin:/usr/bin"; int execvp(const char *path, char *const argv[]) { char *buf = NULL; char *p; char *e; char *s0; char *s; EXEC_ALLOC_SIZE(size = 0) /* Do NOT add a semicolon! */ size_t len; size_t plen; if (!path || !*path) { /* Comply with SUSv3. */ BAD: __set_errno(ENOENT); return -1; } if (strchr(path, '/')) { execve(path, argv, __environ); if (errno == ENOEXEC) { char **nargv; EXEC_ALLOC_SIZE(size2) /* Do NOT add a semicolon! */ size_t n; RUN_BIN_SH: /* Need the dimension - 1. We omit counting the trailing * NULL but we actually omit the first entry. */ for (n=0 ; argv[n] ; n++) {} nargv = (char **) EXEC_ALLOC((n+2) * sizeof(char *), size2, EXEC_FUNC_EXECVP); nargv[0] = argv[0]; nargv[1] = (char *)path; memcpy(nargv+2, argv+1, n*sizeof(char *)); execve("/bin/sh", nargv, __environ); EXEC_FREE(nargv, size2); } } else { if ((p = getenv("PATH")) != NULL) { if (!*p) { goto BAD; } } else { p = (char *) default_path; } plen = strlen(path); if (plen > (FILENAME_MAX - 1)) { ALL_TOO_LONG: __set_errno(ENAMETOOLONG); return -1; } len = (FILENAME_MAX - 1) - plen; buf = EXEC_ALLOC(FILENAME_MAX, size, EXEC_FUNC_EXECVP); { int seen_small = 0; s0 = buf + len; memcpy(s0, path, plen+1); do { s = s0; e = strchrnul(p, ':'); if (e > p) { plen = e - p; if (e[-1] != '/') { ++plen; } if (plen > len) { goto NEXT; } s -= plen; memcpy(s, p, plen); s[plen-1] = '/'; } execve(s, argv, __environ); seen_small = 1; if (errno == ENOEXEC) { path = s; goto RUN_BIN_SH; } NEXT: if (!*e) { if (!seen_small) { goto ALL_TOO_LONG; } break; } p = e + 1; } while (1); } } EXEC_FREE(buf, size); return -1; } libc_hidden_def(execvp) #endif /**********************************************************************/