-5

I have a program that list all the files in a directory. If a file is broken link, I am wanting to skip that file and continue scanning other files in a file. I would highly appreciate if someone can point where I am wrong. Following is part of my code

d = opendir(".");
while((dir = readdir(d)) != NULL) {

 char buff[256];
 int target = readlink (dir->d_name, buff, sizeof(buff));
    if (target == -1)
    {
        printf("i found broken link  so continuing to next file..\n");
        continue;
    }
}

The problem I have is, it is printing following while I only have one broken link

i found broken link  so continuing to next file
i found broken link  so continuing to next file
i found broken link  so continuing to next file

and goes on until the last file.

DeiDei
  • 10,205
  • 6
  • 55
  • 80
Sulav k
  • 55
  • 1
  • 7
  • 1
    `... On error, -1 is returned and errno is set to indicate the error....` -->> `EINVAL The named file is not a symbolic link.` – wildplasser May 16 '18 at 19:25

2 Answers2

4

Your question should have some MCVE. See also inode(7) & symlink(7). Read Advanced Linux Programming or something newer.

Consider using nftw(3) or fts(3) (if you need to scan the subdirectories recursively) or at least do a stat(2) with the file path (since you are scanning the current directory, you don't need to construct that file path). Remember to skip the entries for . and ..; so try perhaps

 d = opendir(".");
 while((dir = readdir(d)) != NULL) {
   struct stat mystat;
   if (!strcmp(dir->d_name, ".") || !strcmp(dir->d_name, "..")) continue;
   memset (&mystat, 0, sizeof(mystat));
   if (stat(dir->d_name, &mystat) ||  S_ISLNK(mystat.st_mode))
     continue;
   /// etc...
 } 

There are many cases that you might want to handle. See also errno(3). What about a symlink to self? What about unix(7) sockets? fifo(7)-s? permissions?

(We don't have time and space to explain all the details; you do need to read a lot)

Increasingly Idiotic
  • 5,700
  • 5
  • 35
  • 73
Basile Starynkevitch
  • 223,805
  • 18
  • 296
  • 547
  • 2
    Please don't just point people at a bunch of manpages, manpages often only make sense if you have already internalized how to read them. Also, please recommend `fts` instead of `nftw`, as `nftw` is not guaranteed to be thread-safe, and also may not support very large directories. – zwol May 16 '18 at 20:14
  • 1
    @zwol: [`nftw()`](http://man7.org/linux/man-pages/man3/nftw.3.html) most definitely supports very large directories. The GNU C library implementation is known to be thread-safe, unless one of the functions (or another thread) changes the current working directory (`nftw()` itself does not change it). POSIX.1 does not guarantee it. `nftw()` does not need a descriptor per depth; it only uses *up to* one per depth. Plus, it is POSIX.1-2008, whereas `fts` is BSD-specific (but happens to be supported by GNU C library). – Nominal Animal May 16 '18 at 21:35
2

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.

zwol
  • 135,547
  • 38
  • 252
  • 361