3

This question is Linux specific; the solution does not need to be portable.

I am looking for a library function/syscall, or some combination thereof which will give me either the device (eg: /dev/sdb1) or mount point (eg: /home) for any arbitrary file. It would appear the f_fsid field of a statfs struct would do the trick, but it is not used on Linux.

I can find this info easily using the shell:

df "$filename" | awk 'NR==1 {next} {print $6; exit}'

but it would seem none of the exec family of functions return output from the command that was run, and I would prefer to keep my solution pure C anyway.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278

3 Answers3

1

It seems that stat(2) (or lstat) is the function you are looking for. It will get you the device numbers in st_dev.

I think df(1) just reads /etc/mtab, though.

remram
  • 4,805
  • 1
  • 29
  • 42
  • Hi remram. df definitely uses stat to find mount points. I went for a quick and dirty `popen()` solution because I wanted the code to run ASAP, but I re-wrote it today in pure C. – Darren Kirby Sep 28 '14 at 20:10
  • Finding the mountpoint is easy, so is getting the device numbers. But getting the device filename under `/dev/` (which is what df prints) requires reading mtab. – remram Sep 29 '14 at 14:33
0

I wrote a pure C solution using stat(). For anyone who may find this in a search, here it is in a function:

int getmntpt(char *path, char *mount_point) {
    struct stat cur_stat;
    struct stat last_stat;

    char dir_name[PATH_MAX];
    char *dirname_p = dir_name;
    char cur_cwd[255];
    char *cur_cwd_p = cur_cwd;
    char saved_cwd[PATH_MAX];
    if (getcwd(saved_cwd, PATH_MAX) == NULL) {
        errno = EIO;
        return ERROR;
    }

    if (lstat(path, &cur_stat) < 0) {
        errno = EIO;
        return ERROR;
    }

    if (S_ISDIR (cur_stat.st_mode)) {
        last_stat = cur_stat;
        if (chdir("..") < 0)
            return ERROR;
        if (getcwd(cur_cwd_p, 255) == NULL) {
            errno = EIO;
            return ERROR;
        }
    } else { /* path is a file */
        size_t path_len, suffix_len, dir_len;
        path_len = strlen(path);
        suffix_len = strlen(strrchr(path, 47)); /* 47 = '/' */
        dir_len = path_len - suffix_len;
        dirname_p = strncpy(dirname_p, path, dir_len);
        if (chdir(dirname_p) < 0) 
            return ERROR;
        if (lstat(".", &last_stat) < 0)
            return ERROR;
    }

    for (;;) {
        if (lstat("..", &cur_stat) < 0)
            return ERROR;
        if (cur_stat.st_dev != last_stat.st_dev || cur_stat.st_ino == last_stat.st_ino)
            break; /* this is the mount point */
        if (chdir("..") < 0)
            return ERROR;
        last_stat = cur_stat;
    }
    if (getcwd(mount_point, PATH_MAX) == NULL)
        return ERROR;
    if (chdir(saved_cwd) < 0)
        return ERROR;
    return SUCCESS;
}
0

Here's a C solution that I wrote based on @DarrenKirby's answer, but it avoids using chdir, and is much shorter. [Also, it handles the edge case where the mountpoint itself is passed as an argument].

int getmntpt(char const  *path, char *mount_point) {

    char *test_path = malloc(PATH_MAX), *test_end;
    struct stat cur_stat, prev_stat;

    if (lstat(path, &prev_stat) < 0)
        return ERROR;

    test_end = stpcpy(test_path, path);
    if (!S_ISDIR(prev_stat.st_mode)) {
        test_end = strrchr(test_path, '/');
        if (test_end == NULL)
            test_end = stpcpy(test_path, ".");
        else
            *test_end = '\0';
    }

    for (;;) {
        test_end = stpcpy(test_end, "/..");     
        if (lstat(test_path, &cur_stat) < 0)
            return ERROR;
        if (cur_stat.st_dev != prev_stat.st_dev || cur_stat.st_ino == prev_stat.st_ino) /* root */
            break; /* this is the mount point */
        prev_stat = cur_stat;
    }

    *(test_end - 3) = '\0';
    if (realpath(test_path, mount_point) == NULL) {
        free(test_path);
        return -1;
    }

    return 0;
}
Reinstate Monica
  • 588
  • 7
  • 21