Something went wrong. Try again.
A breadth-first version of the UNIX find command tavianator.com/projects/bfs.html
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305// Copyright © Tavian Barnes <tavianator@tavianator.com>// SPDX-License-Identifier: 0BSD
#include "mtab.h"
#include "alloc.h"#include "bfs.h"#include "bfstd.h"#include "stat.h"#include "trie.h"
#include <errno.h>#include <fcntl.h>#include <stdlib.h>#include <string.h>#include <sys/types.h>
#ifndef BFS_USE_MNTENT# define BFS_USE_MNTENT BFS_HAS_GETMNTENT_1#endif#ifndef BFS_USE_MNTINFO# define BFS_USE_MNTINFO (!BFS_USE_MNTENT && BFS_HAS_GETMNTINFO)#endif#ifndef BFS_USE_MNTTAB# define BFS_USE_MNTTAB (!BFS_USE_MNTINFO && BFS_HAS_GETMNTENT_2)#endif
#if BFS_USE_MNTENT# include <mntent.h># include <paths.h># include <stdio.h>#elif BFS_USE_MNTINFO# include <sys/mount.h>#elif BFS_USE_MNTTAB# include <stdio.h># include <sys/mnttab.h>#endif
/** * A mount point in the table. */struct bfs_mount { /** The path to the mount point. */ char *path; /** The filesystem type. */ char *type; /** Buffer for the strings. */ char buf[];};
struct bfs_mtab { /** Mount point arena. */ struct varena varena;
/** The array of mount points. */ struct bfs_mount **mounts; /** The number of mount points. */ size_t nmounts;
/** The basenames of every mount point. */ struct trie names;
/** A map from device ID to fstype (populated lazily). */ struct trie types; /** Whether the types map has been populated. */ bool types_filled;};
/** * Add an entry to the mount table. */[[_maybe_unused]]static int bfs_mtab_add(struct bfs_mtab *mtab, const char *path, const char *type) { size_t path_size = strlen(path) + 1; size_t type_size = strlen(type) + 1; size_t size = path_size + type_size; struct bfs_mount *mount = varena_alloc(&mtab->varena, size); if (!mount) { return -1; }
struct bfs_mount **ptr = RESERVE(struct bfs_mount *, &mtab->mounts, &mtab->nmounts); if (!ptr) { goto free; } *ptr = mount;
mount->path = mount->buf; memcpy(mount->path, path, path_size);
mount->type = mount->buf + path_size; memcpy(mount->type, type, type_size);
const char *name = path + xbaseoff(path); if (!trie_insert_str(&mtab->names, name)) { goto shrink; }
return 0;
shrink: --mtab->nmounts;free: varena_free(&mtab->varena, mount, size); return -1;}
struct bfs_mtab *bfs_mtab_parse(void) { struct bfs_mtab *mtab = ZALLOC(struct bfs_mtab); if (!mtab) { return NULL; }
VARENA_INIT(&mtab->varena, struct bfs_mount, buf);
trie_init(&mtab->names); trie_init(&mtab->types);
int error = 0;
#if BFS_USE_MNTENT
FILE *file = setmntent(_PATH_MOUNTED, "r"); if (!file) { // In case we're in a chroot or something with /proc but no /etc/mtab error = errno; file = setmntent("/proc/mounts", "r"); } if (!file) { goto fail; }
struct mntent *mnt; while ((mnt = getmntent(file))) { if (bfs_mtab_add(mtab, mnt->mnt_dir, mnt->mnt_type) != 0) { error = errno; endmntent(file); goto fail; } }
endmntent(file);
#elif BFS_USE_MNTINFO
#if __NetBSD__ typedef struct statvfs bfs_statfs;#else typedef struct statfs bfs_statfs;#endif
bfs_statfs *mntbuf; int size = getmntinfo(&mntbuf, MNT_WAIT); if (size <= 0) { error = errno; goto fail; }
for (bfs_statfs *mnt = mntbuf; mnt < mntbuf + size; ++mnt) { if (bfs_mtab_add(mtab, mnt->f_mntonname, mnt->f_fstypename) != 0) { error = errno; goto fail; } }
#elif BFS_USE_MNTTAB
FILE *file = xfopen(MNTTAB, O_RDONLY | O_CLOEXEC); if (!file) { error = errno; goto fail; }
struct mnttab mnt; while (getmntent(file, &mnt) == 0) { if (bfs_mtab_add(mtab, mnt.mnt_mountp, mnt.mnt_fstype) != 0) { error = errno; fclose(file); goto fail; } }
fclose(file);
#else
error = ENOTSUP; goto fail;
#endif
return mtab;
fail: bfs_mtab_free(mtab); errno = error; return NULL;}
static int bfs_mtab_fill_types(struct bfs_mtab *mtab) { const enum bfs_stat_flags flags = BFS_STAT_NOFOLLOW | BFS_STAT_NOSYNC; int ret = -1;
// It's possible that /path/to/mount was unmounted between bfs_mtab_parse() and bfs_mtab_fill_types(). // In that case, the dev_t of /path/to/mount will be the same as /path/to, which should not get its // fstype from the old mount record of /path/to/mount. // // Detect this by comparing the st_dev of the parent (/path/to) and child (/path/to/mount). Only when // they differ can the filesystem type actually change between them. As a minor optimization, we keep // the parent directory open in case multiple mounts have the same parent (e.g. /mnt). char *parent_dir = NULL; int parent_fd = -1; int parent_ret = -1; struct bfs_stat parent_stat;
for (size_t i = 0; i < mtab->nmounts; ++i) { struct bfs_mount *mount = mtab->mounts[i]; const char *path = mount->path; int fd = AT_FDCWD;
char *dir = xdirname(path); if (!dir) { goto fail; }
if (parent_dir && strcmp(parent_dir, dir) == 0) { // Same parent free(dir); } else { free(parent_dir); parent_dir = dir;
if (parent_fd >= 0) { xclose(parent_fd); } parent_fd = open(parent_dir, O_SEARCH | O_CLOEXEC | O_DIRECTORY);
parent_ret = -1; if (parent_fd >= 0) { parent_ret = bfs_stat(parent_fd, NULL, flags, &parent_stat); } }
if (parent_fd >= 0) { fd = parent_fd; path += xbaseoff(path); }
struct bfs_stat sb; if (bfs_stat(fd, path, flags, &sb) != 0) { continue; }
if (parent_ret == 0 && parent_stat.dev == sb.dev && parent_stat.ino != sb.ino) { // Not a mount point any more (or a bind mount, but with the same fstype) continue; }
if (trie_set_mem(&mtab->types, &sb.mnt_id, sizeof(sb.mnt_id), mount->type) != 0) { goto fail; } }
mtab->types_filled = true; ret = 0;
fail: if (parent_fd >= 0) { xclose(parent_fd); } free(parent_dir); return ret;}
const char *bfs_fstype(const struct bfs_mtab *mtab, const struct bfs_stat *statbuf) { if (!mtab->types_filled) { if (bfs_mtab_fill_types((struct bfs_mtab *)mtab) != 0) { return NULL; } }
const char *type = trie_get_mem(&mtab->types, &statbuf->mnt_id, sizeof(statbuf->mnt_id)); if (type) { return type; } else { return "unknown"; }}
bool bfs_might_be_mount(const struct bfs_mtab *mtab, const char *name) { return trie_find_str(&mtab->names, name);}
void bfs_mtab_free(struct bfs_mtab *mtab) { if (mtab) { trie_destroy(&mtab->types); trie_destroy(&mtab->names);
free(mtab->mounts); varena_destroy(&mtab->varena);
free(mtab); }}