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