bug-gnulib
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

mkfifoat, renameat


From: Eric Blake
Subject: mkfifoat, renameat
Date: Mon, 07 Sep 2009 21:43:35 -0600
User-agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.23) Gecko/20090812 Thunderbird/2.0.0.23 Mnenhy/0.7.6.666

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Two more *at functions.  mkfifoat is rather simple; the hardest part was
ensuring the test still compiles on mingw.  renameat is more complex,
although I intentionally made it call into a helper function, at_func2, so
that I can reuse that function when I do linkat.  The renameat testsuite
intentionally exercises as many successful control paths of the helper
function as I could manage, prior to doing semantic checks of various
error conditions.  There are still some pending questions to the Austin
Group on what POSIX says rename _should_ do, so for now, I'm okay with
commented out portions in the test suite.  I verified that the unit test
passed on Linux, Solaris 10, cygwin 1.5, and cygwin 1.7, although it
failed miserably on mingw.

Part of the problem on mingw is that the fchdir module relies on
canonicalize doing the right thing, but mingw's paths (with drive letters
and \, rather than leading / for absolute) throws canonicalize for a loop;
and cross-compilation tries to replace getcwd, even though mingw's getcwd
always works.  At that point, the populated dirs[fd].name mapping have
invalid entries, which hurt attempts to do fstat(dfd).  It doesn't help
that mingw stat() reports st_ino == 0 for every directory, rendering
SAME_INODE useless.  Oh well, I'll have to spend more time porting those
pieces to mingw.  But for now, I think renameat is far enough along to at
least post for review.

I suppose there might still be some pathological cases where renameat
currently fails but could be made to succeed.  For example,
renameat(super_deep,a,root,b) will probably fail with ENAMETOOLONG when
changing to super_deep and converting a to an absolute name, while
changing to root and converting b to absolute would avoid that issue.  But
I'm not too worried about those unless someone actually reports that they
encountered it in practice.

- --
Don't work too hard, make some time for fun as well!

Eric Blake             address@hidden
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (Cygwin)
Comment: Public key at home.comcast.net/~ericblake/eblake.gpg
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/

iEYEARECAAYFAkql0ucACgkQ84KuGfSFAYDRWwCZAfNS9a/nHigBF5Dh4FU1u+Qi
ZYYAn1reDC2j3lUocZCmEI+cXmtz/wV8
=og/P
-----END PGP SIGNATURE-----
>From 625504c335cdbded06262bf5607d07c1fb7675d0 Mon Sep 17 00:00:00 2001
From: Eric Blake <address@hidden>
Date: Mon, 7 Sep 2009 05:38:18 -0600
Subject: [PATCH 1/2] mkfifoat: new module

* modules/mkfifoat: New file.
* lib/mkfifoat.c: Likewise.
* m4/mkfifoat.m4 (gl_FUNC_MKFIFOAT): 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 (mkfifoat, mknodat): Declare them.
* MODULES.html.sh (File system functions): Mention module.
* doc/posix-functions/mkfifoat.texi (mkfifoat): Likewise.
* doc/posix-functions/mknodat.texi (mknodat): Likewise.
* modules/mkfifoat-tests: New test.
* tests/test-mkfifoat.c: Likewise.

Signed-off-by: Eric Blake <address@hidden>
---
 ChangeLog                         |   13 ++++
 MODULES.html.sh                   |    1 +
 doc/posix-functions/mkfifoat.texi |   13 +++-
 doc/posix-functions/mknodat.texi  |   13 +++-
 lib/mkfifoat.c                    |  101 ++++++++++++++++++++++++++++++
 lib/sys_stat.in.h                 |   23 +++++++
 m4/mkfifoat.m4                    |   23 +++++++
 m4/sys_stat_h.m4                  |   32 +++++----
 modules/mkfifoat                  |   29 +++++++++
 modules/mkfifoat-tests            |   11 +++
 modules/sys_stat                  |    9 ++-
 tests/test-mkfifoat.c             |  124 +++++++++++++++++++++++++++++++++++++
 12 files changed, 368 insertions(+), 24 deletions(-)
 create mode 100644 lib/mkfifoat.c
 create mode 100644 m4/mkfifoat.m4
 create mode 100644 modules/mkfifoat
 create mode 100644 modules/mkfifoat-tests
 create mode 100644 tests/test-mkfifoat.c

diff --git a/ChangeLog b/ChangeLog
index 7df9226..c409f2f 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,18 @@
 2009-09-08  Eric Blake  <address@hidden>

+       mkfifoat: new module
+       * modules/mkfifoat: New file.
+       * lib/mkfifoat.c: Likewise.
+       * m4/mkfifoat.m4 (gl_FUNC_MKFIFOAT): 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 (mkfifoat, mknodat): Declare them.
+       * MODULES.html.sh (File system functions): Mention module.
+       * doc/posix-functions/mkfifoat.texi (mkfifoat): Likewise.
+       * doc/posix-functions/mknodat.texi (mknodat): Likewise.
+       * modules/mkfifoat-tests: New test.
+       * tests/test-mkfifoat.c: Likewise.
+
        fstatat: fix compilation on Solaris
        * lib/fstatat.c (includes): Add fcntl.h.
        Reported by Pádraig Brady.
diff --git a/MODULES.html.sh b/MODULES.html.sh
index abfd93b..76741b3 100755
--- a/MODULES.html.sh
+++ b/MODULES.html.sh
@@ -2465,6 +2465,7 @@ func_all_modules ()
   func_module lchmod
   func_module lchown
   func_module mkancesdirs
+  func_module mkfifoat
   func_module mkdir-p
   func_module modechange
   func_module mountlist
diff --git a/doc/posix-functions/mkfifoat.texi 
b/doc/posix-functions/mkfifoat.texi
index 087b4d7..c3e39ca 100644
--- a/doc/posix-functions/mkfifoat.texi
+++ b/doc/posix-functions/mkfifoat.texi
@@ -4,16 +4,21 @@ mkfifoat

 POSIX specification: 
@url{http://www.opengroup.org/onlinepubs/9699919799/functions/mkfifoat.html}

-Gnulib module: ---
+Gnulib module: mkfifoat

 Portability problems fixed by Gnulib:
 @itemize
address@hidden
+This function is missing on some platforms:
+glibc 2.3.6, MacOS X 10.3, FreeBSD 6.0, NetBSD 3.0, OpenBSD 3.8, AIX
+5.1, HP-UX 11, IRIX 6.5, OSF/1 5.1, Solaris 10, Cygwin 1.5.x, mingw,
+Interix 3.5, BeOS.
 @end itemize

 Portability problems not fixed by Gnulib:
 @itemize
 @item
-This function is missing on some platforms:
-glibc 2.3.6, MacOS X 10.3, FreeBSD 6.0, NetBSD 3.0, OpenBSD 3.8, AIX
-5.1, HP-UX 11, IRIX 6.5, OSF/1 5.1, Solaris 10, Cygwin 1.5.x, mingw, Interix 
3.5, BeOS.
+The gnulib replacement function always fails with @samp{ENOSYS} on
+some platforms:
+mingw.
 @end itemize
diff --git a/doc/posix-functions/mknodat.texi b/doc/posix-functions/mknodat.texi
index aec66a6..d1af058 100644
--- a/doc/posix-functions/mknodat.texi
+++ b/doc/posix-functions/mknodat.texi
@@ -4,16 +4,21 @@ mknodat

 POSIX specification: 
@url{http://www.opengroup.org/onlinepubs/9699919799/functions/mknodat.html}

-Gnulib module: ---
+Gnulib module: mkfifoat

 Portability problems fixed by Gnulib:
 @itemize
address@hidden
+This function is missing on some platforms:
+glibc 2.3.6, MacOS X 10.3, FreeBSD 6.0, NetBSD 3.0, OpenBSD 3.8, AIX
+5.1, HP-UX 11, IRIX 6.5, OSF/1 5.1, Solaris 10, Cygwin 1.5.x, mingw,
+Interix 3.5, BeOS.
 @end itemize

 Portability problems not fixed by Gnulib:
 @itemize
 @item
-This function is missing on some platforms:
-glibc 2.3.6, MacOS X 10.3, FreeBSD 6.0, NetBSD 3.0, OpenBSD 3.8, AIX
-5.1, HP-UX 11, IRIX 6.5, OSF/1 5.1, Solaris 10, Cygwin 1.5.x, mingw, Interix 
3.5, BeOS.
+The gnulib replacement function always fails with @samp{ENOSYS} on
+some platforms:
+mingw.
 @end itemize
diff --git a/lib/mkfifoat.c b/lib/mkfifoat.c
new file mode 100644
index 0000000..a230b00
--- /dev/null
+++ b/lib/mkfifoat.c
@@ -0,0 +1,101 @@
+/* Create a named fifo relative to an open directory.
+   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 "dirname.h" /* solely for definition of IS_ABSOLUTE_FILE_NAME */
+#include "openat.h"
+#include "openat-priv.h"
+#include "save-cwd.h"
+
+#ifndef HAVE_MKFIFO
+# define HAVE_MKFIFO 0
+#endif
+#ifndef HAVE_MKNOD
+# define HAVE_MKNOD 0
+#endif
+
+/* For now, all known systems either have both mkfifo and mknod, or
+   neither.  If this is not true, we can implement the portable
+   aspects of one using the other (POSIX only requires mknod to create
+   fifos; all other uses of mknod are for root users and outside the
+   realm of POSIX).  */
+#if HAVE_MKNOD != HAVE_MKFIFO
+# error Please report this message and your system to address@hidden
+#endif
+
+#if !HAVE_MKFIFO
+/* Mingw lacks mkfifo and mknod, so this wrapper is trivial.  */
+
+# include <errno.h>
+
+int
+mkfifoat (int fd _UNUSED_PARAMETER_, char const *path _UNUSED_PARAMETER_,
+         mode_t mode _UNUSED_PARAMETER_)
+{
+  errno = ENOSYS;
+  return -1;
+}
+
+int
+mknodat (int fd _UNUSED_PARAMETER_, char const *path _UNUSED_PARAMETER_,
+        mode_t mode _UNUSED_PARAMETER_, dev_t dev _UNUSED_PARAMETER_)
+{
+  errno = ENOSYS;
+  return -1;
+}
+
+#else /* HAVE_MKFIFO */
+
+/* Create a named fifo FILE relative to directory FD, with access
+   permissions in MODE.  If possible, do it without changing the
+   working directory.  Otherwise, resort to using save_cwd/fchdir,
+   then mkfifo/restore_cwd.  If either the save_cwd or the restore_cwd
+   fails, then give a diagnostic and exit nonzero.  */
+
+# define AT_FUNC_NAME mkfifoat
+# define AT_FUNC_F1 mkfifo
+# define AT_FUNC_POST_FILE_PARAM_DECLS , mode_t mode
+# define AT_FUNC_POST_FILE_ARGS        , mode
+# include "at-func.c"
+# undef AT_FUNC_NAME
+# undef AT_FUNC_F1
+# undef AT_FUNC_POST_FILE_PARAM_DECLS
+# undef AT_FUNC_POST_FILE_ARGS
+
+/* Create a file system node FILE relative to directory FD, with
+   access permissions and file type in MODE, and device type in DEV.
+   Usually, non-root applications can only create named fifos, with
+   DEV set to 0.  If possible, create the node without changing the
+   working directory.  Otherwise, resort to using save_cwd/fchdir,
+   then mknod/restore_cwd.  If either the save_cwd or the restore_cwd
+   fails, then give a diagnostic and exit nonzero.  */
+
+# define AT_FUNC_NAME mknodat
+# define AT_FUNC_F1 mknod
+# define AT_FUNC_POST_FILE_PARAM_DECLS , mode_t mode, dev_t dev
+# define AT_FUNC_POST_FILE_ARGS        , mode, dev
+# include "at-func.c"
+# undef AT_FUNC_NAME
+# undef AT_FUNC_F1
+# undef AT_FUNC_POST_FILE_PARAM_DECLS
+# undef AT_FUNC_POST_FILE_ARGS
+
+#endif /* HAVE_MKFIFO */
diff --git a/lib/sys_stat.in.h b/lib/sys_stat.in.h
index 0c45229..b0ea92d 100644
--- a/lib/sys_stat.in.h
+++ b/lib/sys_stat.in.h
@@ -345,6 +345,29 @@ extern int mkdirat (int fd, char const *file, mode_t mode);
      mkdirat (d, n, m))
 #endif

+#if @GNULIB_MKFIFOAT@
+# if address@hidden@
+int mkfifoat (int fd, char const *file, mode_t mode);
+# endif
+#elif defined GNULIB_POSIXCHECK
+# undef mkfifoat
+# define mkfifoat(d,n,m)                                    \
+    (GL_LINK_WARNING ("mkfifoat is not portable - " \
+                      "use gnulib module mkfifoat for portability"), \
+     mkfifoat (d, n, m))
+#endif
+
+#if @GNULIB_MKNODAT@
+# if address@hidden@
+int mknodat (int fd, char const *file, mode_t mode, dev_t dev);
+# endif
+#elif defined GNULIB_POSIXCHECK
+# undef mknodat
+# define mknodat(f,n,m,d)                           \
+    (GL_LINK_WARNING ("mknodat is not portable - " \
+                      "use gnulib module mkfifoat for portability"), \
+     mknodat (f, n, m, d))
+#endif

 #if @REPLACE_FCHDIR@
 # define fstat rpl_fstat
diff --git a/m4/mkfifoat.m4 b/m4/mkfifoat.m4
new file mode 100644
index 0000000..99e2336
--- /dev/null
+++ b/m4/mkfifoat.m4
@@ -0,0 +1,23 @@
+# serial 1
+# See if we need to provide mkfifoat/mknodat replacement.
+
+dnl Copyright (C) 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,
+dnl with or without modifications, as long as this notice is preserved.
+
+# Written by Eric Blake.
+
+AC_DEFUN([gl_FUNC_MKFIFOAT],
+[
+  AC_REQUIRE([gl_SYS_STAT_H_DEFAULTS])
+  AC_REQUIRE([gl_FUNC_OPENAT])
+  AC_REQUIRE([gl_USE_SYSTEM_EXTENSIONS])
+  AC_CHECK_FUNCS_ONCE([mkfifo mknod mkfifoat mknodat])
+  if test $ac_cv_func_mkfifoat = no; then
+    # No known system has mkfifoat but not mknodat
+    HAVE_MKFIFOAT=0
+    HAVE_MKNODAT=0
+    AC_LIBOBJ([mkfifoat])
+  fi
+])
diff --git a/m4/sys_stat_h.m4 b/m4/sys_stat_h.m4
index d29f695..8b62902 100644
--- a/m4/sys_stat_h.m4
+++ b/m4/sys_stat_h.m4
@@ -1,11 +1,11 @@
-# sys_stat_h.m4 serial 13   -*- Autoconf -*-
+# sys_stat_h.m4 serial 14   -*- 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,
 dnl with or without modifications, as long as this notice is preserved.

 dnl From Eric Blake.
-dnl Test whether <sys/stat.h> contains lstat and mkdir or must be substituted.
+dnl Provide a GNU-like <sys/stat.h>.

 AC_DEFUN([gl_HEADER_SYS_STAT_H],
 [
@@ -49,17 +49,21 @@ AC_DEFUN([gl_SYS_STAT_MODULE_INDICATOR],
 AC_DEFUN([gl_SYS_STAT_H_DEFAULTS],
 [
   AC_REQUIRE([gl_UNISTD_H_DEFAULTS]) dnl for REPLACE_FCHDIR
-  GNULIB_FCHMODAT=0; AC_SUBST([GNULIB_FCHMODAT])
-  GNULIB_FSTATAT=0;  AC_SUBST([GNULIB_FSTATAT])
-  GNULIB_LCHMOD=0;   AC_SUBST([GNULIB_LCHMOD])
-  GNULIB_LSTAT=0;    AC_SUBST([GNULIB_LSTAT])
-  GNULIB_MKDIRAT=0;  AC_SUBST([GNULIB_MKDIRAT])
+  GNULIB_FCHMODAT=0;  AC_SUBST([GNULIB_FCHMODAT])
+  GNULIB_FSTATAT=0;   AC_SUBST([GNULIB_FSTATAT])
+  GNULIB_LCHMOD=0;    AC_SUBST([GNULIB_LCHMOD])
+  GNULIB_LSTAT=0;     AC_SUBST([GNULIB_LSTAT])
+  GNULIB_MKDIRAT=0;   AC_SUBST([GNULIB_MKDIRAT])
+  GNULIB_MKFIFOAT=0;  AC_SUBST([GNULIB_MKFIFOAT])
+  GNULIB_MKNODAT=0;   AC_SUBST([GNULIB_MKNODAT])
   dnl Assume proper GNU behavior unless another module says otherwise.
-  HAVE_FCHMODAT=1;   AC_SUBST([HAVE_FCHMODAT])
-  HAVE_FSTATAT=1;    AC_SUBST([HAVE_FSTATAT])
-  HAVE_LCHMOD=1;     AC_SUBST([HAVE_LCHMOD])
-  HAVE_MKDIRAT=1;    AC_SUBST([HAVE_MKDIRAT])
-  REPLACE_FSTATAT=0; AC_SUBST([REPLACE_FSTATAT])
-  REPLACE_LSTAT=0;   AC_SUBST([REPLACE_LSTAT])
-  REPLACE_MKDIR=0;   AC_SUBST([REPLACE_MKDIR])
+  HAVE_FCHMODAT=1;    AC_SUBST([HAVE_FCHMODAT])
+  HAVE_FSTATAT=1;     AC_SUBST([HAVE_FSTATAT])
+  HAVE_LCHMOD=1;      AC_SUBST([HAVE_LCHMOD])
+  HAVE_MKDIRAT=1;     AC_SUBST([HAVE_MKDIRAT])
+  HAVE_MKFIFOAT=1;    AC_SUBST([HAVE_MKFIFOAT])
+  HAVE_MKNODAT=1;     AC_SUBST([HAVE_MKNODAT])
+  REPLACE_FSTATAT=0;  AC_SUBST([REPLACE_FSTATAT])
+  REPLACE_LSTAT=0;    AC_SUBST([REPLACE_LSTAT])
+  REPLACE_MKDIR=0;    AC_SUBST([REPLACE_MKDIR])
 ])
diff --git a/modules/mkfifoat b/modules/mkfifoat
new file mode 100644
index 0000000..db2f0da
--- /dev/null
+++ b/modules/mkfifoat
@@ -0,0 +1,29 @@
+Description:
+mkfifoat() and mknodat(): create named FIFOs relative to a directory
+
+Files:
+lib/mkfifoat.c
+m4/mkfifoat.m4
+
+Depends-on:
+extensions
+fcntl-h
+openat
+sys_stat
+
+configure.ac:
+gl_FUNC_MKFIFOAT
+gl_UNISTD_MODULE_INDICATOR([mkfifoat])
+gl_UNISTD_MODULE_INDICATOR([mknodat])
+
+Makefile.am:
+
+Include:
+<fcntl.h>
+<sys/stat.h>
+
+License:
+GPL
+
+Maintainer:
+Jim Meyering, Eric Blake
diff --git a/modules/mkfifoat-tests b/modules/mkfifoat-tests
new file mode 100644
index 0000000..9ff5091
--- /dev/null
+++ b/modules/mkfifoat-tests
@@ -0,0 +1,11 @@
+Files:
+tests/test-mkfifoat.c
+
+Depends-on:
+
+configure.ac:
+
+Makefile.am:
+TESTS += test-mkfifoat
+check_PROGRAMS += test-mkfifoat
+test_mkfifoat_LDADD = $(LDADD) @LIBINTL@
diff --git a/modules/sys_stat b/modules/sys_stat
index 35bafcc..19fa5dd 100644
--- a/modules/sys_stat
+++ b/modules/sys_stat
@@ -31,15 +31,20 @@ sys/stat.h: sys_stat.in.h
              -e 's|@''GNULIB_LCHMOD''@|$(GNULIB_LCHMOD)|g' \
              -e 's|@''GNULIB_LSTAT''@|$(GNULIB_LSTAT)|g' \
              -e 's|@''GNULIB_MKDIRAT''@|$(GNULIB_MKDIRAT)|g' \
+             -e 's|@''GNULIB_MKFIFOAT''@|$(GNULIB_MKFIFOAT)|g' \
+             -e 's|@''GNULIB_MKNODAT''@|$(GNULIB_MKNODAT)|g' \
+             -e 's|@''GNULIB_OPENAT''@|$(GNULIB_OPENAT)|g' \
              -e 's|@''HAVE_FCHMODAT''@|$(HAVE_FCHMODAT)|g' \
              -e 's|@''HAVE_FSTATAT''@|$(HAVE_FSTATAT)|g' \
              -e 's|@''HAVE_LCHMOD''@|$(HAVE_LCHMOD)|g' \
              -e 's|@''HAVE_LSTAT''@|$(HAVE_LSTAT)|g' \
              -e 's|@''HAVE_MKDIRAT''@|$(HAVE_MKDIRAT)|g' \
-             -e 's|@''REPLACE_LSTAT''@|$(REPLACE_LSTAT)|g' \
-             -e 's|@''REPLACE_MKDIR''@|$(REPLACE_MKDIR)|g' \
+             -e 's|@''HAVE_MKFIFOAT''@|$(HAVE_MKFIFOAT)|g' \
+             -e 's|@''HAVE_MKNODAT''@|$(HAVE_MKNODAT)|g' \
              -e 's|@''REPLACE_FCHDIR''@|$(REPLACE_FCHDIR)|g' \
              -e 's|@''REPLACE_FSTATAT''@|$(REPLACE_FSTATAT)|g' \
+             -e 's|@''REPLACE_LSTAT''@|$(REPLACE_LSTAT)|g' \
+             -e 's|@''REPLACE_MKDIR''@|$(REPLACE_MKDIR)|g' \
              -e '/definition of GL_LINK_WARNING/r $(LINK_WARNING_H)' \
              < $(srcdir)/sys_stat.in.h; \
        } > address@hidden && \
diff --git a/tests/test-mkfifoat.c b/tests/test-mkfifoat.c
new file mode 100644
index 0000000..3853dfe
--- /dev/null
+++ b/tests/test-mkfifoat.c
@@ -0,0 +1,124 @@
+/* Tests of mkfifoat and mknodat.
+   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 <address@hidden>, 2009.  */
+
+#include <config.h>
+
+#include <sys/stat.h>
+
+#include <fcntl.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define ASSERT(expr) \
+  do                                                                         \
+    {                                                                        \
+      if (!(expr))                                                           \
+       {                                                                    \
+         fprintf (stderr, "%s:%d: assertion failed\n", __FILE__, __LINE__);  \
+         fflush (stderr);                                                   \
+         abort ();                                                          \
+       }                                                                    \
+    }                                                                        \
+  while (0)
+
+typedef int (*test_func) (int, char const *, mode_t);
+
+/* Wrapper to make testing mknodat easier.  */
+static int
+test_mknodat (int fd, char const *name, mode_t mode)
+{
+  /* This is the only portable use of mknodat, per POSIX.  */
+  return mknodat (fd, name, mode | S_IFIFO, 0);
+}
+
+int
+main ()
+{
+  int i;
+  test_func funcs[2] = { mkfifoat, test_mknodat };
+  const char *fifo = "test-mkfifoat.fifo";
+
+  /* Create handle for future use.  */
+  int dfd = openat (AT_FDCWD, ".", O_RDONLY);
+  ASSERT (0 <= dfd);
+
+#if !HAVE_MKFIFO
+  fputs ("skipping test: no support for named fifos\n", stderr);
+  return 77;
+#endif
+
+  /* Clean up anything from previous incomplete test.  */
+  remove (fifo);
+
+  /* Test both functions.  */
+  for (i = 0; i < 2; i++)
+    {
+      struct stat st;
+      test_func func = funcs[i];
+
+      /* Sanity checks of failures.  */
+      errno = 0;
+      ASSERT (func (AT_FDCWD, "", 0600) == -1);
+      ASSERT (errno == ENOENT);
+      errno = 0;
+      ASSERT (func (dfd, "", S_IRUSR | S_IWUSR) == -1);
+      ASSERT (errno == ENOENT);
+      errno = 0;
+      ASSERT (func (AT_FDCWD, ".", 0600) == -1);
+      /* POSIX requires EEXIST, but Solaris gives EINVAL.  */
+      ASSERT (errno == EEXIST || errno == EINVAL);
+      errno = 0;
+      ASSERT (func (dfd, ".", 0600) == -1);
+      ASSERT (errno == EEXIST || errno == EINVAL);
+
+      /* Create fifo while cwd is '.', then stat it from '..'.  */
+      ASSERT (func (AT_FDCWD, fifo, 0600) == 0);
+      errno = 0;
+      ASSERT (func (dfd, fifo, 0600) == -1);
+      ASSERT (errno == EEXIST);
+      ASSERT (chdir ("..") == 0);
+      errno = 0;
+      ASSERT (fstatat (AT_FDCWD, fifo, &st, 0) == -1);
+      ASSERT (errno == ENOENT);
+      memset (&st, 0, sizeof st);
+      ASSERT (fstatat (dfd, fifo, &st, 0) == 0);
+      ASSERT (S_ISFIFO (st.st_mode));
+      ASSERT (unlinkat (dfd, fifo, 0) == 0);
+
+      /* Create fifo while cwd is '..', then stat it from '.'.  */
+      ASSERT (func (dfd, fifo, 0600) == 0);
+      ASSERT (fchdir (dfd) == 0);
+      errno = 0;
+      ASSERT (func (AT_FDCWD, fifo, 0600) == -1);
+      ASSERT (errno == EEXIST);
+      memset (&st, 0, sizeof st);
+      ASSERT (fstatat (AT_FDCWD, fifo, &st, AT_SYMLINK_NOFOLLOW) == 0);
+      ASSERT (S_ISFIFO (st.st_mode));
+      memset (&st, 0, sizeof st);
+      ASSERT (fstatat (dfd, fifo, &st, AT_SYMLINK_NOFOLLOW) == 0);
+      ASSERT (S_ISFIFO (st.st_mode));
+      ASSERT (unlink (fifo) == 0);
+    }
+
+  ASSERT (close (dfd) == 0);
+
+  return 0;
+}
-- 
1.6.3.2


>From 1fdee599436ff8016fbcc7f6bfcf28c20aaf2639 Mon Sep 17 00:00:00 2001
From: Eric Blake <address@hidden>
Date: Mon, 7 Sep 2009 06:45:59 -0600
Subject: [PATCH 2/2] renameat: new module

* modules/renameat: New file.
* lib/at-func2.c (at_func2): Likewise.
* lib/renameat.c (renameat): Likewise.
* m4/renameat.m4 (gl_FUNC_RENAMEAT): Likewise.
* m4/stdio_h.m4 (gl_STDIO_H_DEFAULTS): Add witnesses.
* modules/stdio (Makefile.am): Substitute them.
* lib/stdio.in.h (renameat): Declare it.
* MODULES.html.sh (systems lacking POSIX:2008): Mention module.
* doc/posix-functions/renameat.texi (renameat): Likewise.
* doc/posix-functions/rename.texi (rename): Tweak wording.
* modules/renameat-tests: New test.
* tests/test-renameat.c: Likewise.

Signed-off-by: Eric Blake <address@hidden>
---
 ChangeLog                         |   14 ++
 MODULES.html.sh                   |    1 +
 doc/posix-functions/rename.texi   |    3 +-
 doc/posix-functions/renameat.texi |   12 +-
 lib/at-func2.c                    |  283 +++++++++++++++++++++++++++++++++++++
 lib/openat-priv.h                 |    5 +
 lib/renameat.c                    |   35 +++++
 lib/stdio.in.h                    |   12 ++
 m4/renameat.m4                    |   23 +++
 m4/stdio_h.m4                     |    4 +-
 modules/renameat                  |   33 +++++
 modules/renameat-tests            |   14 ++
 modules/stdio                     |    2 +
 tests/test-renameat.c             |  184 ++++++++++++++++++++++++
 14 files changed, 619 insertions(+), 6 deletions(-)
 create mode 100644 lib/at-func2.c
 create mode 100644 lib/renameat.c
 create mode 100644 m4/renameat.m4
 create mode 100644 modules/renameat
 create mode 100644 modules/renameat-tests
 create mode 100644 tests/test-renameat.c

diff --git a/ChangeLog b/ChangeLog
index c409f2f..1d1187a 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,19 @@
 2009-09-08  Eric Blake  <address@hidden>

+       renameat: new module
+       * modules/renameat: New file.
+       * lib/at-func2.c (at_func2): Likewise.
+       * lib/renameat.c (renameat): Likewise.
+       * m4/renameat.m4 (gl_FUNC_RENAMEAT): Likewise.
+       * m4/stdio_h.m4 (gl_STDIO_H_DEFAULTS): Add witnesses.
+       * modules/stdio (Makefile.am): Substitute them.
+       * lib/stdio.in.h (renameat): Declare it.
+       * MODULES.html.sh (systems lacking POSIX:2008): Mention module.
+       * doc/posix-functions/renameat.texi (renameat): Likewise.
+       * doc/posix-functions/rename.texi (rename): Tweak wording.
+       * modules/renameat-tests: New test.
+       * tests/test-renameat.c: Likewise.
+
        mkfifoat: new module
        * modules/mkfifoat: New file.
        * lib/mkfifoat.c: Likewise.
diff --git a/MODULES.html.sh b/MODULES.html.sh
index 76741b3..df47168 100755
--- a/MODULES.html.sh
+++ b/MODULES.html.sh
@@ -2342,6 +2342,7 @@ func_all_modules ()
   func_module nanosleep
   func_module regex
   func_module rename
+  func_module renameat
   func_module rmdir
   func_module search
   func_module sigaction
diff --git a/doc/posix-functions/rename.texi b/doc/posix-functions/rename.texi
index 84a03ab..d7f4cb7 100644
--- a/doc/posix-functions/rename.texi
+++ b/doc/posix-functions/rename.texi
@@ -20,7 +20,8 @@ rename

 Portability problems not fixed by Gnulib:
 @itemize
-This function will not replace a destination that is currently opened
address@hidden
+This function will not rename a source that is currently opened
 by any process:
 mingw.
 @item
diff --git a/doc/posix-functions/renameat.texi 
b/doc/posix-functions/renameat.texi
index de7fa9a..509794e 100644
--- a/doc/posix-functions/renameat.texi
+++ b/doc/posix-functions/renameat.texi
@@ -4,16 +4,20 @@ renameat

 POSIX specification: 
@url{http://www.opengroup.org/onlinepubs/9699919799/functions/renameat.html}

-Gnulib module: ---
+Gnulib module: renameat

 Portability problems fixed by Gnulib:
 @itemize
address@hidden
+This function is missing on some platforms:
+glibc 2.3.6, MacOS X 10.3, FreeBSD 6.0, NetBSD 3.0, OpenBSD 3.8, AIX
+5.1, HP-UX 11, IRIX 6.5, OSF/1 5.1, Cygwin 1.5.x, mingw, Interix 3.5, BeOS.
 @end itemize

 Portability problems not fixed by Gnulib:
 @itemize
 @item
-This function is missing on some platforms:
-glibc 2.3.6, MacOS X 10.3, FreeBSD 6.0, NetBSD 3.0, OpenBSD 3.8, AIX
-5.1, HP-UX 11, IRIX 6.5, OSF/1 5.1, Cygwin 1.5.x, mingw, Interix 3.5, BeOS.
+This function will not rename a source that is currently opened
+by any process:
+mingw.
 @end itemize
diff --git a/lib/at-func2.c b/lib/at-func2.c
new file mode 100644
index 0000000..083bc18
--- /dev/null
+++ b/lib/at-func2.c
@@ -0,0 +1,283 @@
+/* Define an at-style functions like linkat or renameat.
+   Copyright (C) 2006, 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 Jim Meyering and Eric Blake */
+
+#include <config.h>
+
+#include "openat-priv.h"
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "dirname.h" /* solely for definition of IS_ABSOLUTE_FILE_NAME */
+#include "openat.h"
+#include "same-inode.h"
+#include "save-cwd.h"
+
+/* Call FUNC to operate on a pair of files, where FILE1 is relative to FD1,
+   and FILE2 is relative to FD2.  If possible, do it without changing the
+   working directory.  Otherwise, resort to using save_cwd/fchdir,
+   FUNC, restore_cwd (up to two times).  If either the save_cwd or the
+   restore_cwd fails, then give a diagnostic and exit nonzero.  */
+int
+at_func2 (int fd1, char const *file1,
+          int fd2, char const *file2,
+          int (*func) (char const *file1, char const *file2))
+{
+  struct saved_cwd saved_cwd;
+  int saved_errno;
+  int err;
+  char *file1_alt;
+  char *file2_alt;
+
+  /* There are 16 possible scenarios, based on whether an fd is
+     AT_FDCWD or real, and whether a file is absolute or relative:
+
+         fd1  file1 fd2  file2  action
+     0   cwd  abs   cwd  abs    direct call
+     1   cwd  abs   cwd  rel    direct call
+     2   cwd  abs   fd   abs    direct call
+     3   cwd  abs   fd   rel    chdir to fd2
+     4   cwd  rel   cwd  abs    direct call
+     5   cwd  rel   cwd  rel    direct call
+     6   cwd  rel   fd   abs    direct call
+     7   cwd  rel   fd   rel    convert file1 to abs, then case 3
+     8   fd   abs   cwd  abs    direct call
+     9   fd   abs   cwd  rel    direct call
+     10  fd   abs   fd   abs    direct call
+     11  fd   abs   fd   rel    chdir to fd2
+     12  fd   rel   cwd  abs    chdir to fd1
+     13  fd   rel   cwd  rel    convert file2 to abs, then case 12
+     14  fd   rel   fd   abs    chdir to fd1
+     15a fd1  rel   fd1  rel    chdir to fd1
+     15b fd1  rel   fd2  rel    chdir to fd1, then case 7
+
+     Try some optimizations to reduce fd to AT_FDCWD, or to at least
+     avoid converting an absolute name or doing a double chdir.  */
+
+  if ((fd1 == AT_FDCWD || IS_ABSOLUTE_FILE_NAME (file1))
+      && (fd2 == AT_FDCWD || IS_ABSOLUTE_FILE_NAME (file2)))
+    return func (file1, file2); /* Case 0-2, 4-6, 8-10.  */
+
+  /* If /proc/self/fd works, we don't need any stat or chdir.  */
+  {
+    char proc_buf1[OPENAT_BUFFER_SIZE];
+    char *proc_file1 = ((fd1 == AT_FDCWD || IS_ABSOLUTE_FILE_NAME (file1))
+                        ? (char *) file1
+                        : openat_proc_name (proc_buf1, fd1, file1));
+    if (proc_file1)
+      {
+        char proc_buf2[OPENAT_BUFFER_SIZE];
+        char *proc_file2 = ((fd2 == AT_FDCWD || IS_ABSOLUTE_FILE_NAME (file2))
+                            ? (char *) file2
+                            : openat_proc_name (proc_buf2, fd2, file2));
+        if (proc_file2)
+          {
+            int proc_result = func (proc_file1, proc_file2);
+            int proc_errno = errno;
+            if (proc_file1 != proc_buf1 && proc_file1 != file1)
+              free (proc_file1);
+            if (proc_file2 != proc_buf2 && proc_file2 != file2)
+              free (proc_file2);
+            /* If the syscall succeeds, or if it fails with an unexpected
+               errno value, then return right away.  Otherwise, fall through
+               and resort to using save_cwd/restore_cwd.  */
+            if (0 <= proc_result)
+              return proc_result;
+            if (! EXPECTED_ERRNO (proc_errno))
+              {
+                errno = proc_errno;
+                return proc_result;
+              }
+          }
+        else if (proc_file1 != proc_buf1 && proc_file1 != file1)
+          free (proc_buf1);
+      }
+  }
+
+  /* Cases 3, 7, 11-15 remain.  Time to normalize directory fds, if
+     possible.  */
+  if (IS_ABSOLUTE_FILE_NAME (file1))
+    fd1 = AT_FDCWD; /* Case 11 reduced to 3.  */
+  else if (IS_ABSOLUTE_FILE_NAME (file2))
+    fd2 = AT_FDCWD; /* Case 14 reduced to 12.  */
+
+  /* Cases 3, 7, 12, 13, 15 remain.  */
+
+  if (fd1 == AT_FDCWD) /* Cases 3, 7.  */
+    {
+      struct stat st1;
+      struct stat st2;
+      if (stat (".", &st1) == -1 || fstat (fd2, &st2) == -1)
+        return -1;
+      if (!S_ISDIR (st2.st_mode))
+        {
+          errno = ENOTDIR;
+          return -1;
+        }
+      if (SAME_INODE (st1, st2)) /* Reduced to cases 1, 5.  */
+        return func (file1, file2);
+    }
+  else if (fd2 == AT_FDCWD) /* Cases 12, 13.  */
+    {
+      struct stat st1;
+      struct stat st2;
+      if (stat (".", &st2) == -1 || fstat (fd1, &st1) == -1)
+        return -1;
+      if (!S_ISDIR (st1.st_mode))
+        {
+          errno = ENOTDIR;
+          return -1;
+        }
+      if (SAME_INODE (st1, st2)) /* Reduced to cases 4, 5.  */
+        return func (file1, file2);
+    }
+  else if (fd1 != fd2) /* Case 15b.  */
+    {
+      struct stat st1;
+      struct stat st2;
+      if (fstat (fd1, &st1) == -1 || fstat (fd2, &st2) == -1)
+        return -1;
+      if (!S_ISDIR (st1.st_mode) || !S_ISDIR (st2.st_mode))
+        {
+          errno = ENOTDIR;
+          return -1;
+        }
+      if (SAME_INODE (st1, st2)) /* Reduced to case 15a.  */
+        {
+          fd2 = fd1;
+          if (stat (".", &st1) == 0 && SAME_INODE (st1, st2))
+            return func (file1, file2); /* Further reduced to case 5.  */
+        }
+    }
+
+  /* Cases 3, 7, 12, 13, 15a, 15b remain.  With all reductions in
+     place, it is time to start changing directories.  */
+
+  if (save_cwd (&saved_cwd) != 0)
+    openat_save_fail (errno);
+
+  if (fd1 != AT_FDCWD && fd2 != AT_FDCWD && fd1 != fd2) /* Case 15b.  */
+    {
+      if (fchdir (fd1) != 0)
+        {
+          saved_errno = errno;
+          free_cwd (&saved_cwd);
+          errno = saved_errno;
+          return -1;
+        }
+      fd1 = AT_FDCWD; /* Reduced to case 7.  */
+    }
+
+  /* Cases 3, 7, 12, 13, 15a remain.  Convert one relative name to
+     absolute, if necessary.  */
+
+  file1_alt = (char *) file1;
+  file2_alt = (char *) file2;
+
+  if (fd1 == AT_FDCWD && !IS_ABSOLUTE_FILE_NAME (file1)) /* Case 7.  */
+    {
+      /* It would be nicer to use:
+         file1_alt = file_name_concat (xgetcwd (), file1, NULL);
+         but libraries should not call xalloc_die.  */
+      char *cwd = getcwd (NULL, 0);
+      size_t len;
+      if (!cwd)
+        {
+          saved_errno = errno;
+          free_cwd (&saved_cwd);
+          errno = saved_errno;
+          return -1;
+        }
+      len = strlen (cwd);
+      if (cwd[len - 1] == '/')
+        cwd[--len] = '\0';
+      file1_alt = malloc (len + strlen (file1) + 2);
+      if (!file1_alt)
+        {
+          saved_errno = errno;
+          free (cwd);
+          free_cwd (&saved_cwd);
+          errno = saved_errno;
+          return -1;
+        }
+      stpcpy (stpcpy (stpcpy (file1_alt, cwd), "/"), file1);
+      free (cwd); /* Reduced to case 3.  */
+    }
+  else if (fd2 == AT_FDCWD && !IS_ABSOLUTE_FILE_NAME (file2)) /* Case 13.  */
+    {
+      char *cwd = getcwd (NULL, 0);
+      size_t len;
+      if (!cwd)
+        {
+          saved_errno = errno;
+          free_cwd (&saved_cwd);
+          errno = saved_errno;
+          return -1;
+        }
+      len = strlen (cwd);
+      if (cwd[len - 1] == '/')
+        cwd[--len] = '\0';
+      file2_alt = malloc (len + strlen (file2) + 2);
+      if (!file2_alt)
+        {
+          saved_errno = errno;
+          free (cwd);
+          free_cwd (&saved_cwd);
+          errno = saved_errno;
+          return -1;
+        }
+      stpcpy (stpcpy (stpcpy (file2_alt, cwd), "/"), file2);
+      free (cwd); /* Reduced to case 12.  */
+    }
+
+  /* Cases 3, 12, 15a remain.  Change to the correct directory.  */
+  if (fchdir (fd1 == AT_FDCWD ? fd2 : fd1) != 0)
+    {
+      saved_errno = errno;
+      free_cwd (&saved_cwd);
+      if (file1 != file1_alt)
+        free (file1_alt);
+      else if (file2 != file2_alt)
+        free (file2_alt);
+      errno = saved_errno;
+      return -1;
+    }
+
+  /* Finally safe to perform the user's function, then clean up.  */
+
+  err = func (file1_alt, file2_alt);
+  saved_errno = (err < 0 ? errno : 0);
+
+  if (file1 != file1_alt)
+    free (file1_alt);
+  else if (file2 != file2_alt)
+    free (file2_alt);
+
+  if (restore_cwd (&saved_cwd) != 0)
+    openat_restore_fail (errno);
+
+  free_cwd (&saved_cwd);
+
+  if (saved_errno)
+    errno = saved_errno;
+  return err;
+}
+#undef CALL_FUNC
+#undef FUNC_RESULT
diff --git a/lib/openat-priv.h b/lib/openat-priv.h
index 2280416..24411bd 100644
--- a/lib/openat-priv.h
+++ b/lib/openat-priv.h
@@ -32,3 +32,8 @@ char *openat_proc_name (char buf[OPENAT_BUFFER_SIZE], int fd, 
char const *file);
    || (Errno) == EPERM || (Errno) == EACCES    \
    || (Errno) == ENOSYS /* Solaris 8 */                \
    || (Errno) == EOPNOTSUPP /* FreeBSD */)
+
+/* Wrapper function shared among linkat and renameat.  */
+int at_func2 (int fd1, char const *file1,
+             int fd2, char const *file2,
+             int (*func) (char const *file1, char const *file2));
diff --git a/lib/renameat.c b/lib/renameat.c
new file mode 100644
index 0000000..244ee38
--- /dev/null
+++ b/lib/renameat.c
@@ -0,0 +1,35 @@
+/* Rename a file relative to open directories.
+   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 <stdio.h>
+
+#include "openat-priv.h"
+
+/* Rename FILE1, in the directory open on descriptor FD1, to FILE2, in
+   the directory open on descriptor FD2.  If possible, do it without
+   changing the working directory.  Otherwise, resort to using
+   save_cwd/fchdir, then rename/restore_cwd.  If either the save_cwd or
+   the restore_cwd fails, then give a diagnostic and exit nonzero.  */
+
+int
+renameat (int fd1, char const *file1, int fd2, char const *file2)
+{
+  return at_func2 (fd1, file1, fd2, file2, rename);
+}
diff --git a/lib/stdio.in.h b/lib/stdio.in.h
index 27cd305..7d14f84 100644
--- a/lib/stdio.in.h
+++ b/lib/stdio.in.h
@@ -430,6 +430,18 @@ extern int rename (const char *old, const char *new);
     rename (o, n))
 #endif

+#if @GNULIB_RENAMEAT@
+# if address@hidden@
+extern int renameat (int fd1, char const *file1, int fd2, char const *file2);
+# endif
+#elif defined GNULIB_POSIXCHECK
+# undef renameat
+# define renameat(d1,f1,d2,f2)            \
+    (GL_LINK_WARNING ("renameat is not portable - " \
+                      "use gnulib module renameat for portability"), \
+     renameat (d1, f1, d2, f2))
+#endif
+
 #if @GNULIB_SNPRINTF@
 # if @REPLACE_SNPRINTF@
 #  define snprintf rpl_snprintf
diff --git a/m4/renameat.m4 b/m4/renameat.m4
new file mode 100644
index 0000000..2c34e98
--- /dev/null
+++ b/m4/renameat.m4
@@ -0,0 +1,23 @@
+# serial 1
+# See if we need to provide renameat replacement.
+
+dnl Copyright (C) 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,
+dnl with or without modifications, as long as this notice is preserved.
+
+# Written by Eric Blake.
+
+AC_DEFUN([gl_FUNC_RENAMEAT],
+[
+  AC_REQUIRE([gl_FUNC_OPENAT])
+  AC_REQUIRE([gl_FUNC_RENAME_TRAILING_DEST_SLASH])
+  AC_REQUIRE([gl_FUNC_RENAME])
+  AC_REQUIRE([gl_USE_SYSTEM_EXTENSIONS])
+  AC_CHECK_FUNCS_ONCE([renameat])
+  if test $ac_cv_func_renameat = no; then
+    HAVE_RENAMEAT=0
+    AC_LIBOBJ([renameat])
+    AC_LIBOBJ([at-func2])
+  fi
+])
diff --git a/m4/stdio_h.m4 b/m4/stdio_h.m4
index ac5e20a..fa2b577 100644
--- a/m4/stdio_h.m4
+++ b/m4/stdio_h.m4
@@ -1,4 +1,4 @@
-# stdio_h.m4 serial 18
+# stdio_h.m4 serial 19
 dnl Copyright (C) 2007-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,
@@ -68,6 +68,7 @@ AC_DEFUN([gl_STDIO_H_DEFAULTS],
   GNULIB_PUTCHAR=0;              AC_SUBST([GNULIB_PUTCHAR])
   GNULIB_PUTS=0;                 AC_SUBST([GNULIB_PUTS])
   GNULIB_RENAME=0;               AC_SUBST([GNULIB_RENAME])
+  GNULIB_RENAMEAT=0;             AC_SUBST([GNULIB_RENAMEAT])
   GNULIB_SNPRINTF=0;             AC_SUBST([GNULIB_SNPRINTF])
   GNULIB_SPRINTF_POSIX=0;        AC_SUBST([GNULIB_SPRINTF_POSIX])
   GNULIB_STDIO_H_SIGPIPE=0;      AC_SUBST([GNULIB_STDIO_H_SIGPIPE])
@@ -89,6 +90,7 @@ AC_DEFUN([gl_STDIO_H_DEFAULTS],
   HAVE_DPRINTF=1;                AC_SUBST([HAVE_DPRINTF])
   HAVE_FSEEKO=1;                 AC_SUBST([HAVE_FSEEKO])
   HAVE_FTELLO=1;                 AC_SUBST([HAVE_FTELLO])
+  HAVE_RENAMEAT=1;               AC_SUBST([HAVE_RENAMEAT])
   HAVE_VASPRINTF=1;              AC_SUBST([HAVE_VASPRINTF])
   HAVE_VDPRINTF=1;               AC_SUBST([HAVE_VDPRINTF])
   REPLACE_DPRINTF=0;             AC_SUBST([REPLACE_DPRINTF])
diff --git a/modules/renameat b/modules/renameat
new file mode 100644
index 0000000..0b3727b
--- /dev/null
+++ b/modules/renameat
@@ -0,0 +1,33 @@
+Description:
+renameat(): rename a file, relative to two directories
+
+Files:
+lib/at-func2.c
+lib/renameat.c
+m4/renameat.m4
+
+Depends-on:
+extensions
+fcntl-h
+openat
+rename
+rename-dest-slash
+same-inode
+stdio
+stpcpy
+
+configure.ac:
+gl_FUNC_RENAMEAT
+gl_STDIO_MODULE_INDICATOR([renameat])
+
+Makefile.am:
+
+Include:
+<fcntl.h>
+<stdio.h>
+
+License:
+GPL
+
+Maintainer:
+Jim Meyering, Eric Blake
diff --git a/modules/renameat-tests b/modules/renameat-tests
new file mode 100644
index 0000000..61403c1
--- /dev/null
+++ b/modules/renameat-tests
@@ -0,0 +1,14 @@
+Files:
+tests/test-renameat.c
+
+Depends-on:
+filenamecat
+progname
+xgetcwd
+
+configure.ac:
+
+Makefile.am:
+TESTS += test-renameat
+check_PROGRAMS += test-renameat
+test_renameat_LDADD = $(LDADD) @LIBINTL@
diff --git a/modules/stdio b/modules/stdio
index eb58269..a775eb8 100644
--- a/modules/stdio
+++ b/modules/stdio
@@ -53,6 +53,7 @@ stdio.h: stdio.in.h
              -e 's|@''GNULIB_PUTCHAR''@|$(GNULIB_PUTCHAR)|g' \
              -e 's|@''GNULIB_PUTS''@|$(GNULIB_PUTS)|g' \
              -e 's|@''GNULIB_RENAME''@|$(GNULIB_RENAME)|g' \
+             -e 's|@''GNULIB_RENAMEAT''@|$(GNULIB_RENAMEAT)|g' \
              -e 's|@''GNULIB_SNPRINTF''@|$(GNULIB_SNPRINTF)|g' \
              -e 's|@''GNULIB_SPRINTF_POSIX''@|$(GNULIB_SPRINTF_POSIX)|g' \
              -e 's|@''GNULIB_STDIO_H_SIGPIPE''@|$(GNULIB_STDIO_H_SIGPIPE)|g' \
@@ -71,6 +72,7 @@ stdio.h: stdio.in.h
              -e 's|@''HAVE_DECL_SNPRINTF''@|$(HAVE_DECL_SNPRINTF)|g' \
              -e 's|@''HAVE_DECL_VSNPRINTF''@|$(HAVE_DECL_VSNPRINTF)|g' \
              -e 's|@''HAVE_DPRINTF''@|$(HAVE_DPRINTF)|g' \
+             -e 's|@''HAVE_RENAMEAT''@|$(HAVE_RENAMEAT)|g' \
              -e 's|@''HAVE_VASPRINTF''@|$(HAVE_VASPRINTF)|g' \
              -e 's|@''HAVE_VDPRINTF''@|$(HAVE_VDPRINTF)|g' \
              -e 's|@''REPLACE_DPRINTF''@|$(REPLACE_DPRINTF)|g' \
diff --git a/tests/test-renameat.c b/tests/test-renameat.c
new file mode 100644
index 0000000..96f8eb3
--- /dev/null
+++ b/tests/test-renameat.c
@@ -0,0 +1,184 @@
+/* Tests of renameat.
+   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 <address@hidden>, 2009.  */
+
+#include <config.h>
+
+#include <stdio.h>
+
+#include <fcntl.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "filenamecat.h"
+#include "xgetcwd.h"
+
+#define ASSERT(expr) \
+  do                                                                         \
+    {                                                                        \
+      if (!(expr))                                                           \
+       {                                                                    \
+         fprintf (stderr, "%s:%d: assertion failed\n", __FILE__, __LINE__);  \
+         fflush (stderr);                                                   \
+         abort ();                                                          \
+       }                                                                    \
+    }                                                                        \
+  while (0)
+
+#define BASE "test-renameat.t"
+
+int
+main ()
+{
+  int i;
+  int dfd;
+  char *cwd = xgetcwd ();
+
+  /* Clean up any trash from prior testsuite runs.  */
+  ASSERT (system ("rm -rf " BASE "*") == 0);
+
+  /* Create locations to manipulate.  */
+  ASSERT (mkdir (BASE "sub1", 0700) == 0);
+  ASSERT (mkdir (BASE "sub2", 0700) == 0);
+  dfd = creat (BASE "00", 0600);
+  ASSERT (0 <= dfd);
+  ASSERT (close (dfd) == 0);
+
+  dfd = open (BASE "sub1", O_RDONLY);
+  ASSERT (0 <= dfd);
+  ASSERT (chdir (BASE "sub2") == 0);
+
+  /* There are 16 possible scenarios, based on whether an fd is
+     AT_FDCWD or real, and whether a file is absolute or relative.
+
+     To ensure that we test all of the code paths (rather than
+     triggering early normalization optimizations), we use a loop to
+     repeatedly rename a file in the parent directory, use an fd open
+     on subdirectory 1, all while executing in subdirectory 2; all
+     relative names are thus given with a leading "../".  Finally, the
+     last scenario (two relative paths given, neither one AT_FDCWD)
+     has two paths, based on whether the two fds are equivalent, so we
+     do the other variant after the loop.  */
+  for (i = 0; i < 16; i++)
+    {
+      int fd1 = (i & 8) ? dfd : AT_FDCWD;
+      char *file1 = file_name_concat ((i & 4) ? ".." : cwd, BASE "xx", NULL);
+      int fd2 = (i & 2) ? dfd : AT_FDCWD;
+      char *file2 = file_name_concat ((i & 1) ? ".." : cwd, BASE "xx", NULL);
+
+      ASSERT (sprintf (strchr (file1, '\0') - 2, "%02d", i) == 2);
+      ASSERT (sprintf (strchr (file2, '\0') - 2, "%02d", i + 1) == 2);
+      ASSERT (renameat (fd1, file1, fd2, file2) == 0);
+      free (file1);
+      free (file2);
+    }
+  {
+    int fd2 = open ("..", O_RDONLY);
+    ASSERT (0 <= fd2);
+    ASSERT (renameat (dfd, "../" BASE "16", fd2, BASE "17") == 0);
+    ASSERT (close (fd2) == 0);
+  }
+
+  /* Now we change back to the parent directory, and set dfd to ".";
+     using dfd in remaining tests will expose any bugs if emulation
+     via /proc/self/fd doesn't check for empty names.  */
+  ASSERT (chdir ("..") == 0);
+  ASSERT (close (dfd) == 0);
+  dfd = open (".", O_RDONLY);
+  ASSERT (0 <= dfd);
+
+  {
+    int fd = creat (BASE "sub2/file", 0600);
+    ASSERT (0 <= fd);
+    ASSERT (close (fd) == 0);
+    errno = 0;
+    ASSERT (renameat (dfd, BASE "sub1", dfd, BASE "sub2") == -1);
+    ASSERT (errno == EEXIST || errno == ENOTEMPTY);
+    ASSERT (unlink (BASE "sub2/file") == 0);
+  }
+#if 0
+  /* Rename module doesn't work around this Cygwin 1.5 bug yet.  */
+  errno = 0;
+  ASSERT (renameat (dfd, BASE "sub2", dfd, BASE "sub1/.") == -1);
+  ASSERT (errno == EINVAL);
+  errno = 0;
+  ASSERT (renameat (dfd, BASE "sub2/.", dfd, BASE "sub1") == -1);
+  ASSERT (errno == EINVAL);
+#endif
+  errno = 0;
+  ASSERT (renameat (dfd, BASE "17", dfd, BASE "sub1") == -1);
+  ASSERT (errno == EISDIR);
+  errno = 0;
+  ASSERT (renameat (dfd, BASE "nosuch", dfd, BASE "18") == -1);
+  ASSERT (errno == ENOENT);
+  errno = 0;
+  ASSERT (renameat (dfd, "", dfd, BASE "17") == -1);
+  ASSERT (errno == ENOENT);
+  errno = 0;
+  ASSERT (renameat (dfd, BASE "17", dfd, "") == -1);
+  ASSERT (errno == ENOENT);
+#if 0
+  /* Rename module doesn't work around this Cygwin 1.5 bug yet.  */
+  errno = 0;
+  ASSERT (renameat (dfd, BASE "sub2", dfd, BASE "17") == -1);
+  ASSERT (errno == ENOTDIR);
+#endif
+#if 0
+  /* Rename module doesn't work around this Solaris bug yet.  */
+  errno = 0;
+  ASSERT (renameat (dfd, BASE "17/", dfd, BASE "18") == -1);
+  ASSERT (errno == ENOTDIR);
+  errno = 0;
+  ASSERT (renameat (dfd, BASE "17", dfd, BASE "18/") == -1);
+  ASSERT (errno == ENOTDIR);
+#endif
+
+  /* Finally, make sure we can overwrite existing files.  Make sure
+     dfd is closed before we start manipulating sub1, since not all
+     platforms can rename on top of an in-use directory.  */
+  ASSERT (close (dfd) == 0);
+  {
+    int fd = creat (BASE "sub2/file", 0600);
+    ASSERT (0 <= fd);
+    ASSERT (close (fd) == 0);
+    errno = 0;
+    ASSERT (renameat (AT_FDCWD, BASE "sub2", AT_FDCWD, BASE "sub1") == 0);
+    ASSERT (renameat (AT_FDCWD, BASE "sub1/file", AT_FDCWD, BASE "17") == 0);
+  }
+
+  /* Cleanup.  */
+  errno = 0;
+  ASSERT (unlink (BASE "sub1/file") == -1);
+  ASSERT (errno == ENOENT);
+  ASSERT (unlink (BASE "17") == 0);
+  ASSERT (rmdir (BASE "sub1") == 0);
+#if 0
+  /* Rename module doesn't work around a Cygwin 1.5 bug yet, where
+     renaming a directory appears to work until the overwritten
+     directory is removed; so ignore the status of this rmdir.  */
+  errno = 0;
+  ASSERT (rmdir (BASE "sub2") == -1);
+  ASSERT (errno == ENOENT);
+#else
+  rmdir (BASE "sub2");
+#endif
+  free (cwd);
+
+  return 0;
+}
-- 
1.6.3.2


reply via email to

[Prev in Thread] Current Thread [Next in Thread]