From 5794d89bd24981d2974f1504a04bf09b7efaf251 Mon Sep 17 00:00:00 2001 From: Eric Blake Date: Tue, 15 Sep 2009 07:11:40 -0600 Subject: [PATCH] stat: new module, for mingw bug Depending on the current directory, either stat(".",buf) or stat("./",buf) would fail on mingw. * modules/stat: New file. * lib/stat.c: Likewise. * m4/stat.m4 (gl_FUNC_STAT): Likewise. * m4/sys_stat_h.m4 (gl_SYS_STAT_H_DEFAULTS): Add witnesses. * modules/sys_stat (Makefile.am): Use them. * lib/sys_stat.in.h (stat): Declare replacement. * lib/openat.c (fstatat): Deal with lstat and stat being function macros. * modules/openat (Depends-on): Add inline. * MODULES.html.sh (systems lacking POSIX:2008): Mention module. * doc/posix-functions/stat.texi (stat): Likewise. * modules/stat-tests: New test. * tests/test-stat.c: Likewise. Signed-off-by: Eric Blake --- ChangeLog | 17 +++++++++ MODULES.html.sh | 1 + doc/posix-functions/stat.texi | 14 +++++++- lib/openat.c | 20 ++++++++++- lib/stat.c | 78 +++++++++++++++++++++++++++++++++++++++++ lib/sys_stat.in.h | 16 +++++++++ m4/stat.m4 | 31 +++++++++++++++++ m4/sys_stat_h.m4 | 4 ++- modules/openat | 1 + modules/stat | 26 ++++++++++++++ modules/stat-tests | 12 +++++++ modules/sys_stat | 2 ++ tests/test-stat.c | 81 +++++++++++++++++++++++++++++++++++++++++++ 13 files changed, 300 insertions(+), 3 deletions(-) create mode 100644 lib/stat.c create mode 100644 m4/stat.m4 create mode 100644 modules/stat create mode 100644 modules/stat-tests create mode 100644 tests/test-stat.c diff --git a/ChangeLog b/ChangeLog index fab07337b..fde8d07f2 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,20 @@ +2009-09-19 Eric Blake + + stat: new module, for mingw bug + * modules/stat: New file. + * lib/stat.c: Likewise. + * m4/stat.m4 (gl_FUNC_STAT): Likewise. + * m4/sys_stat_h.m4 (gl_SYS_STAT_H_DEFAULTS): Add witnesses. + * modules/sys_stat (Makefile.am): Use them. + * lib/sys_stat.in.h (stat): Declare replacement. + * lib/openat.c (fstatat): Deal with lstat and stat being function + macros. + * modules/openat (Depends-on): Add inline. + * MODULES.html.sh (systems lacking POSIX:2008): Mention module. + * doc/posix-functions/stat.texi (stat): Likewise. + * modules/stat-tests: New test. + * tests/test-stat.c: Likewise. + 2009-09-19 Jim Meyering syntax-check: detect unnecessary inclusion of canonicalize.h diff --git a/MODULES.html.sh b/MODULES.html.sh index 76741b3b6..fbea183f8 100755 --- a/MODULES.html.sh +++ b/MODULES.html.sh @@ -2331,6 +2331,7 @@ func_all_modules () func_module socket func_module spawn func_module sprintf-posix + func_module stat func_module strdup-posix func_module string func_module strings diff --git a/doc/posix-functions/stat.texi b/doc/posix-functions/stat.texi index 97992e408..a7011ed4e 100644 --- a/doc/posix-functions/stat.texi +++ b/doc/posix-functions/stat.texi @@ -4,10 +4,14 @@ POSIX specification: @url{http://www.opengroup.org/onlinepubs/9699919799/functions/stat.html} -Gnulib module: --- +Gnulib module: stat Portability problems fixed by Gnulib: @itemize +@item +On some platforms, @code{stat(".",buf)} and @code{stat("./",buf)} give +different results: +mingw. @end itemize Portability problems not fixed by Gnulib: @@ -19,4 +23,12 @@ use the @code{AC_SYS_LARGEFILE} macro. @item Cygwin's @code{stat} function sometimes sets @code{errno} to @code{EACCES} when @code{ENOENT} would be more appropriate. +@item +On Windows platforms (excluding Cygwin), @code{st_ino} is always 0. +@item +Because of the definition of @code{struct stat}, it is not possible to +portably replace @code{stat} via an object-like macro. Therefore, +expressions such as @code{(islnk ? lstat : stat) (name, buf)} are not +portable, and should instead be written @code{islnk ? lstat (name, +buf) : stat (name, buf)}. @end itemize diff --git a/lib/openat.c b/lib/openat.c index d22d830b1..a0d0ab455 100644 --- a/lib/openat.c +++ b/lib/openat.c @@ -157,6 +157,24 @@ openat_needs_fchdir (void) return needs_fchdir; } +/* On mingw, the gnulib defines `stat' as a function-like + macro; but using it in AT_FUNC_F2 causes compilation failure + because the preprocessor sees a use of a macro that requires two + arguments but is only given one. Hence, we need an inline + forwarder to get past the preprocessor. */ +static inline int +stat_func (char const *name, struct stat *st) +{ + return stat (name, st); +} + +/* Likewise, if there is no native `lstat', then the gnulib + defined it as stat, which also needs adjustment. */ +#if !HAVE_LSTAT +# undef lstat +# define lstat stat_func +#endif + /* Replacement for Solaris' function by the same name. First, try to simulate it via l?stat ("/proc/self/fd/FD/FILE"). @@ -167,7 +185,7 @@ openat_needs_fchdir (void) #define AT_FUNC_NAME fstatat #define AT_FUNC_F1 lstat -#define AT_FUNC_F2 stat +#define AT_FUNC_F2 stat_func #define AT_FUNC_USE_F1_COND AT_SYMLINK_NOFOLLOW #define AT_FUNC_POST_FILE_PARAM_DECLS , struct stat *st, int flag #define AT_FUNC_POST_FILE_ARGS , st diff --git a/lib/stat.c b/lib/stat.c new file mode 100644 index 000000000..c3400d5b2 --- /dev/null +++ b/lib/stat.c @@ -0,0 +1,78 @@ +/* Work around platform bugs in stat. + 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 */ + +#include + +#include + +#include +#include +#include +#include + +#undef stat + +/* For now, mingw is the only known platform where stat(".") and + stat("./") give different results. Mingw stat has other bugs (such + as st_ino always being 0 on success) which this wrapper does not + work around. But at least this implementation provides the ability + to emulate fchdir correctly. */ + +int +rpl_stat (char const *name, struct stat *st) +{ + int result = stat (name, st); + if (result == -1 && errno == ENOENT) + { + /* Due to mingw's oddities, there are some directories (like + c:\) where stat() only succeeds with a trailing slash, and + other directories (like c:\windows) where stat() only + succeeds without a trailing slash. But we want the two to be + synonymous, since chdir() manages either style. Likewise, Mingw also + reports ENOENT for names longer than PATH_MAX, when we want + ENAMETOOLONG, and for stat("file/"), when we want ENOTDIR. + Fortunately, mingw PATH_MAX is small enough for stack + allocation. */ + char fixed_name[PATH_MAX + 1] = {0}; + size_t len = strlen (name); + bool check_dir = false; + if (PATH_MAX <= len) + errno = ENAMETOOLONG; + else if (len) + { + strcpy (fixed_name, name); + if (ISSLASH (fixed_name[len - 1])) + { + check_dir = true; + while (len && ISSLASH (fixed_name[len - 1])) + fixed_name[--len] = '\0'; + if (!len) + fixed_name[0] = '/'; + } + else + fixed_name[len++] = '/'; + result = stat (fixed_name, st); + if (result == 0 && check_dir && !S_ISDIR (st->st_mode)) + { + result = -1; + errno = ENOTDIR; + } + } + } + return result; +} diff --git a/lib/sys_stat.in.h b/lib/sys_stat.in.h index 869cb0f93..cc438229e 100644 --- a/lib/sys_stat.in.h +++ b/lib/sys_stat.in.h @@ -302,6 +302,22 @@ extern int rpl_lstat (const char *name, struct stat *buf); lstat (p, b)) #endif +#if @GNULIB_STAT@ +# if @REPLACE_STAT@ +/* We can't use the object-like #define stat rpl_stat, because of + struct stat. This means that rpl_stat will not be used if the user + does (stat)(a,b). Oh well. */ +# undef stat +# define stat(name, st) rpl_stat (name, st) +extern int stat (const char *name, struct stat *buf); +# endif +#elif defined GNULIB_POSIXCHECK +# undef stat +# define stat(p,b) \ + (GL_LINK_WARNING ("stat is unportable - " \ + "use gnulib module stat for portability"), \ + stat (p, b)) +#endif #if @GNULIB_FCHMODAT@ # if !@HAVE_FCHMODAT@ diff --git a/m4/stat.m4 b/m4/stat.m4 new file mode 100644 index 000000000..9cc0b4a0a --- /dev/null +++ b/m4/stat.m4 @@ -0,0 +1,31 @@ +# serial 1 + +# Copyright (C) 2009 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +AC_DEFUN([gl_FUNC_STAT], +[ + AC_REQUIRE([AC_CANONICAL_HOST]) dnl for cross-compiles + AC_REQUIRE([gl_AC_DOS]) + AC_REQUIRE([gl_SYS_STAT_H_DEFAULTS]) + dnl mingw is the only known platform where stat(".") and stat("./") differ + AC_CACHE_CHECK([whether stat handles trailing slashes], + [gl_cv_func_stat_works], + [AC_RUN_IFELSE( + [AC_LANG_PROGRAM( + [[#include +]], [[struct stat st; return stat (".", &st) != stat ("./", &st);]])], + [gl_cv_func_stat_works=yes], [gl_cv_func_stat_works=no], + [case $host_os in + mingw*) gl_cv_func_stat_works="guessing no";; + *) gl_cv_func_stat_works="guessing yes";; + esac])]) + case $gl_cv_func_stat_works in + *yes) ;; + *) REPLACE_STAT=1 + AC_LIBOBJ([stat]);; + esac +]) diff --git a/m4/sys_stat_h.m4 b/m4/sys_stat_h.m4 index 4c01807b4..df7b238a4 100644 --- a/m4/sys_stat_h.m4 +++ b/m4/sys_stat_h.m4 @@ -1,4 +1,4 @@ -# sys_stat_h.m4 serial 15 -*- Autoconf -*- +# sys_stat_h.m4 serial 16 -*- Autoconf -*- 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, @@ -56,6 +56,7 @@ AC_DEFUN([gl_SYS_STAT_H_DEFAULTS], GNULIB_MKDIRAT=0; AC_SUBST([GNULIB_MKDIRAT]) GNULIB_MKFIFOAT=0; AC_SUBST([GNULIB_MKFIFOAT]) GNULIB_MKNODAT=0; AC_SUBST([GNULIB_MKNODAT]) + GNULIB_STAT=0; AC_SUBST([GNULIB_STAT]) dnl Assume proper GNU behavior unless another module says otherwise. HAVE_FCHMODAT=1; AC_SUBST([HAVE_FCHMODAT]) HAVE_FSTATAT=1; AC_SUBST([HAVE_FSTATAT]) @@ -67,4 +68,5 @@ AC_DEFUN([gl_SYS_STAT_H_DEFAULTS], REPLACE_FSTATAT=0; AC_SUBST([REPLACE_FSTATAT]) REPLACE_LSTAT=0; AC_SUBST([REPLACE_LSTAT]) REPLACE_MKDIR=0; AC_SUBST([REPLACE_MKDIR]) + REPLACE_STAT=0; AC_SUBST([REPLACE_STAT]) ]) diff --git a/modules/openat b/modules/openat index 276882b5a..27e5c5038 100644 --- a/modules/openat +++ b/modules/openat @@ -22,6 +22,7 @@ fchdir fcntl-h fdopendir gettext-h +inline intprops lchown lstat diff --git a/modules/stat b/modules/stat new file mode 100644 index 000000000..9729b8499 --- /dev/null +++ b/modules/stat @@ -0,0 +1,26 @@ +Description: +stat(): query file information + +Files: +lib/stat.c +m4/dos.m4 +m4/stat.m4 + +Depends-on: +stdbool +sys_stat + +configure.ac: +gl_FUNC_STAT +gl_SYS_STAT_MODULE_INDICATOR([stat]) + +Makefile.am: + +Include: + + +License: +LGPLv2+ + +Maintainer: +Eric Blake diff --git a/modules/stat-tests b/modules/stat-tests new file mode 100644 index 000000000..3d31b5262 --- /dev/null +++ b/modules/stat-tests @@ -0,0 +1,12 @@ +Files: +tests/test-stat.c + +Depends-on: +pathmax +same-inode + +configure.ac: + +Makefile.am: +TESTS += test-stat +check_PROGRAMS += test-stat diff --git a/modules/sys_stat b/modules/sys_stat index b2be2ed8d..a4abafe99 100644 --- a/modules/sys_stat +++ b/modules/sys_stat @@ -33,6 +33,7 @@ sys/stat.h: sys_stat.in.h -e 's|@''GNULIB_MKDIRAT''@|$(GNULIB_MKDIRAT)|g' \ -e 's|@''GNULIB_MKFIFOAT''@|$(GNULIB_MKFIFOAT)|g' \ -e 's|@''GNULIB_MKNODAT''@|$(GNULIB_MKNODAT)|g' \ + -e 's|@''GNULIB_STAT''@|$(GNULIB_STAT)|g' \ -e 's|@''HAVE_FCHMODAT''@|$(HAVE_FCHMODAT)|g' \ -e 's|@''HAVE_FSTATAT''@|$(HAVE_FSTATAT)|g' \ -e 's|@''HAVE_LCHMOD''@|$(HAVE_LCHMOD)|g' \ @@ -44,6 +45,7 @@ sys/stat.h: sys_stat.in.h -e 's|@''REPLACE_FSTATAT''@|$(REPLACE_FSTATAT)|g' \ -e 's|@''REPLACE_LSTAT''@|$(REPLACE_LSTAT)|g' \ -e 's|@''REPLACE_MKDIR''@|$(REPLACE_MKDIR)|g' \ + -e 's|@''REPLACE_STAT''@|$(REPLACE_STAT)|g' \ -e '/definition of GL_LINK_WARNING/r $(LINK_WARNING_H)' \ < $(srcdir)/sys_stat.in.h; \ } > $@-t && \ diff --git a/tests/test-stat.c b/tests/test-stat.c new file mode 100644 index 000000000..337c819c1 --- /dev/null +++ b/tests/test-stat.c @@ -0,0 +1,81 @@ +/* Tests of stat. + 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 + +#include "pathmax.h" +#include "same-inode.h" + +#define ASSERT(expr) \ + do \ + { \ + if (!(expr)) \ + { \ + fprintf (stderr, "%s:%d: assertion failed\n", __FILE__, __LINE__); \ + fflush (stderr); \ + abort (); \ + } \ + } \ + while (0) + +#define BASE "test-stat.t" + +int +main () +{ + struct stat st1; + struct stat st2; + char cwd[PATH_MAX]; + + ASSERT (getcwd (cwd, PATH_MAX) == cwd); + ASSERT (stat (".", &st1) == 0); + ASSERT (stat ("./", &st2) == 0); + ASSERT (SAME_INODE (st1, st2)); + ASSERT (stat (cwd, &st2) == 0); + ASSERT (SAME_INODE (st1, st2)); + ASSERT (stat ("/", &st1) == 0); + ASSERT (stat ("///", &st2) == 0); + ASSERT (SAME_INODE (st1, st2)); + + errno = 0; + ASSERT (stat ("", &st1) == -1); + ASSERT (errno == ENOENT); + errno = 0; + ASSERT (stat ("nosuch", &st1) == -1); + ASSERT (errno == ENOENT); + errno = 0; + ASSERT (stat ("nosuch/", &st1) == -1); + ASSERT (errno == ENOENT); + + ASSERT (close (creat (BASE "file", 0600)) == 0); + ASSERT (stat (BASE "file", &st1) == 0); + errno = 0; + ASSERT (stat (BASE "file/", &st1) == -1); + ASSERT (errno == ENOTDIR); + ASSERT (unlink (BASE "file") == 0); + + return 0; +} -- 2.11.0