X-Git-Url: https://ruderich.org/simon/gitweb/?a=blobdiff_plain;f=src%2Fcoloredstderr.c;h=44726899e4fb7590345d0890410cc6ee57abf3ca;hb=31a1fdf921bf767154470e94f0f28c299d56cd5a;hp=b2ef2975ef8d6e5ffae8625e925ae5caf46e3523;hpb=f369605227fb876e2edb0957a7c6ace98c49f7d3;p=coloredstderr%2Fcoloredstderr.git diff --git a/src/coloredstderr.c b/src/coloredstderr.c index b2ef297..4472689 100644 --- a/src/coloredstderr.c +++ b/src/coloredstderr.c @@ -18,14 +18,11 @@ * along with this program. If not, see . */ -#include "config.h" +#include /* Must be loaded before the following headers. */ #include "ldpreload.h" -/* FIXME: use correct declaration for fcntl() */ -#define fcntl fcntl_ignore - #include #include #include @@ -33,15 +30,24 @@ #include #include -#undef fcntl +#ifdef HAVE_ERROR_H +# include +#endif + /* Conflicting declaration in glibc. */ #undef fwrite_unlocked /* Used by various functions, including debug(). */ -static ssize_t (*real_write)(int, const void *, size_t); +static ssize_t (*real_write)(int, void const *, size_t); static int (*real_close)(int); -static size_t (*real_fwrite)(const void *, size_t, size_t, FILE *); +static size_t (*real_fwrite)(void const *, size_t, size_t, FILE *); + +/* Did we already (try to) parse the environment and setup the necessary + * variables? */ +static int initialized; +/* Force hooked writes even when not writing to a tty. Used for tests. */ +static int force_write_to_non_tty; #include "constants.h" @@ -56,62 +62,57 @@ static size_t (*real_fwrite)(const void *, size_t, size_t, FILE *); /* Should the "action" handler be invoked for this file descriptor? */ static int check_handle_fd(int fd) { - /* Never touch anything not going to a terminal. */ - if (!isatty(fd)) { - return 0; - } - /* Load state from environment. Only necessary once per process. */ - if (!tracked_fds) { + if (!initialized) { init_from_environment(); } - if (tracked_fds_count == 0) { + /* tracked_fds_find() is most likely faster than calling isatty(), + * therefore check if we are tracking this file descriptor first. */ + if (!tracked_fds_find(fd)) { + return 0; + } + + /* Never touch anything not going to a terminal - unless we are explicitly + * asked to do so. */ + if (!force_write_to_non_tty && !isatty(fd)) { return 0; } - return tracked_fds_find(fd); + + return 1; } static void dup_fd(int oldfd, int newfd) { #ifdef DEBUG - debug("%d -> %d\t\t\t[%d]\n", oldfd, newfd, getpid()); + debug("%3d -> %3d\t\t\t[%d]\n", oldfd, newfd, getpid()); #endif - if (!tracked_fds) { + if (!initialized) { init_from_environment(); } - if (tracked_fds_count == 0) { - return; - } /* We are already tracking this file descriptor, add newfd to the list as * it will reference the same descriptor. */ if (tracked_fds_find(oldfd)) { if (!tracked_fds_find(newfd)) { tracked_fds_add(newfd); - update_environment(); } /* We are not tracking this file descriptor, remove newfd from the list * (if present). */ } else { - if (tracked_fds_remove(newfd)) { - update_environment(); - } + tracked_fds_remove(newfd); } } static void close_fd(int fd) { #ifdef DEBUG - debug("%d -> .\t\t\t[%d]\n", fd, getpid()); + debug("%3d -> .\t\t\t[%d]\n", fd, getpid()); #endif - if (!tracked_fds) { + if (!initialized) { init_from_environment(); } - if (tracked_fds_count == 0) { - return; - } tracked_fds_remove(fd); } @@ -178,13 +179,13 @@ static void handle_file_post(FILE *stream, int action) { /* Hook all important output functions to manipulate their output. */ HOOK_FD3(ssize_t, write, fd, - int, fd, const void *, buf, size_t, count) + int, fd, void const *, buf, size_t, count) HOOK_FILE4(size_t, fwrite, stream, - const void *, ptr, size_t, size, size_t, nmemb, FILE *, stream) + void const *, ptr, size_t, size, size_t, nmemb, FILE *, stream) /* puts(3) */ HOOK_FILE2(int, fputs, stream, - const char *, s, FILE *, stream) + char const *, s, FILE *, stream) HOOK_FILE2(int, fputc, stream, int, c, FILE *, stream) HOOK_FILE2(int, putc, stream, @@ -192,32 +193,32 @@ HOOK_FILE2(int, putc, stream, HOOK_FILE1(int, putchar, stdout, int, c) HOOK_FILE1(int, puts, stdout, - const char *, s) + char const *, s) /* printf(3), excluding all s*() and vs*() functions (no output) */ HOOK_VAR_FILE1(int, printf, stdout, vprintf, - const char *, format) + char const *, format) HOOK_VAR_FILE2(int, fprintf, stream, vfprintf, - FILE *, stream, const char *, format) + FILE *, stream, char const *, format) HOOK_FILE2(int, vprintf, stdout, - const char *, format, va_list, ap) + char const *, format, va_list, ap) HOOK_FILE3(int, vfprintf, stream, - FILE *, stream, const char *, format, va_list, ap) -/* Hardening functions (-D_FORTIFY_SOURCE=2). */ + FILE *, stream, char const *, format, va_list, ap) +/* Hardening functions (-D_FORTIFY_SOURCE=2), only functions from above */ HOOK_VAR_FILE2(int, __printf_chk, stdout, __vprintf_chk, - int, flag, const char *, format) + int, flag, char const *, format) HOOK_VAR_FILE3(int, __fprintf_chk, fp, __vfprintf_chk, - FILE *, fp, int, flag, const char *, format) + FILE *, fp, int, flag, char const *, format) HOOK_FILE3(int, __vprintf_chk, stdout, - int, flag, const char *, format, va_list, ap) + int, flag, char const *, format, va_list, ap) HOOK_FILE4(int, __vfprintf_chk, stream, - FILE *, stream, int, flag, const char *, format, va_list, ap) + FILE *, stream, int, flag, char const *, format, va_list, ap) /* unlocked_stdio(3), only functions from above are hooked */ HOOK_FILE4(size_t, fwrite_unlocked, stream, - const void *, ptr, size_t, size, size_t, nmemb, FILE *, stream) + void const *, ptr, size_t, size, size_t, nmemb, FILE *, stream) HOOK_FILE2(int, fputs_unlocked, stream, - const char *, s, FILE *, stream) + char const *, s, FILE *, stream) HOOK_FILE2(int, fputc_unlocked, stream, int, c, FILE *, stream) HOOK_FILE2(int, putc_unlocked, stream, @@ -225,7 +226,79 @@ HOOK_FILE2(int, putc_unlocked, stream, HOOK_FILE1(int, putchar_unlocked, stdout, int, c) HOOK_FILE1(int, puts_unlocked, stdout, - const char *, s) + char const *, s) + +/* perror(3) */ +HOOK_VOID1(void, perror, STDERR_FILENO, + char const *, s) + +/* error(3) */ +#ifdef HAVE_ERROR_H +static void error_vararg(int status, int errnum, + char const *filename, unsigned int linenum, + char const *format, va_list ap) { + static char const *last_filename; + static unsigned int last_linenum; + + /* Skip this error message if requested and if there was already an error + * in the same file/line. */ + if (error_one_per_line + && filename != NULL && linenum != 0 + && filename == last_filename && linenum == last_linenum) { + return; + } + last_filename = filename; + last_linenum = linenum; + + error_message_count++; + + fflush(stdout); + + if (error_print_progname) { + error_print_progname(); + } else { + fprintf(stderr, "%s:", program_invocation_name); + } + if (filename != NULL && linenum != 0) { + fprintf(stderr, "%s:%u:", filename, linenum); + if (error_print_progname) { + fprintf(stderr, " "); + } + } + if (!error_print_progname) { + fprintf(stderr, " "); + } + + + vfprintf(stderr, format, ap); + + if (errnum != 0) { + fprintf(stderr, ": %s", strerror(errnum)); + } + + fprintf(stderr, "\n"); + + if (status != 0) { + exit(status); + } +} +void error_at_line(int status, int errnum, + char const *filename, unsigned int linenum, + char const *format, ...) { + va_list ap; + + va_start(ap, format); + error_vararg(status, errnum, filename, linenum, format, ap); + va_end(ap); +} +void error(int status, int errnum, char const *format, ...) { + va_list ap; + + va_start(ap, format); + error_vararg(status, errnum, NULL, 0, format, ap); + va_end(ap); +} +#endif /* Hook functions which duplicate file descriptors to track them. */ @@ -272,13 +345,30 @@ int dup3(int oldfd, int newfd, int flags) { return newfd; } -static int (*real_fcntl)(int, int, int); -int fcntl(int fd, int cmd, int arg) { +static int (*real_fcntl)(int, int, ...); +int fcntl(int fd, int cmd, ...) { int result; + va_list ap; DLSYM_FUNCTION(real_fcntl, "fcntl"); - result = real_fcntl(fd, cmd, arg); + /* fcntl() takes different types of arguments depending on the cmd type + * (int, void and pointers are used at the moment). Handling these + * arguments for different systems and with possible changes in the future + * is error prone. + * + * Therefore always retrieve a void-pointer from our arguments (even if it + * wasn't there) and pass it to real_fcntl(). This shouldn't cause any + * problems because a void-pointer is most-likely bigger than an int + * (something which is not true in reverse) and shouldn't cause + * truncation. For register based calling conventions an invalid register + * content is passed, but ignored by real_fcntl(). Not perfect, but should + * work fine. + */ + va_start(ap, cmd); + result = real_fcntl(fd, cmd, va_arg(ap, void *)); + va_end(ap); + /* We only care about duping fds. */ if (cmd == F_DUPFD && result != -1) { int saved_errno = errno; @@ -303,3 +393,145 @@ int fclose(FILE *fp) { close_fd(fileno(fp)); return real_fclose(fp); } + + +/* Hook functions which are necessary for correct tracking. */ + +#if defined(HAVE_VFORK) && defined(HAVE_FORK) +pid_t vfork(void) { + /* vfork() is similar to fork() but the address space is shared between + * father and child. It's designed for fork()/exec() usage because it's + * faster than fork(). However according to the POSIX standard the "child" + * isn't allowed to perform any memory-modifications before the exec() + * (except the pid_t result variable of vfork()). + * + * As some programs don't adhere to the standard (e.g. the "child" closes + * or dups a descriptor before the exec()) and this breaks our tracking of + * file descriptors (e.g. it gets closed in the parent as well), we just + * fork() instead. This is in compliance with the POSIX standard and as + * most systems use copy-on-write anyway not a performance issue. */ + return fork(); +} +#endif + + +/* Hook execve() and the other exec*() functions. Some shells use exec*() with + * a custom environment which doesn't necessarily contain our updates to + * ENV_NAME_FDS. It's also faster to update the environment only when + * necessary, right before the exec() to pass it to the new process. */ + +static int (*real_execve)(char const *filename, char * const argv[], char * const env[]); +int execve(char const *filename, char * const argv[], char * const env[]) { + DLSYM_FUNCTION(real_execve, "execve"); + + int found = 0; + size_t index = 0; + + /* Count arguments and search for existing ENV_NAME_FDS environment + * variable. */ + size_t count = 0; + char * const *x = env; + while (*x) { + if (!strncmp(*x, ENV_NAME_FDS "=", strlen(ENV_NAME_FDS) + 1)) { + found = 1; + index = count; + } + + x++; + count++; + } + /* Terminating NULL. */ + count++; + + char *env_copy[count + 1 /* space for our new entry if necessary */]; + memcpy(env_copy, env, count * sizeof(char *)); + + /* Make sure the information from the environment is loaded. We can't just + * do nothing (like update_environment()) because the caller might pass a + * different environment which doesn't include any of our settings. */ + if (!initialized) { + init_from_environment(); + } + + char fds_env[strlen(ENV_NAME_FDS) + 1 + update_environment_buffer_size()]; + strcpy(fds_env, ENV_NAME_FDS "="); + update_environment_buffer(fds_env + strlen(ENV_NAME_FDS) + 1); + + if (found) { + env_copy[index] = fds_env; + } else { + /* If the process removed ENV_NAME_FDS from the environment, re-add + * it. */ + env_copy[count-1] = fds_env; + env_copy[count] = NULL; + } + + return real_execve(filename, argv, env_copy); +} + +#define EXECL_COPY_VARARGS_START(args) \ + va_list ap; \ + char *x; \ + \ + /* Count arguments. */ \ + size_t count = 1; /* arg */ \ + va_start(ap, arg); \ + while (va_arg(ap, const char *)) { \ + count++; \ + } \ + va_end(ap); \ + \ + /* Copy varargs. */ \ + char *args[count + 1 /* terminating NULL */]; \ + args[0] = (char *)arg; \ + \ + size_t i = 1; \ + va_start(ap, arg); \ + while ((x = va_arg(ap, char *))) { \ + args[i++] = x; \ + } \ + args[i] = NULL; +#define EXECL_COPY_VARARGS_END(args) \ + va_end(ap); +#define EXECL_COPY_VARARGS(args) \ + EXECL_COPY_VARARGS_START(args); \ + EXECL_COPY_VARARGS_END(args); + +int execl(char const *path, char const *arg, ...) { + EXECL_COPY_VARARGS(args); + + update_environment(); + return execv(path, args); +} + +int execlp(char const *file, char const *arg, ...) { + EXECL_COPY_VARARGS(args); + + update_environment(); + return execvp(file, args); +} + +int execle(char const *path, char const *arg, ... /*, char * const envp[] */) { + EXECL_COPY_VARARGS_START(args); + /* Get envp[] located after arguments. */ + char * const *envp = va_arg(ap, char * const *); + EXECL_COPY_VARARGS_END(args); + + return execve(path, args, envp); +} + +static int (*real_execv)(char const *path, char * const argv[]); +int execv(char const *path, char * const argv[]) { + DLSYM_FUNCTION(real_execv, "execv"); + + update_environment(); + return real_execv(path, argv); +} + +static int (*real_execvp)(char const *path, char * const argv[]); +int execvp(char const *path, char * const argv[]) { + DLSYM_FUNCTION(real_execvp, "execvp"); + + update_environment(); + return real_execvp(path, argv); +}