/* Must be loaded before the following headers. */
#include "ldpreload.h"
-/* FIXME: use correct declaration for fcntl() */
-#define fcntl fcntl_ignore
-
#include <errno.h>
#include <fcntl.h>
#include <stdarg.h>
#include <string.h>
#include <unistd.h>
-#undef fcntl
/* Conflicting declaration in glibc. */
#undef fwrite_unlocked
static int (*real_close)(int);
static size_t (*real_fwrite)(const void *, 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"
#ifdef DEBUG
# include "debug.h"
#endif
-#include "macros.h"
+#include "hookmacros.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) {
+ if (!initialized) {
init_from_environment();
}
+ /* 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;
+ }
+
if (tracked_fds_count == 0) {
return 0;
}
debug("%d -> %d\t\t\t[%d]\n", oldfd, newfd, getpid());
#endif
- if (!tracked_fds) {
+ if (!initialized) {
init_from_environment();
}
if (tracked_fds_count == 0) {
debug("%d -> .\t\t\t[%d]\n", fd, getpid());
#endif
- if (!tracked_fds) {
+ if (!initialized) {
init_from_environment();
}
HOOK_FILE1(int, puts_unlocked, stdout,
const char *, s)
+/* perror(3) */
+HOOK_VOID1(void, perror, STDERR_FILENO,
+ const char *, s)
+
/* Hook functions which duplicate file descriptors to track them. */
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) {
+ if (cmd == F_DUPFD && result != -1) {
int saved_errno = errno;
dup_fd(fd, result);
errno = saved_errno;
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