/[chase]/tags/chase/release-0.5.2/chase.c
ViewVC logotype

Contents of /tags/chase/release-0.5.2/chase.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 16 - (show annotations) (download)
Sun Oct 12 23:19:24 2003 UTC (9 years, 8 months ago) by rotty
File MIME type: text/plain
File size: 15096 byte(s)
Released 0.5.2
1 /* chase - chase a symbolic link
2
3 Copyright (C) 1998, 1999, 2000 Antti-Juhani Kaijanaho
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
18 USA
19
20 The author can be reached via mail at (ISO 8859-1 charset for the city)
21 Antti-Juhani Kaijanaho
22 Helvintie 2 e as 9
23 FIN-40500 JYVÄSKYLÄ
24 FINLAND
25 EUROPE
26 and via electronic mail from
27 gaia@iki.fi
28 If you have a choice, use the email address; it is more likely to
29 stay current.
30
31 */
32
33 /* The algorithm is very simple: we check, whether the current file is
34 a symlink, and if it is, we loop with the destination as the
35 current file. */
36
37 #include <assert.h>
38 #include <errno.h>
39 #include <getopt.h>
40 #include <locale.h>
41 #include <stdio.h>
42 #include <stdlib.h>
43 #include <string.h>
44 #include <unistd.h>
45 #include "config.h"
46 #include "gcollect.h"
47 #include "fname.h"
48
49 #define _(String) (String)
50 #define N_(String) (String)
51 #define textdomain(Domain)
52 #define bindtextdomain(Package, Directory)
53
54 /* This structure is used to form a list of file names for loop
55 detection. */
56 struct filelist_t {
57 char * fn;
58 struct filelist_t * rest;
59 };
60
61 /* Globals */
62 char * program_name; /* program_name points to argv[0]. */
63 int verbose = 0; /* Are we being verbose? */
64 int record_chain_p = 1; /* Should we record the chain of symlinks? */
65 unsigned long int warn_count = 30; /* Warn after this many iterations. */
66 unsigned long int giveup_count = 500; /* Give up after this many iterations. */
67
68 /* The following macros are used to control what gets displayed and
69 what doesn't. */
70 #if (NDEBUG == 1)
71 # define CHAT(str,fn) do { ; } while (0)
72 #else /* NDEBUG != 1 */
73 # define CHAT(str,fn) fprintf (stderr, _ ("Note (%s): %s\n"), fn, str)
74 #endif /* NDEBUG != 1 */
75
76 #define VERBOSE(str,fn) do { if (verbose) printf ("%s%s\n", str, fn); \
77 } while (0);
78
79 /* Print an error message concerning file fn in directory dir (must
80 end in a slash), based on errno. */
81 #define PRERR(dir,fn) fprintf (stderr, "%s: %s%s: %s\n", program_name, \
82 dir, fn, strerror (errno))
83
84 /* Print an error message concerning file fn in directory dir (must
85 end in a slash). */
86 #define ERRMSG(msg,f,dir) fprintf (stderr, "%s: %s%s: %s\n", program_name, \
87 dir, f, msg)
88
89 #define IMPLIES(p,q) ( (! (p)) || (q))
90
91 /* Change work directory to wd, with perroring on error. Return from
92 the parent function with return value err, if error happened. The
93 do...while(0) is a trick to allow xchdir be treated like a function
94 call. */
95 #define xchdir(wd,err) \
96 do { \
97 if (chdir (wd) == -1) \
98 { \
99 PRERR("",wd); \
100 return (err); \
101 } \
102 } while (0)
103
104 /* Return a freshly allocated string whose content was read from f, or
105 0 on error or end of file. The return value is a block of text
106 read from the file, whose end is signalled in both the file and the
107 return value by a null character. fname is used with status
108 reporting. */
109 static char *
110 fread_until_null(FILE * f, const char * fname)
111 {
112 char * rv;
113 size_t i = 0;
114 int c;
115 size_t rvlen = 16;
116
117 assert (f != 0);
118 assert (fname != 0);
119
120 rv = gc_xmalloc(rvlen);
121
122 CHAT (_ ("reading..."), fname);
123 while ( (c = getc(f)) != EOF)
124 {
125 if (i >= rvlen)
126 rv = gc_xrealloc(rv, rvlen *= 2);
127 *(rv + i++) = (unsigned char) c;
128 if (c == 0)
129 break;
130 }
131 if (c == EOF)
132 {
133 if (ferror(f))
134 perror(0);
135 return 0;
136 }
137
138 return rv;
139 }
140
141 /* Return a freshly allocated string whose content was read from f, or
142 0 on error or end of file. The return value is a complete text
143 line read from the file, without the trailing newline. fname is
144 used with status reporting. */
145 static char *
146 freadline (FILE * f, const char * fname)
147 {
148 char * rv;
149 char * rvptr; /* Where are we now writing? */
150 size_t rvlen = 0;
151 const size_t rvlen_inc = 80;
152
153 assert (f != 0);
154 assert (fname != 0);
155
156 rvlen = rvlen_inc;
157 rv = rvptr = gc_xmalloc (rvlen);
158
159 *rvptr = '\0';
160
161 do
162 {
163 char * result;
164
165 assert (*rvptr == '\0');
166
167 CHAT (_ ("reading..."), fname);
168 result = fgets (rvptr, rvlen - (rvptr - rv), f);
169 if (result == 0)
170 return 0;
171
172 /* We might write more... */
173 rvptr = rvptr + strlen (rvptr);
174
175 assert (rvptr >= rv);
176 if (rvptr == rv || rvptr [-1] != '\n')
177 {
178 ptrdiff_t rvptr_rv = rvptr - rv;
179
180 CHAT (_ ("line is longer than buffer, resizing..."), fname);
181 rvlen += rvlen_inc;
182 rv = gc_xrealloc (rv, rvlen);
183 rvptr = rv + rvptr_rv;
184 }
185 }
186 while (rvptr == rv || rvptr [-1] != '\n');
187
188 assert (rvptr > rv);
189 assert (*rvptr == '\0');
190 if (rvptr [-1] == '\n')
191 rvptr [-1] = '\0';
192
193 return rv;
194 }
195
196 /* Return value is a freshly allocated copy of the name of the file
197 that is not itself a symlink but that is reachable via symlinks. */
198 static char *
199 chase_symlink (char * fname)
200 {
201 char * buf = 0;
202 char * wd; /* the original work directory */
203 size_t blen = 0;
204 const size_t blen_inc = 20;
205 int result = 0;
206 unsigned long int iter_count = 0;
207 struct filelist_t * flist = 0;
208
209 blen = blen_inc;
210 buf = gc_xmalloc (blen);
211
212 wd = fname_absolutify (gnu_getcwd ());
213
214 /* We want to be in the directory that contains the file fname;
215 therefore, we can only use its base name from now on. */
216 xchdir (fname_dir (fname), 0);
217 fname = fname_chopdir (fname);
218
219 do
220 {
221 struct filelist_t * it;
222 char * dir;
223 char * fnamewithdir;
224
225 assert (buf != 0);
226 assert (blen != 0);
227 assert (result != -1);
228
229 dir = gnu_getcwd ();
230 fnamewithdir = gc_xmalloc (strlen (dir) + strlen (fname) + 1);
231 strcat (strcpy (fnamewithdir, dir), fname);
232
233 /* Detect loops. This is a O(n^2) algorithm (when combined with
234 the enclosing loop), but it does not matter, as n is usually
235 very small. */
236 for (it = flist; it != 0; it = it->rest)
237 {
238 CHAT (_ ("it->fn"), it->fn);
239 CHAT (_ ("fnamewithdir"), fnamewithdir);
240 if (strcmp(it->fn, fnamewithdir) == 0)
241 {
242 ERRMSG (_ ("symlink loop detected, giving up..."), fname, dir);
243 return 0;
244 }
245 }
246
247 if (record_chain_p)
248 {
249 struct filelist_t * new;
250
251 new = gc_xmalloc_container (sizeof (struct filelist_t));
252
253 new->fn = fnamewithdir;
254 new->rest = flist;
255 flist = new;
256 }
257
258 ++iter_count;
259
260 if (giveup_count != 0 && iter_count >= giveup_count)
261 {
262 ERRMSG (_ ("too many symlink hops, giving up..."), fname, dir);
263 return 0;
264 }
265
266 if (warn_count != 0 &&
267 iter_count % warn_count == 0 && iter_count >= warn_count)
268 {
269 ERRMSG (_ ("quite many symlink hops, hope we're not looping..."),
270 fname, dir);
271 }
272
273 while (1)
274 {
275 result = readlink (fname, buf, blen);
276 if (result == -1 && errno != EINVAL)
277 {
278 PRERR(dir, fname);
279 return 0;
280 }
281 if (result == blen)
282 {
283 CHAT (_ ("buffer was too small, resizing..."), fname);
284 blen += blen_inc;
285 buf = gc_xrealloc (buf, blen);
286 continue;
287 }
288 if (result != -1)
289 {
290 CHAT (_ ("read link"), fname);
291 buf [result] = '\0'; /* Terminate the string. */
292 VERBOSE ("-> ", buf);
293 fname = buf; /* In the next round we'll use this file. */
294 }
295 break;
296 }
297 /* We want to be in the directory that contains the file fname;
298 however, we can only use its base name from now on. */
299 xchdir (fname_dir (fname), 0);
300 fname = fname_chopdir (fname);
301 }
302 while (result != -1);
303
304 /* Now, we want the absolute path name to fname. */
305 fname = fname_absolutify (fname);
306
307 xchdir (wd, 0);
308
309 /* When we reach this point, we have a genuine non-symlink in
310 fname. */
311 if (fname != buf)
312 {
313 /* Make the return value malloc()ed. */
314 fname = gc_xstrdup (fname);
315 }
316 return fname;
317 }
318
319 /* Chase name and report to user. Return zero iff the name was
320 nonexistent or a dangling symlink. */
321 int
322 chase_and_report (const char * name)
323 {
324 char * s;
325 char * fname;
326
327 fname = gc_xstrdup (name);
328
329 VERBOSE ("", fname);
330 s = chase_symlink (fname);
331 if (s == 0)
332 return 0;
333 else
334 {
335 puts (s);
336 return 1;
337 }
338 }
339
340 /* Chase all file names in f. Return zero iff some name was
341 nonexistent or a dangling symlink. fname is used in diagnostics.
342 reader is used to get the file name from f. It is supposed to be
343 either freadline or fread_until_null. */
344 int
345 handle_file(FILE * f, char * (*reader)(FILE *, const char *),
346 const char * fname)
347 {
348 char * line;
349 int rv = 1;
350
351 VERBOSE (_ ("Reading file names from "), fname);
352
353 line = reader (f, fname);
354 while (line != 0)
355 {
356 if (line [0] != '\0')
357 rv = chase_and_report (line) ? rv : 0;
358 else
359 CHAT (_ ("empty file name found"), fname);
360
361 line = reader (f, fname);
362 }
363
364 assert (line == 0);
365 if (feof (f))
366 CHAT (_ ("bumped to eof"), fname);
367 else
368 PRERR("", fname);
369
370 return rv;
371 }
372
373 /* main() was adopted from the example in GNU's getopt_long(3) manual
374 page. */
375 int
376 main (int argc, char * argv[])
377 {
378 int c;
379 int rv = EXIT_SUCCESS;
380 int from_file = 0;
381 char * (*reader)(FILE *, const char *) = freadline;
382
383 /* i18n */
384 setlocale (LC_ALL, "");
385 bindtextdomain (PACKAGE, LOCALEDIR);
386 textdomain (PACKAGE);
387
388 program_name = argv[0];
389
390 while (1)
391 {
392 int option_index = 0;
393 static struct option long_options[] =
394 {
395 {"from-file", 0, 0, (int) 'f'},
396 {"loop-warn-threshold", 1, 0, (int) 'w'},
397 {"loop-fail-threshold", 1, 0, (int) 'l'},
398 /* the next two are just for compatibility's sake */
399 {"loop-warn-treshold", 1, 0, (int) 'w'},
400 {"loop-fail-treshold", 1, 0, (int) 'l'},
401
402 {"disable-loop-detection", 0, 0, (int) 'D'},
403 {"null", 0, 0, (int) '0'},
404 {"verbose", 0, &verbose, 1 },
405 {"help", 0, 0, (int) 'h'},
406 {"version", 0, 0, (int) 'v'},
407 {0, 0, 0, 0}
408 };
409
410 c = getopt_long (argc, argv, "0Dfhl:vw:",
411 long_options, &option_index);
412 if (c == -1)
413 break;
414
415 switch (c)
416 {
417 case 'D':
418 record_chain_p = 0;
419 break;
420
421 case '0':
422 reader = fread_until_null;
423 /* fall through to 'f' */
424
425 case 'f':
426 from_file = 1;
427 break;
428
429 case 'v':
430 printf ("chase %s\n", VERSION);
431 printf ("Copyright (C) %s Antti-Juhani Kaijanaho.\n",
432 COPYRIGHT_YEAR);
433 puts (_ ("This program comes with NO WARRANTY,"));
434 puts (_ ("to the extent permitted by law."));
435 puts (_ ("You may redistribute copies of this program"));
436 puts (_ ("under the terms of the GNU General Public License,"));
437 puts (_ ("version 2 or later. For more information about"));
438 printf (_ ("these matters, see the file %s .\n"), COPYING_LOCATION);
439 return 0;
440
441
442 case 'h':
443 printf ("%s: %s [options ... ] [file ... ]\n"
444 " %s -h|--help\n %s -v|--version\n",
445 _ ("Usage"), program_name, program_name, program_name);
446 puts ("");
447 puts (_ ("Options:"));
448 puts (" --verbose");
449 puts (_ (" Chat about what is being done."));
450 puts (" -D, --disable-loop-detection");
451 puts (_ (" Do not keep a record of the chain of symlinks relevant"));
452 puts (_ (" to the file name being chased. This inhibits reliable"));
453 puts (_ (" detection of symlink loops"));
454 puts (" -f, --from-file");
455 puts (_ (" Treat the file names as sources for names to chase."));
456 puts (" -0, --null");
457 puts (_ (" Treat null as the file name separator instead of newline."));
458 puts (_ (" Implies -f."));
459 puts (" -w WCOUNT, --loop-warn-threshold=WCOUNT");
460 puts (" -l LCOUNT, --loop-fail-threshold=LCOUNT");
461 puts (_ (" Set the threshold for warning about a possible symlink loop"));
462 puts (_ (" (WCOUNT) and for failing because of one (LCOUNT). Zero"));
463 printf (_ (" disables the check. Default values: WCOUNT %lu, LCOUNT %lu.\n"),
464 warn_count, giveup_count);
465 puts (" -h, --help");
466 puts (_ (" Output this usage summary and exit successfully."));
467 puts (" -v, --version");
468 puts (_ (" Show version information and exit successfully."));
469 puts ("");
470 puts (_ ("Please report bugs to <gaia@iki.fi>."));
471 return 0;
472
473 case 'l':
474 {
475 char * tail;
476
477 errno = 0;
478 giveup_count = strtoul (optarg, &tail, 10);
479 if (errno != 0)
480 {
481 perror ("loop-fail-threshold");
482 exit (EXIT_FAILURE);
483 }
484 }
485 break;
486
487 case 'w':
488 {
489 char * tail;
490
491 errno = 0;
492 warn_count = strtoul (optarg, &tail, 10);
493 if (errno != 0)
494 {
495 perror ("loop-warn-threshold");
496 exit (EXIT_FAILURE);
497 }
498 }
499 break;
500
501 default:
502 break;
503 }
504 }
505
506 if (from_file)
507 {
508 if (optind == argc)
509 {
510 rv = handle_file (stdin, reader, gc_xstrdup ("(stdin)"))
511 ? rv : EXIT_FAILURE;
512 }
513 else for (/* using inherited optind */; optind < argc; optind++)
514 {
515 FILE * f;
516 char * fname;
517
518 if (strcmp (argv [optind], "-") == 0)
519 {
520 f = stdin;
521 fname = gc_xstrdup ("(stdin)");
522 }
523 else
524 {
525 fname = argv [optind];
526 f = fopen (fname, "r");
527 if (f == NULL)
528 {
529 PRERR ("", fname);
530 continue;
531 }
532 }
533
534 rv = handle_file (f, reader, fname) ? rv : EXIT_FAILURE;
535
536 if (f != stdin)
537 fclose (f);
538 }
539 }
540 else
541 {
542 for (/* using inherited optind */; optind < argc; optind++)
543 rv = chase_and_report (argv [optind]) ? rv : EXIT_FAILURE;
544 }
545
546 return rv;
547 }
548

  ViewVC Help
Powered by ViewVC 1.1.5