summaryrefslogtreecommitdiff
path: root/lib/fchdir.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/fchdir.c')
-rw-r--r--lib/fchdir.c279
1 files changed, 279 insertions, 0 deletions
diff --git a/lib/fchdir.c b/lib/fchdir.c
new file mode 100644
index 0000000..e3ec2fc
--- /dev/null
+++ b/lib/fchdir.c
@@ -0,0 +1,279 @@
+/* fchdir replacement.
+ Copyright (C) 2006, 2007 Free Software Foundation, Inc.
+
+ 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 2, 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, write to the Free Software Foundation,
+ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
+
+#include <config.h>
+
+/* Specification. */
+#include <unistd.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <dirent.h>
+
+#include "canonicalize.h"
+#include "dirfd.h"
+
+/* This replacement assumes that a directory is not renamed while opened
+ through a file descriptor. */
+
+/* Array of file descriptors opened. If it points to a directory, it stores
+ info about this directory; otherwise it stores an errno value of ENOTDIR. */
+typedef struct
+{
+ char *name; /* Absolute name of the directory, or NULL. */
+ int saved_errno; /* If name == NULL: The error code describing the failure
+ reason. */
+} dir_info_t;
+static dir_info_t *dirs;
+static size_t dirs_allocated;
+
+/* Try to ensure dirs has enough room for a slot at index fd. */
+static void
+ensure_dirs_slot (size_t fd)
+{
+ if (fd >= dirs_allocated)
+ {
+ size_t new_allocated;
+ dir_info_t *new_dirs;
+ size_t i;
+
+ new_allocated = 2 * dirs_allocated + 1;
+ if (new_allocated <= fd)
+ new_allocated = fd + 1;
+ new_dirs =
+ (dirs != NULL
+ ? (dir_info_t *) realloc (dirs, new_allocated * sizeof (dir_info_t))
+ : (dir_info_t *) malloc (new_allocated * sizeof (dir_info_t)));
+ if (new_dirs != NULL)
+ {
+ for (i = dirs_allocated; i < new_allocated; i++)
+ {
+ new_dirs[i].name = NULL;
+ new_dirs[i].saved_errno = ENOTDIR;
+ }
+ dirs = new_dirs;
+ dirs_allocated = new_allocated;
+ }
+ }
+}
+
+/* Override open() and close(), to keep track of the open file descriptors. */
+
+int
+close (int fd)
+#undef close
+{
+ int retval = close (fd);
+
+ if (retval >= 0 && fd >= 0 && fd < dirs_allocated)
+ {
+ if (dirs[fd].name != NULL)
+ free (dirs[fd].name);
+ dirs[fd].name = NULL;
+ dirs[fd].saved_errno = ENOTDIR;
+ }
+ return retval;
+}
+
+int
+open (const char *filename, int flags, ...)
+#undef open
+{
+ mode_t mode;
+ int fd;
+ struct stat statbuf;
+
+ mode = 0;
+ if (flags & O_CREAT)
+ {
+ va_list arg;
+ va_start (arg, flags);
+
+ /* If mode_t is narrower than int, use the promoted type (int),
+ not mode_t. Use sizeof to guess whether mode_t is narrower;
+ we don't know of any practical counterexamples. */
+ mode = (sizeof (mode_t) < sizeof (int)
+ ? va_arg (arg, int)
+ : va_arg (arg, mode_t));
+
+ va_end (arg);
+ }
+ fd = open (filename, flags, mode);
+ if (fd >= 0)
+ {
+ ensure_dirs_slot (fd);
+ if (fd < dirs_allocated
+ && fstat (fd, &statbuf) >= 0 && S_ISDIR (statbuf.st_mode))
+ {
+ dirs[fd].name = canonicalize_file_name (filename);
+ if (dirs[fd].name == NULL)
+ dirs[fd].saved_errno = errno;
+ }
+ }
+ return fd;
+}
+
+/* Override opendir() and closedir(), to keep track of the open file
+ descriptors. Needed because there is a function dirfd(). */
+
+int
+closedir (DIR *dp)
+#undef closedir
+{
+ int fd = dirfd (dp);
+ int retval = closedir (dp);
+
+ if (retval >= 0 && fd >= 0 && fd < dirs_allocated)
+ {
+ if (dirs[fd].name != NULL)
+ free (dirs[fd].name);
+ dirs[fd].name = NULL;
+ dirs[fd].saved_errno = ENOTDIR;
+ }
+ return retval;
+}
+
+DIR *
+opendir (const char *filename)
+#undef opendir
+{
+ DIR *dp;
+
+ dp = opendir (filename);
+ if (dp != NULL)
+ {
+ int fd = dirfd (dp);
+ if (fd >= 0)
+ {
+ ensure_dirs_slot (fd);
+ if (fd < dirs_allocated)
+ {
+ dirs[fd].name = canonicalize_file_name (filename);
+ if (dirs[fd].name == NULL)
+ dirs[fd].saved_errno = errno;
+ }
+ }
+ }
+ return dp;
+}
+
+/* Override dup() and dup2(), to keep track of open file descriptors. */
+
+int
+dup (int oldfd)
+#undef dup
+{
+ int newfd = dup (oldfd);
+
+ if (oldfd >= 0 && newfd >= 0)
+ {
+ ensure_dirs_slot (newfd);
+ if (newfd < dirs_allocated)
+ {
+ if (oldfd < dirs_allocated)
+ {
+ if (dirs[oldfd].name != NULL)
+ {
+ dirs[newfd].name = strdup (dirs[oldfd].name);
+ if (dirs[newfd].name == NULL)
+ dirs[newfd].saved_errno = ENOMEM;
+ }
+ else
+ {
+ dirs[newfd].name = NULL;
+ dirs[newfd].saved_errno = dirs[oldfd].saved_errno;
+ }
+ }
+ else
+ {
+ dirs[newfd].name = NULL;
+ dirs[newfd].saved_errno = ENOMEM;
+ }
+ }
+ }
+ return newfd;
+}
+
+int
+dup2 (int oldfd, int newfd)
+#undef dup2
+{
+ int retval = dup2 (oldfd, newfd);
+
+ if (retval >= 0 && oldfd >= 0 && newfd >= 0 && newfd != oldfd)
+ {
+ ensure_dirs_slot (newfd);
+ if (newfd < dirs_allocated)
+ {
+ if (oldfd < dirs_allocated)
+ {
+ if (dirs[oldfd].name != NULL)
+ {
+ dirs[newfd].name = strdup (dirs[oldfd].name);
+ if (dirs[newfd].name == NULL)
+ dirs[newfd].saved_errno = ENOMEM;
+ }
+ else
+ {
+ dirs[newfd].name = NULL;
+ dirs[newfd].saved_errno = dirs[oldfd].saved_errno;
+ }
+ }
+ else
+ {
+ dirs[newfd].name = NULL;
+ dirs[newfd].saved_errno = ENOMEM;
+ }
+ }
+ }
+ return retval;
+}
+
+/* Implement fchdir() in terms of chdir(). */
+
+int
+fchdir (int fd)
+{
+ if (fd >= 0)
+ {
+ if (fd < dirs_allocated)
+ {
+ if (dirs[fd].name != NULL)
+ return chdir (dirs[fd].name);
+ else
+ {
+ errno = dirs[fd].saved_errno;
+ return -1;
+ }
+ }
+ else
+ {
+ errno = ENOMEM;
+ return -1;
+ }
+ }
+ else
+ {
+ errno = EBADF;
+ return -1;
+ }
+}