Home » Questions » Computers [ Ask a new question ]

What's up with stat on Mac OS X/Darwin? Or filesystems without names

What's up with stat on Mac OS X/Darwin? Or filesystems without names

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:

Asked by: Guest | Views: 65
Total answers/comments: 2
Guest [Entry]

"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);
}
}"
Guest [Entry]

"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."