Bump to m4 1.4.19
[platform/upstream/m4.git] / tests / test-getcwd.c
1 /* Test of getcwd() function.
2    Copyright (C) 2009-2021 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 <https://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 "pathmax.h"
30 #include "macros.h"
31
32 #if !(HAVE_GETPAGESIZE || defined getpagesize)
33 # define getpagesize() 0
34 #endif
35
36 /* This size is chosen to be larger than PATH_MAX (4k), yet smaller than
37    the 16kB pagesize on ia64 linux.  Those conditions make the code below
38    trigger a bug in glibc's getcwd implementation before 2.4.90-10.  */
39 #define TARGET_LEN (5 * 1024)
40
41 #if defined HAVE_OPENAT || (defined GNULIB_OPENAT && defined HAVE_FDOPENDIR)
42 # define HAVE_OPENAT_SUPPORT 1
43 #else
44 # define HAVE_OPENAT_SUPPORT 0
45 #endif
46
47 /* Keep this test in sync with m4/getcwd-abort-bug.m4.  */
48 static int
49 test_abort_bug (void)
50 {
51   char *cwd;
52   size_t initial_cwd_len;
53   int fail = 0;
54
55   /* The bug is triggered when PATH_MAX < getpagesize (), so skip
56      this relatively expensive and invasive test if that's not true.  */
57 #ifdef PATH_MAX
58   int bug_possible = PATH_MAX < getpagesize ();
59 #else
60   int bug_possible = 0;
61 #endif
62   if (! bug_possible)
63     return 0;
64
65   cwd = getcwd (NULL, 0);
66   if (cwd == NULL)
67     return 2;
68
69   initial_cwd_len = strlen (cwd);
70   free (cwd);
71
72   if (HAVE_OPENAT_SUPPORT)
73     {
74       static char const dir_name[] = "confdir-14B---";
75       size_t desired_depth = ((TARGET_LEN - 1 - initial_cwd_len)
76                               / sizeof dir_name);
77       size_t d;
78       for (d = 0; d < desired_depth; d++)
79         {
80           if (mkdir (dir_name, S_IRWXU) < 0 || chdir (dir_name) < 0)
81             {
82               if (! (errno == ERANGE || errno == ENAMETOOLONG
83                      || errno == ENOENT))
84                 fail = 3; /* Unable to construct deep hierarchy.  */
85               break;
86             }
87         }
88
89       /* If libc has the bug in question, this invocation of getcwd
90          results in a failed assertion.  */
91       cwd = getcwd (NULL, 0);
92       if (cwd == NULL)
93         fail = 4; /* getcwd didn't assert, but it failed for a long name
94                      where the answer could have been learned.  */
95       free (cwd);
96
97       /* Call rmdir first, in case the above chdir failed.  */
98       rmdir (dir_name);
99       while (0 < d--)
100         {
101           if (chdir ("..") < 0)
102             {
103               fail = 5;
104               break;
105             }
106           rmdir (dir_name);
107         }
108     }
109
110   return fail;
111 }
112
113 /* The length of this name must be 8.  */
114 #define DIR_NAME "confdir3"
115 #define DIR_NAME_LEN 8
116 #define DIR_NAME_SIZE (DIR_NAME_LEN + 1)
117
118 /* The length of "../".  */
119 #define DOTDOTSLASH_LEN 3
120
121 /* Leftover bytes in the buffer, to work around library or OS bugs.  */
122 #define BUF_SLOP 20
123
124 /* Keep this test in sync with m4/getcwd-path-max.m4.  */
125 static int
126 test_long_name (void)
127 {
128 #ifndef PATH_MAX
129   /* The Hurd doesn't define this, so getcwd can't exhibit the bug --
130      at least not on a local file system.  And if we were to start worrying
131      about remote file systems, we'd have to enable the wrapper function
132      all of the time, just to be safe.  That's not worth the cost.  */
133   return 0;
134 #elif ((INT_MAX / (DIR_NAME_SIZE / DOTDOTSLASH_LEN + 1) \
135         - DIR_NAME_SIZE - BUF_SLOP) \
136        <= PATH_MAX)
137   /* FIXME: Assuming there's a system for which this is true,
138      this should be done in a compile test.  */
139   return 0;
140 #else
141   char buf[PATH_MAX * (DIR_NAME_SIZE / DOTDOTSLASH_LEN + 1)
142            + DIR_NAME_SIZE + BUF_SLOP];
143   char *cwd = getcwd (buf, PATH_MAX);
144   size_t initial_cwd_len;
145   size_t cwd_len;
146   int fail = 0;
147   size_t n_chdirs = 0;
148
149   if (cwd == NULL)
150     return 1;
151
152   cwd_len = initial_cwd_len = strlen (cwd);
153
154   while (1)
155     {
156 # ifdef HAVE_GETCWD_SHORTER
157       /* On OS/X <= 10.9 for example, we're restricted to shorter paths
158          as lstat() doesn't support more than PATH_MAX.  */
159       size_t dotdot_max = PATH_MAX * 2;
160 # else
161       size_t dotdot_max = PATH_MAX * (DIR_NAME_SIZE / DOTDOTSLASH_LEN);
162 # endif
163       char *c = NULL;
164
165       cwd_len += DIR_NAME_SIZE;
166       /* If mkdir or chdir fails, it could be that this system cannot create
167          any file with an absolute name longer than PATH_MAX, such as cygwin.
168          If so, leave fail as 0, because the current working directory can't
169          be too long for getcwd if it can't even be created.  On Linux with
170          the 9p file system, mkdir fails with error EINVAL when cwd_len gets
171          too long; ignore this failure because the getcwd() system call
172          produces good results whereas the gnulib substitute calls getdents64
173          which fails with error EPROTO.
174          For other errors, be pessimistic and consider that as a failure,
175          too.  */
176       if (mkdir (DIR_NAME, S_IRWXU) < 0 || chdir (DIR_NAME) < 0)
177         {
178           if (! (errno == ERANGE || errno == ENAMETOOLONG || errno == ENOENT))
179             #ifdef __linux__
180             if (! (errno == EINVAL))
181             #endif
182               fail = 2;
183           break;
184         }
185
186       if (PATH_MAX <= cwd_len && cwd_len < PATH_MAX + DIR_NAME_SIZE)
187         {
188           c = getcwd (buf, PATH_MAX);
189           if (!c && errno == ENOENT)
190             {
191               fail = 3;
192               break;
193             }
194           if (c)
195             {
196               fail = 4;
197               break;
198             }
199           if (! (errno == ERANGE || errno == ENAMETOOLONG))
200             {
201               fail = 5;
202               break;
203             }
204         }
205
206       if (dotdot_max <= cwd_len - initial_cwd_len)
207         {
208           if (dotdot_max + DIR_NAME_SIZE < cwd_len - initial_cwd_len)
209             break;
210           c = getcwd (buf, cwd_len + 1);
211           if (!c)
212             {
213               if (! (errno == ERANGE || errno == ENOENT
214                      || errno == ENAMETOOLONG))
215                 {
216                   fail = 6;
217                   break;
218                 }
219               if (HAVE_OPENAT_SUPPORT || errno == ERANGE || errno == ENOENT)
220                 {
221                   fail = 7;
222                   break;
223                 }
224             }
225         }
226
227       if (c && strlen (c) != cwd_len)
228         {
229           fail = 8;
230           break;
231         }
232       ++n_chdirs;
233     }
234
235   /* Leaving behind such a deep directory is not polite.
236      So clean up here, right away, even though the driving
237      shell script would also clean up.  */
238   {
239     size_t i;
240
241     /* Try rmdir first, in case the chdir failed.  */
242     rmdir (DIR_NAME);
243     for (i = 0; i <= n_chdirs; i++)
244       {
245         if (chdir ("..") < 0)
246           break;
247         if (rmdir (DIR_NAME) != 0)
248           break;
249       }
250   }
251
252   return fail;
253 #endif
254 }
255
256 int
257 main (int argc, char **argv)
258 {
259   return test_abort_bug () * 10 + test_long_name ();
260 }