From: Simon Ruderich Date: Fri, 31 May 2013 16:19:12 +0000 (+0200) Subject: Initial commit. X-Git-Tag: 0.1~137 X-Git-Url: https://ruderich.org/simon/gitweb/?a=commitdiff_plain;h=abc3d7889f3774717baf5795ffab2efb396d2570;p=coloredstderr%2Fcoloredstderr.git Initial commit. --- abc3d7889f3774717baf5795ffab2efb396d2570 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e52dbb8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,20 @@ +/Makefile +/Makefile.in +/aclocal.m4 +/autom4te.cache/ +/build-aux/ +/config.h +/config.h.in +/config.h.in~ +/config.log +/config.status +/configure +/libtool +/m4/ +/src/.deps/ +/src/.libs/ +/src/Makefile +/src/Makefile.in +/src/coloredstderr.lo +/src/libcoloredstderr.la +/stamp-h1 diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..26226b0 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,3 @@ +SUBDIRS = src + +ACLOCAL_AMFLAGS = -I m4 diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..6cd9d8f --- /dev/null +++ b/configure.ac @@ -0,0 +1,51 @@ +dnl Copyright (C) 2013 Simon Ruderich +dnl +dnl This program is free software: you can redistribute it and/or modify +dnl it under the terms of the GNU General Public License as published by +dnl the Free Software Foundation, either version 3 of the License, or +dnl (at your option) any later version. +dnl +dnl This program is distributed in the hope that it will be useful, +dnl but WITHOUT ANY WARRANTY; without even the implied warranty of +dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +dnl GNU General Public License for more details. +dnl +dnl You should have received a copy of the GNU General Public License +dnl along with this program. If not, see . + + +AC_PREREQ([2.69]) +AC_INIT([coloredstderr],[0.1],[simon@ruderich.org]) + +AC_CONFIG_AUX_DIR([build-aux]) +AC_CONFIG_MACRO_DIR([m4]) +AC_CONFIG_HEADERS([config.h]) +AC_CONFIG_SRCDIR([src/coloredstderr.c]) + +AM_PROG_AR +AM_INIT_AUTOMAKE([foreign -Wall -Werror]) +LT_INIT([disable-static]) + +AC_PROG_CC + +AC_C_INLINE + +AC_CHECK_HEADERS([fcntl.h]) + +AC_TYPE_SIZE_T +AC_TYPE_SSIZE_T + +AC_FUNC_MALLOC +AC_FUNC_REALLOC +AC_CHECK_FUNCS([dup2 memmove setenv strdup]) + +AC_SEARCH_LIBS([dlsym], [dl], [], [AC_MSG_ERROR([dlsym() is required])]) + +AC_ARG_ENABLE([debug], + [AS_HELP_STRING([--enable-debug],[enable debug output])], + [if test "x$enableval" = xyes; then + AC_DEFINE([DEBUG], 1, [Define to enable debug output.]) + fi]) + +AC_CONFIG_FILES([Makefile src/Makefile]) +AC_OUTPUT diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..7dff92c --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,7 @@ +lib_LTLIBRARIES = libcoloredstderr.la +libcoloredstderr_la_SOURCES = coloredstderr.c \ + constants.h \ + debug.h \ + ldpreload.h \ + macros.h \ + trackfds.h diff --git a/src/coloredstderr.c b/src/coloredstderr.c new file mode 100644 index 0000000..32b1dd8 --- /dev/null +++ b/src/coloredstderr.c @@ -0,0 +1,305 @@ +/* + * Hook output functions (like printf(3)) with LD_PRELOAD to color stderr (or + * other file descriptors). + * + * Copyright (C) 2013 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +/* Must be loaded before the following headers. */ +#include "ldpreload.h" + +/* FIXME: use correct declaration for fcntl() */ +#define fcntl fcntl_ignore + +#include +#include +#include +#include +#include +#include + +#undef fcntl +/* Conflicting declaration in glibc. */ +#undef fwrite_unlocked + + +/* Used by various functions, including debug(). */ +static ssize_t (*real_write)(int, const void *, size_t); +static int (*real_close)(int); +static size_t (*real_fwrite)(const void *, size_t, size_t, FILE *); + + +#include "constants.h" +#ifdef DEBUG +# include "debug.h" +#endif + +#include "macros.h" +#include "trackfds.h" + + + +/* 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) { + init_from_environment(); + } + + if (tracked_fds_count == 0) { + return 0; + } + return tracked_fds_find(fd); +} + +static void dup_fd(int oldfd, int newfd) { +#ifdef DEBUG + debug("%d -> %d\t\t\t[%d]\n", oldfd, newfd, getpid()); +#endif + + if (!tracked_fds) { + 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(); + } + } +} + +static void close_fd(int fd) { +#ifdef DEBUG + debug("%d -> .\t\t\t[%d]\n", fd, getpid()); +#endif + + if (!tracked_fds) { + init_from_environment(); + } + + if (tracked_fds_count == 0) { + return; + } + tracked_fds_remove(fd); +} + + +/* "Action" handlers called when a file descriptor is matched. */ + +static char *pre_string; +static size_t pre_string_size; +static char *post_string; +static size_t post_string_size; + +/* Load alternative pre/post strings from the environment if available, fall + * back to default values. */ +inline static void init_pre_post_string() { + pre_string = getenv(ENV_NAME_PRE_STRING); + if (!pre_string) { + pre_string = DEFAULT_PRE_STRING; + } + pre_string_size = strlen(pre_string); + + post_string = getenv(ENV_NAME_POST_STRING); + if (!post_string) { + post_string = DEFAULT_POST_STRING; + } + post_string_size = strlen(post_string); +} + +static void handle_fd_pre(int fd, int action) { + (void)action; + + if (!pre_string || !post_string) { + init_pre_post_string(); + } + + DLSYM_FUNCTION(real_write, "write"); + real_write(fd, pre_string, pre_string_size); +} +static void handle_fd_post(int fd, int action) { + (void)action; + + /* write() already loaded above in handle_fd_pre(). */ + real_write(fd, post_string, post_string_size); +} + +static void handle_file_pre(FILE *stream, int action) { + (void)action; + + if (!pre_string || !post_string) { + init_pre_post_string(); + } + + DLSYM_FUNCTION(real_fwrite, "fwrite"); + real_fwrite(pre_string, pre_string_size, 1, stream); +} +static void handle_file_post(FILE *stream, int action) { + (void)action; + + /* fwrite() already loaded above in handle_file_pre(). */ + real_fwrite(post_string, post_string_size, 1, stream); +} + + + +/* Hook all important output functions to manipulate their output. */ + +HOOK_FD3(ssize_t, write, fd, + int, fd, const void *, buf, size_t, count) +HOOK_FILE4(size_t, fwrite, stream, + const void *, ptr, size_t, size, size_t, nmemb, FILE *, stream) + +/* puts(3) */ +HOOK_FILE2(int, fputs, stream, + const char *, s, FILE *, stream) +HOOK_FILE2(int, fputc, stream, + int, c, FILE *, stream) +HOOK_FILE2(int, putc, stream, + int, c, FILE *, stream) +HOOK_FILE1(int, putchar, stdout, + int, c) +HOOK_FILE1(int, puts, stdout, + const char *, s) + +/* printf(3), excluding all s*() and vs*() functions (no output) */ +HOOK_VAR_FILE1(int, printf, stdout, vprintf, + const char *, format) +HOOK_VAR_FILE2(int, fprintf, stream, vfprintf, + FILE *, stream, const char *, format) +HOOK_FILE2(int, vprintf, stdout, + const char *, format, va_list, ap) +HOOK_FILE3(int, vfprintf, stream, + FILE *, stream, const char *, format, va_list, ap) +/* Hardening functions (-D_FORTIFY_SOURCE=2). */ +HOOK_VAR_FILE2(int, __printf_chk, stdout, __vprintf_chk, + int, flag, const char *, format) +HOOK_VAR_FILE3(int, __fprintf_chk, fp, __vfprintf_chk, + FILE *, fp, int, flag, const char *, format) +HOOK_FILE3(int, __vprintf_chk, stdout, + int, flag, const char *, format, va_list, ap) +HOOK_FILE4(int, __vfprintf_chk, stream, + FILE *, stream, int, flag, const char *, 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) +HOOK_FILE2(int, fputs_unlocked, stream, + const char *, s, FILE *, stream) +HOOK_FILE2(int, fputc_unlocked, stream, + int, c, FILE *, stream) +HOOK_FILE2(int, putc_unlocked, stream, + int, c, FILE *, stream) +HOOK_FILE1(int, putchar_unlocked, stdout, + int, c) +HOOK_FILE1(int, puts_unlocked, stdout, + const char *, s) + + +/* Hook functions which duplicate file descriptors to track them. */ + +static int (*real_dup)(int); +static int (*real_dup2)(int, int); +static int (*real_dup3)(int, int, int); +int dup(int oldfd) { + int newfd; + + DLSYM_FUNCTION(real_dup, "dup"); + + newfd = real_dup(oldfd); + if (newfd != -1) { + int saved_errno = errno; + dup_fd(oldfd, newfd); + errno = saved_errno; + } + + return newfd; +} +int dup2(int oldfd, int newfd) { + DLSYM_FUNCTION(real_dup2, "dup2"); + + newfd = real_dup2(oldfd, newfd); + if (newfd != -1) { + int saved_errno = errno; + dup_fd(oldfd, newfd); + errno = saved_errno; + } + + return newfd; +} +int dup3(int oldfd, int newfd, int flags) { + DLSYM_FUNCTION(real_dup3, "dup3"); + + newfd = real_dup3(oldfd, newfd, flags); + if (newfd != -1) { + int saved_errno = errno; + dup_fd(oldfd, newfd); + errno = saved_errno; + } + + return newfd; +} + +static int (*real_fcntl)(int, int, int); +int fcntl(int fd, int cmd, int arg) { + int result; + + DLSYM_FUNCTION(real_fcntl, "fcntl"); + + result = real_fcntl(fd, cmd, arg); + /* We only care about duping fds. */ + if (cmd == F_DUPFD) { + int saved_errno = errno; + dup_fd(fd, result); + errno = saved_errno; + } + + return result; +} + +static int (*real_close)(int); +int close(int fd) { + DLSYM_FUNCTION(real_close, "close"); + + close_fd(fd); + return real_close(fd); +} +static int (*real_fclose)(FILE *); +int fclose(FILE *fp) { + DLSYM_FUNCTION(real_fclose, "fclose"); + + close_fd(fileno(fp)); + return real_fclose(fp); +} diff --git a/src/constants.h b/src/constants.h new file mode 100644 index 0000000..103ca63 --- /dev/null +++ b/src/constants.h @@ -0,0 +1,39 @@ +/* + * Global constants and defines. + * + * Copyright (C) 2013 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef CONSTANTS_H +#define CONSTANTS_H 1 + +/* Names of used environment variables. */ +#define ENV_NAME_FDS "COLORED_STDERR_FDS" +#define ENV_NAME_PRE_STRING "COLORED_STDERR_PRE" +#define ENV_NAME_POST_STRING "COLORED_STDERR_POST" + +/* Strings written before/after each matched function. */ +#define DEFAULT_PRE_STRING "\e[91m" +#define DEFAULT_POST_STRING "\e[0m" + +/* Number of new elements to allocate per realloc(). */ +#define TRACKFDS_REALLOC_STEP 10 + +#ifdef DEBUG +# define DEBUG_FILE "colored_stderr_debug_log.txt" +#endif + +#endif diff --git a/src/debug.h b/src/debug.h new file mode 100644 index 0000000..6456898 --- /dev/null +++ b/src/debug.h @@ -0,0 +1,50 @@ +/* + * Debug functions. + * + * Copyright (C) 2013 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef DEBUG_H +#define DEBUG_H 1 + +static void debug(const char *format, ...) { + char buffer[1024]; + + /* If the file doesn't exist, do nothing. Prevents writing log files in + * unexpected places. The user must create the file manually. */ + int fd = open(DEBUG_FILE, O_WRONLY | O_APPEND); + if (fd == -1) + return; + + va_list ap; + va_start(ap, format); + int written = vsnprintf(buffer, sizeof(buffer), format, ap); + va_end(ap); + + /* Make sure these functions are loaded. */ + DLSYM_FUNCTION(real_write, "write"); + DLSYM_FUNCTION(real_close, "close"); + + static int first_call = 0; + if (!first_call++) { + char nl = '\n'; + real_write(fd, &nl, 1); + } + real_write(fd, buffer, (size_t)written); + real_close(fd); +} + +#endif diff --git a/src/ldpreload.h b/src/ldpreload.h new file mode 100644 index 0000000..33a2e75 --- /dev/null +++ b/src/ldpreload.h @@ -0,0 +1,49 @@ +/* + * Helper header for LD_PRELOAD related headers macros. Must be loaded _first_ + * (for RTLD_NEXT)! + * + * Copyright (C) 2012-2013 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef LDPRELOAD_H +#define LDPRELOAD_H 1 + +/* Necessary for RTLD_NEXT. */ +#define _GNU_SOURCE + +/* abort() */ +#include +/* dlsym() */ +#include + + +/* Load the function name using dlsym() if necessary and store it in pointer. + * Terminate program on failure. */ +#define DLSYM_FUNCTION(pointer, name) \ + if (NULL == (pointer)) { \ + char *error; \ + dlerror(); /* Clear possibly existing error. */ \ + \ + *(void **) (&(pointer)) = dlsym(RTLD_NEXT, (name)); \ + \ + if (NULL != (error = dlerror())) { \ + /* Not much we can do. Most likely the other output functions \ + * failed to load too. */ \ + abort(); \ + } \ + } + +#endif diff --git a/src/macros.h b/src/macros.h new file mode 100644 index 0000000..ffbc6b6 --- /dev/null +++ b/src/macros.h @@ -0,0 +1,130 @@ +/* + * Macros to hook functions. + * + * Copyright (C) 2013 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef MACROS_H +#define MACROS_H 1 + +/* Hook the function by creating a function with the same name. With + * LD_PRELOAD our function will be preferred. The original function is stored + * in a static variable (real_*). */ + +#define _HOOK_PRE(type, name) \ + int handle; \ + type result; \ + DLSYM_FUNCTION(real_ ## name, #name); +#define _HOOK_PRE_FD(type, name, fd) \ + _HOOK_PRE(type, name) \ + handle = check_handle_fd(fd); \ + if (handle) { \ + handle_fd_pre(fd, handle); \ + } +#define _HOOK_PRE_FILE(type, name, file) \ + _HOOK_PRE(type, name) \ + handle = check_handle_fd(fileno(file)); \ + if (handle) { \ + handle_file_pre(file, handle); \ + } +/* Save and restore the errno to make sure we return the errno of the original + * function call. */ +#define _HOOK_POST_FD(fd) \ + if (handle) { \ + int saved_errno = errno; \ + handle_fd_post(fd, handle); \ + errno = saved_errno; \ + } \ + return result; +#define _HOOK_POST_FILE(file) \ + if (handle) { \ + int saved_errno = errno; \ + handle_file_post(file, handle); \ + errno = saved_errno; \ + } \ + return result; + + +#define HOOK_FD3(type, name, fd, type1, arg1, type2, arg2, type3, arg3) \ + static type (*real_ ## name)(type1, type2, type3); \ + type name(type1 arg1, type2 arg2, type3 arg3) { \ + _HOOK_PRE_FD(type, name, fd) \ + result = real_ ## name(arg1, arg2, arg3); \ + _HOOK_POST_FD(fd) \ + } + +#define HOOK_FILE1(type, name, file, type1, arg1) \ + static type (*real_ ## name)(type1); \ + type name(type1 arg1) { \ + _HOOK_PRE_FILE(type, name, file) \ + result = real_ ## name(arg1); \ + _HOOK_POST_FILE(file) \ + } +#define HOOK_FILE2(type, name, file, type1, arg1, type2, arg2) \ + static type (*real_ ## name)(type1, type2); \ + type name(type1 arg1, type2 arg2) { \ + _HOOK_PRE_FILE(type, name, file) \ + result = real_ ## name(arg1, arg2); \ + _HOOK_POST_FILE(file) \ + } +#define HOOK_FILE3(type, name, file, type1, arg1, type2, arg2, type3, arg3) \ + static type (*real_ ## name)(type1, type2, type3); \ + type name(type1 arg1, type2 arg2, type3 arg3) { \ + _HOOK_PRE_FILE(type, name, file) \ + result = real_ ## name(arg1, arg2, arg3); \ + _HOOK_POST_FILE(file) \ + } +#define HOOK_FILE4(type, name, file, type1, arg1, type2, arg2, type3, arg3, type4, arg4) \ + static type (*real_ ## name)(type1, type2, type3, type4); \ + type name(type1 arg1, type2 arg2, type3 arg3, type4 arg4) { \ + _HOOK_PRE_FILE(type, name, file) \ + result = real_ ## name(arg1, arg2, arg3, arg4); \ + _HOOK_POST_FILE(file) \ + } + +#define HOOK_VAR_FILE1(type, name, file, func, type1, arg1) \ + static type (*real_ ## func)(type1, va_list); \ + type name(type1 arg1, ...) { \ + va_list ap; \ + _HOOK_PRE_FILE(type, func, file) \ + va_start(ap, arg1); \ + result = real_ ## func(arg1, ap); \ + va_end(ap); \ + _HOOK_POST_FILE(file) \ + } +#define HOOK_VAR_FILE2(type, name, file, func, type1, arg1, type2, arg2) \ + static type (*real_ ## func)(type1, type2, va_list); \ + type name(type1 arg1, type2 arg2, ...) { \ + va_list ap; \ + _HOOK_PRE_FILE(type, func, file) \ + va_start(ap, arg2); \ + result = real_ ## func(arg1, arg2, ap); \ + va_end(ap); \ + _HOOK_POST_FILE(file) \ + } +#define HOOK_VAR_FILE3(type, name, file, func, type1, arg1, type2, arg2, type3, arg3) \ + static type (*real_ ## func)(type1, type2, type3, va_list); \ + type name(type1 arg1, type2 arg2, type3 arg3, ...) \ + { \ + va_list ap; \ + _HOOK_PRE_FILE(type, func, file) \ + va_start(ap, arg3); \ + result = real_ ## func(arg1, arg2, arg3, ap); \ + va_end(ap); \ + _HOOK_POST_FILE(file) \ + } + +#endif diff --git a/src/trackfds.h b/src/trackfds.h new file mode 100644 index 0000000..378c327 --- /dev/null +++ b/src/trackfds.h @@ -0,0 +1,179 @@ +/* + * Utility functions to track file descriptors. + * + * Copyright (C) 2013 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef TRACKFDS_H +#define TRACKFDS_H 1 + +/* List of tracked file descriptors. */ +static int *tracked_fds; +/* Current number of items in the list. */ +static size_t tracked_fds_count; +/* Allocated items, used to reduce realloc()s. */ +static size_t tracked_fds_space; + + +/* 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,". */ +static void init_from_environment(void) { + tracked_fds_count = 0; + + const char *env = getenv(ENV_NAME_FDS); + if (!env) { + return; + } + /* Environment is read-only. */ + char *env_copy = strdup(env); + if (!env_copy) { + return; + } + + char *x; + + size_t count = 0; + for (x = env_copy; *x; x++) { + if (*x == ',') { + count++; + } + } + tracked_fds_space = count + TRACKFDS_REALLOC_STEP; + + tracked_fds = malloc(tracked_fds_space * sizeof(*tracked_fds)); + if (!tracked_fds) { + free(env_copy); + return; + } + + size_t i = 0; + + /* Parse file descriptor numbers from environment string and store them as + * integers in tracked_fds. */ + char *last; + for (x = env_copy, last = env_copy; *x; x++) { + if (*x != ',') { + continue; + } + /* ',' at the beginning or double ',' - ignore. */ + if (x == last) { + last = x + 1; + continue; + } + + if (i == count) { + break; + } + + *x = 0; + tracked_fds[i++] = atoi(last); + + last = x + 1; + } + + tracked_fds_count = count; + + free(env_copy); +} + +static void update_environment(void) { + /* An integer (32-bit) has at most 10 digits, + 1 for the comma after each + * number. Bigger file descriptors (which shouldn't occur in reality) are + * truncated. */ + char env[tracked_fds_count * (10 + 1) * sizeof(char)]; + char *x = env; + + size_t i; + for (i = 0; i < tracked_fds_count; i++) { + int length = snprintf(x, 10 + 1, "%d", tracked_fds[i]); + if (length >= 10 + 1) + return; + + /* Write comma after number. */ + x += length; + *x++ = ','; + /* Make sure the string is always zero terminated. */ + *x = 0; + } + + setenv(ENV_NAME_FDS, env, 1 /* overwrite */); +} + + +#ifdef DEBUG +static void tracked_fds_debug() { + debug("tracked_fds: %d/%d\n", tracked_fds_count, tracked_fds_space); + size_t i; + for (i = 0; i < tracked_fds_count; i++) { + debug("tracked_fds[%d]: %d\n", i, tracked_fds[i]); + } +} +#endif + +static void tracked_fds_add(int fd) { + if (tracked_fds_count >= tracked_fds_space) { + size_t new_space = tracked_fds_space + TRACKFDS_REALLOC_STEP; + if (!realloc(tracked_fds, sizeof(*tracked_fds) * new_space)) { + /* 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. */ + return; + } + tracked_fds_space = new_space; + } + + tracked_fds[tracked_fds_count++] = fd; + +#ifdef DEBUG + tracked_fds_debug(); +#endif +} +static int tracked_fds_remove(int fd) { + size_t i; + for (i = 0; i < tracked_fds_count; i++) { + if (fd != tracked_fds[i]) { + continue; + } + + memmove(tracked_fds + i, tracked_fds + i + 1, + sizeof(*tracked_fds) * (tracked_fds_count - i - 1)); + tracked_fds_count--; + +#ifdef DEBUG + tracked_fds_debug(); +#endif + + /* Found. */ + return 1; + } + + /* Not found. */ + return 0; +} +static int tracked_fds_find(int fd) { + size_t i; + for (i = 0; i < tracked_fds_count; i++) { + if (fd == tracked_fds[i]) { + return 1; + } + } + return 0; +} + +#endif