* m4/fchdir.m4 (gl_FUNC_FCHDIR): Check for mingw bug.
* lib/open.c (open) [FCHDIR_REPLACEMENT]: If directories can't be
opened, then use a substitute.
* lib/sys_stat.in.h (fstat) [REPLACE_OPEN_DIRECTORY]: Declare
replacement.
* lib/fchdir.c (fstat) [REPLACE_OPEN_DIRECTORY]: Implement it.
(_gl_register_fd): No need to check stat if open already filters
all directories.
(fchdir): Fix error condition to match POSIX.
* modules/fchdir (Depends-on): Add sys_stat.
* doc/posix-functions/open.texi (open): Document the limitation.
* modules/fchdir-tests: New file.
* tests/test-fchdir.c: Likewise.
Signed-off-by: Eric Blake <ebb9@byu.net>
2009-08-31 Eric Blake <ebb9@byu.net>
+ fchdir: port to mingw
+ * m4/fchdir.m4 (gl_FUNC_FCHDIR): Check for mingw bug.
+ * lib/open.c (open) [FCHDIR_REPLACEMENT]: If directories can't be
+ opened, then use a substitute.
+ * lib/sys_stat.in.h (fstat) [REPLACE_OPEN_DIRECTORY]: Declare
+ replacement.
+ * lib/fchdir.c (fstat) [REPLACE_OPEN_DIRECTORY]: Implement it.
+ (_gl_register_fd): No need to check stat if open already filters
+ all directories.
+ (fchdir): Fix error condition to match POSIX.
+ * modules/fchdir (Depends-on): Add sys_stat.
+ * doc/posix-functions/open.texi (open): Document the limitation.
+ * modules/fchdir-tests: New file.
+ * tests/test-fchdir.c: Likewise.
+
canonicalize: allow cross-testing from cygwin to mingw
* modules/canonicalize-tests (configure.ac): Define HAVE_SYMLINK.
(Makefile.am): Pass it through TESTS_ENVIRONMENT.
POSIX specification: @url{http://www.opengroup.org/onlinepubs/9699919799/functions/open.html}
-Gnulib module: open
+Gnulib module: open, fchdir
-Portability problems fixed by Gnulib:
+Portability problems fixed by the Gnulib module open:
@itemize
@item
This function does not fail when the file name argument ends in a slash
recognize the @file{/dev/null} filename.
@end itemize
+Portability problems fixed by the Gnulib module fchdir:
+@itemize
+@item
+On Windows platforms (excluding Cygwin), this function fails to open a
+read-only descriptor for directories.
+@end itemize
+
Portability problems not fixed by Gnulib:
@itemize
@item
#include "canonicalize.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.
+
+ 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 it points to a directory, it stores
info about this directory; otherwise it stores an errno value of ENOTDIR. */
/* 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)
{
}
}
+/* Mark FD as visiting FILENAME. FD must be positive, 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. */
void
_gl_register_fd (int fd, const char *filename)
{
ensure_dirs_slot (fd);
if (fd < dirs_allocated
- && fstat (fd, &statbuf) >= 0 && S_ISDIR (statbuf.st_mode))
+ && (REPLACE_OPEN_DIRECTORY
+ || (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;
+ dirs[fd].saved_errno = errno;
}
}
+/* Return stat information about FD in STATBUF. Needed when
+ rpl_open() used a dummy file to work around an open() that can't
+ normally visit directories. */
+#if REPLACE_OPEN_DIRECTORY
+int
+rpl_fstat (int fd, struct stat *statbuf)
+{
+ if (0 <= fd && fd <= dirs_allocated && dirs[fd].name != NULL)
+ return stat (dirs[fd].name, statbuf);
+ return fstat (fd, statbuf);
+}
+#endif
+
/* Override opendir() and closedir(), to keep track of the open file
descriptors. Needed because there is a function dirfd(). */
int
fchdir (int fd)
{
- if (fd >= 0)
+ if (fd >= 0 && fd < dirs_allocated && dirs[fd].name != NULL)
+ return chdir (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)
{
- 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;
- }
+ if (dup2 (fd, fd) == fd)
+ errno = ENOTDIR;
}
else
- {
- errno = EBADF;
- return -1;
- }
+ errno = EBADF;
+ return -1;
}
#include <sys/types.h>
#include <sys/stat.h>
+#ifndef REPLACE_OPEN_DIRECTORY
+# define REPLACE_OPEN_DIRECTORY 0
+#endif
+
int
open (const char *filename, int flags, ...)
{
fd = orig_open (filename, flags, mode);
+#ifdef FCHDIR_REPLACEMENT
+ /* Implementing fchdir and fdopendir requires the ability to open a
+ directory file descriptor. If open doesn't support that (as on
+ mingw), we use a dummy file that behaves the same as directories
+ on Linux (ie. always reports EOF on attempts to read()), and
+ override fstat() in fchdir.c to hide the fact that we have a
+ dummy. */
+ if (REPLACE_OPEN_DIRECTORY && fd < 0 && errno == EACCES
+ && (mode & O_ACCMODE) == O_RDONLY)
+ {
+ struct stat statbuf;
+ if (stat (filename, &statbuf) == 0 && S_ISDIR (statbuf.st_mode))
+ {
+ /* Maximum recursion depth of 1. */
+ fd = open ("/dev/null", flags, mode);
+ if (0 <= fd)
+ _gl_register_fd (fd, filename);
+ }
+ else
+ errno = EACCES;
+ }
+#endif
+
#if OPEN_TRAILING_SLASH_BUG
/* If the filename ends in a slash and fd does not refer to a directory,
then fail.
#endif
#ifdef FCHDIR_REPLACEMENT
- if (fd >= 0)
+ if (!REPLACE_OPEN_DIRECTORY && 0 <= fd)
_gl_register_fd (fd, filename);
#endif
lstat (p, b))
#endif
+#if defined FCHDIR_REPLACEMENT && REPLACE_OPEN_DIRECTORY
+# define fstat rpl_fstat
+extern int fstat (int fd, struct stat *buf);
+#endif
#if @REPLACE_MKDIR@
# undef mkdir
-# fchdir.m4 serial 7
+# fchdir.m4 serial 8
dnl Copyright (C) 2006-2009 Free Software Foundation, Inc.
dnl This file is free software; the Free Software Foundation
dnl gives unlimited permission to copy and/or distribute it,
gl_REPLACE_OPEN
gl_REPLACE_CLOSE
gl_REPLACE_DIRENT_H
+ AC_CACHE_CHECK([whether open can visit directories],
+ [gl_cv_func_open_directory_works],
+ [AC_RUN_IFELSE([AC_LANG_PROGRAM([[#include <fcntl.h>
+]], [return open(".", O_RDONLY);])],
+ [gl_cv_func_open_directory_works=yes],
+ [gl_cv_func_open_directory_works=no],
+ [gl_cv_func_open_directory_works="guessing no"])])
+ if test "$gl_cv_func_open_directory_works" != yes; then
+ AC_DEFINE([REPLACE_OPEN_DIRECTORY], [1], [Define to 1 if open() should
+work around the inability to open a directory.])
+ fi
fi
])
include_next
open
strdup
+sys_stat
unistd
configure.ac:
--- /dev/null
+Files:
+tests/test-fchdir.c
+
+Depends-on:
+getcwd
+
+configure.ac:
+
+Makefile.am:
+TESTS += test-fchdir
+check_PROGRAMS += test-fchdir
--- /dev/null
+/* Test changing to a directory named by a file descriptor.
+ Copyright (C) 2009 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 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
+ 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, see <http://www.gnu.org/licenses/>. */
+
+/* Written by Eric Blake <ebb9@byu.net>, 2009. */
+
+#include <config.h>
+
+#include <unistd.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define ASSERT(expr) \
+ do \
+ { \
+ if (!(expr)) \
+ { \
+ fprintf (stderr, "%s:%d: assertion failed\n", __FILE__, __LINE__); \
+ fflush (stderr); \
+ abort (); \
+ } \
+ } \
+ while (0)
+
+int
+main ()
+{
+ char *cwd = getcwd (NULL, 0);
+ int fd = open (".", O_RDONLY);
+ int i;
+
+ ASSERT (cwd);
+ ASSERT (0 <= fd);
+
+ /* Check for failure cases. */
+ {
+ int bad_fd = open ("/dev/null", O_RDONLY);
+ ASSERT (0 <= bad_fd);
+ errno = 0;
+ ASSERT (fchdir (bad_fd) == -1);
+ ASSERT (errno == ENOTDIR);
+ ASSERT (close (bad_fd) == 0);
+ errno = 0;
+ ASSERT (fchdir (-1) == -1);
+ ASSERT (errno == EBADF);
+ }
+
+ /* Repeat test twice, once in '.' and once in '..'. */
+ for (i = 0; i < 2; i++)
+ {
+ ASSERT (chdir (".." + 1 - i) == 0);
+ ASSERT (fchdir (fd) == 0);
+ {
+ size_t len = strlen (cwd) + 1;
+ char *new_dir = malloc (len);
+ ASSERT (new_dir);
+ ASSERT (getcwd (new_dir, len) == new_dir);
+ ASSERT (strcmp (cwd, new_dir) == 0);
+ free (new_dir);
+ }
+
+ /* For second iteration, use a cloned fd, to ensure that dup
+ remembers whether an fd was associated with a directory. */
+ if (!i)
+ {
+ int new_fd = dup (fd);
+ ASSERT (0 <= new_fd);
+ ASSERT (close (fd) == 0);
+ fd = new_fd;
+ }
+ }
+
+ free (cwd);
+ return 0;
+}