CPUやメモリの統計は/proc以下のファイルを見れば調べられますが、ファイルシステムの容量などはどうやって取得しているんだろうと気になったでdf(1)のコードを眺めてみました。
ライブラリの動作検証で用意したコードはこちら。
Linuxの場合
Linuxでは、ファイルシステムの情報はstatvfs(3)で取得できるようです。これはstatfs(2)システムコールのラッパーという扱いですが、基本的にはstatvfs(3)を使うように推奨されます。statvfs(3)は以下のように使います。
#include <stdio.h> #include <string.h> #include <stdint.h> #include <inttypes.h> #include <errno.h> #include <sys/statvfs.h> char *argv0; void fstat(char *); uintmax_t bused(struct statvfs *); uintmax_t bsize(struct statvfs *, uintmax_t); int main(int argc, char **argv) { int i; argv0 = argv[0]; for(i = 1; i < argc; i++) fstat(argv[i]); return 0; } void fstat(char *path) { struct statvfs fs; if(statvfs(path, &fs) < 0){ fprintf(stderr, "%s: statvfs: %s\n", argv0, strerror(errno)); exit(1); } printf("path %s\n", path); printf("block size %lu\n", fs.f_bsize); printf("fragment size %lu\n", fs.f_frsize); printf("fragments in a block %lu\n", fs.f_bsize/fs.f_frsize); printf("used blocks %" PRIuMAX "\n", bsize(&fs, bused(&fs))); printf("avail blocks %" PRIuMAX "\n", bsize(&fs, fs.f_bavail)); printf("total blocks %" PRIuMAX "\n", bsize(&fs, fs.f_blocks)); printf("used inodes %u\n", fs.f_files - fs.f_ffree); printf("free inodes %u\n", fs.f_ffree); printf("flags"); if(fs.f_flag&ST_RDONLY) printf(" readonly"); printf("\n"); printf("namelen %lu\n", fs.f_namemax); printf("\n"); } uintmax_t bused(struct statvfs *f) { return f->f_blocks - f->f_bfree; } uintmax_t bsize(struct statvfs *f, uintmax_t n) { return n * f->f_frsize / 1024; }
実行した結果、df(1)の出力と同じ値になっているので正しそうですね。
$ cc fs_linux.c $ ./a.out / path / block size 4096 fragment size 4096 fragments in a block 1 used blocks 15635224 avail blocks 42476028 total blocks 61252420 used inodes 561671 free inodes 3345913 flags namelen 255 $ df -P / Filesystem 1024-blocks Used Available Capacity Mounted on overlay 61252420 15635224 42476028 27% / $ df -i / Filesystem Inodes IUsed IFree IUse% Mounted on overlay 3907584 561671 3345913 15% /
マウントされたファイルシステムのリストは、getmntent(3)を使うと取得できます。使い方はこんな雰囲気。
FILE *r; struct mntent *p; r = setmntent("/etc/mtab", "r"); while(p = getmntent(r)) printf("%s\n", p->mnt_dir); endmntent(r);
setmntent(3)の第1引数にファイルパスを渡すようになっていて、上の例でもそうですがこのファイルパスには度々/etc/mtabが使われます。手元の環境では、/etc/mtabは/proc/self/mountinfoへのシンボリックリンクとなっているようでした*1。getfsent(3)という似た名前の関数もあるけど、こちらは非推奨のようです。
macOSの場合
macOSでは、getmntent(3)が存在しないので、上記のコードがコンパイルできません。また、statvfs(3)は存在しますが、f_ffreeの値がdf(1)の結果と異なっています。
% ./a.out / path / block size 1048576 fragment size 4096 fragments in a block 256 used blocks 22031496 avail blocks 670026888 total blocks 976490576 used inodes 488433 free inodes 586997151 <- この値だけおかしい flags readonly namelen 255 % df / Filesystem 512-blocks Used Available Capacity iused ifree %iused Mounted on /dev/disk1s5 976490576 22031496 670024808 4% 488433 4881964447 0% /
macOS版df(1)のソースコードを読むと、statvfs(3)ではなくstatfs(2)が使われていて、こちらを使えばdf(1)の出力と同じ値を取得できました。ただし、statfs.f_bsize
はstatvfs.f_frsize
相当の値で、statfs.f_iosize
はstatvfs.f_bsize
相当となっていて、f_bsize
の意味が異なっているところは難しいですね。
struct statfs fs; if(statfs(path, &fs) < 0){ fprintf(stderr, "%s: statfs: %s\n", argv0, strerror(errno)); exit(1); } printf("mounted on %s\n", fs.f_mntonname); printf("fstype %s\n", fs.f_fstypename); printf("block size %u\n", fs.f_iosize); printf("fragment size %u\n", fs.f_bsize); printf("fragments in a block %u\n", fs.f_iosize/fs.f_bsize); ...
これでdfと同じ値を読むことができました。
% ./a.out / mounted on / fstype apfs block size 1048576 fragment size 4096 fragments in a block 256 used blocks 22031496 avail blocks 670031712 total blocks 976490576 used inodes 488433 free inodes 4881964447 flags readonly % df / Filesystem 512-blocks Used Available Capacity iused ifree %iused Mounted on /dev/disk1s5 976490576 22031496 670031712 4% 488433 4881964447 0% /
最後にmacOSでマウントしているファイルシステムの列挙は、getmntinfo(3)で取れます。
struct statfs *f, *p, *e; int n; n = getmntinfo(&f, MNT_NOWAIT); if(n < 0){ fprintf(stderr, "%s: getmntinfo: %s\n", argv0, strerror(errno)); return; } e = f+n; for(p = f; p < e; p++){ printf("mounted on %s\n", p->f_mntonname); ... } free(f); /* mallocされるのでfreeが必要 */
getmntinfo(3)の代わりに、もっと低レベルなgetfsstat(2)を使っても実現できます。
/* 最初の引数にNULLを渡すとマウントされたファイルシステムの数を調べられる */ n = getfsstat(NULL, 0, MNT_NOWAIT); if(n < 0){ fprintf(stderr, "%s: getfsstat: %s\n", argv0, strerror(errno)); return; } f = malloc(sizeof(*f)*n); if(f == NULL){ fprintf(stderr, "%s: malloc: %s\n", argv0, strerror(errno)); return; } if(getfsstat(f, sizeof(*f)*n, MNT_NOWAIT) < 0){ fprintf(stderr, "%s: getfsstat: %s\n", argv0, strerror(errno)); return; }
他OSの場合
手元にないので実験していませんが、coreutilsのコードを読む限りは大変そうな気配がありました。
*1:手元とは言ったけど他もだいたい同じかな