|  | // SPDX-License-Identifier: GPL-2.0 | 
|  | /* | 
|  | * Copyright (c) 2005-2006 Silicon Graphics, Inc. | 
|  | * All Rights Reserved. | 
|  | */ | 
|  |  | 
|  | #include <paths.h> | 
|  | #include <errno.h> | 
|  | #include <stdio.h> | 
|  | #include <stdlib.h> | 
|  | #include <string.h> | 
|  | #include <unistd.h> | 
|  | #include <sys/types.h> | 
|  | #include <sys/stat.h> | 
|  | #include "paths.h" | 
|  | #include "input.h" | 
|  | #include "projects.h" | 
|  | #include <limits.h> | 
|  |  | 
|  | extern char *progname; | 
|  |  | 
|  | int fs_count; | 
|  | int xfs_fs_count; | 
|  | struct fs_path *fs_table; | 
|  | struct fs_path *fs_path; | 
|  |  | 
|  | char *mtab_file; | 
|  | #define PROC_MOUNTS	"/proc/self/mounts" | 
|  |  | 
|  | static int | 
|  | fs_device_number( | 
|  | const char	*name, | 
|  | dev_t		*devnum) | 
|  | { | 
|  | struct stat	sbuf; | 
|  |  | 
|  | if (stat(name, &sbuf) < 0) | 
|  | return errno; | 
|  | /* | 
|  | * We want to match st_rdev if the path provided is a device | 
|  | * special file.  Otherwise we are looking for the the | 
|  | * device id for the containing filesystem, in st_dev. | 
|  | */ | 
|  | if (S_ISBLK(sbuf.st_mode) || S_ISCHR(sbuf.st_mode)) | 
|  | *devnum = sbuf.st_rdev; | 
|  | else | 
|  | *devnum = sbuf.st_dev; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Find the FS table entry for the given path.  The "flags" argument | 
|  | * is a mask containing FS_MOUNT_POINT or FS_PROJECT_PATH (or both) | 
|  | * to indicate the type of table entry sought. | 
|  | * fs_table_lookup() finds the fs table entry for the filesystem hosting | 
|  | * the file represented in the "dir" argument. To compare against actual | 
|  | * mount point entries, use fs_table_lookup_mount() instead. | 
|  | */ | 
|  | struct fs_path * | 
|  | fs_table_lookup( | 
|  | const char	*dir, | 
|  | uint		flags) | 
|  | { | 
|  | uint		i; | 
|  | dev_t		dev = 0; | 
|  |  | 
|  | if (fs_device_number(dir, &dev)) | 
|  | return NULL; | 
|  |  | 
|  | for (i = 0; i < fs_count; i++) { | 
|  | if (flags && !(flags & fs_table[i].fs_flags)) | 
|  | continue; | 
|  | if (fs_table[i].fs_datadev == dev) | 
|  | return &fs_table[i]; | 
|  | } | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | static struct fs_path * | 
|  | __fs_table_lookup_mount( | 
|  | const char	*dir, | 
|  | const char	*blkdev) | 
|  | { | 
|  | uint		i; | 
|  | char		rpath[PATH_MAX]; | 
|  | char		dpath[PATH_MAX]; | 
|  |  | 
|  | if (!dir && !blkdev) | 
|  | return NULL; | 
|  |  | 
|  | if (dir && !realpath(dir, dpath)) | 
|  | return NULL; | 
|  | if (blkdev && !realpath(blkdev, dpath)) | 
|  | return NULL; | 
|  |  | 
|  | for (i = 0; i < fs_count; i++) { | 
|  | if (fs_table[i].fs_flags != FS_MOUNT_POINT) | 
|  | continue; | 
|  | if (dir && !realpath(fs_table[i].fs_dir, rpath)) | 
|  | continue; | 
|  | if (blkdev && !realpath(fs_table[i].fs_name, rpath)) | 
|  | continue; | 
|  | if (strcmp(rpath, dpath) == 0) | 
|  | return &fs_table[i]; | 
|  | } | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Find the FS table entry describing an actual mount for the given path. | 
|  | * Unlike fs_table_lookup(), fs_table_lookup_mount() compares the "dir" | 
|  | * argument to actual mount point entries in the table. Accordingly, it | 
|  | * will find matches only if the "dir" argument is indeed mounted. | 
|  | */ | 
|  | struct fs_path * | 
|  | fs_table_lookup_mount( | 
|  | const char	*dir) | 
|  | { | 
|  | return __fs_table_lookup_mount(dir, NULL); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Find the FS table entry describing an actual mount for the block device. | 
|  | * Unlike fs_table_lookup(), fs_table_lookup_blkdev() compares the "bdev" | 
|  | * argument to actual mount point names in the table. Accordingly, it | 
|  | * will find matches only if the "bdev" argument is indeed mounted. | 
|  | */ | 
|  | struct fs_path * | 
|  | fs_table_lookup_blkdev( | 
|  | const char	*bdev) | 
|  | { | 
|  | return __fs_table_lookup_mount(NULL, bdev); | 
|  | } | 
|  |  | 
|  | static int | 
|  | fs_table_insert( | 
|  | char		*dir, | 
|  | uint		prid, | 
|  | uint		flags, | 
|  | char		*fsname, | 
|  | char		*fslog, | 
|  | char		*fsrt) | 
|  | { | 
|  | dev_t		datadev, logdev, rtdev; | 
|  | struct fs_path	*tmp_fs_table; | 
|  | int		error; | 
|  |  | 
|  | datadev = logdev = rtdev = 0; | 
|  | error = fs_device_number(dir, &datadev); | 
|  | if (error) | 
|  | goto out_nodev; | 
|  | if (fslog) { | 
|  | error = fs_device_number(fslog, &logdev); | 
|  | if (error) | 
|  | goto out_nodev; | 
|  | } | 
|  | if (fsrt) { | 
|  | error = fs_device_number(fsrt, &rtdev); | 
|  | if (error) | 
|  | goto out_nodev; | 
|  | } | 
|  |  | 
|  | if (!platform_test_xfs_path(dir)) | 
|  | flags |= FS_FOREIGN; | 
|  |  | 
|  | /* | 
|  | * Make copies of the directory and data device path. | 
|  | * The log device and real-time device, if non-null, | 
|  | * are already the result of strdup() calls so we | 
|  | * don't need to duplicate those.  In fact, this | 
|  | * function is assumed to "consume" both of those | 
|  | * pointers, meaning if an error occurs they will | 
|  | * both get freed. | 
|  | */ | 
|  | error = ENOMEM; | 
|  | dir = strdup(dir); | 
|  | if (!dir) | 
|  | goto out_nodev; | 
|  | fsname = strdup(fsname); | 
|  | if (!fsname) | 
|  | goto out_noname; | 
|  |  | 
|  | tmp_fs_table = realloc(fs_table, sizeof(fs_path_t) * (fs_count + 1)); | 
|  | if (!tmp_fs_table) | 
|  | goto out_norealloc; | 
|  | fs_table = tmp_fs_table; | 
|  |  | 
|  | /* Put foreign filesystems at the end, xfs filesystems at the front */ | 
|  | if (flags & FS_FOREIGN || fs_count == 0) { | 
|  | fs_path = &fs_table[fs_count]; | 
|  | } else { | 
|  | /* move foreign fs entries down, insert new one just before */ | 
|  | memmove(&fs_table[xfs_fs_count + 1], &fs_table[xfs_fs_count], | 
|  | sizeof(fs_path_t)*(fs_count - xfs_fs_count)); | 
|  | fs_path = &fs_table[xfs_fs_count]; | 
|  | } | 
|  | fs_path->fs_dir = dir; | 
|  | fs_path->fs_prid = prid; | 
|  | fs_path->fs_flags = flags; | 
|  | fs_path->fs_name = fsname; | 
|  | fs_path->fs_log = fslog; | 
|  | fs_path->fs_rt = fsrt; | 
|  | fs_path->fs_datadev = datadev; | 
|  | fs_path->fs_logdev = logdev; | 
|  | fs_path->fs_rtdev = rtdev; | 
|  | fs_count++; | 
|  | if (!(flags & FS_FOREIGN)) | 
|  | xfs_fs_count++; | 
|  |  | 
|  | return 0; | 
|  |  | 
|  | out_norealloc: | 
|  | free(fsname); | 
|  | out_noname: | 
|  | free(dir); | 
|  | out_nodev: | 
|  | /* "Consume" fslog and fsrt even if there's an error */ | 
|  | free(fslog); | 
|  | free(fsrt); | 
|  |  | 
|  | return error; | 
|  | } | 
|  |  | 
|  | /* Remove all the cached entries in the fs table. */ | 
|  | void | 
|  | fs_table_destroy(void) | 
|  | { | 
|  | int		i; | 
|  | struct fs_path	*fsp; | 
|  |  | 
|  | for (i = 0, fsp = fs_table; i < fs_count; i++, fsp++) { | 
|  | free(fsp->fs_name); | 
|  | free(fsp->fs_dir); | 
|  | free(fsp->fs_log); | 
|  | free(fsp->fs_rt); | 
|  | } | 
|  |  | 
|  | fs_count = 0; | 
|  | xfs_fs_count = 0; | 
|  | free(fs_table); | 
|  | fs_table = NULL; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Table iteration (cursor-based) interfaces | 
|  | */ | 
|  |  | 
|  | /* | 
|  | * Initialize an fs_table cursor.  If a directory path is supplied, | 
|  | * the cursor is set up to appear as though the table contains only | 
|  | * a single entry which represents the directory specified. | 
|  | * Otherwise it is set up to prepare for visiting all entries in the | 
|  | * global table, starting with the first.  "flags" can be either | 
|  | * FS_MOUNT_POINT or FS_PROJECT_PATH to limit what type of entries | 
|  | * will be selected by fs_cursor_next_entry().  0 can be used as a | 
|  | * wild card (selecting either type). | 
|  | */ | 
|  | void | 
|  | fs_cursor_initialise( | 
|  | char		*dir, | 
|  | uint		flags, | 
|  | fs_cursor_t	*cur) | 
|  | { | 
|  | fs_path_t	*path; | 
|  |  | 
|  | memset(cur, 0, sizeof(*cur)); | 
|  | if (dir) { | 
|  | if ((path = fs_table_lookup(dir, flags)) == NULL) | 
|  | return; | 
|  | cur->local = *path; | 
|  | cur->count = 1; | 
|  | cur->table = &cur->local; | 
|  | } else { | 
|  | cur->count = fs_count; | 
|  | cur->table = fs_table; | 
|  | } | 
|  | cur->flags = flags; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Use the cursor to find the next entry in the table having the | 
|  | * type specified by the cursor's "flags" field. | 
|  | */ | 
|  | struct fs_path * | 
|  | fs_cursor_next_entry( | 
|  | fs_cursor_t	*cur) | 
|  | { | 
|  | while (cur->index < cur->count) { | 
|  | fs_path_t	*next = &cur->table[cur->index++]; | 
|  |  | 
|  | if (!cur->flags || (cur->flags & next->fs_flags)) | 
|  | return next; | 
|  | } | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  |  | 
|  | #if defined(HAVE_GETMNTENT) | 
|  | #include <mntent.h> | 
|  |  | 
|  | /* | 
|  | * Determines whether the "logdev" or "rtdev" mount options are | 
|  | * present for the given mount point.  If so, the value for each (a | 
|  | * device path) is returned in the pointers whose addresses are | 
|  | * provided.  The pointers are assigned NULL for an option not | 
|  | * present.  Note that the path buffers returned are allocated | 
|  | * dynamically and it is the caller's responsibility to free them. | 
|  | */ | 
|  | static int | 
|  | fs_extract_mount_options( | 
|  | struct mntent	*mnt, | 
|  | char		**logp, | 
|  | char		**rtp) | 
|  | { | 
|  | char		*fslog, *fsrt; | 
|  |  | 
|  | /* | 
|  | * Extract log device and realtime device from mount options. | 
|  | * | 
|  | * Note: the glibc hasmntopt implementation requires that the | 
|  | * character in mnt_opts immediately after the search string | 
|  | * must be a NULL ('\0'), a comma (','), or an equals ('='). | 
|  | * Therefore we cannot search for 'logdev=' directly. | 
|  | */ | 
|  | if ((fslog = hasmntopt(mnt, "logdev")) && fslog[6] == '=') | 
|  | fslog += 7; | 
|  | if ((fsrt = hasmntopt(mnt, "rtdev")) && fsrt[5] == '=') | 
|  | fsrt += 6; | 
|  |  | 
|  | /* Do this only after we've finished processing mount options */ | 
|  | if (fslog) { | 
|  | fslog = strndup(fslog, strcspn(fslog, " ,")); | 
|  | if (!fslog) | 
|  | goto out_nomem; | 
|  | } | 
|  | if (fsrt) { | 
|  | fsrt = strndup(fsrt, strcspn(fsrt, " ,")); | 
|  | if (!fsrt) { | 
|  | free(fslog); | 
|  | goto out_nomem; | 
|  | } | 
|  | } | 
|  | *logp = fslog; | 
|  | *rtp = fsrt; | 
|  |  | 
|  | return 0; | 
|  |  | 
|  | out_nomem: | 
|  | *logp = NULL; | 
|  | *rtp = NULL; | 
|  | fprintf(stderr, _("%s: unable to extract mount options for \"%s\"\n"), | 
|  | progname, mnt->mnt_dir); | 
|  | return ENOMEM; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * If *path is NULL, initialize the fs table with all xfs mount points in mtab | 
|  | * If *path is specified, search for that path in mtab | 
|  | * | 
|  | * Everything - path, devices, and mountpoints - are boiled down to realpath() | 
|  | * for comparison, but fs_table is populated with what comes from getmntent. | 
|  | */ | 
|  | static int | 
|  | fs_table_initialise_mounts( | 
|  | char		*path) | 
|  | { | 
|  | struct mntent	*mnt; | 
|  | FILE		*mtp; | 
|  | char		*fslog, *fsrt; | 
|  | int		error, found; | 
|  | char		rpath[PATH_MAX], rmnt_fsname[PATH_MAX], rmnt_dir[PATH_MAX]; | 
|  |  | 
|  | error = found = 0; | 
|  | fslog = fsrt = NULL; | 
|  |  | 
|  | if (!mtab_file) { | 
|  | mtab_file = PROC_MOUNTS; | 
|  | if (access(mtab_file, R_OK) != 0) | 
|  | mtab_file = MOUNTED; | 
|  | } | 
|  |  | 
|  | if ((mtp = setmntent(mtab_file, "r")) == NULL) | 
|  | return ENOENT; | 
|  |  | 
|  | /* Use realpath to resolve symlinks, relative paths, etc */ | 
|  | if (path) | 
|  | if (!realpath(path, rpath)) | 
|  | return errno; | 
|  |  | 
|  | while ((mnt = getmntent(mtp)) != NULL) { | 
|  | if (!strcmp(mnt->mnt_type, "autofs")) | 
|  | continue; | 
|  | if (!realpath(mnt->mnt_dir, rmnt_dir)) | 
|  | continue; | 
|  | if (!realpath(mnt->mnt_fsname, rmnt_fsname)) | 
|  | continue; | 
|  |  | 
|  | if (path && | 
|  | ((strcmp(rpath, rmnt_dir) != 0) && | 
|  | (strcmp(rpath, rmnt_fsname) != 0))) | 
|  | continue; | 
|  | if (fs_extract_mount_options(mnt, &fslog, &fsrt)) | 
|  | continue; | 
|  | (void) fs_table_insert(mnt->mnt_dir, 0, FS_MOUNT_POINT, | 
|  | mnt->mnt_fsname, fslog, fsrt); | 
|  | if (path) { | 
|  | found = 1; | 
|  | break; | 
|  | } | 
|  | } | 
|  | endmntent(mtp); | 
|  |  | 
|  | if (path && !found) | 
|  | error = ENXIO; | 
|  |  | 
|  | return error; | 
|  | } | 
|  |  | 
|  | #else | 
|  | # error "How do I extract info about mounted filesystems on this platform?" | 
|  | #endif | 
|  |  | 
|  | /* | 
|  | * Given a directory, match it up to a filesystem mount point. | 
|  | */ | 
|  | static struct fs_path * | 
|  | fs_mount_point_from_path( | 
|  | const char	*dir) | 
|  | { | 
|  | fs_cursor_t	cursor; | 
|  | fs_path_t	*fs; | 
|  | dev_t		dev = 0; | 
|  |  | 
|  | if (fs_device_number(dir, &dev)) | 
|  | return NULL; | 
|  |  | 
|  | fs_cursor_initialise(NULL, FS_MOUNT_POINT, &cursor); | 
|  | while ((fs = fs_cursor_next_entry(&cursor))) { | 
|  | if (fs->fs_datadev == dev) | 
|  | break; | 
|  | } | 
|  | return fs; | 
|  | } | 
|  |  | 
|  | static void | 
|  | fs_table_insert_mount( | 
|  | char		*mount) | 
|  | { | 
|  | int		error; | 
|  |  | 
|  | error = fs_table_initialise_mounts(mount); | 
|  | if (error) | 
|  | fprintf(stderr, _("%s: cannot setup path for mount %s: %s\n"), | 
|  | progname, mount, strerror(error)); | 
|  | } | 
|  |  | 
|  | static int | 
|  | fs_table_initialise_projects( | 
|  | char		*project, | 
|  | bool		all_mps_initialised) | 
|  | { | 
|  | fs_project_path_t *path; | 
|  | fs_path_t	*fs; | 
|  | prid_t		prid = 0; | 
|  | int		error = 0, found = 0; | 
|  |  | 
|  | if (project) | 
|  | prid = prid_from_string(project); | 
|  |  | 
|  | setprpathent(); | 
|  | while ((path = getprpathent()) != NULL) { | 
|  | if (project && prid != path->pp_prid) | 
|  | continue; | 
|  | fs = fs_mount_point_from_path(path->pp_pathname); | 
|  | if (!fs) { | 
|  | if (all_mps_initialised) | 
|  | fprintf(stderr, | 
|  | _("%s: cannot find mount point for path `%s': %s\n"), progname, | 
|  | path->pp_pathname, strerror(errno)); | 
|  | continue; | 
|  | } | 
|  | (void) fs_table_insert(path->pp_pathname, path->pp_prid, | 
|  | FS_PROJECT_PATH, fs->fs_name, | 
|  | NULL, NULL); | 
|  | if (project) { | 
|  | found = 1; | 
|  | break; | 
|  | } | 
|  | } | 
|  | endprpathent(); | 
|  |  | 
|  | if (project && !found) | 
|  | error = ENOENT; | 
|  |  | 
|  | return error; | 
|  | } | 
|  |  | 
|  | static void | 
|  | fs_table_insert_project( | 
|  | char		*project, | 
|  | bool		all_mps_initialised) | 
|  | { | 
|  | int		error; | 
|  |  | 
|  | error = fs_table_initialise_projects(project, all_mps_initialised); | 
|  | if (error) | 
|  | fprintf(stderr, _("%s: cannot setup path for project %s: %s\n"), | 
|  | progname, project, strerror(error)); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Initialize fs_table to contain the given set of mount points and | 
|  | * projects.  If mount_count is zero, mounts is ignored and the | 
|  | * table is populated with mounted filesystems.  If project_count is | 
|  | * zero, projects is ignored and the table is populated with all | 
|  | * projects defined in the projects file. | 
|  | */ | 
|  | void | 
|  | fs_table_initialise( | 
|  | int	mount_count, | 
|  | char	*mounts[], | 
|  | int	project_count, | 
|  | char	*projects[]) | 
|  | { | 
|  | int	error; | 
|  | int	i; | 
|  |  | 
|  | if (mount_count) { | 
|  | for (i = 0; i < mount_count; i++) | 
|  | fs_table_insert_mount(mounts[i]); | 
|  | } else { | 
|  | error = fs_table_initialise_mounts(NULL); | 
|  | if (error) | 
|  | goto out_error; | 
|  | } | 
|  | if (project_count) { | 
|  | for (i = 0; i < project_count; i++) | 
|  | fs_table_insert_project(projects[i], mount_count == 0); | 
|  | } else { | 
|  | error = fs_table_initialise_projects(NULL, mount_count == 0); | 
|  | if (error) | 
|  | goto out_error; | 
|  | } | 
|  |  | 
|  | return; | 
|  |  | 
|  | out_error: | 
|  | fprintf(stderr, _("%s: cannot initialise path table: %s\n"), | 
|  | progname, strerror(error)); | 
|  | } | 
|  |  | 
|  | int | 
|  | fs_table_insert_project_path( | 
|  | char		*dir, | 
|  | prid_t		prid) | 
|  | { | 
|  | fs_path_t	*fs; | 
|  | int		error = 0; | 
|  |  | 
|  | fs = fs_mount_point_from_path(dir); | 
|  | if (fs) | 
|  | error = fs_table_insert(dir, prid, FS_PROJECT_PATH, | 
|  | fs->fs_name, NULL, NULL); | 
|  | else | 
|  | error = ENOENT; | 
|  |  | 
|  | return error; | 
|  | } |