tests: don't assume fd 99 is closed
[gnulib.git] / tests / test-getcwd.c
index debace7..4b951e0 100644 (file)
@@ -1,9 +1,9 @@
 /* Test of getcwd() function.
 /* Test of getcwd() function.
-   Copyright (C) 2009 Free Software Foundation, Inc.
+   Copyright (C) 2009-2013 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
 
    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 2 of the License, or
+   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,
    (at your option) any later version.
 
    This program is distributed in the hope that it will be useful,
 
 #include <unistd.h>
 
 
 #include <unistd.h>
 
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <sys/stat.h>
 
 
-#define ASSERT(expr) \
-  do                                                                         \
-    {                                                                        \
-      if (!(expr))                                                           \
-        {                                                                    \
-          fprintf (stderr, "%s:%d: assertion failed\n", __FILE__, __LINE__); \
-          fflush (stderr);                                                   \
-          abort ();                                                          \
-        }                                                                    \
-    }                                                                        \
-  while (0)
+#include "pathmax.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;
+
+#ifdef PATH_MAX
+  /* 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;
+#endif
+
+  cwd = getcwd (NULL, 0);
+  if (cwd == NULL)
+    return 2;
+
+  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 (chdir (argv[1]) == 0)
-        printf ("changed to directory %s\n", argv[1]);
+      if (mkdir (dir_name, S_IRWXU) < 0 || chdir (dir_name) < 0)
+        {
+          if (! (errno == ERANGE || errno == ENAMETOOLONG || errno == ENOENT))
+            fail = 3; /* Unable to construct deep hierarchy.  */
+          break;
+        }
     }
 
     }
 
-  pwd1 = getcwd (NULL, 0);
-  ASSERT (pwd1 && *pwd1);
-  if (1 < argc)
-    printf ("cwd=%s\n", pwd1);
+  /* 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 didn't assert, but it failed for a long name
+                 where the answer could have been learned.  */
+  free (cwd);
 
 
-  /* Make sure the result is usable.  */
-  ASSERT (chdir (pwd1) == 0);
-  ASSERT (chdir ("././.") == 0);
+  /* Call rmdir first, in case the above chdir failed.  */
+  rmdir (dir_name);
+  while (0 < d--)
+    {
+      if (chdir ("..") < 0)
+        {
+          fail = 5;
+          break;
+        }
+      rmdir (dir_name);
+    }
 
 
-  /* Make sure that result is normalized.  */
-  pwd2 = getcwd (NULL, 0);
-  ASSERT (pwd2);
-  ASSERT (strcmp (pwd1, pwd2) == 0);
-  free (pwd2);
+  return fail;
+}
+
+/* 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)
+
+/* 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 1;
+
+  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 || errno == ENOENT))
+            fail = 2;
+          break;
+        }
+
+      if (PATH_MAX <= cwd_len && cwd_len < PATH_MAX + DIR_NAME_SIZE)
+        {
+          c = getcwd (buf, PATH_MAX);
+          if (!c && errno == ENOENT)
+            {
+              fail = 3;
+              break;
+            }
+          if (c)
+            {
+              fail = 4;
+              break;
+            }
+          if (! (errno == ERANGE || errno == ENAMETOOLONG))
+            {
+              fail = 5;
+              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 = 6;
+                  break;
+                }
+              if (AT_FDCWD || errno == ERANGE || errno == ENOENT)
+                {
+                  fail = 7;
+                  break;
+                }
+            }
+        }
+
+      if (c && strlen (c) != cwd_len)
+        {
+          fail = 8;
+          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 () * 10 + test_long_name ();
 }
 }