tags:

views:

331

answers:

4

I'm looking to detect loops in symbolic links in a C program:

$ ln -s self self
$ ln -s a b
$ ln -s b a

Here's what I've got so far:

#include <sys/stat.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

int 
main(int argc, char *argv[])
{
     struct stat buffer;
     int status;

     if (argc != 2) {
          fprintf(stderr, "error: file name required\n");
          return 0;
     }

     errno = 0;
     status = lstat(argv[1], &buffer);

     if (errno == ELOOP) {
          fprintf(stderr, "loop found");
     }

     return 1;
}

I'm running my program like this:

$ findloops self
$ findloops a

Any idea what I'm doing wrong?

This is NOT homework.

This is where I got the idea from.

A: 

I would take a look at the buffer returned. According to the documentation of lstat the buffer contains two items that would be relevant:

  • st_ino - The inode for the file (note that this number is unique to each distinct file and all directories on a Linux file system, but the same inode number can appear in different file systems).
  • st_dev - The device that the file currently resides on.

If you create a list containing these two items per element+the directory where the link is located as the previously visited elements, you could detect loops. Also don't forget to pop them off when you leave the directory that they were created in.

I'm not convinced that ELOOP is the value that you think it is. According to this, it identifies the maximum links tolerated in the class path, but it won't tell you which link looped first.

The documentation on the page claimed this: "ELOOP: Too many symbolic links were encountered in translating the pathname. "

monksy
I see what you're saying about ELOOP. I think you're right!
Jenna
+1  A: 

The trouble is that 'lstat()' looks at the symlink and its properties, and the symlinks actually exist.

If you replace the call with 'stat()', then you will get the ELOOP error. This tries to get the information at the far end of the symlink, and that cannot be found because of the ELOOP condition.

You should only test errno after you have verified that status indicates a failure. With a genuine system call, it is unlikely that errno would be set when the call succeeds, but with library functions, you can find errno is set even though the call succeeds. For example, with some standard I/O library implementations, you can have errno == ENOTTY even after a successful function call; the code checks whether the file descriptor represents a terminal and errno is set to indicate that it isn't, but since the function succeeded, it is not legitimate to check errno.

Jonathan Leffler
A: 

ELOOP doesn't have to mean that there is a loop. It can also mean that there are too many symbolic links from source to target, as in

a -> b -> c -> d -> e ... -> z

do this enough times and the OS kernel (particularily for some cases on linux) will give up trying to follow the links, even if they are all valid and non-cyclic.

You may also be interested in man 2 readlink.

Peeter Joot
Note that readlink is a badly behaved function; it does not null-terminate the result string. Caveat lector!
Jonathan Leffler
A: 

After some playing with code, it looks like you've found either a feature or a bug with lstat(2). According to the man page on lstat, which is also stat and fstat, the difference between stat and lstat is:

stat() stats the file pointed to by path and fills in buf.

lstat() is identical to stat(), except that if path is a symbolic link, then the link itself is stat-ed, not the file that it refers to

I took your program and played with it a little. I used lstat, stat, and fopen to check the link. Code is below. The bottom line is that both stat and fopen detected the link properly, while lstat failed. I have no explanation for this.

The program below, executed on file bar created as 'ln -s bar bar', gave the following output:

./foo ./bar
Errno as returned from lstat = 0
Errno as returned from stat = 92
loop found
Errno as returned from fopen = 92
loop found

Code:

#include <sys/stat.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

int
main(int argc, char *argv[])
{
     struct stat buffer;
     int status;
     int savedErrno1;
     int savedErrno2;
     int savedErrno3;
     FILE *theFile;

     if (argc != 2) {
          printf("error: file name required\n");
          return 0;
     }

     errno = 0;
     status = lstat(argv[1], &buffer);
     savedErrno1 = errno;

     printf("Errno as returned from lstat = %d\n", savedErrno1);

     if (savedErrno1 == ELOOP) {
          printf("loop found\n");
     }

     errno = 0;
     status = stat(argv[1], &buffer);
     savedErrno2 = errno;

     printf("Errno as returned from stat = %d\n", savedErrno2);

     if (savedErrno2 == ELOOP) {
          printf("loop found\n");
     }

     errno = 0;
     theFile = fopen(argv[1], "w");
     savedErrno3 = errno;

     printf("Errno as returned from fopen = %d\n", savedErrno3);

     if (savedErrno3 == ELOOP) {
          printf("loop found\n");
     }

     return 1;
}
jfawcett