Search code examples
cmountreaddir

How to skip mounted files when walking through a directory tree with readdir_r


I am writing a function that recursively deletes all files and sub-directories in a directory tree, and this function will be used in multithread environment, so I would prefer opendir/readdir_r than nftw (the code is for Linux/Mac OSX/Solaris, while nftw is not thread-safe on some platform).

Since the function is deleting files, security is a great concern. If there's a link pointing to a sensitive location (e.g., the /usr/lib system directory), I don't want my function to try to delete the files under that directory. For symbolic/hard link files, lstat then S_ISLNK will do the job. However, if there's a mount point, S_ISDIR just returns true on it.

Maybe setmntent/getmntent would help, but my experiment on Linux found it can't handle following situation:

  1. mount //192.168.0.1/share at ~/work/share (need root privilege)
  2. mv ~/work /tmp/work (does not need root privilege)
  3. now getmntent still reports ~/work/share as the mount point

What I want is like the FTW_MOUNT flag to nftw:

man nftw:
...

    FTW_MOUNT
                  If set, stay within the same file system.

I am not sure if the st_dev field from struct stat is good for this, I don't know if the dev numbers are always different beyond a mount point.

with readdir_r is there a way to figure out mounted points?

Thank you!


Solution

  • From the Single Unix Specification - Issue 7:

    3.228 Mount Point

    Either the system root directory or a directory for which the st_dev field of structure stat differs from that of its parent directory.

    Note: The stat structure is defined in detail in <sys/stat.h>.

    In other words, yes, you can rely upon the device ID to determine whether you're at a mount point or not. The key to understand what a mount point is involves understanding that if something like /usr resides on the same file system as /, you will never type mount device /usr.

    A simple example where /home, /tmp, /usr, and /usr/src are all on different devices:

    #include <errno.h>
    #include <stdio.h>
    #include <string.h>
    
    #include <sys/stat.h>
    
    int main(int argc, char *argv[])
    {
        struct stat stbuf;
    
        if (stat(".", &stbuf) == -1) {
            perror("couldn't get working directory");
            return 1;
        }
    
        printf("Device ID for directory .: %lX\n", stbuf.st_dev);
    
        /* Loop through the command line arguments. */
        while (*++argv) {
            if (stat(*argv, &stbuf) == -1) {
                fprintf(stderr, "error: couldn't get device ID for directory '%s': %s\n", *argv, strerror(error));
                continue;
            }
    
            printf("Device ID for directory %s: %lX\n", *argv, stbuf.st_dev);
        }
    
        return 0;
    }
    

    Sample run:

    sh$ ./a.out /usr ~/misc\ files /nonexistent/path /usr/src /tmp
    Device ID for directory .: 807
    Device ID for directory /usr: 803
    Device ID for directory /home/kit/misc files: 807
    error: couldn't get device ID for directory '/nonexistent/path': No such file or directory
    Device ID for directory /usr/src: 805
    Device ID for directory /tmp: 802