+
+
+/* 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. */
+
+/* int execve(char const *, char * const [], char * const []) */
+HOOK_FUNC_DEF3(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, char *)) { \
+ count++; \
+ } \
+ va_end(ap); \
+ \
+ /* Copy varargs. */ \
+ char *args[count + 1 /* terminating NULL */]; \
+ args[0] = (char *)arg; /* there's no other way around the cast */ \
+ \
+ 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);
+
+ /* execv() updates the environment. */
+ return execv(path, args);
+}
+int execlp(char const *file, char const *arg, ...) {
+ EXECL_COPY_VARARGS(args);
+
+ /* execvp() updates the environment. */
+ return execvp(file, args);
+}
+int execle(char const *path, char const *arg, ... /*, char * const envp[] */) {
+ char * const *envp;
+
+ EXECL_COPY_VARARGS_START(args);
+ /* Get envp[] located after arguments. */
+ envp = va_arg(ap, char * const *);
+ EXECL_COPY_VARARGS_END(args);
+
+ /* execve() updates the environment. */
+ return execve(path, args, envp);
+}
+
+/* int execv(char const *, char * const []) */
+HOOK_FUNC_DEF2(int, execv, char const *, path, char * const *, argv) {
+ DLSYM_FUNCTION(real_execv, "execv");
+
+ update_environment();
+ return real_execv(path, argv);
+}
+
+/* int execvp(char const *, char * const []) */
+HOOK_FUNC_DEF2(int, execvp, char const *, file, char * const *, argv) {
+ DLSYM_FUNCTION(real_execvp, "execvp");
+
+ update_environment();
+ return real_execvp(file, argv);
+}
+
+#ifdef HAVE_EXECVPE
+extern char **environ;
+int execvpe(char const *file, char * const argv[], char * const envp[]) {
+ /* Fake the environment so we can reuse execvp(). */
+ environ = (char **)envp;
+
+ /* execvp() updates the environment. */
+ return execvp(file, argv);
+}
+#endif