Take the 2-minute tour ×
Super User is a question and answer site for computer enthusiasts and power users. It's 100% free, no registration required.

In response to a question I asked on SO, Give the mount point of a path, one respondant suggested using stat to get the device name associated with the volume of a given path. This works nicely on Linux, but gives crazy results on Mac OS X 10.4. For my system, df and mount give:

cas cas$ df
Filesystem              512-blocks     Used     Avail Capacity  Mounted on
/dev/disk0s3              58342896 49924456   7906440    86%    /
devfs                          194      194         0   100%    /dev
fdesc                            2        2         0   100%    /dev
<volfs>                       1024     1024         0   100%    /.vol
automount -nsl [166]             0        0         0   100%    /Network
automount -fstab [170]           0        0         0   100%    /automount/Servers
automount -static [170]          0        0         0   100%    /automount/static
/dev/disk2s1             163577856 23225520 140352336    14%    /Volumes/Snapshot
/dev/disk2s2             409404102  5745938 383187960     1%    /Volumes/Sparse
cas cas$ mount
/dev/disk0s3 on / (local, journaled)
devfs on /dev (local)
fdesc on /dev (union)
<volfs> on /.vol
automount -nsl [166] on /Network (automounted)
automount -fstab [170] on /automount/Servers (automounted)
automount -static [170] on /automount/static (automounted)
/dev/disk2s1 on /Volumes/Snapshot (local, nodev, nosuid, journaled)
/dev/disk2s2 on /Volumes/Sparse (asynchronous, local, nodev, nosuid)

Trying to get the devices from the mount points, though:

cas cas$ df | grep -e/ 
            | awk '{print $NF}' 
            |  while read line; do echo $line $(stat -f"%Sdr" $line); done
/ disk0s3r
/dev ???r
/dev ???r
/.vol ???r
/Network ???r
/automount/Servers ???r
/automount/static ???r
/Volumes/Snapshot disk2s1r
/Volumes/Sparse disk2s2r

Here, I'm feeding each of the mount points scraped from df to stat, outputting the results of the "%Sdr" format string, which is supposed to be the device name: Cf. stat(1) man page:

The special output specifier S may be used to indicate that the
output, if applicable, should be in string format.  May be used
in combination with:
...
             dr      Display actual device name.

What's going on? Is it a bug in stat, or some Darwin VFS weirdness?

Postscript Per Andrew McGregor, try passing "%Sd" to stat for more weirdness. It lists some apparently arbitrary subset of files from CWD...

share|improve this question

3 Answers 3

up vote 3 down vote accepted

First, as a stat format, "%Sdr" is interpreted as the d field selector, modified by the S specifier, followed by a literal lower-case R character.

The manpage would probably be more clear if it said "d, r" instead of "dr" under the description of S. d and r are both separate field selectors. r selects the device number from the stat(2) info (i.e. st_rdev; only useful when stat-ing device entries (i.e. the entries under /dev)). d selects the device number of the device which holds the stat-ed entry (i.e. st_dev). The number it prints is a combination of the major and minor numbers shown by ls (major << 24 | minor).

S is a modifier that can be applied to both d, r, and several other field selectors. When applied to d or r, it tries to print the name of the device instead of its raw number. Some devices, like the virtual filesystem devices, do not have proper names, so it prints ??? instead (it might be nicer if it printed <major>,<minor> instead). This does not mean that these filesystems do not have devices, just that their devices do not have normal names like "disk0s3".

The “shell weirdness” (“apparently arbitrary subset of files from CWD”) is due to a lack of quoting. The shell sees the (unquoted) "???" result and expands it as a glob pattern. If the cwd contains any entries with exactly three bytes (characters, depending on locale?), the shell will substitute those entries for the glob pattern. The behavior (the quoting and the glob expansion) varies by shell, and can usually be modified by various shell options.

You can modify your original command like this to avoid the globbing and the trailing "r":

df | grep -e/ | awk '{print $NF}' |  while read line; do echo "$line" "$(stat -f%Sd "$line"); done

But I would probably write it like this instead:

df | tail +2 | awk '{print $NF}' | xargs stat -f'%N %Sd %d'
  • Note that this fails for any mounted filesystems have spaces in their mount point names. Neither the output of mount or df is super easy to readily parse (both have fields that might have nearly arbitrary strings in them).

With that output (name, device number, device name), you can maybe get a better idea of what is going on.

Or, maybe you would like to see the major and minor numbers in addition of the raw device numbers (compare these to what you see in (e.g.) ls -l /dev/disk0s3):

df | tail +2 | awk '{print $NF}' | xargs stat -f'%N %Sd %d' | 
  awk 'BEGIN{f=2^24} {$(NF+1) = int($NF/f) "," ($NF%f) } 1'

Here is a small C program that can replace the problematic "df | head | awk" pipeline.

Of course, such a C program could do the rest work itself, but it might be nice to have a stand-alone program that can just spit out NUL terminated mount points.

mountz | xargs -0 stat -f'%N %Sd %d' | 
  awk 'BEGIN{f=2^24} {$(NF+1) = int($NF/f) "," ($NF%f) } 1'

Code:

#include <sys/mount.h>
#include <stdio.h>
#include <stdlib.h>

/* usage: mountz | xargs -0 command_for_each_mount_point */

int main(int argc, const char *argv[]) {
    struct statfs *buf;
    int flags = MNT_NOWAIT, num_fs, num_stat, i;
    unsigned bufsz;

    num_fs = getfsstat(NULL, 0, flags);
    if (num_fs < 0) {
        perror("unable to count mounted filesystems: getfsstat");
    exit(1);
    }

    bufsz = sizeof(*buf) * num_fs;
    buf = malloc(bufsz);
    if (!buf) {
        perror("unable to allocate %u statfs structs");
    exit(1);
    }
    fprintf(stderr, "p=%p\n", buf);

    num_stat = getfsstat(buf, bufsz, flags);
    if (num_stat < 0) {
        perror("unable to getfsstat");
    exit(1);
    }
    if (num_stat != num_fs) {
    fprintf(stderr, "Hmm, expected %u, got %d.\n", num_fs, num_stat);
    }

    for (i = 0; i < num_stat; i++) {
    fprintf(stdout, "%s%c", buf[i].f_mntonname, 0);
    }
}
share|improve this answer
    
I'd figured this all out, but I've changed this to the accepted post because you lay this all out so clearly. Wrt. "The manpage would probably be more clear if it said "d, r" instead of "dr" under the description of S" - well, you can delete the probably :-> –  Charles Stewart Mar 31 '10 at 12:50

So the virtual file systems don't have devices - I didn't try it for those filesystems. It's not a problem for /Network, /automount/Servers or /automount/static as those shouldn't contain files. But /dev is interesting.

share|improve this answer
    
The insight that Darwin doesn't think that mount points need to be associated with filesystems is the key. I changed the accepted post to Chris', because he lays out all of the issues so clearly. –  Charles Stewart Mar 31 '10 at 12:52
df | grep -e/ | awk '{print $NF}' |  while read line; do echo $line $(stat -f"%Sd" $line); done
/ disk0s2
/dev XGS bin tmp
/net XGS bin tmp
/home XGS bin tmp

I'd say, Darwin VFS weirdness.

share|improve this answer
    
Ah! I understand one thing: that's regular UNIX shell weirdness: the filesystems are called "???", which when echoed turn into the three-letter filenames in the current directory, just like echo $(echo \?\?\?). But it does show that stat's interpretation of format strings does not match what the stat(1) manpage says: "%Sd" is doing what the manpage says "%Sdr" should do. –  Charles Stewart Feb 3 '10 at 8:55

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.