openat: don’t close (-1)
[gnulib.git] / tests / test-getcwd.c
blobe9d893408ef284f97a7bdd112c8572e2673e1c84
1 /* Test of getcwd() function.
2 Copyright (C) 2009-2024 Free Software Foundation, Inc.
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.
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.
14 You should have received a copy of the GNU General Public License
15 along with this program. If not, see <https://www.gnu.org/licenses/>. */
17 #include <config.h>
19 #include <unistd.h>
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>
29 #include "pathmax.h"
30 #include "qemu.h"
31 #include "macros.h"
33 /* This size is chosen to be larger than PATH_MAX (4k), yet smaller than
34 the 16kB pagesize on ia64 linux. Those conditions make the code below
35 trigger a bug in glibc's getcwd implementation before 2.4.90-10. */
36 #define TARGET_LEN (5 * 1024)
38 #if defined HAVE_OPENAT || (defined GNULIB_OPENAT && defined HAVE_FDOPENDIR)
39 # define HAVE_OPENAT_SUPPORT 1
40 #else
41 # define HAVE_OPENAT_SUPPORT 0
42 #endif
44 /* Keep this test in sync with m4/getcwd-abort-bug.m4. */
45 static int
46 test_abort_bug (void)
48 char *cwd;
49 size_t initial_cwd_len;
50 int fail = 0;
52 /* The bug is triggered when PATH_MAX < page size, so skip
53 this relatively expensive and invasive test if that's not true. */
54 #if defined PATH_MAX && defined _SC_PAGESIZE
55 int bug_possible = PATH_MAX < sysconf (_SC_PAGESIZE);
56 #else
57 int bug_possible = 0;
58 #endif
59 if (! bug_possible)
60 return 0;
62 cwd = getcwd (NULL, 0);
63 if (cwd == NULL)
64 return 2;
66 initial_cwd_len = strlen (cwd);
67 free (cwd);
69 if (HAVE_OPENAT_SUPPORT)
71 static char const dir_name[] = "confdir-14B---";
72 size_t desired_depth = ((TARGET_LEN - 1 - initial_cwd_len)
73 / sizeof dir_name);
74 size_t d;
75 for (d = 0; d < desired_depth; d++)
77 if (mkdir (dir_name, S_IRWXU) < 0 || chdir (dir_name) < 0)
79 if (! (errno == ERANGE || errno == ENAMETOOLONG
80 || errno == ENOENT))
81 fail = 3; /* Unable to construct deep hierarchy. */
82 break;
86 /* If libc has the bug in question, this invocation of getcwd
87 results in a failed assertion. */
88 cwd = getcwd (NULL, 0);
89 if (cwd == NULL)
90 fail = 4; /* getcwd didn't assert, but it failed for a long name
91 where the answer could have been learned. */
92 free (cwd);
94 /* Call rmdir first, in case the above chdir failed. */
95 rmdir (dir_name);
96 while (0 < d--)
98 if (chdir ("..") < 0)
100 fail = 5;
101 break;
103 rmdir (dir_name);
107 return fail;
110 /* The length of this name must be 8. */
111 #define DIR_NAME "confdir3"
112 #define DIR_NAME_LEN 8
113 #define DIR_NAME_SIZE (DIR_NAME_LEN + 1)
115 /* The length of "../". */
116 #define DOTDOTSLASH_LEN 3
118 /* Leftover bytes in the buffer, to work around library or OS bugs. */
119 #define BUF_SLOP 20
121 /* Keep this test in sync with m4/getcwd-path-max.m4. */
122 static int
123 test_long_name (void)
125 #ifndef PATH_MAX
126 /* The Hurd doesn't define this, so getcwd can't exhibit the bug --
127 at least not on a local file system. And if we were to start worrying
128 about remote file systems, we'd have to enable the wrapper function
129 all of the time, just to be safe. That's not worth the cost. */
130 return 0;
131 #elif ((INT_MAX / (DIR_NAME_SIZE / DOTDOTSLASH_LEN + 1) \
132 - DIR_NAME_SIZE - BUF_SLOP) \
133 <= PATH_MAX)
134 /* FIXME: Assuming there's a system for which this is true,
135 this should be done in a compile test. */
136 return 0;
137 #else
138 /* For a process running under QEMU user-mode, the "/" directory is not
139 really the root directory, but the value of the QEMU_LD_PREFIX environment
140 variable or of the -L command-line option. This causes the logic from
141 glibc/sysdeps/posix/getcwd.c to fail. In this case, skip the test. */
142 if (is_running_under_qemu_user ())
143 return 77;
145 char buf[PATH_MAX * (DIR_NAME_SIZE / DOTDOTSLASH_LEN + 1)
146 + DIR_NAME_SIZE + BUF_SLOP];
147 char *cwd = getcwd (buf, PATH_MAX);
148 size_t initial_cwd_len;
149 size_t cwd_len;
150 int fail = 0;
151 size_t n_chdirs = 0;
153 if (cwd == NULL)
154 return 1;
156 cwd_len = initial_cwd_len = strlen (cwd);
158 while (1)
160 # ifdef HAVE_GETCWD_SHORTER
161 /* On OS/X <= 10.9 for example, we're restricted to shorter paths
162 as lstat() doesn't support more than PATH_MAX. */
163 size_t dotdot_max = PATH_MAX * 2;
164 # else
165 size_t dotdot_max = PATH_MAX * (DIR_NAME_SIZE / DOTDOTSLASH_LEN);
166 # endif
167 char *c = NULL;
169 cwd_len += DIR_NAME_SIZE;
170 /* If mkdir or chdir fails, it could be that this system cannot create
171 any file with an absolute name longer than PATH_MAX, such as cygwin.
172 If so, leave fail as 0, because the current working directory can't
173 be too long for getcwd if it can't even be created. On Linux with
174 the 9p file system, mkdir fails with error EINVAL when cwd_len gets
175 too long; ignore this failure because the getcwd() system call
176 produces good results whereas the gnulib substitute calls getdents64
177 which fails with error EPROTO.
178 For other errors, be pessimistic and consider that as a failure,
179 too. */
180 if (mkdir (DIR_NAME, S_IRWXU) < 0 || chdir (DIR_NAME) < 0)
182 if (! (errno == ERANGE || errno == ENAMETOOLONG || errno == ENOENT))
183 #ifdef __linux__
184 if (! (errno == EINVAL))
185 #endif
186 fail = 2;
187 break;
190 if (PATH_MAX <= cwd_len && cwd_len < PATH_MAX + DIR_NAME_SIZE)
192 c = getcwd (buf, PATH_MAX);
193 if (!c && errno == ENOENT)
195 fail = 3;
196 break;
198 if (c)
200 fail = 4;
201 break;
203 if (! (errno == ERANGE || errno == ENAMETOOLONG))
205 fail = 5;
206 break;
210 if (dotdot_max <= cwd_len - initial_cwd_len)
212 if (dotdot_max + DIR_NAME_SIZE < cwd_len - initial_cwd_len)
213 break;
214 c = getcwd (buf, cwd_len + 1);
215 if (!c)
217 if (! (errno == ERANGE || errno == ENOENT
218 || errno == ENAMETOOLONG))
220 fail = 6;
221 break;
223 if (HAVE_OPENAT_SUPPORT || errno == ERANGE || errno == ENOENT)
225 fail = 7;
226 break;
231 if (c && strlen (c) != cwd_len)
233 fail = 8;
234 break;
236 ++n_chdirs;
239 /* Leaving behind such a deep directory is not polite.
240 So clean up here, right away, even though the driving
241 shell script would also clean up. */
243 size_t i;
245 /* Try rmdir first, in case the chdir failed. */
246 rmdir (DIR_NAME);
247 for (i = 0; i <= n_chdirs; i++)
249 if (chdir ("..") < 0)
250 break;
251 if (rmdir (DIR_NAME) != 0)
252 break;
256 return fail;
257 #endif
261 main ()
263 int err1 = test_abort_bug ();
264 int err2 = test_long_name ();
265 int result = err1 * 10 + (err1 != 0 && err2 == 77 ? 0 : err2);
266 return (result ? result : test_exit_status);