install-reloc: Support multi-binary installation.
[gnulib.git] / lib / fchdir.c
index f3a9dfd..36a8e35 100644 (file)
@@ -1,10 +1,10 @@
 /* fchdir replacement.
-   Copyright (C) 2006, 2007 Free Software Foundation, Inc.
+   Copyright (C) 2006-2013 Free Software Foundation, Inc.
 
-   This program is free software; you can redistribute it and/or modify
+   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.
+   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
    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.  */
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
 
 #include <config.h>
 
+/* Specification.  */
+#include <unistd.h>
+
+#include <assert.h>
+#include <dirent.h>
 #include <errno.h>
 #include <fcntl.h>
-#include <stdarg.h>
+#include <stdbool.h>
 #include <stdlib.h>
 #include <string.h>
-#include <unistd.h>
 #include <sys/types.h>
 #include <sys/stat.h>
-#include <dirent.h>
 
-#include "canonicalize.h"
-#include "dirfd.h"
+#include "dosname.h"
+#include "filenamecat.h"
+
+#ifndef REPLACE_OPEN_DIRECTORY
+# define REPLACE_OPEN_DIRECTORY 0
+#endif
 
 /* This replacement assumes that a directory is not renamed while opened
-   through a file descriptor.  */
+   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.  */
+   FIXME: On mingw, this would be possible to enforce if we were to
+   also open a HANDLE to each directory currently visited by a file
+   descriptor, since mingw refuses to rename any in-use file system
+   object.  */
+
+/* Array of file descriptors opened.  If REPLACE_OPEN_DIRECTORY or if it points
+   to a directory, it stores info about this directory.  */
 typedef struct
 {
   char *name;       /* Absolute name of the directory, or NULL.  */
-  int saved_errno;  /* If name == NULL: The error code describing the failure
-                      reason.  */
+  /* FIXME - add a DIR* member to make dirfd possible on mingw?  */
 } 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
+/* Try to ensure dirs has enough room for a slot at index fd; free any
+   contents already in that slot.  Return false and set errno to
+   ENOMEM on allocation failure.  */
+static bool
 ensure_dirs_slot (size_t fd)
 {
-  if (fd >= dirs_allocated)
+  if (fd < dirs_allocated)
+    free (dirs[fd].name);
+  else
     {
       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_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;
-       }
+        (dirs != NULL
+         ? (dir_info_t *) realloc (dirs, new_allocated * sizeof *dirs)
+         : (dir_info_t *) malloc (new_allocated * sizeof *dirs));
+      if (new_dirs == NULL)
+        return false;
+      memset (new_dirs + dirs_allocated, 0,
+              (new_allocated - dirs_allocated) * sizeof *dirs);
+      dirs = new_dirs;
+      dirs_allocated = new_allocated;
     }
+  return true;
 }
 
-/* Override open() and close(), to keep track of the open file descriptors.  */
-
-int
-close (int fd)
-#undef close
+/* Return an absolute name of DIR in malloc'd storage.  */
+static char *
+get_name (char const *dir)
 {
-  int retval = close (fd);
+  char *cwd;
+  char *result;
+  int saved_errno;
+
+  if (IS_ABSOLUTE_FILE_NAME (dir))
+    return strdup (dir);
+
+  /* We often encounter "."; treat it as a special case.  */
+  cwd = getcwd (NULL, 0);
+  if (!cwd || (dir[0] == '.' && dir[1] == '\0'))
+    return cwd;
+
+  result = mfile_name_concat (cwd, dir, NULL);
+  saved_errno = errno;
+  free (cwd);
+  errno = saved_errno;
+  return result;
+}
 
-  if (retval >= 0 && fd >= 0 && fd < dirs_allocated)
+/* Hook into the gnulib replacements for open() and close() to keep track
+   of the open file descriptors.  */
+
+/* Close FD, cleaning up any fd to name mapping if fd was visiting a
+   directory.  */
+void
+_gl_unregister_fd (int fd)
+{
+  if (fd >= 0 && fd < dirs_allocated)
     {
-      if (dirs[fd].name != NULL)
-       free (dirs[fd].name);
+      free (dirs[fd].name);
       dirs[fd].name = NULL;
-      dirs[fd].saved_errno = ENOTDIR;
     }
-  return retval;
 }
 
+/* Mark FD as visiting FILENAME.  FD must be non-negative, and refer
+   to an open file descriptor.  If REPLACE_OPEN_DIRECTORY is non-zero,
+   this should only be called if FD is visiting a directory.  Close FD
+   and return -1 if there is insufficient memory to track the
+   directory name; otherwise return FD.  */
 int
-open (const char *filename, int flags, ...)
-#undef open
+_gl_register_fd (int fd, const char *filename)
 {
-  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)
+  assert (0 <= fd);
+  if (REPLACE_OPEN_DIRECTORY
+      || (fstat (fd, &statbuf) == 0 && S_ISDIR (statbuf.st_mode)))
     {
-      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;
-       }
+      if (!ensure_dirs_slot (fd)
+          || (dirs[fd].name = get_name (filename)) == NULL)
+        {
+          int saved_errno = errno;
+          close (fd);
+          errno = saved_errno;
+          return -1;
+        }
     }
   return fd;
 }
 
-/* Override opendir() and closedir(), to keep track of the open file
-   descriptors.  Needed because there is a function dirfd().  */
-
+/* Mark NEWFD as a duplicate of OLDFD; useful from dup, dup2, dup3,
+   and fcntl.  Both arguments must be valid and distinct file
+   descriptors.  Close NEWFD and return -1 if OLDFD is tracking a
+   directory, but there is insufficient memory to track the same
+   directory in NEWFD; otherwise return NEWFD.  */
 int
-closedir (DIR *dp)
-#undef closedir
+_gl_register_dup (int oldfd, int newfd)
 {
-  int fd = dirfd (dp);
-  int retval = closedir (dp);
-
-  if (retval >= 0 && fd >= 0 && fd < dirs_allocated)
+  assert (0 <= oldfd && 0 <= newfd && oldfd != newfd);
+  if (oldfd < dirs_allocated && dirs[oldfd].name)
     {
-      if (dirs[fd].name != NULL)
-       free (dirs[fd].name);
-      dirs[fd].name = NULL;
-      dirs[fd].saved_errno = ENOTDIR;
+      /* Duplicated a directory; must ensure newfd is allocated.  */
+      if (!ensure_dirs_slot (newfd)
+          || (dirs[newfd].name = strdup (dirs[oldfd].name)) == NULL)
+        {
+          int saved_errno = errno;
+          close (newfd);
+          errno = saved_errno;
+          newfd = -1;
+        }
     }
-  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)
+  else if (newfd < dirs_allocated)
     {
-      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;
-           }
-       }
+      /* Duplicated a non-directory; ensure newfd is cleared.  */
+      free (dirs[newfd].name);
+      dirs[newfd].name = NULL;
     }
   return newfd;
 }
 
-int
-dup2 (int oldfd, int newfd)
-#undef dup2
+/* If FD is currently visiting a directory, then return the name of
+   that directory.  Otherwise, return NULL and set errno.  */
+const char *
+_gl_directory_name (int fd)
 {
-  int retval = dup2 (oldfd, newfd);
-
-  if (retval >= 0 && oldfd >= 0 && newfd >= 0 && newfd != oldfd)
+  if (0 <= fd && fd < dirs_allocated && dirs[fd].name != NULL)
+    return dirs[fd].name;
+  /* At this point, fd is either invalid, or open but not a directory.
+     If dup2 fails, errno is correctly EBADF.  */
+  if (0 <= fd)
     {
-      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;
-           }
-       }
+      if (dup2 (fd, fd) == fd)
+        errno = ENOTDIR;
     }
-  return retval;
+  else
+    errno = EBADF;
+  return NULL;
 }
 
+
 /* 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;
-    }
+  const char *name = _gl_directory_name (fd);
+  return name ? chdir (name) : -1;
 }