| 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 |
|