Blame lib/fchdir.c

Packit 709fb3
/* fchdir replacement.
Packit 709fb3
   Copyright (C) 2006-2017 Free Software Foundation, Inc.
Packit 709fb3
Packit 709fb3
   This program is free software: you can redistribute it and/or modify
Packit 709fb3
   it under the terms of the GNU General Public License as published by
Packit 709fb3
   the Free Software Foundation; either version 3 of the License, or
Packit 709fb3
   (at your option) any later version.
Packit 709fb3
Packit 709fb3
   This program is distributed in the hope that it will be useful,
Packit 709fb3
   but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit 709fb3
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Packit 709fb3
   GNU General Public License for more details.
Packit 709fb3
Packit 709fb3
   You should have received a copy of the GNU General Public License
Packit 709fb3
   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
Packit 709fb3
Packit 709fb3
#include <config.h>
Packit 709fb3
Packit 709fb3
/* Specification.  */
Packit 709fb3
#include <unistd.h>
Packit 709fb3
Packit 709fb3
#include <dirent.h>
Packit 709fb3
#include <errno.h>
Packit 709fb3
#include <fcntl.h>
Packit 709fb3
#include <stdbool.h>
Packit 709fb3
#include <stdlib.h>
Packit 709fb3
#include <string.h>
Packit 709fb3
#include <sys/types.h>
Packit 709fb3
#include <sys/stat.h>
Packit 709fb3
Packit 709fb3
#include "assure.h"
Packit 709fb3
#include "dosname.h"
Packit 709fb3
#include "filenamecat.h"
Packit 709fb3
Packit 709fb3
#ifndef REPLACE_OPEN_DIRECTORY
Packit 709fb3
# define REPLACE_OPEN_DIRECTORY 0
Packit 709fb3
#endif
Packit 709fb3
Packit 709fb3
/* This replacement assumes that a directory is not renamed while opened
Packit 709fb3
   through a file descriptor.
Packit 709fb3
Packit 709fb3
   FIXME: On mingw, this would be possible to enforce if we were to
Packit 709fb3
   also open a HANDLE to each directory currently visited by a file
Packit 709fb3
   descriptor, since mingw refuses to rename any in-use file system
Packit 709fb3
   object.  */
Packit 709fb3
Packit 709fb3
/* Array of file descriptors opened.  If REPLACE_OPEN_DIRECTORY or if it points
Packit 709fb3
   to a directory, it stores info about this directory.  */
Packit 709fb3
typedef struct
Packit 709fb3
{
Packit 709fb3
  char *name;       /* Absolute name of the directory, or NULL.  */
Packit 709fb3
  /* FIXME - add a DIR* member to make dirfd possible on mingw?  */
Packit 709fb3
} dir_info_t;
Packit 709fb3
static dir_info_t *dirs;
Packit 709fb3
static size_t dirs_allocated;
Packit 709fb3
Packit 709fb3
/* Try to ensure dirs has enough room for a slot at index fd; free any
Packit 709fb3
   contents already in that slot.  Return false and set errno to
Packit 709fb3
   ENOMEM on allocation failure.  */
Packit 709fb3
static bool
Packit 709fb3
ensure_dirs_slot (size_t fd)
Packit 709fb3
{
Packit 709fb3
  if (fd < dirs_allocated)
Packit 709fb3
    free (dirs[fd].name);
Packit 709fb3
  else
Packit 709fb3
    {
Packit 709fb3
      size_t new_allocated;
Packit 709fb3
      dir_info_t *new_dirs;
Packit 709fb3
Packit 709fb3
      new_allocated = 2 * dirs_allocated + 1;
Packit 709fb3
      if (new_allocated <= fd)
Packit 709fb3
        new_allocated = fd + 1;
Packit 709fb3
      new_dirs =
Packit 709fb3
        (dirs != NULL
Packit 709fb3
         ? (dir_info_t *) realloc (dirs, new_allocated * sizeof *dirs)
Packit 709fb3
         : (dir_info_t *) malloc (new_allocated * sizeof *dirs));
Packit 709fb3
      if (new_dirs == NULL)
Packit 709fb3
        return false;
Packit 709fb3
      memset (new_dirs + dirs_allocated, 0,
Packit 709fb3
              (new_allocated - dirs_allocated) * sizeof *dirs);
Packit 709fb3
      dirs = new_dirs;
Packit 709fb3
      dirs_allocated = new_allocated;
Packit 709fb3
    }
Packit 709fb3
  return true;
Packit 709fb3
}
Packit 709fb3
Packit 709fb3
/* Return an absolute name of DIR in malloc'd storage.  */
Packit 709fb3
static char *
Packit 709fb3
get_name (char const *dir)
Packit 709fb3
{
Packit 709fb3
  char *cwd;
Packit 709fb3
  char *result;
Packit 709fb3
  int saved_errno;
Packit 709fb3
Packit 709fb3
  if (IS_ABSOLUTE_FILE_NAME (dir))
Packit 709fb3
    return strdup (dir);
Packit 709fb3
Packit 709fb3
  /* We often encounter "."; treat it as a special case.  */
Packit 709fb3
  cwd = getcwd (NULL, 0);
Packit 709fb3
  if (!cwd || (dir[0] == '.' && dir[1] == '\0'))
Packit 709fb3
    return cwd;
Packit 709fb3
Packit 709fb3
  result = mfile_name_concat (cwd, dir, NULL);
Packit 709fb3
  saved_errno = errno;
Packit 709fb3
  free (cwd);
Packit 709fb3
  errno = saved_errno;
Packit 709fb3
  return result;
Packit 709fb3
}
Packit 709fb3
Packit 709fb3
/* Hook into the gnulib replacements for open() and close() to keep track
Packit 709fb3
   of the open file descriptors.  */
Packit 709fb3
Packit 709fb3
/* Close FD, cleaning up any fd to name mapping if fd was visiting a
Packit 709fb3
   directory.  */
Packit 709fb3
void
Packit 709fb3
_gl_unregister_fd (int fd)
Packit 709fb3
{
Packit 709fb3
  if (fd >= 0 && fd < dirs_allocated)
Packit 709fb3
    {
Packit 709fb3
      free (dirs[fd].name);
Packit 709fb3
      dirs[fd].name = NULL;
Packit 709fb3
    }
Packit 709fb3
}
Packit 709fb3
Packit 709fb3
/* Mark FD as visiting FILENAME.  FD must be non-negative, and refer
Packit 709fb3
   to an open file descriptor.  If REPLACE_OPEN_DIRECTORY is non-zero,
Packit 709fb3
   this should only be called if FD is visiting a directory.  Close FD
Packit 709fb3
   and return -1 if there is insufficient memory to track the
Packit 709fb3
   directory name; otherwise return FD.  */
Packit 709fb3
int
Packit 709fb3
_gl_register_fd (int fd, const char *filename)
Packit 709fb3
{
Packit 709fb3
  struct stat statbuf;
Packit 709fb3
Packit 709fb3
  assure (0 <= fd);
Packit 709fb3
  if (REPLACE_OPEN_DIRECTORY
Packit 709fb3
      || (fstat (fd, &statbuf) == 0 && S_ISDIR (statbuf.st_mode)))
Packit 709fb3
    {
Packit 709fb3
      if (!ensure_dirs_slot (fd)
Packit 709fb3
          || (dirs[fd].name = get_name (filename)) == NULL)
Packit 709fb3
        {
Packit 709fb3
          int saved_errno = errno;
Packit 709fb3
          close (fd);
Packit 709fb3
          errno = saved_errno;
Packit 709fb3
          return -1;
Packit 709fb3
        }
Packit 709fb3
    }
Packit 709fb3
  return fd;
Packit 709fb3
}
Packit 709fb3
Packit 709fb3
/* Mark NEWFD as a duplicate of OLDFD; useful from dup, dup2, dup3,
Packit 709fb3
   and fcntl.  Both arguments must be valid and distinct file
Packit 709fb3
   descriptors.  Close NEWFD and return -1 if OLDFD is tracking a
Packit 709fb3
   directory, but there is insufficient memory to track the same
Packit 709fb3
   directory in NEWFD; otherwise return NEWFD.  */
Packit 709fb3
int
Packit 709fb3
_gl_register_dup (int oldfd, int newfd)
Packit 709fb3
{
Packit 709fb3
  assure (0 <= oldfd && 0 <= newfd && oldfd != newfd);
Packit 709fb3
  if (oldfd < dirs_allocated && dirs[oldfd].name)
Packit 709fb3
    {
Packit 709fb3
      /* Duplicated a directory; must ensure newfd is allocated.  */
Packit 709fb3
      if (!ensure_dirs_slot (newfd)
Packit 709fb3
          || (dirs[newfd].name = strdup (dirs[oldfd].name)) == NULL)
Packit 709fb3
        {
Packit 709fb3
          int saved_errno = errno;
Packit 709fb3
          close (newfd);
Packit 709fb3
          errno = saved_errno;
Packit 709fb3
          newfd = -1;
Packit 709fb3
        }
Packit 709fb3
    }
Packit 709fb3
  else if (newfd < dirs_allocated)
Packit 709fb3
    {
Packit 709fb3
      /* Duplicated a non-directory; ensure newfd is cleared.  */
Packit 709fb3
      free (dirs[newfd].name);
Packit 709fb3
      dirs[newfd].name = NULL;
Packit 709fb3
    }
Packit 709fb3
  return newfd;
Packit 709fb3
}
Packit 709fb3
Packit 709fb3
/* If FD is currently visiting a directory, then return the name of
Packit 709fb3
   that directory.  Otherwise, return NULL and set errno.  */
Packit 709fb3
const char *
Packit 709fb3
_gl_directory_name (int fd)
Packit 709fb3
{
Packit 709fb3
  if (0 <= fd && fd < dirs_allocated && dirs[fd].name != NULL)
Packit 709fb3
    return dirs[fd].name;
Packit 709fb3
  /* At this point, fd is either invalid, or open but not a directory.
Packit 709fb3
     If dup2 fails, errno is correctly EBADF.  */
Packit 709fb3
  if (0 <= fd)
Packit 709fb3
    {
Packit 709fb3
      if (dup2 (fd, fd) == fd)
Packit 709fb3
        errno = ENOTDIR;
Packit 709fb3
    }
Packit 709fb3
  else
Packit 709fb3
    errno = EBADF;
Packit 709fb3
  return NULL;
Packit 709fb3
}
Packit 709fb3
Packit 709fb3
Packit 709fb3
/* Implement fchdir() in terms of chdir().  */
Packit 709fb3
Packit 709fb3
int
Packit 709fb3
fchdir (int fd)
Packit 709fb3
{
Packit 709fb3
  const char *name = _gl_directory_name (fd);
Packit 709fb3
  return name ? chdir (name) : -1;
Packit 709fb3
}