summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEric Blake <eblake@redhat.com>2011-04-26 14:51:24 -0600
committerEric Blake <eblake@redhat.com>2011-04-27 11:58:00 -0600
commit02923aada23a2fa2122b415f313730f2e5e266ec (patch)
treefb419a124d615ec5085e4f1a4c47a43cdbf8381b
parent043be67acb94a724f96e3b18c3a09aefffd463af (diff)
downloadgnulib-02923aada23a2fa2122b415f313730f2e5e266ec.tar.gz
getcwd: enhance tests
The existing getcwd was weak, and only tested things guaranteed by the new getcwd-lgpl. Move those tests into a new file and strengthen them slightly (such as guaranteeing the ERANGE error for a non-zero but too-small size). Then copy the m4 tests for PATH_MAX failures into the GPL getcwd tests, to prove that our replacement really is fixing the things that cause us to reject the system getcwd as non-robust. * tests/test-getcwd-lgpl.c: New file, taken from... * tests/test-getcwd.c: ...old contents. Rewrite this file to repeat long path stress tests from m4 probe. * modules/getcwd-lgpl-tests: New module. * modules/getcwd-tests (Depends-on): Depend on lgpl tests. * m4/getcwd-abort-bug.m4: Update comment. * m4/getcwd-path-max.m4: Likewise. Signed-off-by: Eric Blake <eblake@redhat.com>
-rw-r--r--ChangeLog11
-rw-r--r--m4/getcwd-abort-bug.m41
-rw-r--r--m4/getcwd-path-max.m41
-rw-r--r--modules/getcwd-lgpl-tests12
-rw-r--r--modules/getcwd-tests6
-rw-r--r--tests/test-getcwd-lgpl.c87
-rw-r--r--tests/test-getcwd.c222
7 files changed, 299 insertions, 41 deletions
diff --git a/ChangeLog b/ChangeLog
index b3d036ec35..8cef5ecf43 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,14 @@
+2011-04-26 Eric Blake <eblake@redhat.com>
+
+ getcwd: enhance tests
+ * tests/test-getcwd-lgpl.c: New file, taken from...
+ * tests/test-getcwd.c: ...old contents. Rewrite this file to
+ repeat long path stress tests from m4 probe.
+ * modules/getcwd-lgpl-tests: New module.
+ * modules/getcwd-tests (Depends-on): Depend on lgpl tests.
+ * m4/getcwd-abort-bug.m4: Update comment.
+ * m4/getcwd-path-max.m4: Likewise.
+
2011-04-27 Eric Blake <eblake@redhat.com>
getcwd-lgpl: new module
diff --git a/m4/getcwd-abort-bug.m4 b/m4/getcwd-abort-bug.m4
index f15181f089..0b59a6bf21 100644
--- a/m4/getcwd-abort-bug.m4
+++ b/m4/getcwd-abort-bug.m4
@@ -21,6 +21,7 @@ AC_DEFUN([gl_FUNC_GETCWD_ABORT_BUG],
rm -rf confdir-14B---
# Arrange for deletion of the temporary directory this test creates.
ac_clean_files="$ac_clean_files confdir-14B---"
+ dnl Please keep this in sync with tests/test-getcwd.c.
AC_RUN_IFELSE(
[AC_LANG_SOURCE(
[[
diff --git a/m4/getcwd-path-max.m4 b/m4/getcwd-path-max.m4
index feb0d4dbf2..475ae96d16 100644
--- a/m4/getcwd-path-max.m4
+++ b/m4/getcwd-path-max.m4
@@ -21,6 +21,7 @@ AC_DEFUN([gl_FUNC_GETCWD_PATH_MAX],
gl_cv_func_getcwd_path_max,
[# Arrange for deletion of the temporary directory this test creates.
ac_clean_files="$ac_clean_files confdir3"
+ dnl Please keep this in sync with tests/test-getcwd.c.
AC_RUN_IFELSE(
[AC_LANG_SOURCE(
[[
diff --git a/modules/getcwd-lgpl-tests b/modules/getcwd-lgpl-tests
new file mode 100644
index 0000000000..07fdf73d5c
--- /dev/null
+++ b/modules/getcwd-lgpl-tests
@@ -0,0 +1,12 @@
+Files:
+tests/test-getcwd-lgpl.c
+tests/signature.h
+tests/macros.h
+
+Depends-on:
+
+configure.ac:
+
+Makefile.am:
+TESTS += test-getcwd-lgpl
+check_PROGRAMS += test-getcwd-lgpl
diff --git a/modules/getcwd-tests b/modules/getcwd-tests
index 016961fc71..2187acc75f 100644
--- a/modules/getcwd-tests
+++ b/modules/getcwd-tests
@@ -1,9 +1,11 @@
Files:
tests/test-getcwd.c
-tests/signature.h
-tests/macros.h
Depends-on:
+errno
+fcntl-h
+getcwd-lgpl
+sys_stat
configure.ac:
diff --git a/tests/test-getcwd-lgpl.c b/tests/test-getcwd-lgpl.c
new file mode 100644
index 0000000000..67ee66d539
--- /dev/null
+++ b/tests/test-getcwd-lgpl.c
@@ -0,0 +1,87 @@
+/* Test of getcwd() function.
+ Copyright (C) 2009-2011 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/>. */
+
+#include <config.h>
+
+#include <unistd.h>
+
+#include "signature.h"
+SIGNATURE_CHECK (getcwd, char *, (char *, size_t));
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "macros.h"
+
+int
+main (int argc, char **argv)
+{
+ char *pwd1;
+ char *pwd2;
+ /* If the user provides an argument, attempt to chdir there first. */
+ if (1 < argc)
+ {
+ if (chdir (argv[1]) == 0)
+ printf ("changed to directory %s\n", argv[1]);
+ }
+
+ pwd1 = getcwd (NULL, 0);
+ ASSERT (pwd1 && *pwd1);
+ if (1 < argc)
+ printf ("cwd=%s\n", pwd1);
+
+ /* Make sure the result is usable. */
+ ASSERT (chdir (pwd1) == 0);
+ ASSERT (chdir (".//./.") == 0);
+
+ /* Make sure that result is normalized. */
+ pwd2 = getcwd (NULL, 0);
+ ASSERT (pwd2);
+ ASSERT (strcmp (pwd1, pwd2) == 0);
+ free (pwd2);
+ {
+ size_t len = strlen (pwd1);
+ ssize_t i = len - 10;
+ if (i < 1)
+ i = 1;
+ pwd2 = getcwd (NULL, len + 1);
+ ASSERT (pwd2);
+ free (pwd2);
+ pwd2 = malloc (len + 2);
+ for ( ; i <= len; i++)
+ {
+ errno = 0;
+ ASSERT (getcwd (pwd2, i) == NULL);
+ ASSERT (errno == ERANGE);
+ errno = 0;
+ ASSERT (getcwd (NULL, i) == NULL);
+ ASSERT (errno == ERANGE);
+ }
+ ASSERT (getcwd (pwd2, len + 1) == pwd2);
+ pwd2[len] = '/';
+ pwd2[len + 1] = '\0';
+ }
+ ASSERT (strstr (pwd2, "/./") == NULL);
+ ASSERT (strstr (pwd2, "/../") == NULL);
+ ASSERT (strstr (pwd2 + 1 + (pwd2[1] == '/'), "//") == NULL);
+
+ free (pwd1);
+ free (pwd2);
+
+ return 0;
+}
diff --git a/tests/test-getcwd.c b/tests/test-getcwd.c
index 18fc74f94b..0e0f90f241 100644
--- a/tests/test-getcwd.c
+++ b/tests/test-getcwd.c
@@ -18,59 +18,203 @@
#include <unistd.h>
-#include "signature.h"
-SIGNATURE_CHECK (getcwd, char *, (char *, size_t));
-
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <sys/stat.h>
#include "macros.h"
-int
-main (int argc, char **argv)
+#if ! HAVE_GETPAGESIZE
+# define getpagesize() 0
+#endif
+
+/* This size is chosen to be larger than PATH_MAX (4k), yet smaller than
+ the 16kB pagesize on ia64 linux. Those conditions make the code below
+ trigger a bug in glibc's getcwd implementation before 2.4.90-10. */
+#define TARGET_LEN (5 * 1024)
+
+/* Keep this test in sync with m4/getcwd-abort-bug.m4. */
+static int
+test_abort_bug (void)
{
- char *pwd1;
- char *pwd2;
- /* If the user provides an argument, attempt to chdir there first. */
- if (1 < argc)
+ char const *dir_name = "confdir-14B---";
+ char *cwd;
+ size_t initial_cwd_len;
+ int fail = 0;
+ size_t desired_depth;
+ size_t d;
+
+ /* The bug is triggered when PATH_MAX < getpagesize (), so skip
+ this relatively expensive and invasive test if that's not true. */
+ if (getpagesize () <= PATH_MAX)
+ return 0;
+
+ cwd = getcwd (NULL, 0);
+ if (cwd == NULL)
+ return 0;
+
+ initial_cwd_len = strlen (cwd);
+ free (cwd);
+ desired_depth = ((TARGET_LEN - 1 - initial_cwd_len)
+ / (1 + strlen (dir_name)));
+ for (d = 0; d < desired_depth; d++)
+ {
+ if (mkdir (dir_name, S_IRWXU) < 0 || chdir (dir_name) < 0)
+ {
+ fail = 3; /* Unable to construct deep hierarchy. */
+ break;
+ }
+ }
+
+ /* If libc has the bug in question, this invocation of getcwd
+ results in a failed assertion. */
+ cwd = getcwd (NULL, 0);
+ if (cwd == NULL)
+ fail = 4; /* getcwd failed. This is ok, and expected. */
+ free (cwd);
+
+ /* Call rmdir first, in case the above chdir failed. */
+ rmdir (dir_name);
+ while (0 < d--)
{
- if (chdir (argv[1]) == 0)
- printf ("changed to directory %s\n", argv[1]);
+ if (chdir ("..") < 0)
+ break;
+ rmdir (dir_name);
}
- pwd1 = getcwd (NULL, 0);
- ASSERT (pwd1 && *pwd1);
- if (1 < argc)
- printf ("cwd=%s\n", pwd1);
+ return 0;
+}
- /* Make sure the result is usable. */
- ASSERT (chdir (pwd1) == 0);
- ASSERT (chdir ("././.") == 0);
+/* The length of this name must be 8. */
+#define DIR_NAME "confdir3"
+#define DIR_NAME_LEN 8
+#define DIR_NAME_SIZE (DIR_NAME_LEN + 1)
- /* Make sure that result is normalized. */
- pwd2 = getcwd (NULL, 0);
- ASSERT (pwd2);
- ASSERT (strcmp (pwd1, pwd2) == 0);
- free (pwd2);
+/* The length of "../". */
+#define DOTDOTSLASH_LEN 3
+
+/* Leftover bytes in the buffer, to work around library or OS bugs. */
+#define BUF_SLOP 20
+
+/* Keep this test in sync with m4/getcwd-path-max.m4. */
+static int
+test_long_name (void)
+{
+#ifndef PATH_MAX
+ /* The Hurd doesn't define this, so getcwd can't exhibit the bug --
+ at least not on a local file system. And if we were to start worrying
+ about remote file systems, we'd have to enable the wrapper function
+ all of the time, just to be safe. That's not worth the cost. */
+ return 0;
+#elif ((INT_MAX / (DIR_NAME_SIZE / DOTDOTSLASH_LEN + 1) \
+ - DIR_NAME_SIZE - BUF_SLOP) \
+ <= PATH_MAX)
+ /* FIXME: Assuming there's a system for which this is true,
+ this should be done in a compile test. */
+ return 0;
+#else
+ char buf[PATH_MAX * (DIR_NAME_SIZE / DOTDOTSLASH_LEN + 1)
+ + DIR_NAME_SIZE + BUF_SLOP];
+ char *cwd = getcwd (buf, PATH_MAX);
+ size_t initial_cwd_len;
+ size_t cwd_len;
+ int fail = 0;
+ size_t n_chdirs = 0;
+
+ if (cwd == NULL)
+ return 10;
+
+ cwd_len = initial_cwd_len = strlen (cwd);
+
+ while (1)
+ {
+ size_t dotdot_max = PATH_MAX * (DIR_NAME_SIZE / DOTDOTSLASH_LEN);
+ char *c = NULL;
+
+ cwd_len += DIR_NAME_SIZE;
+ /* If mkdir or chdir fails, it could be that this system cannot create
+ any file with an absolute name longer than PATH_MAX, such as cygwin.
+ If so, leave fail as 0, because the current working directory can't
+ be too long for getcwd if it can't even be created. For other
+ errors, be pessimistic and consider that as a failure, too. */
+ if (mkdir (DIR_NAME, S_IRWXU) < 0 || chdir (DIR_NAME) < 0)
+ {
+ if (! (errno == ERANGE || errno == ENAMETOOLONG))
+ fail = 20;
+ break;
+ }
+
+ if (PATH_MAX <= cwd_len && cwd_len < PATH_MAX + DIR_NAME_SIZE)
+ {
+ c = getcwd (buf, PATH_MAX);
+ if (!c && errno == ENOENT)
+ {
+ fail = 11;
+ break;
+ }
+ if (c || ! (errno == ERANGE || errno == ENAMETOOLONG))
+ {
+ fail = 21;
+ break;
+ }
+ }
+
+ if (dotdot_max <= cwd_len - initial_cwd_len)
+ {
+ if (dotdot_max + DIR_NAME_SIZE < cwd_len - initial_cwd_len)
+ break;
+ c = getcwd (buf, cwd_len + 1);
+ if (!c)
+ {
+ if (! (errno == ERANGE || errno == ENOENT
+ || errno == ENAMETOOLONG))
+ {
+ fail = 22;
+ break;
+ }
+ if (AT_FDCWD || errno == ERANGE || errno == ENOENT)
+ {
+ fail = 12;
+ break;
+ }
+ }
+ }
+
+ if (c && strlen (c) != cwd_len)
+ {
+ fail = 23;
+ break;
+ }
+ ++n_chdirs;
+ }
+
+ /* Leaving behind such a deep directory is not polite.
+ So clean up here, right away, even though the driving
+ shell script would also clean up. */
{
- size_t len = strlen (pwd1);
- ssize_t i = len - 10;
- if (i < 0)
- i = 0;
- pwd2 = malloc (len + 2);
- for ( ; i < len; i++)
- ASSERT (getcwd (pwd2, i) == NULL);
- pwd2 = getcwd (pwd2, len + 1);
- ASSERT (pwd2);
- pwd2[len] = '/';
- pwd2[len + 1] = '\0';
+ size_t i;
+
+ /* Try rmdir first, in case the chdir failed. */
+ rmdir (DIR_NAME);
+ for (i = 0; i <= n_chdirs; i++)
+ {
+ if (chdir ("..") < 0)
+ break;
+ if (rmdir (DIR_NAME) != 0)
+ break;
+ }
}
- ASSERT (strstr (pwd2, "/./") == NULL);
- ASSERT (strstr (pwd2, "/../") == NULL);
- free (pwd1);
- free (pwd2);
+ return fail;
+#endif
+}
- return 0;
+int
+main (int argc, char **argv)
+{
+ return test_abort_bug () + test_long_name ();
}