#include #include #include #include #include #include #include #include "comp_features.h" //#include "view.h" #include "args.h" __attribute__((used)) // For loading debug binary symbol const enum compiled_features sink_compiled_features = FEATURE_FLAGS; #define r_stdin 0 #define r_stdout 1 #define r_stderr 2 static int r_parent_stderr = r_stderr; #define b_stderr r_parent_stderr #define LIKELY(e) __builtin_expect((e) != 0, 1) #define UNLIKELY(e) __builtin_expect((e) != 0, 0) #define eprintf(...) fprintf(stderr, __VA_ARGS__) #ifdef DEBUG #define dprintf(...) eprintf(__VA_ARGS__) #else //#pragma GCC diagnostic ignored "-Wunused #define dprintf(...) ((void)0) #endif #if FEATURE_HAS_FLAG(REPLACE_STDERR) #define _replace_stderr #else #define _replace_stderr __attribute__((unused)) #endif /// Private dup (F_DUPFD_CLOEXEC) _replace_stderr static inline int pdup(int fd) { return fcntl(fd, F_DUPFD_CLOEXEC, fd); } __attribute__((unused)) // No longer needed static inline int pfd(int fd) { return fcntl(fd, FD_CLOEXEC); } __attribute__((pure)) _replace_stderr static int fdreopen(FILE** stream, int with, const char mode[static 1]) { if(UNLIKELY( fclose(*stream) != 0 )) return (perror("failed to close stream to fdreopen"), 0); return (*stream = fdopen(with, mode)) != NULL; } /// Save r_stderr -> b_stderr. and replace the `stderr` stream with a new wrapper around the CLOEXEC'd `dup()` of `r_stderr`. _replace_stderr static int save_stderr() { /*if(UNLIKELY( (b_stderr = dup(r_stderr)) < 0 )) { perror("failed to dup() parent stderr"); return 0; }*/ fflush(stderr); if(UNLIKELY( (b_stderr = pdup(r_stderr)) < 0 )) { perror("failed to pdup(stderr)"); return 0; } if(UNLIKELY( !fdreopen(&stderr, b_stderr, "wb") )) { perror("failed to reopen parent stderr with pdup()'d stream"); return 0; } return 1; } static inline int dupall(int from) { if(UNLIKELY(dup2(from, r_stdin) < 0)) { perror("failed to dup2() stdin to sink"); return 1; } if(UNLIKELY(dup2(from, r_stdout) < 0)) { perror("failed to dup2() stdout to sink"); return 2; } #if FEATURE_HAS_FLAG(REPLACE_STDERR) // Save parent's stderr to a private one if(UNLIKELY(!save_stderr())) { return 3; } // Replace the child's stderr with null if(UNLIKELY(dup2(from, r_stderr) < 0)) { perror("failed to dup2() r_stderr to sink"); return 3; } #endif return 0; } #if ! FEATURE_HAS_FLAG(NO_SEARCH_PATH) inline static int err_not_found(int er) { return er == ENOENT; } static inline int path_exists(const char fname[restrict static 1]) { return access(fname, F_OK | X_OK) != -1; } static int path_lookup(size_t sz; const char name[restrict static 1], char fullpath[restrict sz], size_t sz, const char envpath[restrict static 1]) { char* paths = strdup(envpath); char* _tmpfree = paths; int found = 0; const char* item; while((item = strsep(&paths, ":")) != NULL) { if(UNLIKELY( ((size_t)snprintf(fullpath, sz, "%s/%s", item, name)) >= sz )) { dprintf("Warning: normalised path item '%s' truncated (from %s/%s). Full path was longer than %lu bytes\n", fullpath, item, name, sz); errno = ENAMETOOLONG; continue; } dprintf(">> PATH search in `%s'... ", item); if(path_exists(fullpath)) { dprintf("FOUND\n"); found = 1; break; } else dprintf("failed\n"); } free(_tmpfree); return found; } #endif // ! NO_SEARCH_PATH #ifdef DEBUG static inline size_t count_list(const void*const* p) { size_t n=0; while(*p++) n+=1; return n; } #if !FEATURE_HAS_FLAG(DEBUG_IGNORE_SPLASH) static void print_compiled_features() { #define _X "\t%s\n" #define X(name) fprintf(stderr, _X, FEATURE_STRING(FEATURE_FLAGS, name)) X(REPLACE_STDERR); X(NO_SEARCH_PATH); X(NO_ENV); X(DEBUG_IGNORE_SPLASH); fprintf(stderr, "Build mode: \n\t" #if defined(DEBUG) && defined(RELEASE) "release (+debug paths)" #elif defined(DEBUG) "debug" #elif degined(RELEASE) "release" #else "unknown" #endif "\n"); #undef X #undef _X } #endif static void print_debug_info(int argc, char* const* argv, char* const* envp) { #ifndef RELEASE fprintf(stderr, "[DEBUG BUILD]\n"); #else fprintf(stderr, "[RELEASE BUILD]\n"); #endif #if ! FEATURE_HAS_FLAG(DEBUG_IGNORE_SPLASH) fprintf(stderr, _PROJECT " v" _VERSION ": " _DESCRIPTION "\n"); fprintf(stderr, " :: written by " _AUTHOR " with <3 (License " _LICENSE ")\n"); fprintf(stderr, "Built with features:\n"); print_compiled_features(); fprintf(stderr, "---\n"); #endif fprintf(stderr, "> program: %s (path lookup: " #if FEATURE_HAS_FLAG(NO_SEARCH_PATH) "false" #else "true" #endif ")\n", argv ? (argv[1] ?: "(unbound)") : "" ); fprintf(stderr, "> argc (expected): %d\n", argc-1); fprintf(stderr, "> argv: %p (# %lu)\n", (const void*)(argv+1), (argv+1) ? count_list((const void*const*)(argv+1)) : 0); fprintf(stderr, "> environ: %p (# %lu)\n", (const void*)envp, LIKELY(envp) ? count_list((const void*const*)envp) : 0); fprintf(stderr, "---\n"); fflush(stderr); } #else #define print_debug_info(a,b,c) ((void)((void)(a), (void)(b), (void)(c))) #endif static bool parse_args(int *restrict argc, char** *restrict argv, char** *restrict envp) { return a_parse(argc, argv, envp) /* == NULL */ || true; //TODO: Print proper error messages on failures. } int main(int argc, char** argv, char** envp) { if(!parse_args(&argc, &argv, &envp)) return 1; #if FEATURE_HAS_FLAG(NO_ENV) (void)envp; #define GET_ENV ((char*[]){NULL}) #else #define GET_ENV envp #endif print_debug_info(argc, argv, GET_ENV); #define $execve(path) execve((path), argv+1, GET_ENV) int null = open("/dev/null", O_RDWR); if(UNLIKELY(null<0)) { perror("failed to open /dev/null"); return 1; } register int rc = dupall(null); if(LIKELY(!rc)) close(null); else return rc; if(argv[1]) { #if FEATURE_HAS_FLAG(NO_SEARCH_PATH) if(UNLIKELY($execve(argv[1]) < 0)) { perror("execve() failed"); return errno; } #else #define $try_execve(path) do { if(UNLIKELY($execve((path)) < 0)) { perror("execve() failed"); return errno; } else __builtin_unreachable(); } while(0) const char* path = NULL; int err = 0; dprintf("Attempting to execve() direct path %s...\n", argv[1]); if(LIKELY(($execve(argv[1]) < 0) && (err_not_found(err = errno)) && (path = getenv("PATH")))) { dprintf("Failed to exec raw pathname %s, lookup in PATH (%s)\n", argv[1], path); char fullpath[PATH_MAX+1]; errno = 0; if(path_lookup(argv[1], fullpath, PATH_MAX, path)) { dprintf("Attempting to execve() to found path %s...\n", fullpath); $try_execve(fullpath); } else { eprintf("Error: failed to find %s in PATH\n", argv[1]); return errno ?: -1; } } else { if(UNLIKELY(!err)) err = errno; dprintf("execve() failed in unexpected way with code %d (ENOENT? %s, PATH? %s)\n", err, err_not_found(err) ? "yes" : "no", path ?: ""); if(err_not_found(err)) { perror(path ? "execve() failed. Program not found in PATH" : "execve() failed. No PATH set to look through"); } else perror("execve() failed"); return err ?: -1; } #endif } return 0; }