getcwd: enhance tests
[gnulib.git] / tests / test-getcwd.c
1 /* Test of getcwd() function.
2    Copyright (C) 2009-2011 Free Software Foundation, Inc.
3
4    This program is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation; either version 3 of the License, or
7    (at your option) any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
16
17 #include <config.h>
18
19 #include <unistd.h>
20
21 #include <errno.h>
22 #include <fcntl.h>
23 #include <limits.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <sys/stat.h>
28
29 #include "macros.h"
30
31 #if ! HAVE_GETPAGESIZE
32 # define getpagesize() 0
33 #endif
34
35 /* This size is chosen to be larger than PATH_MAX (4k), yet smaller than
36    the 16kB pagesize on ia64 linux.  Those conditions make the code below
37    trigger a bug in glibc's getcwd implementation before 2.4.90-10.  */
38 #define TARGET_LEN (5 * 1024)
39
40 /* Keep this test in sync with m4/getcwd-abort-bug.m4.  */
41 static int
42 test_abort_bug (void)
43 {
44   char const *dir_name = "confdir-14B---";
45   char *cwd;
46   size_t initial_cwd_len;
47   int fail = 0;
48   size_t desired_depth;
49   size_t d;
50
51   /* The bug is triggered when PATH_MAX < getpagesize (), so skip
52      this relatively expensive and invasive test if that's not true.  */
53   if (getpagesize () <= PATH_MAX)
54     return 0;
55
56   cwd = getcwd (NULL, 0);
57   if (cwd == NULL)
58     return 0;
59
60   initial_cwd_len = strlen (cwd);
61   free (cwd);
62   desired_depth = ((TARGET_LEN - 1 - initial_cwd_len)
63                    / (1 + strlen (dir_name)));
64   for (d = 0; d < desired_depth; d++)
65     {
66       if (mkdir (dir_name, S_IRWXU) < 0 || chdir (dir_name) < 0)
67         {
68           fail = 3; /* Unable to construct deep hierarchy.  */
69           break;
70         }
71     }
72
73   /* If libc has the bug in question, this invocation of getcwd
74      results in a failed assertion.  */
75   cwd = getcwd (NULL, 0);
76   if (cwd == NULL)
77     fail = 4; /* getcwd failed.  This is ok, and expected.  */
78   free (cwd);
79
80   /* Call rmdir first, in case the above chdir failed.  */
81   rmdir (dir_name);
82   while (0 < d--)
83     {
84       if (chdir ("..") < 0)
85         break;
86       rmdir (dir_name);
87     }
88
89   return 0;
90 }
91
92 /* The length of this name must be 8.  */
93 #define DIR_NAME "confdir3"
94 #define DIR_NAME_LEN 8
95 #define DIR_NAME_SIZE (DIR_NAME_LEN + 1)
96
97 /* The length of "../".  */
98 #define DOTDOTSLASH_LEN 3
99
100 /* Leftover bytes in the buffer, to work around library or OS bugs.  */
101 #define BUF_SLOP 20
102
103 /* Keep this test in sync with m4/getcwd-path-max.m4.  */
104 static int
105 test_long_name (void)
106 {
107 #ifndef PATH_MAX
108   /* The Hurd doesn't define this, so getcwd can't exhibit the bug --
109      at least not on a local file system.  And if we were to start worrying
110      about remote file systems, we'd have to enable the wrapper function
111      all of the time, just to be safe.  That's not worth the cost.  */
112   return 0;
113 #elif ((INT_MAX / (DIR_NAME_SIZE / DOTDOTSLASH_LEN + 1) \
114         - DIR_NAME_SIZE - BUF_SLOP) \
115        <= PATH_MAX)
116   /* FIXME: Assuming there's a system for which this is true,
117      this should be done in a compile test.  */
118   return 0;
119 #else
120   char buf[PATH_MAX * (DIR_NAME_SIZE / DOTDOTSLASH_LEN + 1)
121            + DIR_NAME_SIZE + BUF_SLOP];
122   char *cwd = getcwd (buf, PATH_MAX);
123   size_t initial_cwd_len;
124   size_t cwd_len;
125   int fail = 0;
126   size_t n_chdirs = 0;
127
128   if (cwd == NULL)
129     return 10;
130
131   cwd_len = initial_cwd_len = strlen (cwd);
132
133   while (1)
134     {
135       size_t dotdot_max = PATH_MAX * (DIR_NAME_SIZE / DOTDOTSLASH_LEN);
136       char *c = NULL;
137
138       cwd_len += DIR_NAME_SIZE;
139       /* If mkdir or chdir fails, it could be that this system cannot create
140          any file with an absolute name longer than PATH_MAX, such as cygwin.
141          If so, leave fail as 0, because the current working directory can't
142          be too long for getcwd if it can't even be created.  For other
143          errors, be pessimistic and consider that as a failure, too.  */
144       if (mkdir (DIR_NAME, S_IRWXU) < 0 || chdir (DIR_NAME) < 0)
145         {
146           if (! (errno == ERANGE || errno == ENAMETOOLONG))
147             fail = 20;
148           break;
149         }
150
151       if (PATH_MAX <= cwd_len && cwd_len < PATH_MAX + DIR_NAME_SIZE)
152         {
153           c = getcwd (buf, PATH_MAX);
154           if (!c && errno == ENOENT)
155             {
156               fail = 11;
157               break;
158             }
159           if (c || ! (errno == ERANGE || errno == ENAMETOOLONG))
160             {
161               fail = 21;
162               break;
163             }
164         }
165
166       if (dotdot_max <= cwd_len - initial_cwd_len)
167         {
168           if (dotdot_max + DIR_NAME_SIZE < cwd_len - initial_cwd_len)
169             break;
170           c = getcwd (buf, cwd_len + 1);
171           if (!c)
172             {
173               if (! (errno == ERANGE || errno == ENOENT
174                      || errno == ENAMETOOLONG))
175                 {
176                   fail = 22;
177                   break;
178                 }
179               if (AT_FDCWD || errno == ERANGE || errno == ENOENT)
180                 {
181                   fail = 12;
182                   break;
183                 }
184             }
185         }
186
187       if (c && strlen (c) != cwd_len)
188         {
189           fail = 23;
190           break;
191         }
192       ++n_chdirs;
193     }
194
195   /* Leaving behind such a deep directory is not polite.
196      So clean up here, right away, even though the driving
197      shell script would also clean up.  */
198   {
199     size_t i;
200
201     /* Try rmdir first, in case the chdir failed.  */
202     rmdir (DIR_NAME);
203     for (i = 0; i <= n_chdirs; i++)
204       {
205         if (chdir ("..") < 0)
206           break;
207         if (rmdir (DIR_NAME) != 0)
208           break;
209       }
210   }
211
212   return fail;
213 #endif
214 }
215
216 int
217 main (int argc, char **argv)
218 {
219   return test_abort_bug () + test_long_name ();
220 }