1 | /* Copyright (C) 2000-2022 Free Software Foundation, Inc. |
2 | This file is part of the GNU C Library. |
3 | |
4 | The GNU C Library is free software; you can redistribute it and/or |
5 | modify it under the terms of the GNU Lesser General Public |
6 | License as published by the Free Software Foundation; either |
7 | version 2.1 of the License, or (at your option) any later version. |
8 | |
9 | The GNU C Library 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 GNU |
12 | Lesser General Public License for more details. |
13 | |
14 | You should have received a copy of the GNU Lesser General Public |
15 | License along with the GNU C Library; if not, see |
16 | <https://www.gnu.org/licenses/>. */ |
17 | |
18 | #include <dirent.h> |
19 | #include <errno.h> |
20 | #include <fcntl.h> |
21 | #include <mcheck.h> |
22 | #include <stddef.h> |
23 | #include <stdio.h> |
24 | #include <stdlib.h> |
25 | #include <string.h> |
26 | #include <unistd.h> |
27 | #include <sys/stat.h> |
28 | #include <libc-diag.h> |
29 | |
30 | /* We expect four arguments: |
31 | - source directory name |
32 | - object directory |
33 | - common object directory |
34 | - the program name with path |
35 | */ |
36 | int |
37 | main (int argc, char *argv[]) |
38 | { |
39 | const char *srcdir; |
40 | const char *objdir; |
41 | const char *common_objdir; |
42 | const char *progpath; |
43 | struct stat64 st1; |
44 | struct stat64 st2; |
45 | struct stat64 st3; |
46 | DIR *dir1; |
47 | DIR *dir2; |
48 | int result = 0; |
49 | struct dirent64 *d; |
50 | union |
51 | { |
52 | struct dirent64 d; |
53 | char room [offsetof (struct dirent64, d_name[0]) + NAME_MAX + 1]; |
54 | } |
55 | direntbuf; |
56 | char *objdir_copy1; |
57 | char *objdir_copy2; |
58 | char *buf; |
59 | int fd; |
60 | |
61 | mtrace (); |
62 | |
63 | if (argc < 5) |
64 | { |
65 | puts (s: "not enough parameters" ); |
66 | exit (1); |
67 | } |
68 | |
69 | /* Make parameters available with nicer names. */ |
70 | srcdir = argv[1]; |
71 | objdir = argv[2]; |
72 | common_objdir = argv[3]; |
73 | progpath = argv[4]; |
74 | |
75 | /* First test the current source dir. We cannot really compare the |
76 | result of `getpwd' with the srcdir string but we have other means. */ |
77 | if (stat64 (file: "." , buf: &st1) < 0) |
78 | { |
79 | printf (format: "cannot stat starting directory: %m\n" ); |
80 | exit (1); |
81 | } |
82 | |
83 | if (chdir (path: srcdir) < 0) |
84 | { |
85 | printf (format: "cannot change to source directory: %m\n" ); |
86 | exit (1); |
87 | } |
88 | if (stat64 (file: "." , buf: &st2) < 0) |
89 | { |
90 | printf (format: "cannot stat source directory: %m\n" ); |
91 | exit (1); |
92 | } |
93 | |
94 | /* The two last stat64 calls better were for the same directory. */ |
95 | if (st1.st_dev != st2.st_dev || st1.st_ino != st2.st_ino) |
96 | { |
97 | printf (format: "stat of source directory failed: (%lld,%lld) vs (%lld,%lld)\n" , |
98 | (long long int) st1.st_dev, (long long int) st1.st_ino, |
99 | (long long int) st2.st_dev, (long long int) st2.st_ino); |
100 | exit (1); |
101 | } |
102 | |
103 | /* Change to the object directory. */ |
104 | if (chdir (path: objdir) < 0) |
105 | { |
106 | printf (format: "cannot change to object directory: %m\n" ); |
107 | exit (1); |
108 | } |
109 | if (stat64 (file: "." , buf: &st1) < 0) |
110 | { |
111 | printf (format: "cannot stat object directory: %m\n" ); |
112 | exit (1); |
113 | } |
114 | /* Is this the same we get as with the full path? */ |
115 | if (stat64 (file: objdir, buf: &st2) < 0) |
116 | { |
117 | printf (format: "cannot stat object directory with full path: %m\n" ); |
118 | exit (1); |
119 | } |
120 | if (st1.st_dev != st2.st_dev || st1.st_ino != st2.st_ino) |
121 | { |
122 | printf (format: "stat of object directory failed: (%lld,%lld) vs (%lld,%lld)\n" , |
123 | (long long int) st1.st_dev, (long long int) st1.st_ino, |
124 | (long long int) st2.st_dev, (long long int) st2.st_ino); |
125 | exit (1); |
126 | } |
127 | |
128 | objdir_copy1 = getcwd (NULL, size: 0); |
129 | if (objdir_copy1 == NULL) |
130 | { |
131 | printf (format: "cannot get current directory name for object directory: %m\n" ); |
132 | result = 1; |
133 | } |
134 | |
135 | /* First test: this directory must include our program. */ |
136 | if (stat64 (file: progpath, buf: &st2) < 0) |
137 | { |
138 | printf (format: "cannot stat program: %m\n" ); |
139 | exit (1); |
140 | } |
141 | |
142 | dir1 = opendir (name: "." ); |
143 | if (dir1 == NULL) |
144 | { |
145 | printf (format: "cannot open object directory: %m\n" ); |
146 | exit (1); |
147 | } |
148 | |
149 | while ((d = readdir64 (dirp: dir1)) != NULL) |
150 | { |
151 | if (d->d_type != DT_UNKNOWN && d->d_type != DT_REG) |
152 | continue; |
153 | |
154 | if (d->d_ino == st2.st_ino) |
155 | { |
156 | /* Might be it. Test the device. We could use the st_dev |
157 | element from st1 but what the heck, do more testing. */ |
158 | if (stat64 (file: d->d_name, buf: &st3) < 0) |
159 | { |
160 | printf (format: "cannot stat entry from readdir: %m\n" ); |
161 | result = 1; |
162 | d = NULL; |
163 | break; |
164 | } |
165 | |
166 | if (st3.st_dev == st2.st_dev) |
167 | break; |
168 | } |
169 | } |
170 | |
171 | if (d == NULL) |
172 | { |
173 | puts (s: "haven't found program in object directory" ); |
174 | result = 1; |
175 | } |
176 | |
177 | /* We leave dir1 open. */ |
178 | |
179 | /* Stat using file descriptor. */ |
180 | if (fstat64 (dirfd (dir1), buf: &st2) < 0) |
181 | { |
182 | printf (format: "cannot fstat object directory: %m\n" ); |
183 | result = 1; |
184 | } |
185 | if (st1.st_dev != st2.st_dev || st1.st_ino != st2.st_ino) |
186 | { |
187 | printf (format: "fstat of object directory failed: (%lld,%lld) vs (%lld,%lld)\n" , |
188 | (long long int) st1.st_dev, (long long int) st1.st_ino, |
189 | (long long int) st2.st_dev, (long long int) st2.st_ino); |
190 | exit (1); |
191 | } |
192 | |
193 | if (chdir (path: ".." ) < 0) |
194 | { |
195 | printf (format: "cannot go to common object directory with \"..\": %m\n" ); |
196 | exit (1); |
197 | } |
198 | |
199 | if (stat64 (file: "." , buf: &st1) < 0) |
200 | { |
201 | printf (format: "cannot stat common object directory: %m\n" ); |
202 | exit (1); |
203 | } |
204 | /* Is this the same we get as with the full path? */ |
205 | if (stat64 (file: common_objdir, buf: &st2) < 0) |
206 | { |
207 | printf (format: "cannot stat common object directory with full path: %m\n" ); |
208 | exit (1); |
209 | } |
210 | if (st1.st_dev != st2.st_dev || st1.st_ino != st2.st_ino) |
211 | { |
212 | printf (format: "stat of object directory failed: (%lld,%lld) vs (%lld,%lld)\n" , |
213 | (long long int) st1.st_dev, (long long int) st1.st_ino, |
214 | (long long int) st2.st_dev, (long long int) st2.st_ino); |
215 | exit (1); |
216 | } |
217 | |
218 | /* Stat using file descriptor. */ |
219 | if (fstat64 (dirfd (dir1), buf: &st2) < 0) |
220 | { |
221 | printf (format: "cannot fstat object directory: %m\n" ); |
222 | result = 1; |
223 | } |
224 | |
225 | dir2 = opendir (name: common_objdir); |
226 | if (dir2 == NULL) |
227 | { |
228 | printf (format: "cannot open common object directory: %m\n" ); |
229 | exit (1); |
230 | } |
231 | |
232 | while ((d = readdir64 (dirp: dir2)) != NULL) |
233 | { |
234 | if (d->d_type != DT_UNKNOWN && d->d_type != DT_DIR) |
235 | continue; |
236 | |
237 | if (d->d_ino == st2.st_ino) |
238 | { |
239 | /* Might be it. Test the device. We could use the st_dev |
240 | element from st1 but what the heck, do more testing. */ |
241 | if (stat64 (file: d->d_name, buf: &st3) < 0) |
242 | { |
243 | printf (format: "cannot stat entry from readdir: %m\n" ); |
244 | result = 1; |
245 | d = NULL; |
246 | break; |
247 | } |
248 | |
249 | if (st3.st_dev == st2.st_dev) |
250 | break; |
251 | } |
252 | } |
253 | |
254 | /* This better should be the object directory again. */ |
255 | if (fchdir (dirfd (dir1)) < 0) |
256 | { |
257 | printf (format: "cannot fchdir to object directory: %m\n" ); |
258 | exit (1); |
259 | } |
260 | |
261 | objdir_copy2 = getcwd (NULL, size: 0); |
262 | if (objdir_copy2 == NULL) |
263 | { |
264 | printf (format: "cannot get current directory name for object directory: %m\n" ); |
265 | result = 1; |
266 | } |
267 | if (strcmp (objdir_copy1, objdir_copy2) != 0) |
268 | { |
269 | puts (s: "getcwd returned a different string the second time" ); |
270 | result = 1; |
271 | } |
272 | |
273 | /* This better should be the common object directory again. */ |
274 | if (fchdir (dirfd (dir2)) < 0) |
275 | { |
276 | printf (format: "cannot fchdir to common object directory: %m\n" ); |
277 | exit (1); |
278 | } |
279 | |
280 | if (stat64 (file: "." , buf: &st2) < 0) |
281 | { |
282 | printf (format: "cannot stat common object directory: %m\n" ); |
283 | exit (1); |
284 | } |
285 | if (st1.st_dev != st2.st_dev || st1.st_ino != st2.st_ino) |
286 | { |
287 | printf (format: "stat of object directory failed: (%lld,%lld) vs (%lld,%lld)\n" , |
288 | (long long int) st1.st_dev, (long long int) st1.st_ino, |
289 | (long long int) st2.st_dev, (long long int) st2.st_ino); |
290 | exit (1); |
291 | } |
292 | |
293 | buf = (char *) malloc (size: strlen (objdir_copy1) + 1 + sizeof "tst-dir.XXXXXX" ); |
294 | if (buf == NULL) |
295 | { |
296 | printf (format: "cannot allocate buffer: %m" ); |
297 | exit (1); |
298 | } |
299 | |
300 | stpcpy (stpcpy (stpcpy (buf, objdir_copy1), "/" ), "tst-dir.XXXXXX" ); |
301 | if (mkdtemp (template: buf) == NULL) |
302 | { |
303 | printf (format: "cannot create test directory in object directory: %m\n" ); |
304 | exit (1); |
305 | } |
306 | if (stat64 (file: buf, buf: &st1) < 0) |
307 | { |
308 | printf (format: "cannot stat new directory \"%s\": %m\n" , buf); |
309 | exit (1); |
310 | } |
311 | if (chmod (file: buf, mode: 0700) < 0) |
312 | { |
313 | printf (format: "cannot change mode of new directory: %m\n" ); |
314 | exit (1); |
315 | } |
316 | |
317 | /* The test below covers the deprecated readdir64_r function. */ |
318 | DIAG_PUSH_NEEDS_COMMENT; |
319 | DIAG_IGNORE_NEEDS_COMMENT (4.9, "-Wdeprecated-declarations" ); |
320 | |
321 | /* Try to find the new directory. */ |
322 | rewinddir (dirp: dir1); |
323 | while (readdir64_r (dirp: dir1, entry: &direntbuf.d, result: &d) == 0 && d != NULL) |
324 | { |
325 | if (d->d_type != DT_UNKNOWN && d->d_type != DT_DIR) |
326 | continue; |
327 | |
328 | if (d->d_ino == st1.st_ino) |
329 | { |
330 | /* Might be it. Test the device. We could use the st_dev |
331 | element from st1 but what the heck, do more testing. */ |
332 | size_t len = strlen (objdir) + 1 + _D_EXACT_NAMLEN (d) + 1; |
333 | char tmpbuf[len]; |
334 | |
335 | stpcpy (stpcpy (stpcpy (tmpbuf, objdir), "/" ), d->d_name); |
336 | |
337 | if (stat64 (file: tmpbuf, buf: &st3) < 0) |
338 | { |
339 | printf (format: "cannot stat entry from readdir: %m\n" ); |
340 | result = 1; |
341 | d = NULL; |
342 | break; |
343 | } |
344 | |
345 | if (st3.st_dev == st2.st_dev |
346 | && strcmp (d->d_name, buf + strlen (buf) - 14) == 0) |
347 | break; |
348 | } |
349 | } |
350 | |
351 | DIAG_POP_NEEDS_COMMENT; |
352 | |
353 | if (d == NULL) |
354 | { |
355 | printf (format: "haven't found new directory \"%s\"\n" , buf); |
356 | exit (1); |
357 | } |
358 | |
359 | if (closedir (dirp: dir2) < 0) |
360 | { |
361 | printf (format: "closing dir2 failed: %m\n" ); |
362 | result = 1; |
363 | } |
364 | |
365 | if (chdir (path: buf) < 0) |
366 | { |
367 | printf (format: "cannot change to new directory: %m\n" ); |
368 | exit (1); |
369 | } |
370 | |
371 | dir2 = opendir (name: buf); |
372 | if (dir2 == NULL) |
373 | { |
374 | printf (format: "cannot open new directory: %m\n" ); |
375 | exit (1); |
376 | } |
377 | |
378 | if (fstat64 (dirfd (dir2), buf: &st2) < 0) |
379 | { |
380 | printf (format: "cannot fstat new directory \"%s\": %m\n" , buf); |
381 | exit (1); |
382 | } |
383 | if (st1.st_dev != st2.st_dev || st1.st_ino != st2.st_ino) |
384 | { |
385 | printf (format: "stat of new directory failed: (%lld,%lld) vs (%lld,%lld)\n" , |
386 | (long long int) st1.st_dev, (long long int) st1.st_ino, |
387 | (long long int) st2.st_dev, (long long int) st2.st_ino); |
388 | exit (1); |
389 | } |
390 | |
391 | if (mkdir (path: "another-dir" , mode: 0777) < 0) |
392 | { |
393 | printf (format: "cannot create \"another-dir\": %m\n" ); |
394 | exit (1); |
395 | } |
396 | fd = open (file: "and-a-file" , O_RDWR | O_CREAT | O_EXCL, 0666); |
397 | if (fd == -1) |
398 | { |
399 | printf (format: "cannot create \"and-a-file\": %m\n" ); |
400 | exit (1); |
401 | } |
402 | close (fd: fd); |
403 | |
404 | /* Some tests about error reporting. */ |
405 | errno = 0; |
406 | if (chdir (path: "and-a-file" ) >= 0) |
407 | { |
408 | printf (format: "chdir to \"and-a-file\" succeeded\n" ); |
409 | exit (1); |
410 | } |
411 | if (errno != ENOTDIR) |
412 | { |
413 | printf (format: "chdir to \"and-a-file\" didn't set correct error\n" ); |
414 | result = 1; |
415 | } |
416 | |
417 | errno = 0; |
418 | if (chdir (path: "and-a-file/.." ) >= 0) |
419 | { |
420 | printf (format: "chdir to \"and-a-file/..\" succeeded\n" ); |
421 | exit (1); |
422 | } |
423 | if (errno != ENOTDIR) |
424 | { |
425 | printf (format: "chdir to \"and-a-file/..\" didn't set correct error\n" ); |
426 | result = 1; |
427 | } |
428 | |
429 | errno = 0; |
430 | if (chdir (path: "another-dir/../and-a-file" ) >= 0) |
431 | { |
432 | printf (format: "chdir to \"another-dir/../and-a-file\" succeeded\n" ); |
433 | exit (1); |
434 | } |
435 | if (errno != ENOTDIR) |
436 | { |
437 | printf (format: "chdir to \"another-dir/../and-a-file\" didn't set correct error\n" ); |
438 | result = 1; |
439 | } |
440 | |
441 | /* The test below covers the deprecated readdir64_r function. */ |
442 | DIAG_PUSH_NEEDS_COMMENT; |
443 | DIAG_IGNORE_NEEDS_COMMENT (4.9, "-Wdeprecated-declarations" ); |
444 | |
445 | /* We now should have a directory and a file in the new directory. */ |
446 | rewinddir (dirp: dir2); |
447 | while (readdir64_r (dirp: dir2, entry: &direntbuf.d, result: &d) == 0 && d != NULL) |
448 | { |
449 | if (strcmp (d->d_name, "." ) == 0 |
450 | || strcmp (d->d_name, ".." ) == 0 |
451 | || strcmp (d->d_name, "another-dir" ) == 0) |
452 | { |
453 | if (d->d_type != DT_UNKNOWN && d->d_type != DT_DIR) |
454 | { |
455 | printf (format: "d_type for \"%s\" is wrong\n" , d->d_name); |
456 | result = 1; |
457 | } |
458 | if (stat64 (file: d->d_name, buf: &st3) < 0) |
459 | { |
460 | printf (format: "cannot stat \"%s\" is wrong\n" , d->d_name); |
461 | result = 1; |
462 | } |
463 | else if (! S_ISDIR (st3.st_mode)) |
464 | { |
465 | printf (format: "\"%s\" is no directory\n" , d->d_name); |
466 | result = 1; |
467 | } |
468 | } |
469 | else if (strcmp (d->d_name, "and-a-file" ) == 0) |
470 | { |
471 | if (d->d_type != DT_UNKNOWN && d->d_type != DT_REG) |
472 | { |
473 | printf (format: "d_type for \"%s\" is wrong\n" , d->d_name); |
474 | result = 1; |
475 | } |
476 | if (stat64 (file: d->d_name, buf: &st3) < 0) |
477 | { |
478 | printf (format: "cannot stat \"%s\" is wrong\n" , d->d_name); |
479 | result = 1; |
480 | } |
481 | else if (! S_ISREG (st3.st_mode)) |
482 | { |
483 | printf (format: "\"%s\" is no regular file\n" , d->d_name); |
484 | result = 1; |
485 | } |
486 | } |
487 | else |
488 | { |
489 | printf (format: "unexpected directory entry \"%s\"\n" , d->d_name); |
490 | result = 1; |
491 | } |
492 | } |
493 | |
494 | DIAG_POP_NEEDS_COMMENT; |
495 | |
496 | if (stat64 (file: "does-not-exist" , buf: &st1) >= 0) |
497 | { |
498 | puts (s: "stat for unexisting file did not fail" ); |
499 | result = 1; |
500 | } |
501 | |
502 | /* Free all resources. */ |
503 | |
504 | if (closedir (dirp: dir1) < 0) |
505 | { |
506 | printf (format: "closing dir1 failed: %m\n" ); |
507 | result = 1; |
508 | } |
509 | if (closedir (dirp: dir2) < 0) |
510 | { |
511 | printf (format: "second closing dir2 failed: %m\n" ); |
512 | result = 1; |
513 | } |
514 | |
515 | if (rmdir (path: "another-dir" ) < 0) |
516 | { |
517 | printf (format: "cannot remove \"another-dir\": %m\n" ); |
518 | result = 1; |
519 | } |
520 | |
521 | if (unlink (name: "and-a-file" ) < 0) |
522 | { |
523 | printf (format: "cannot remove \"and-a-file\": %m\n" ); |
524 | result = 1; |
525 | } |
526 | |
527 | /* One more test before we leave: mkdir() is supposed to fail with |
528 | EEXIST if the named file is a symlink. */ |
529 | if (symlink (from: "a-symlink" , to: "a-symlink" ) != 0) |
530 | { |
531 | printf (format: "cannot create symlink \"a-symlink\": %m\n" ); |
532 | result = 1; |
533 | } |
534 | else |
535 | { |
536 | if (mkdir (path: "a-symlink" , mode: 0666) == 0) |
537 | { |
538 | puts (s: "can make directory \"a-symlink\"" ); |
539 | result = 1; |
540 | } |
541 | else if (errno != EEXIST) |
542 | { |
543 | puts (s: "mkdir(\"a-symlink\") does not fail with EEXIST\n" ); |
544 | result = 1; |
545 | } |
546 | if (unlink (name: "a-symlink" ) < 0) |
547 | { |
548 | printf (format: "cannot unlink \"a-symlink\": %m\n" ); |
549 | result = 1; |
550 | } |
551 | } |
552 | |
553 | if (chdir (path: srcdir) < 0) |
554 | { |
555 | printf (format: "cannot change back to source directory: %m\n" ); |
556 | exit (1); |
557 | } |
558 | |
559 | if (rmdir (path: buf) < 0) |
560 | { |
561 | printf (format: "cannot remove \"%s\": %m\n" , buf); |
562 | result = 1; |
563 | } |
564 | free (ptr: objdir_copy1); |
565 | free (ptr: objdir_copy2); |
566 | |
567 | if (result == 0) |
568 | puts (s: "all OK" ); |
569 | |
570 | return result; |
571 | } |
572 | |