diff --git a/Makefile b/Makefile index cdf14ff..6805a01 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,5 @@ # `sink` - sinks all input to /dev/null +# Usage: `sink [ []]` # # Targets: # `sink` (default): stripped `release` target. output: `sink` diff --git a/README.md b/README.md index 7c16d1b..b30dc92 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,16 @@ # `sink` - sinks all input into `/dev/null` -Re-routes `stdin`, `stdout` (and optionally, `stderr`) to `/dev/null`. +Re-routes `stdin`, `stdout` (and optionally, `stderr`) of a program to `/dev/null`. + +## Usage +Command usage is in the form of: `./sink [ []]`. + +Example: +```shell +$ sink /usr/bin/cat sink.c # Identical to `>>/dev/null 2>&1 cat sink.c` +``` + +When invoked with ``: `execve()`s into the program (if it exists or is found in `$PATH`) with `` and a passed-through `envp` (see below in *Compiler flags* on changing this behaviour.) ## Building @@ -10,6 +20,8 @@ To build for debugging, run `make debug`. ### Compiler flags `-DREPLACE_STDERR` - Add to `CFLAGS` when building a target to re-route the program's `stderr` stream to the sink as well. By default, it is closed after the other streams have been successfully rewritten. +`-DNO_SEARCH_PATH` - Add to `CFLAGS` when building to prevent the program looking up its argument in the `PATH` environment variable. +`-DNO_ENV` - Add to `CFLAGS` when building to prevent `envp` passthrough to the `execve()`'d program. ### Installation The program must have been built before installation, and installation and uninstallation must be done as root. diff --git a/sink.c b/sink.c index 1a4905f..da3ac70 100644 --- a/sink.c +++ b/sink.c @@ -1,6 +1,10 @@ #include +#include +#include #include #include +#include +#include #define r_stdin 0 #define r_stdout 1 @@ -22,25 +26,112 @@ static inline int dupall(int from) #ifdef REPLACE_STDERR if(UNLIKELY(dup2(from, r_stderr) < 0)) { perror("failed to dup2() stderr to sink"); -#else +/*#else if(UNLIKELY(close(r_stderr) != 0)) { perror("failed to close stderr"); - -#endif + */ return 3; } +#endif return 0; } -int main(void) +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 )) { +#if defined(DEBUG) && !defined(REPLACE_STDERR) + fprintf(stderr, "Warning: normalised path item '%s' truncated (from %s/%s). Full path was longer than %lu bytes\n", fullpath, item, name, sz); +#endif + errno = ENAMETOOLONG; + continue; + } + if(path_exists(fullpath)) { + found = 1; + break; + } + } + + free(_tmpfree); + return found; +} + +int main(int argc, char** argv, char** envp) { + (void)argc; +#ifdef NO_ENV +#define GET_ENV ((char*[]){}) + (void)envp; +#else +#define GET_ENV envp +#endif + +#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); - return rc; + if(LIKELY(!rc)) close(null); + else return rc; + +#ifdef REPLACE_STDERR +#define perror(v) ((void)(v)) +#define eprintf(...) ((void)(__VA_ARGS__)) +#else +#define eprintf(...) fprintf(stderr, __VA_ARGS__) +#endif + + + if(argv[1]) { +#if 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; + if(LIKELY(($execve(argv[1]) < 0) + && (err_not_found(err = errno)) + && (path = getenv("PATH")))) { + // Failed to exec raw pathname (not found), lookup in $PATH + char fullpath[PATH_MAX+1]; + errno = 0; + if(path_lookup(argv[1], fullpath, PATH_MAX, path)) $try_execve(fullpath); + else { + eprintf("Error: failed to find %s in PATH\n", argv[1]); + return errno ?: -1; + } + } else { + if(UNLIKELY(!err)) err = errno; + + 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; }