Viewing: liblustreapi_root.c
// SPDX-License-Identifier: LGPL-2.1+
/*
* Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved.
* Use is subject to license terms.
*
* Copyright (c) 2011, 2017, Intel Corporation.
*
* Copyright (c) 2018, 2022, Data Direct Networks
*/
/*
* This file is part of Lustre, http://www.lustre.org/
*
* lustreapi library for managing the root fd cache for llapi internal use.
*/
/* for O_DIRECTORY and struct file_handle */
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <errno.h>
#include <fcntl.h>
#include <libgen.h> /* for dirname() */
#include <mntent.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/sysmacros.h> /* for makedev() */
#include <sys/types.h>
#include <unistd.h>
#include <assert.h>
#include <libcfs/util/ioctl.h>
#include <libcfs/util/string.h>
#include <lustre/lustreapi.h>
#include <linux/lustre/lustre_fid.h>
#include "lustreapi_internal.h"
/* could have an array of these for a handful of different paths */
static struct root_cache {
dev_t dev;
char fsname[PATH_MAX];
char mnt_dir[PATH_MAX];
int fd; /* cached fd on filesystem root for internal use only */
} root_cached = { 0 };
static pthread_rwlock_t root_cached_lock = PTHREAD_RWLOCK_INITIALIZER;
static int get_root_fd(const char *rootpath, int *outfd)
{
int rc = 0;
int fd;
fd = open(rootpath, O_RDONLY | O_DIRECTORY | O_NONBLOCK);
if (fd < 0) {
rc = -errno;
llapi_error(LLAPI_MSG_ERROR, rc,
"cannot open '%s'", rootpath);
} else {
*outfd = fd;
}
return rc;
}
static int get_file_dev(const char *path, dev_t *dev)
{
#ifdef HAVE_STATX
struct statx stx;
if (!dev)
return -EINVAL;
if (statx(AT_FDCWD, path, 0, 0, &stx))
return -errno;
*dev = makedev(stx.stx_dev_major, stx.stx_dev_minor);
#else
struct stat st;
if (!dev)
return -EINVAL;
if (stat(path, &st) != 0)
return -errno;
*dev = st.st_dev;
#endif
return 0;
}
/**
* get_root_path_fast() - get mountpoint info from internal root_cache
*
* @want: bitmask of WANT_* flags indicating what information to return
* @fsname: if WANT_FSNAME is set, used as a buffer to return filesystem name
* (size is assumed to be PATH_MAX). Otherwise used to match the mount point
* @outfd: pointer to return open file descriptor (internal use only)
* @path: if WANT_PATH is set, used as a buffer to return mount point path
* (size is assumed to be PATH_MAX). Otherwise used to match the mount point
* @dev: pointer to return device ID
*
* Return:
* * %0 on success
* * %-ENODEV if no matching Lustre mount is found
* * %-ENAMETOOLONG if filesystem name, path, or NID is truncated
* * %negative error code on other failures
*/
static int get_root_path_fast(int want, char *fsname, int *outfd, char *path,
dev_t *dev)
{
int fsnamelen;
int mntlen;
int rc2;
int rc = -ENODEV;
if (root_cached.dev == 0)
return rc;
/* hold a write lock on the cache if fd is going to be updated */
if ((want & WANT_FD) && outfd && root_cached.fd <= 0)
pthread_rwlock_wrlock(&root_cached_lock);
else
pthread_rwlock_rdlock(&root_cached_lock);
if (root_cached.dev == 0)
goto out_unlock;
fsnamelen = strlen(root_cached.fsname);
mntlen = strlen(root_cached.mnt_dir);
/* Check the dev for a match, if given */
if (!(want & WANT_DEV) && dev && *dev == root_cached.dev) {
rc = 0;
/* Check the fsname for a match, if given */
} else if (!(want & WANT_FSNAME) && fsname &&
strlen(fsname) == fsnamelen &&
(strncmp(root_cached.fsname, fsname, fsnamelen) == 0)) {
rc = 0;
/* Otherwise find the longest matching path */
} else if (path && strlen(path) >= mntlen &&
(strncmp(root_cached.mnt_dir, path, mntlen) == 0) &&
(strlen(path) == mntlen || path[mntlen] == '/')) {
rc = 0;
}
if (rc)
goto out_unlock;
if ((want & WANT_FSNAME) && fsname) {
rc2 = scnprintf(fsname, PATH_MAX, "%s", root_cached.fsname);
if (rc2 < 0 || rc2 >= PATH_MAX - 1) {
rc = rc2 < 0 ? rc2 : -ENAMETOOLONG;
goto out_unlock;
}
}
if ((want & WANT_PATH) && path) {
rc2 = scnprintf(path, PATH_MAX, "%s", root_cached.mnt_dir);
if (rc2 < 0 || rc2 >= PATH_MAX - 1) {
rc = rc2 < 0 ? rc2 : -ENAMETOOLONG;
goto out_unlock;
}
}
if ((want & WANT_DEV) && dev)
*dev = root_cached.dev;
if ((want & WANT_FD) && outfd) {
if (root_cached.fd > 0) {
*outfd = root_cached.fd;
} else {
rc = get_root_fd(root_cached.mnt_dir, outfd);
if (!rc)
root_cached.fd = *outfd;
}
}
out_unlock:
pthread_rwlock_unlock(&root_cached_lock);
return rc;
}
/**
* get_root_path_slow() - get mountpoint info from /proc/mounts and add to cache
*
* @want: bitmask of WANT_* flags indicating what information to return
* @fsname: if WANT_FSNAME is set, used as a buffer to return filesystem name
* (size is assumed to be PATH_MAX). Otherwise used to match the mount point
* @outfd: pointer to return open file descriptor (internal use only)
* @path: if WANT_PATH is set, used as a buffer to return mount point path
* (size is assumed to be PATH_MAX). Otherwise used to match the mount point
* @index: if WANT_INDEX is set, specifies which Lustre mount to find
* @dev: pointer to return device ID
* @out_nid: buffer to return a nidlist (dynamically allocated if rc == 0)
*
* Return:
* * %0 on success
* * %-ENODEV if no matching Lustre mount is found
* * %-ENAMETOOLONG if filesystem name, path, or NID is truncated
* * %negative error code on other failures
*/
static int get_root_path_slow(int want, char *fsname, int *outfd, char *path,
int index, dev_t *dev, char **out_nid)
{
struct mntent mnt;
char buf[PATH_MAX];
char *ptr, *ptr_end;
FILE *fp;
int idx = -1, mntlen = 0;
int fsnamelen = 0;
dev_t devmnt = 0;
int rc2;
int rc = -ENODEV;
/* get the mount point */
fp = setmntent(PROC_MOUNTS, "r");
if (!fp) {
rc = -EIO;
llapi_error(LLAPI_MSG_ERROR, rc,
"cannot retrieve filesystem mount point");
return rc;
}
while (getmntent_r(fp, &mnt, buf, sizeof(buf))) {
if (!llapi_is_lustre_mnt(&mnt))
continue;
idx++;
mntlen = strlen(mnt.mnt_dir);
ptr = strchr(mnt.mnt_fsname, '/');
while (ptr && *ptr == '/')
ptr++;
/*
* thanks to the call to llapi_is_lustre_mnt() above,
* we are sure that mnt.mnt_fsname contains ":/",
* so ptr should never be NULL
*/
if (!ptr)
continue;
ptr_end = ptr;
while (*ptr_end != '/' && *ptr_end != '\0')
ptr_end++;
fsnamelen = ptr_end - ptr;
/* avoid stat/statx call if path does not match mountpoint */
if (path && (strlen(path) >= mntlen) &&
(strncmp(mnt.mnt_dir, path, mntlen) != 0))
continue;
/* ignore unaccessible filesystem */
if (get_file_dev(mnt.mnt_dir, &devmnt))
continue;
if ((want & WANT_INDEX) && idx == index) {
rc = 0;
break;
}
/* Check the fsname for a match, if given */
if (!(want & WANT_FSNAME) && fsname &&
strlen(fsname) == fsnamelen &&
(strncmp(ptr, fsname, fsnamelen) == 0)) {
rc = 0;
break;
}
/* Check the dev for a match, if given */
if (!(want & WANT_DEV) && dev && *dev == devmnt) {
rc = 0;
break;
}
/*
* Otherwise find the longest matching path beginning of path
* and mnt_dir already verified to be the same.
*/
if (path && (strlen(path) == mntlen || path[mntlen] == '/')) {
rc = 0;
break;
}
}
if (rc)
goto out;
/* Found it */
if (!(want & WANT_INDEX)) {
/* Cache the mount point information */
pthread_rwlock_wrlock(&root_cached_lock);
/* If the entry matches the saved one -> no update needed */
if (strcmp(root_cached.mnt_dir, mnt.mnt_dir) == 0)
goto unlock_root_cached;
if (root_cached.fd > 0) {
close(root_cached.fd);
root_cached.fd = 0;
}
if ((want & WANT_FD) && outfd)
rc = get_root_fd(mnt.mnt_dir, &root_cached.fd);
rc2 = scnprintf(root_cached.fsname, sizeof(root_cached.fsname),
"%.*s", fsnamelen, ptr);
if (rc2 < 0 || rc2 >= sizeof(root_cached.fsname) - 1) {
rc = rc2 < 0 ? rc2 : -ENAMETOOLONG;
goto unlock_root_cached;
}
rc2 = scnprintf(root_cached.mnt_dir,
sizeof(root_cached.mnt_dir), "%.*s", mntlen,
mnt.mnt_dir);
if (rc2 < 0 || rc2 >= sizeof(root_cached.mnt_dir) - 1)
rc = rc2 < 0 ? rc2 : -ENAMETOOLONG;
root_cached.dev = devmnt;
/* if rc, cache was only partially updated and must be reset */
if (rc)
memset(&root_cached, 0, sizeof(root_cached));
unlock_root_cached:
pthread_rwlock_unlock(&root_cached_lock);
if (rc)
goto out;
}
if ((want & WANT_FSNAME) && fsname) {
rc2 = scnprintf(fsname, PATH_MAX, "%.*s", fsnamelen, ptr);
if (rc2 < 0 || rc2 >= PATH_MAX - 1) {
rc = rc2 < 0 ? rc2 : -ENAMETOOLONG;
goto out;
}
}
if ((want & WANT_PATH) && path) {
rc2 = scnprintf(path, PATH_MAX, "%.*s", mntlen, mnt.mnt_dir);
if (rc2 < 0 || rc2 >= PATH_MAX - 1) {
rc = rc2 < 0 ? rc2 : -ENAMETOOLONG;
goto out;
}
}
if ((want & WANT_DEV) && dev)
*dev = devmnt;
if ((want & WANT_FD) && outfd) {
if (root_cached.fd > 0)
*outfd = root_cached.fd;
else
rc = get_root_fd(mnt.mnt_dir, outfd);
}
if (!rc && (want & WANT_NID) && out_nid) {
size_t nid_bufsz;
ptr_end = strstr(mnt.mnt_fsname, ":/");
nid_bufsz = ptr_end - mnt.mnt_fsname + 2;
*out_nid = malloc(nid_bufsz);
if (!*out_nid) {
rc = -ENOMEM;
goto out;
}
rc2 = scnprintf(*out_nid, nid_bufsz, "%.*s",
(int)(ptr_end - mnt.mnt_fsname),
mnt.mnt_fsname);
if (rc2 < 0 || rc2 >= nid_bufsz - 1) {
rc = rc2 < 0 ? rc2 : -ENAMETOOLONG;
free(*out_nid);
*out_nid = NULL;
goto out;
}
}
out:
endmntent(fp);
return rc;
}
/**
* get_root_path() - find filesystem info using cached or slow lookup
*
* @want: bitmask of WANT_* flags indicating what information to return
* @fsname: if WANT_FSNAME is set, used as a buffer to return filesystem name
* (size is assumed to be PATH_MAX). Otherwise used to match the mount point
* @outfd: pointer to return open file descriptor (internal use only)
* @path: if WANT_PATH is set, used as a buffer to return mount point path
* (size is assumed to be PATH_MAX). Otherwise used to match the mount point
* @index: if WANT_INDEX is set, specifies which Lustre mount to find
* @dev: pointer to return device ID
* @out_nid: buffer to return a nidlist (dynamically allocated if rc == 0)
*
* Find the fsname, the full path, and/or an open fd.
* Either the fsname or path must not be NULL.
*
* outfd is for llapi internal use only, do not return it to the application.
*
* Return:
* * %0 on success
* * %-ENODEV if no matching Lustre mount is found
* * %-ENAMETOOLONG if filesystem name, path, or NID is truncated
* * %negative error code on other failures
*/
int get_root_path(int want, char *fsname, int *outfd, char *path, int index,
dev_t *dev, char **out_nid)
{
int rc = -ENODEV;
assert(fsname || path);
if (!(want & WANT_INDEX) && !(want & WANT_NID))
rc = get_root_path_fast(want, fsname, outfd, path, dev);
if (rc)
rc = get_root_path_slow(want, fsname, outfd, path, index, dev,
out_nid);
if (!rc || !(want & WANT_ERROR))
goto out_errno;
if (rc == -ENAMETOOLONG) {
llapi_err_noerrno(LLAPI_MSG_ERROR,
"filesystem name, path, or NID is too long");
goto out_errno;
}
if (dev || !(want & WANT_DEV))
llapi_err_noerrno(LLAPI_MSG_ERROR,
"'%u/%u' dev not on a mounted Lustre filesystem",
major(*dev), minor(*dev));
else
llapi_err_noerrno(LLAPI_MSG_ERROR,
"'%s' not on a mounted Lustre filesystem",
(want & WANT_PATH) ? fsname : path);
out_errno:
errno = -rc;
return rc;
}
/*
* search lustre mounts
*
* Calling this function will return to the user the mount point, mntdir, and
* the file system name, fsname, if the user passed a buffer to this routine.
*
* The user inputs are pathname and index. If the pathname is supplied then
* the value of the index will be ignored. The pathname will return data if
* the pathname is located on a lustre mount. Index is used to pick which
* mount point you want in the case of multiple mounted lustre file systems.
* See function lfs_osts in lfs.c for an example of the index use.
*/
int llapi_search_mounts(const char *pathname, int index, char *mntdir,
char *fsname)
{
int want = WANT_PATH, idx = -1;
if (!pathname || pathname[0] == '\0') {
want |= WANT_INDEX;
idx = index;
} else {
strcpy(mntdir, pathname);
}
if (fsname)
want |= WANT_FSNAME;
return get_root_path(want, fsname, NULL, mntdir, idx, NULL, NULL);
}
/* Given a path, find the corresponding Lustre fsname */
int llapi_search_fsname(const char *pathname, char *fsname)
{
dev_t dev;
int rc;
rc = get_file_dev(pathname, &dev);
if (rc) {
char tmp[PATH_MAX];
char *parent;
int len;
/* file does not exist try the parent */
len = readlink(pathname, tmp, PATH_MAX);
if (len != -1)
tmp[len] = '\0';
else
strncpy(tmp, pathname, PATH_MAX - 1);
parent = dirname(tmp);
rc = get_file_dev(parent, &dev);
}
if (rc) {
llapi_error(LLAPI_MSG_ERROR, rc,
"cannot resolve path '%s'", pathname);
return rc;
}
rc = get_root_path(WANT_FSNAME | WANT_ERROR, fsname, NULL, NULL, -1,
&dev, NULL);
return rc;
}
int llapi_search_rootpath(char *pathname, const char *fsname)
{
if (!pathname)
return -EINVAL;
/*
* pathname can be used as an argument by get_root_path(),
* clear it for safety
*/
pathname[0] = 0;
return get_root_path(WANT_PATH, (char *)fsname, NULL, pathname, -1,
NULL, NULL);
}
int llapi_search_rootpath_by_dev(char *pathname, dev_t dev)
{
if (!pathname)
return -EINVAL;
/*
* pathname can be used as an argument by get_root_path(),
* clear it for safety
*/
pathname[0] = 0;
return get_root_path(WANT_PATH, NULL, NULL, pathname, -1, &dev, NULL);
}