X-Git-Url: https://ruderich.org/simon/gitweb/?p=coloredstderr%2Fcoloredstderr.git;a=blobdiff_plain;f=src%2Ftrackfds.h;h=c749f5e1d6f6cf142e5ce455888fb6d75c7eea58;hp=eddff4c5b5dc92bbf296c0e52b3979231479900c;hb=8a65b4486febf00e3fad5bafc3773a811e675a4c;hpb=488159627f4f579a80b71c5f7713126864d5623f diff --git a/src/trackfds.h b/src/trackfds.h index eddff4c..c749f5e 100644 --- a/src/trackfds.h +++ b/src/trackfds.h @@ -1,7 +1,7 @@ /* * Utility functions to track file descriptors. * - * Copyright (C) 2013 Simon Ruderich + * Copyright (C) 2013-2015 Simon Ruderich * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -50,13 +50,45 @@ static void tracked_fds_debug(void) { } #endif +/* Check if filename occurs in the comma-separated list ignore. */ +static int is_program_ignored(char const *filename, char const *ignore) { + size_t length; + size_t filename_length = strlen(filename); + +#ifdef DEBUG + debug(" is_program_ignored(\"%s\", \"%s\")\n", filename, ignore); +#endif + + for (; *ignore; ignore += length) { + while (*ignore == ',') { + ignore++; + } + + length = strcspn(ignore, ","); + if (length == 0) { + break; + } + + if (length != filename_length) { + continue; + } + if (!strncmp(filename, ignore, length)) { + return 1; + } + } + + return 0; +} + static int init_tracked_fds_list(size_t count) { + assert(count > 0); + /* Reduce reallocs. */ count += TRACKFDS_REALLOC_STEP; tracked_fds_list = malloc(count * sizeof(*tracked_fds_list)); if (!tracked_fds_list) { -#ifdef DEBUG +#ifdef WARNING warning("malloc(tracked_fds_list, %d) failed [%d]\n", count * sizeof(*tracked_fds_list), getpid()); #endif @@ -67,11 +99,14 @@ static int init_tracked_fds_list(size_t count) { return 1; } -/* Load tracked file descriptors from the environment. The environment is used +/* + * Load tracked file descriptors from the environment. The environment is used * to pass the information to child processes. * - * ENV_NAME_FDS has the following format: Each descriptor as string followed - * by a comma; there's a trailing comma. Example: "2,4,". */ + * ENV_NAME_FDS and ENV_NAME_PRIVATE_FDS have the following format: Each + * descriptor as string followed by a comma; there's a trailing comma. + * Example: "2,4,". + */ static void init_from_environment(void) { #ifdef DEBUG debug("init_from_environment()\t\t[%d]\n", getpid()); @@ -80,9 +115,27 @@ static void init_from_environment(void) { int saved_errno = errno; + assert(!initialized); + initialized = 1; tracked_fds_list_count = 0; + /* Don't color writes to stderr for this binary (and its children) if it's + * contained in the comma-separated list in ENV_NAME_IGNORED_BINARIES. */ + env = getenv(ENV_NAME_IGNORED_BINARIES); + if (env) { + char path[512]; + + /* TODO: Don't require /proc/. */ + ssize_t written = readlink("/proc/self/exe", path, sizeof(path) - 1); + if (written > 0) { + path[written] = 0; /* readlink() does not null-terminate! */ + if (is_program_ignored(path, env)) { + return; + } + } + } + /* If ENV_NAME_FORCE_WRITE is set and not empty, allow writes to a non-tty * device. Use with care! Mainly used for the test suite. */ env = getenv(ENV_NAME_FORCE_WRITE); @@ -90,12 +143,24 @@ static void init_from_environment(void) { force_write_to_non_tty = 1; } + /* Prefer user defined list of file descriptors, fall back to file + * descriptors passed through the environment from the parent process. */ env = getenv(ENV_NAME_FDS); + if (env) { + used_fds_set_by_user = 1; + } else { + env = getenv(ENV_NAME_PRIVATE_FDS); + } if (!env) { errno = saved_errno; return; } - /* Environment is read-only. */ +#ifdef DEBUG + debug(" getenv(\"%s\"): \"%s\"\n", ENV_NAME_FDS, env); + debug(" getenv(\"%s\"): \"%s\"\n", ENV_NAME_PRIVATE_FDS, env); +#endif + + /* Environment must be treated read-only. */ char env_copy[strlen(env) + 1]; strcpy(env_copy, env); @@ -119,18 +184,17 @@ static void init_from_environment(void) { } /* ',' at the beginning or double ',' - ignore. */ if (x == last) { - last = x + 1; - continue; - } - - if (i == count) { - break; + goto next; } + /* Replace ',' to null-terminate number for atoi(). */ *x = 0; int fd = atoi(last); - if (fd < TRACKFDS_STATIC_COUNT) { + if (fd < 0) { + goto next; + + } else if (fd < TRACKFDS_STATIC_COUNT) { tracked_fds[fd] = 1; } else { if (!tracked_fds_list) { @@ -161,10 +225,12 @@ next: } static char *update_environment_buffer_entry(char *x, int fd) { + assert(fd >= 0); + int length = snprintf(x, 10 + 1, "%d", fd); - if (length >= 10 + 1) { + if (length >= 10 + 1 || length <= 0 /* shouldn't happen */) { /* Integer too big to fit the buffer, skip it. */ -#ifdef DEBUG +#ifdef WARNING warning("update_environment_buffer_entry(): truncated fd: %d [%d]\n", fd, getpid()); #endif @@ -174,12 +240,14 @@ static char *update_environment_buffer_entry(char *x, int fd) { /* Write comma after number. */ x += length; *x++ = ','; - /* Make sure the string is always zero terminated. */ + /* Make sure the string is always null-terminated. */ *x = 0; return x; } static void update_environment_buffer(char *x) { + assert(initialized); + size_t i; for (i = 0; i < TRACKFDS_STATIC_COUNT; i++) { if (tracked_fds[i]) { @@ -191,6 +259,8 @@ static void update_environment_buffer(char *x) { } } inline static size_t update_environment_buffer_size(void) { + assert(initialized); + /* Use the maximum count (TRACKFDS_STATIC_COUNT) of used descriptors * because it's simple and small enough not to be a problem. * @@ -211,21 +281,36 @@ static void update_environment(void) { return; } + int saved_errno = errno; + char env[update_environment_buffer_size()]; env[0] = 0; update_environment_buffer(env); #if 0 - debug(" setenv('%s', '%s', 1)\n", ENV_NAME_FDS, env); + debug(" setenv(\"%s\", \"%s\", 1)\n", ENV_NAME_PRIVATE_FDS, env); #endif + setenv(ENV_NAME_PRIVATE_FDS, env, 1 /* overwrite */); + + /* Child processes must use ENV_NAME_PRIVATE_FDS to get the updated list + * of tracked file descriptors, not the static list provided by the user + * in ENV_NAME_FDS. + * + * But only remove it if the static list in ENV_NAME_FDS was loaded by + * init_from_environment() and merged into ENV_NAME_PRIVATE_FDS. */ + if (used_fds_set_by_user) { + unsetenv(ENV_NAME_FDS); + } - setenv(ENV_NAME_FDS, env, 1 /* overwrite */); + errno = saved_errno; } static void tracked_fds_add(int fd) { + assert(fd >= 0); + if (fd < TRACKFDS_STATIC_COUNT) { tracked_fds[fd] = 1; #if 0 @@ -245,7 +330,7 @@ static void tracked_fds_add(int fd) { /* We can do nothing, just ignore the error. We made sure not to * destroy our state, so the new descriptor is ignored without any * other consequences. */ -#ifdef DEBUG +#ifdef WARNING warning("realloc(tracked_fds_list, %zu) failed! [%d]\n", sizeof(*tracked_fds_list) * new_space, getpid()); #endif @@ -266,6 +351,8 @@ static void tracked_fds_add(int fd) { #endif } static int tracked_fds_remove(int fd) { + assert(fd >= 0); + if (fd < TRACKFDS_STATIC_COUNT) { int old_value = tracked_fds[fd]; tracked_fds[fd] = 0; @@ -300,7 +387,7 @@ static int tracked_fds_remove(int fd) { return 0; } -static int tracked_fds_find_slow(int fd) __noinline; +static int tracked_fds_find_slow(int fd) noinline; /* * tracked_fds_find() is called for each hook call and should be as fast as * possible. As most file descriptors are < TRACKFDS_STATIC_COUNT, force the @@ -309,15 +396,24 @@ static int tracked_fds_find_slow(int fd) __noinline; * Inlining tracked_fds_add()/tracked_fds_remove() isn't worth the effort as * they are not called often enough. */ -inline static int tracked_fds_find(int fd) __always_inline; -static int tracked_fds_find(int fd) { - if (fd < TRACKFDS_STATIC_COUNT) { +inline static int tracked_fds_find(int fd) always_inline; +inline static int tracked_fds_find(int fd) { + /* Invalid file descriptor. No assert() as we're called from the hooked + * macro. */ + if (unlikely(fd < 0)) { + return 0; + } + + if (likely(fd < TRACKFDS_STATIC_COUNT)) { return tracked_fds[fd]; } return tracked_fds_find_slow(fd); } static int tracked_fds_find_slow(int fd) { + assert(initialized); + assert(fd >= 0); + if (tracked_fds_list_count == 0) { return 0; }