From 6ca71ffe395184749d849f5bba4771d6b2fbb7d6 Mon Sep 17 00:00:00 2001 From: Eric Blake Date: Mon, 14 Sep 2009 16:57:55 -0600 Subject: [PATCH] remove: new module, for mingw and Solaris 9 bugs Mingw obeys C89, but not POSIX, by not handling directories. Solaris remove("file/") mistakenly succeeded. * modules/remove: New file. * lib/remove.c: Likewise. * m4/remove.m4 (gl_FUNC_REMOVE): Likewise. * m4/stdio_h.m4 (gl_STDIO_H_DEFAULTS): Add witnesses. * modules/stdio (Makefile.am): Use them. * lib/stdio.in.h (remove): Declare replacement. * MODULES.html.sh (systems lacking POSIX:2008): Mention module. * doc/posix-functions/remove.texi (remove): Likewise. * modules/remove-tests: New test. * tests/test-remove.c: Likewise. Signed-off-by: Eric Blake --- ChangeLog | 12 ++++ MODULES.html.sh | 1 + doc/posix-functions/remove.texi | 13 ++++- lib/remove.c | 43 ++++++++++++++ lib/stdio.in.h | 14 +++++ m4/remove.m4 | 40 +++++++++++++ m4/stdio_h.m4 | 4 +- modules/remove | 27 +++++++++ modules/remove-tests | 12 ++++ modules/stdio | 2 + tests/test-remove.c | 122 ++++++++++++++++++++++++++++++++++++++++ 11 files changed, 288 insertions(+), 2 deletions(-) create mode 100644 lib/remove.c create mode 100644 m4/remove.m4 create mode 100644 modules/remove create mode 100644 modules/remove-tests create mode 100644 tests/test-remove.c diff --git a/ChangeLog b/ChangeLog index 64bdd7c2f..48defa33f 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,17 @@ 2009-09-19 Eric Blake + remove: new module, for mingw and Solaris 9 bugs + * modules/remove: New file. + * lib/remove.c: Likewise. + * m4/remove.m4 (gl_FUNC_REMOVE): Likewise. + * m4/stdio_h.m4 (gl_STDIO_H_DEFAULTS): Add witnesses. + * modules/stdio (Makefile.am): Use them. + * lib/stdio.in.h (remove): Declare replacement. + * MODULES.html.sh (systems lacking POSIX:2008): Mention module. + * doc/posix-functions/remove.texi (remove): Likewise. + * modules/remove-tests: New test. + * tests/test-remove.c: Likewise. + unlink: new module, for Solaris 9 bug * modules/unlink: New file. * lib/unlink.c: Likewise. diff --git a/MODULES.html.sh b/MODULES.html.sh index f8a2fb42b..9104d5427 100755 --- a/MODULES.html.sh +++ b/MODULES.html.sh @@ -2319,6 +2319,7 @@ func_all_modules () func_module realloc-posix func_module recv func_module recvfrom + func_module remove func_module sched func_module select func_module send diff --git a/doc/posix-functions/remove.texi b/doc/posix-functions/remove.texi index 39950068b..b3e3d1b1f 100644 --- a/doc/posix-functions/remove.texi +++ b/doc/posix-functions/remove.texi @@ -4,10 +4,21 @@ POSIX specification: @url{http://www.opengroup.org/onlinepubs/9699919799/functions/remove.html} -Gnulib module: --- +Gnulib module: remove Portability problems fixed by Gnulib: @itemize +@item +This function fails to reject trailing slashes on non-directories on +some platforms: +Solaris 9. +@item +This function mistakenly removes a directory with +@code{remove("dir/./")} on some platforms: +Cygwin 1.5.x. +@item +This function does not remove empty directories on some platforms: +mingw. @end itemize Portability problems not fixed by Gnulib: diff --git a/lib/remove.c b/lib/remove.c new file mode 100644 index 000000000..49863e52e --- /dev/null +++ b/lib/remove.c @@ -0,0 +1,43 @@ +/* Remove a file or 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 . */ + +/* written by Eric Blake */ + +#include + +#include + +#include +#include + +#undef remove + +/* Remove NAME from the file system. This works around C89 platforms + that don't handle directories like POSIX requires; it also works + around Solaris 9 bugs with trailing slash. */ +int +rpl_remove (char const *name) +{ + /* It is faster to just try rmdir, and fall back on unlink, than it + is to use lstat to see what we are about to remove. Technically, + it is more likely that we want unlink, not rmdir, but we cannot + guarantee the safety of unlink on directories. Trailing slash + bugs are handled by our rmdir and unlink wrappers. */ + int result = rmdir (name); + if (result && errno == ENOTDIR) + result = unlink (name); + return result; +} diff --git a/lib/stdio.in.h b/lib/stdio.in.h index 27cd30551..35109c399 100644 --- a/lib/stdio.in.h +++ b/lib/stdio.in.h @@ -416,6 +416,20 @@ extern int putchar (int c); extern int puts (const char *string); #endif +#if @GNULIB_REMOVE@ +# if @REPLACE_REMOVE@ +# undef remove +# define remove rpl_remove +extern int remove (const char *name); +# endif +#elif defined GNULIB_POSIXCHECK +# undef remove +# define remove(n) \ + (GL_LINK_WARNING ("remove cannot handle directories on some platforms - " \ + "use gnulib module remove for more portability"), \ + remove (n)) +#endif + #if @GNULIB_RENAME@ # if @REPLACE_RENAME@ # undef rename diff --git a/m4/remove.m4 b/m4/remove.m4 new file mode 100644 index 000000000..7bb5a0cf5 --- /dev/null +++ b/m4/remove.m4 @@ -0,0 +1,40 @@ +# remove.m4 serial 1 +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. + +AC_DEFUN([gl_FUNC_REMOVE], +[ + AC_REQUIRE([AC_CANONICAL_HOST]) dnl for cross-compiles + AC_REQUIRE([gl_AC_DOS]) + AC_REQUIRE([gl_STDIO_H_DEFAULTS]) + AC_REQUIRE([gl_FUNC_RMDIR]) + AC_REQUIRE([gl_FUNC_UNLINK]) + if test "$gl_cv_func_rmdir_works:$gl_cv_func_unlink_works" != yes:yes; then + dnl If either underlying syscall is broken, then remove likely has + dnl the same bug; blindly use our replacement. + REPLACE_REMOVE=1 + AC_LIBOBJ([remove]) + else + dnl C89 requires remove(), but only POSIX requires it to handle + dnl directories. On mingw, directories fails with EPERM. + AC_CACHE_CHECK([whether remove handles directories], + [gl_cv_func_remove_dir_works], + [mkdir conftest.dir + AC_RUN_IFELSE( + [AC_LANG_PROGRAM( + [[#include +]], [[return remove ("conftest.dir");]])], + [gl_cv_func_remove_dir_works=yes], [gl_cv_func_remove_dir_works=no], + [case $host_os in + mingw*) gl_cv_func_remove_dir_works="guessing no";; + *) gl_cv_func_remove_dir_works="guessing yes";; + esac]) + rm -rf conftest.dir]) + case $gl_cv_func_remove_dir_works in + *no*) REPLACE_REMOVE=1 + AC_LIBOBJ([remove]);; + esac + fi +]) diff --git a/m4/stdio_h.m4 b/m4/stdio_h.m4 index ac5e20aba..01af04da9 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, @@ -67,6 +67,7 @@ AC_DEFUN([gl_STDIO_H_DEFAULTS], GNULIB_PUTC=0; AC_SUBST([GNULIB_PUTC]) GNULIB_PUTCHAR=0; AC_SUBST([GNULIB_PUTCHAR]) GNULIB_PUTS=0; AC_SUBST([GNULIB_PUTS]) + GNULIB_REMOVE=0; AC_SUBST([GNULIB_REMOVE]) GNULIB_RENAME=0; AC_SUBST([GNULIB_RENAME]) GNULIB_SNPRINTF=0; AC_SUBST([GNULIB_SNPRINTF]) GNULIB_SPRINTF_POSIX=0; AC_SUBST([GNULIB_SPRINTF_POSIX]) @@ -107,6 +108,7 @@ AC_DEFUN([gl_STDIO_H_DEFAULTS], REPLACE_PERROR=0; AC_SUBST([REPLACE_PERROR]) REPLACE_POPEN=0; AC_SUBST([REPLACE_POPEN]) REPLACE_PRINTF=0; AC_SUBST([REPLACE_PRINTF]) + REPLACE_REMOVE=0; AC_SUBST([REPLACE_REMOVE]) REPLACE_RENAME=0; AC_SUBST([REPLACE_RENAME]) REPLACE_SNPRINTF=0; AC_SUBST([REPLACE_SNPRINTF]) REPLACE_SPRINTF=0; AC_SUBST([REPLACE_SPRINTF]) diff --git a/modules/remove b/modules/remove new file mode 100644 index 000000000..c2a305ed2 --- /dev/null +++ b/modules/remove @@ -0,0 +1,27 @@ +Description: +remove(): remove a file or directory + +Files: +lib/remove.c +m4/dos.m4 +m4/remove.m4 + +Depends-on: +rmdir +stdio +unlink + +configure.ac: +gl_FUNC_REMOVE +gl_STDIO_MODULE_INDICATOR([remove]) + +Makefile.am: + +Include: + + +License: +LGPL + +Maintainer: +Eric Blake diff --git a/modules/remove-tests b/modules/remove-tests new file mode 100644 index 000000000..fdc26c272 --- /dev/null +++ b/modules/remove-tests @@ -0,0 +1,12 @@ +Files: +tests/test-remove.c + +Depends-on: +sys_stat + +configure.ac: +AC_CHECK_FUNCS_ONCE([symlink]) + +Makefile.am: +TESTS += test-remove +check_PROGRAMS += test-remove diff --git a/modules/stdio b/modules/stdio index 8f242cc50..22b83f730 100644 --- a/modules/stdio +++ b/modules/stdio @@ -52,6 +52,7 @@ stdio.h: stdio.in.h -e 's|@''GNULIB_PUTC''@|$(GNULIB_PUTC)|g' \ -e 's|@''GNULIB_PUTCHAR''@|$(GNULIB_PUTCHAR)|g' \ -e 's|@''GNULIB_PUTS''@|$(GNULIB_PUTS)|g' \ + -e 's|@''GNULIB_REMOVE''@|$(GNULIB_REMOVE)|g' \ -e 's|@''GNULIB_RENAME''@|$(GNULIB_RENAME)|g' \ -e 's|@''GNULIB_SNPRINTF''@|$(GNULIB_SNPRINTF)|g' \ -e 's|@''GNULIB_SPRINTF_POSIX''@|$(GNULIB_SPRINTF_POSIX)|g' \ @@ -89,6 +90,7 @@ stdio.h: stdio.in.h -e 's|@''REPLACE_PERROR''@|$(REPLACE_PERROR)|g' \ -e 's|@''REPLACE_POPEN''@|$(REPLACE_POPEN)|g' \ -e 's|@''REPLACE_PRINTF''@|$(REPLACE_PRINTF)|g' \ + -e 's|@''REPLACE_REMOVE''@|$(REPLACE_REMOVE)|g' \ -e 's|@''REPLACE_RENAME''@|$(REPLACE_RENAME)|g' \ -e 's|@''REPLACE_SNPRINTF''@|$(REPLACE_SNPRINTF)|g' \ -e 's|@''REPLACE_SPRINTF''@|$(REPLACE_SPRINTF)|g' \ diff --git a/tests/test-remove.c b/tests/test-remove.c new file mode 100644 index 000000000..787cde263 --- /dev/null +++ b/tests/test-remove.c @@ -0,0 +1,122 @@ +/* Tests of remove. + 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 + +#if !HAVE_SYMLINK +# define symlink(a,b) (-1) +#endif + +#define ASSERT(expr) \ + do \ + { \ + if (!(expr)) \ + { \ + fprintf (stderr, "%s:%d: assertion failed\n", __FILE__, __LINE__); \ + fflush (stderr); \ + abort (); \ + } \ + } \ + while (0) + +#define BASE "test-remove.t" + +int +main () +{ + /* Remove any leftovers from a previous partial run. */ + ASSERT (system ("rm -rf " BASE "*") == 0); + + /* Setup. */ + ASSERT (mkdir (BASE "dir", 0700) == 0); + ASSERT (close (creat (BASE "dir/file", 0600)) == 0); + + /* Basic error conditions. */ + errno = 0; + ASSERT (remove ("") == -1); + ASSERT (errno == ENOENT); + errno = 0; + ASSERT (remove ("nosuch") == -1); + ASSERT (errno == ENOENT); + errno = 0; + ASSERT (remove ("nosuch/") == -1); + ASSERT (errno == ENOENT); + errno = 0; + ASSERT (remove (".") == -1); + ASSERT (errno == EINVAL || errno == EBUSY); + /* Resulting errno after ".." or "/" is too varied to test; it is + reasonable to see any of EINVAL, EEXIST, ENOTEMPTY, EACCES. */ + ASSERT (remove ("..") == -1); + ASSERT (remove ("/") == -1); + ASSERT (remove ("///") == -1); + errno = 0; + ASSERT (remove (BASE "dir/file/") == -1); + ASSERT (errno == ENOTDIR); + + /* Non-empty directory. */ + errno = 0; + ASSERT (remove (BASE "dir") == -1); + ASSERT (errno == EEXIST || errno == ENOTEMPTY); + + /* Non-directory. */ + ASSERT (remove (BASE "dir/file") == 0); + + /* Empty directory. */ + errno = 0; + ASSERT (remove (BASE "dir/./") == -1); + ASSERT (errno == EINVAL || errno == EBUSY); + ASSERT (remove (BASE "dir") == 0); + + /* Test symlink behavior. Specifying trailing slash should remove + referent directory, or cause ENOTDIR failure, but not touch + symlink. */ + if (symlink (BASE "dir", BASE "link") != 0) + { + fputs ("skipping test: symlinks not supported on this filesystem\n", + stderr); + return 77; + } + ASSERT (mkdir (BASE "dir", 0700) == 0); + errno = 0; + if (remove (BASE "link/") == 0) + { + struct stat st; + errno = 0; + ASSERT (stat (BASE "link", &st) == -1); + ASSERT (errno == ENOENT); + } + else + ASSERT (remove (BASE "dir") == 0); + { + struct stat st; + ASSERT (lstat (BASE "link", &st) == 0); + ASSERT (S_ISLNK (st.st_mode)); + } + ASSERT (remove (BASE "link") == 0); + + return 0; +} -- 2.11.0