From 8fc864a0b873beea0c37acb7898a8cf5f497fb4e Mon Sep 17 00:00:00 2001 From: Eric Blake Date: Mon, 31 Aug 2009 06:09:08 -0600 Subject: [PATCH] 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. Signed-off-by: Eric Blake --- ChangeLog | 15 +++++++ doc/posix-functions/open.texi | 11 +++++- lib/fchdir.c | 63 +++++++++++++++++++----------- lib/open.c | 29 +++++++++++++- lib/sys_stat.in.h | 4 ++ m4/fchdir.m4 | 13 ++++++- modules/fchdir | 1 + modules/fchdir-tests | 11 ++++++ tests/test-fchdir.c | 91 +++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 211 insertions(+), 27 deletions(-) create mode 100644 modules/fchdir-tests create mode 100644 tests/test-fchdir.c diff --git a/ChangeLog b/ChangeLog index f8745fea1..8bf26215e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,20 @@ 2009-08-31 Eric Blake + 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. diff --git a/doc/posix-functions/open.texi b/doc/posix-functions/open.texi index 27990a0fc..6a919d815 100644 --- a/doc/posix-functions/open.texi +++ b/doc/posix-functions/open.texi @@ -4,9 +4,9 @@ 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 @@ -18,6 +18,13 @@ On Windows platforms (excluding Cygwin), this function does usually not 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 diff --git a/lib/fchdir.c b/lib/fchdir.c index d51d9630f..cedbcde7f 100644 --- a/lib/fchdir.c +++ b/lib/fchdir.c @@ -30,8 +30,17 @@ #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. */ @@ -77,6 +86,8 @@ ensure_dirs_slot (size_t fd) /* 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) { @@ -89,6 +100,9 @@ _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) { @@ -96,14 +110,28 @@ _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(). */ @@ -218,27 +246,16 @@ rpl_dup2_fchdir (int oldfd, int newfd) 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; } diff --git a/lib/open.c b/lib/open.c index 326e6d15c..5cfef1fb7 100644 --- a/lib/open.c +++ b/lib/open.c @@ -39,6 +39,10 @@ orig_open (const char *filename, int flags, mode_t mode) #include #include +#ifndef REPLACE_OPEN_DIRECTORY +# define REPLACE_OPEN_DIRECTORY 0 +#endif + int open (const char *filename, int flags, ...) { @@ -98,6 +102,29 @@ 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. @@ -129,7 +156,7 @@ open (const char *filename, int flags, ...) #endif #ifdef FCHDIR_REPLACEMENT - if (fd >= 0) + if (!REPLACE_OPEN_DIRECTORY && 0 <= fd) _gl_register_fd (fd, filename); #endif diff --git a/lib/sys_stat.in.h b/lib/sys_stat.in.h index da965d027..9d511da6f 100644 --- a/lib/sys_stat.in.h +++ b/lib/sys_stat.in.h @@ -302,6 +302,10 @@ extern int rpl_lstat (const char *name, struct stat *buf); 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 diff --git a/m4/fchdir.m4 b/m4/fchdir.m4 index d5dd3e2d0..49e6634f8 100644 --- a/m4/fchdir.m4 +++ b/m4/fchdir.m4 @@ -1,4 +1,4 @@ -# 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, @@ -18,6 +18,17 @@ AC_DEFUN([gl_FUNC_FCHDIR], 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 +]], [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 ]) diff --git a/modules/fchdir b/modules/fchdir index 17e842c64..d3fe0e538 100644 --- a/modules/fchdir +++ b/modules/fchdir @@ -15,6 +15,7 @@ fcntl-h include_next open strdup +sys_stat unistd configure.ac: diff --git a/modules/fchdir-tests b/modules/fchdir-tests new file mode 100644 index 000000000..f3bb8b237 --- /dev/null +++ b/modules/fchdir-tests @@ -0,0 +1,11 @@ +Files: +tests/test-fchdir.c + +Depends-on: +getcwd + +configure.ac: + +Makefile.am: +TESTS += test-fchdir +check_PROGRAMS += test-fchdir diff --git a/tests/test-fchdir.c b/tests/test-fchdir.c new file mode 100644 index 000000000..b361d0d61 --- /dev/null +++ b/tests/test-fchdir.c @@ -0,0 +1,91 @@ +/* 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 . */ + +/* Written by Eric Blake , 2009. */ + +#include + +#include + +#include +#include +#include +#include +#include + +#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; +} -- 2.11.0