3

I am writing a program to mimic some of the behavior of find that walks a directory tree and calls lstat on the files it finds there to determine their type. The real find will ignore files where the user does not have R or X access in that directory. I can't seem to replicate this behavior; my code will go ahead and make the lstat call and get an Illegal seek error (which is what I'm trying to prevent) even though the code that performs this is inside a block that checks access().

My first thought was that perhaps the second access() call should be on the path and not path/filename, but that didn't seem to work either (and isn't it redundant anyway?)

Any guidance would be greatly appreciated.

My code (I'm cutting out error catching and other stuff for brevity):

    void open_dir( char *dir, char *pattern, char type )
    {
        DIR *d;
        struct dirent *de;

        if ( access(dir, (R_OK | X_OK)) == 0 )
        {
            d = opendir(dir);

            while( ( de = readdir(d) ) )
                examine_de( de, dir, pattern, type );

            closedir(d);
        }
    }

    void examine_de( struct dirent *de, char *dir, char *pattern, char type )
    {
        char fn[ _POSIX_PATH_MAX ];
        strcpy(fn, dir);
        strcat(fn, "/");
        strcat(fn, de->d_name);

        if ( access(fn, (R_OK | X_OK)) == 0 )
        {
            struct stat buf;
            lstat(fn, &buf);
            //check pattern matches, etc., printf fn if appropriate
            if ( ( S_ISDIR(buf.st_mode) ) &&
                 ( strcmp(de->d_name, ".") != 0 ) &&
                 ( strcmp(de->d_name, "..") != 0 ) )
                open_dir(fn, pattern, type);
        }
        return;
    }
Sabrina S
  • 337
  • 8
  • 21

1 Answers1

4

lstat() should never return ESPIPE (Illegal seek). Are you sure it isn't another syscall that is returning that, or a unchanged errno value after a successful lstat()? (In other words, the bug may actually be in your error-checking code that you've elided).

That said, there is no point in using access() in this way anyway - it just introduces a race condition (because the file permissions could change between the access() call and the opendir() / lstat() call), and doesn't gain anything. Simply check the return value of opendir() and lstat() instead:

void open_dir( char *dir, char *pattern, char type )
{
    DIR *d;
    struct dirent *de;

    if (d = opendir(dir))
    {
        while( ( de = readdir(d) ) )
            examine_de( de, dir, pattern, type );

        closedir(d);
    }
}

void examine_de( struct dirent *de, char *dir, char *pattern, char type )
{
    char fn[ _POSIX_PATH_MAX ];
    struct stat buf;

    strcpy(fn, dir);
    strcat(fn, "/");
    strcat(fn, de->d_name);

    if (lstat(fn, &buf) == 0)
    {
        //check pattern matches, etc., printf fn if appropriate
        if ( ( S_ISDIR(buf.st_mode) ) &&
             ( strcmp(de->d_name, ".") != 0 ) &&
             ( strcmp(de->d_name, "..") != 0 ) )
            open_dir(fn, pattern, type);
    }
    return;
}

This is generally the right pattern - rather than checking to see if the operation might work and then trying the operation, instead try the operation unconditionally and then check why it failed.

caf
  • 233,326
  • 40
  • 323
  • 462
  • You probably thought it was an offhand comment, but I found that setting `errno = 0` after each `perror` made some difference, especially in the case of error checking on `readdir`, since `readdir` returns `NULL` at the end of the directory *and* in case of error, so my code was checking whether `errno == 0` before moving on. But I will certainly also take to heart your warning about `access` -- thank you! – Sabrina S Feb 27 '12 at 02:54
  • 1
    @SabrinaStar: You should set `errno = 0;` immediately *before* the `readdir()` call - the important point here is that unsuccessful calls set `errno`, but sucessful ones leave `errno` unchanged. – caf Feb 27 '12 at 03:20