changeset 12036:1762604ec0a7

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 <ebb9@byu.net>
author Eric Blake <ebb9@byu.net>
date Tue, 15 Sep 2009 07:11:40 -0600
parents 2898ee238452
children eb6b9da995d7
files ChangeLog MODULES.html.sh doc/posix-functions/stat.texi lib/openat.c lib/stat.c lib/sys_stat.in.h m4/stat.m4 m4/sys_stat_h.m4 modules/openat modules/stat modules/stat-tests modules/sys_stat tests/test-stat.c
diffstat 13 files changed, 300 insertions(+), 3 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,20 @@
+2009-09-19  Eric Blake  <ebb9@byu.net>
+
+	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  <meyering@redhat.com>
 
 	syntax-check: detect unnecessary inclusion of canonicalize.h
--- a/MODULES.html.sh
+++ b/MODULES.html.sh
@@ -2331,6 +2331,7 @@
   func_module socket
   func_module spawn
   func_module sprintf-posix
+  func_module stat
   func_module strdup-posix
   func_module string
   func_module strings
--- 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 @@
 @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
--- a/lib/openat.c
+++ b/lib/openat.c
@@ -157,6 +157,24 @@
   return needs_fchdir;
 }
 
+/* On mingw, the gnulib <sys/stat.h> 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
+   <sys/stat.h> 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.
    <http://www.google.com/search?q=fstatat+site:docs.sun.com>
    First, try to simulate it via l?stat ("/proc/self/fd/FD/FILE").
@@ -167,7 +185,7 @@
 
 #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
new file mode 100644
--- /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 <http://www.gnu.org/licenses/>.  */
+
+/* written by Eric Blake */
+
+#include <config.h>
+
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <string.h>
+
+#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;
+}
--- a/lib/sys_stat.in.h
+++ b/lib/sys_stat.in.h
@@ -302,6 +302,22 @@
    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@
new file mode 100644
--- /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 <sys/stat.h>
+]], [[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
+])
--- 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 @@
   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 @@
   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])
 ])
--- a/modules/openat
+++ b/modules/openat
@@ -22,6 +22,7 @@
 fcntl-h
 fdopendir
 gettext-h
+inline
 intprops
 lchown
 lstat
new file mode 100644
--- /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:
+<sys/stat.h>
+
+License:
+LGPLv2+
+
+Maintainer:
+Eric Blake
new file mode 100644
--- /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
--- a/modules/sys_stat
+++ b/modules/sys_stat
@@ -33,6 +33,7 @@
 	      -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 @@
 	      -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 && \
new file mode 100644
--- /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 <http://www.gnu.org/licenses/>.  */
+
+/* Written by Eric Blake <ebb9@byu.net>, 2009.  */
+
+#include <config.h>
+
+#include <sys/stat.h>
+
+#include <fcntl.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#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;
+}