Added PATH lookup for program argument in sink.

Fortune for sink's current commit: Half blessing − 半吉
master
Avril 2 years ago
parent e20df37d9b
commit 6eb20eeb46
Signed by: flanchan
GPG Key ID: 284488987C31F630

@ -1,4 +1,5 @@
# `sink` - sinks all input to /dev/null # `sink` - sinks all input to /dev/null
# Usage: `sink [<program> [<args...>]]`
# #
# Targets: # Targets:
# `sink` (default): stripped `release` target. output: `sink` # `sink` (default): stripped `release` target. output: `sink`

@ -1,6 +1,16 @@
# `sink` - sinks all input into `/dev/null` # `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 [<program name> [<arguments...>]]`.
Example:
```shell
$ sink /usr/bin/cat sink.c # Identical to `>>/dev/null 2>&1 cat sink.c`
```
When invoked with `<program name>`: `execve()`s into the program (if it exists or is found in `$PATH`) with `<arguments....>` and a passed-through `envp` (see below in *Compiler flags* on changing this behaviour.)
## Building ## Building
@ -10,6 +20,8 @@ To build for debugging, run `make debug`.
### Compiler flags ### 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. `-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 ### Installation
The program must have been built before installation, and installation and uninstallation must be done as root. The program must have been built before installation, and installation and uninstallation must be done as root.

103
sink.c

@ -1,6 +1,10 @@
#include <stdio.h> #include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h> #include <unistd.h>
#include <fcntl.h> #include <fcntl.h>
#include <limits.h>
#include <errno.h>
#define r_stdin 0 #define r_stdin 0
#define r_stdout 1 #define r_stdout 1
@ -22,25 +26,112 @@ static inline int dupall(int from)
#ifdef REPLACE_STDERR #ifdef REPLACE_STDERR
if(UNLIKELY(dup2(from, r_stderr) < 0)) { if(UNLIKELY(dup2(from, r_stderr) < 0)) {
perror("failed to dup2() stderr to sink"); perror("failed to dup2() stderr to sink");
#else /*#else
if(UNLIKELY(close(r_stderr) != 0)) { if(UNLIKELY(close(r_stderr) != 0)) {
perror("failed to close stderr"); perror("failed to close stderr");
*/
#endif
return 3; return 3;
} }
#endif
return 0; 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); int null = open("/dev/null", O_RDWR);
if(UNLIKELY(null<0)) { if(UNLIKELY(null<0)) {
perror("failed to open /dev/null"); perror("failed to open /dev/null");
return 1; return 1;
} }
register int rc = dupall(null); register int rc = dupall(null);
if(LIKELY(rc)) close(null); if(LIKELY(!rc)) close(null);
return rc; 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;
} }

Loading…
Cancel
Save