|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <fcntl.h>
|
|
|
|
#include <limits.h>
|
|
|
|
#include <errno.h>
|
|
|
|
|
|
|
|
#include "comp_features.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)") : "<invalid>" );
|
|
|
|
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) == A_P_OK || 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 ?: "<null>");
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|