Normally, if you have
d = opendir(dirname);
while((dir = readdir(d)) != NULL) {
some_operation(dir->d_name);
}
some_operation
will fail for every d_name
, because the path you should have passed to some_operation
is ${dirname}/${dir->d_name}
, not just dir->d_name
.
Your program, though, is hardwired to pass the special directory .
to opendir
; when you do that, it is safe to pass just dir->d_name
to some_operation
, since .
is always the current working directory. Your problem is instead that readlink
does not fail when applied to a broken symlink, but does fail when applied to a directory entry that isn't a symlink. It would have been easier to figure this out for yourself if you had included dir->d_name
and strerror(errno)
in your error message, like this:
d = opendir(".");
while ((dir = readdir(d)) != 0) {
char buff[256];
if (readlink(dir->d_name, buff, sizeof buff) {
printf("%s: readlink failed: %s\n", dir->d_name, strerror(errno));
} else {
printf("%s -> %s\n", dir->d_name, buff);
}
}
If you had done that, you would have gotten output like this:
.gnome2: readlink failed: Invalid argument
.python_history: readlink failed: Invalid argument
test.s: readlink failed: Invalid argument
bin -> .local/bin
[etc]
and then it would probably have occurred to you to look at the readlink
manpage and discover that it returns EINVAL
when applied to something that isn't a symlink.
The proper way to detect a broken symlink is by observing that lstat
succeeds but stat
fails with ENOENT
:
struct stat lst, st;
if (lstat(dir->d_name, &lst)) {
/* some other kind of problem */
} else if (!S_ISLNK(lst.st_mode)) {
/* not a symlink at all */
} else if (stat(dir->d_name, &st)) {
if (errno == ENOENT) {
/* broken symlink */
} else {
/* some other kind of problem */
}
} else {
/* valid symlink, `lst` tells you about the link,
`st` tells you about what it points to */
}
If you don't need any of the other information from lst
, and your filesystem supports d_type
, you can skip the lstat
call:
if (dir->d_type == DT_LNK) {
struct stat st;
if (stat(dir->d_name, &st)) {
if (errno == ENOENT) {
/* broken symlink */
}
}
}
But don't neglect to do the entire dance with both lstat
and stat
in the DT_UNKNOWN
case, or you'll be sad when you try to run your program on a filesystem that doesn't report d_type information.