(root)/
tar-1.35/
gnu/
fchdir.c
       1  /* fchdir replacement.
       2     Copyright (C) 2006-2023 Free Software Foundation, Inc.
       3  
       4     This file is free software: you can redistribute it and/or modify
       5     it under the terms of the GNU Lesser General Public License as
       6     published by the Free Software Foundation, either version 3 of the
       7     License, or (at your option) any later version.
       8  
       9     This file is distributed in the hope that it will be useful,
      10     but WITHOUT ANY WARRANTY; without even the implied warranty of
      11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      12     GNU Lesser General Public License for more details.
      13  
      14     You should have received a copy of the GNU Lesser General Public License
      15     along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
      16  
      17  #include <config.h>
      18  
      19  /* Specification.  */
      20  #include <unistd.h>
      21  
      22  #include <dirent.h>
      23  #include <errno.h>
      24  #include <fcntl.h>
      25  #include <stdlib.h>
      26  #include <string.h>
      27  #include <sys/types.h>
      28  #include <sys/stat.h>
      29  
      30  #include "assure.h"
      31  #include "filename.h"
      32  #include "filenamecat.h"
      33  
      34  #ifndef REPLACE_OPEN_DIRECTORY
      35  # define REPLACE_OPEN_DIRECTORY 0
      36  #endif
      37  
      38  /* This replacement assumes that a directory is not renamed while opened
      39     through a file descriptor.
      40  
      41     FIXME: On mingw, this would be possible to enforce if we were to
      42     also open a HANDLE to each directory currently visited by a file
      43     descriptor, since mingw refuses to rename any in-use file system
      44     object.  */
      45  
      46  /* Array of file descriptors opened.  If REPLACE_OPEN_DIRECTORY or if it points
      47     to a directory, it stores info about this directory.  */
      48  typedef struct
      49  {
      50    char *name;       /* Absolute name of the directory, or NULL.  */
      51  } dir_info_t;
      52  static dir_info_t *dirs;
      53  static size_t dirs_allocated;
      54  
      55  /* Try to ensure dirs has enough room for a slot at index fd; free any
      56     contents already in that slot.  Return false and set errno to
      57     ENOMEM on allocation failure.  */
      58  static bool
      59  ensure_dirs_slot (size_t fd)
      60  {
      61    if (fd < dirs_allocated)
      62      free (dirs[fd].name);
      63    else
      64      {
      65        size_t new_allocated;
      66        dir_info_t *new_dirs;
      67  
      68        new_allocated = 2 * dirs_allocated + 1;
      69        if (new_allocated <= fd)
      70          new_allocated = fd + 1;
      71        new_dirs =
      72          (dirs != NULL
      73           ? (dir_info_t *) realloc (dirs, new_allocated * sizeof *dirs)
      74           : (dir_info_t *) malloc (new_allocated * sizeof *dirs));
      75        if (new_dirs == NULL)
      76          return false;
      77        memset (new_dirs + dirs_allocated, 0,
      78                (new_allocated - dirs_allocated) * sizeof *dirs);
      79        dirs = new_dirs;
      80        dirs_allocated = new_allocated;
      81      }
      82    return true;
      83  }
      84  
      85  /* Return an absolute name of DIR in malloc'd storage.
      86     Upon failure, return NULL with errno set.  */
      87  static char *
      88  get_name (char const *dir)
      89  {
      90    char *cwd;
      91    char *result;
      92  
      93    if (IS_ABSOLUTE_FILE_NAME (dir))
      94      return strdup (dir);
      95  
      96    /* We often encounter "."; treat it as a special case.  */
      97    cwd = getcwd (NULL, 0);
      98    if (!cwd || (dir[0] == '.' && dir[1] == '\0'))
      99      return cwd;
     100  
     101    result = mfile_name_concat (cwd, dir, NULL);
     102    free (cwd);
     103    return result;
     104  }
     105  
     106  /* Hook into the gnulib replacements for open() and close() to keep track
     107     of the open file descriptors.  */
     108  
     109  /* Close FD, cleaning up any fd to name mapping if fd was visiting a
     110     directory.  */
     111  void
     112  _gl_unregister_fd (int fd)
     113  {
     114    if (fd >= 0 && fd < dirs_allocated)
     115      {
     116        free (dirs[fd].name);
     117        dirs[fd].name = NULL;
     118      }
     119  }
     120  
     121  /* Mark FD as visiting FILENAME.  FD must be non-negative, and refer
     122     to an open file descriptor.  If REPLACE_OPEN_DIRECTORY is non-zero,
     123     this should only be called if FD is visiting a directory.  Close FD
     124     and return -1 with errno set if there is insufficient memory to track
     125     the directory name; otherwise return FD.  */
     126  int
     127  _gl_register_fd (int fd, const char *filename)
     128  {
     129    struct stat statbuf;
     130  
     131    assure (0 <= fd);
     132    if (REPLACE_OPEN_DIRECTORY
     133        || (fstat (fd, &statbuf) == 0 && S_ISDIR (statbuf.st_mode)))
     134      {
     135        if (!ensure_dirs_slot (fd)
     136            || (dirs[fd].name = get_name (filename)) == NULL)
     137          {
     138            int saved_errno = errno;
     139            close (fd);
     140            errno = saved_errno;
     141            return -1;
     142          }
     143      }
     144    return fd;
     145  }
     146  
     147  /* Mark NEWFD as a duplicate of OLDFD; useful from dup, dup2, dup3,
     148     and fcntl.  Both arguments must be valid and distinct file
     149     descriptors.  Close NEWFD and return -1 if OLDFD is tracking a
     150     directory, but there is insufficient memory to track the same
     151     directory in NEWFD; otherwise return NEWFD.  */
     152  int
     153  _gl_register_dup (int oldfd, int newfd)
     154  {
     155    assure (0 <= oldfd && 0 <= newfd && oldfd != newfd);
     156    if (oldfd < dirs_allocated && dirs[oldfd].name)
     157      {
     158        /* Duplicated a directory; must ensure newfd is allocated.  */
     159        if (!ensure_dirs_slot (newfd)
     160            || (dirs[newfd].name = strdup (dirs[oldfd].name)) == NULL)
     161          {
     162            int saved_errno = errno;
     163            close (newfd);
     164            errno = saved_errno;
     165            newfd = -1;
     166          }
     167      }
     168    else if (newfd < dirs_allocated)
     169      {
     170        /* Duplicated a non-directory; ensure newfd is cleared.  */
     171        free (dirs[newfd].name);
     172        dirs[newfd].name = NULL;
     173      }
     174    return newfd;
     175  }
     176  
     177  /* If FD is currently visiting a directory, then return the name of
     178     that directory.  Otherwise, return NULL and set errno.  */
     179  const char *
     180  _gl_directory_name (int fd)
     181  {
     182    if (0 <= fd && fd < dirs_allocated && dirs[fd].name != NULL)
     183      return dirs[fd].name;
     184    /* At this point, fd is either invalid, or open but not a directory.
     185       If dup2 fails, errno is correctly EBADF.  */
     186    if (0 <= fd)
     187      {
     188        if (dup2 (fd, fd) == fd)
     189          errno = ENOTDIR;
     190      }
     191    else
     192      errno = EBADF;
     193    return NULL;
     194  }
     195  
     196  
     197  /* Implement fchdir() in terms of chdir().  */
     198  
     199  int
     200  fchdir (int fd)
     201  {
     202    const char *name = _gl_directory_name (fd);
     203    return name ? chdir (name) : -1;
     204  }