blob: 9118b0ff32db0d9a0a6af006eb3075aa9a3e28c3 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (c) 2017-2024 Oracle. All Rights Reserved.
* Author: Darrick J. Wong <djwong@kernel.org>
*/
#include "platform_defs.h"
#include "xfs.h"
#include "xfs_arch.h"
#include "list.h"
#include "paths.h"
#include "handle.h"
#include "libfrog/getparents.h"
/* Allocate a buffer for the xfs_getparent_rec array. */
static void *
alloc_records(
struct xfs_getparents *gp,
size_t bufsize)
{
void *buf;
if (bufsize >= UINT32_MAX) {
errno = ENOMEM;
return NULL;
} else if (!bufsize) {
bufsize = XFS_XATTR_LIST_MAX;
}
buf = malloc(bufsize);
if (!buf)
return NULL;
gp->gp_buffer = (uintptr_t)buf;
gp->gp_bufsize = bufsize;
return buf;
}
/* Copy a file handle. */
static inline void
copy_handle(
struct xfs_handle *dest,
const struct xfs_handle *src)
{
memcpy(dest, src, sizeof(struct xfs_handle));
}
/* Initiate a callback for each parent pointer. */
static int
walk_parent_records(
struct xfs_getparents *gp,
walk_parent_fn fn,
void *arg)
{
struct xfs_getparents_rec *gpr;
int ret;
if (gp->gp_oflags & XFS_GETPARENTS_OFLAG_ROOT) {
struct parent_rec rec = {
.p_flags = PARENTREC_FILE_IS_ROOT,
};
return fn(&rec, arg);
}
for (gpr = xfs_getparents_first_rec(gp);
gpr != NULL;
gpr = xfs_getparents_next_rec(gp, gpr)) {
struct parent_rec rec = { };
if (gpr->gpr_name[0] == 0)
break;
copy_handle(&rec.p_handle, &gpr->gpr_parent);
rec.p_name = gpr->gpr_name;
ret = fn(&rec, arg);
if (ret)
return ret;
}
return 0;
}
/* Walk all parent pointers of this fd. Returns 0 or positive errno. */
int
fd_walk_parents(
int fd,
size_t bufsize,
walk_parent_fn fn,
void *arg)
{
struct xfs_getparents gp = { };
void *buf;
int ret;
buf = alloc_records(&gp, bufsize);
if (!buf)
return errno;
while ((ret = ioctl(fd, XFS_IOC_GETPARENTS, &gp)) == 0) {
ret = walk_parent_records(&gp, fn, arg);
if (ret)
goto out_buf;
if (gp.gp_oflags & XFS_GETPARENTS_OFLAG_DONE)
break;
}
if (ret)
ret = errno;
out_buf:
free(buf);
return ret;
}
/* Walk all parent pointers of this handle. Returns 0 or positive errno. */
int
handle_walk_parents(
const void *hanp,
size_t hlen,
size_t bufsize,
walk_parent_fn fn,
void *arg)
{
struct xfs_getparents_by_handle gph = { };
void *buf;
char *mntpt;
int fd;
int ret;
if (hlen != sizeof(struct xfs_handle))
return EINVAL;
/*
* This function doesn't modify the handle, but we don't want to have
* to bump the libhandle major version just to change that.
*/
fd = handle_to_fsfd((void *)hanp, &mntpt);
if (fd < 0)
return errno;
buf = alloc_records(&gph.gph_request, bufsize);
if (!buf)
return errno;
copy_handle(&gph.gph_handle, hanp);
while ((ret = ioctl(fd, XFS_IOC_GETPARENTS_BY_HANDLE, &gph)) == 0) {
ret = walk_parent_records(&gph.gph_request, fn, arg);
if (ret)
goto out_buf;
if (gph.gph_request.gp_oflags & XFS_GETPARENTS_OFLAG_DONE)
break;
}
if (ret)
ret = errno;
out_buf:
free(buf);
return ret;
}
struct walk_ppaths_info {
/* Callback */
walk_path_fn fn;
void *arg;
/* Mountpoint of this filesystem. */
char *mntpt;
/* Path that we're constructing. */
struct path_list *path;
size_t ioctl_bufsize;
};
/*
* Recursively walk upwards through the directory tree, changing out the path
* components as needed. Call the callback when we have a complete path.
*/
static int
find_parent_component(
const struct parent_rec *rec,
void *arg)
{
struct walk_ppaths_info *wpi = arg;
struct path_component *pc;
int ret;
if (rec->p_flags & PARENTREC_FILE_IS_ROOT)
return wpi->fn(wpi->mntpt, wpi->path, wpi->arg);
/*
* If we detect a directory tree cycle, give up. We never made any
* guarantees about concurrent tree updates.
*/
if (path_will_loop(wpi->path, rec->p_handle.ha_fid.fid_ino))
return 0;
pc = path_component_init(rec->p_name, rec->p_handle.ha_fid.fid_ino);
if (!pc)
return errno;
path_list_add_parent_component(wpi->path, pc);
ret = handle_walk_parents(&rec->p_handle, sizeof(rec->p_handle),
wpi->ioctl_bufsize, find_parent_component, wpi);
path_list_del_component(wpi->path, pc);
path_component_free(pc);
return ret;
}
/*
* Call the given function on all known paths from the vfs root to the inode
* described in the handle. Returns 0 for success or positive errno.
*/
int
handle_walk_paths(
const void *hanp,
size_t hlen,
size_t ioctl_bufsize,
walk_path_fn fn,
void *arg)
{
struct walk_ppaths_info wpi = {
.ioctl_bufsize = ioctl_bufsize,
};
int ret;
/*
* This function doesn't modify the handle, but we don't want to have
* to bump the libhandle major version just to change that.
*/
ret = handle_to_fsfd((void *)hanp, &wpi.mntpt);
if (ret < 0)
return errno;
wpi.path = path_list_init();
if (!wpi.path)
return errno;
wpi.fn = fn;
wpi.arg = arg;
ret = handle_walk_parents(hanp, hlen, ioctl_bufsize,
find_parent_component, &wpi);
path_list_free(wpi.path);
return ret;
}
/*
* Call the given function on all known paths from the vfs root to the inode
* referred to by the file description. Returns 0 or positive errno.
*/
int
fd_walk_paths(
int fd,
size_t ioctl_bufsize,
walk_path_fn fn,
void *arg)
{
void *hanp;
size_t hlen;
int ret;
ret = fd_to_handle(fd, &hanp, &hlen);
if (ret)
return errno;
ret = handle_walk_paths(hanp, hlen, ioctl_bufsize, fn, arg);
free_handle(hanp, hlen);
return ret;
}
struct gather_path_info {
char *buf;
size_t len;
size_t written;
};
/* Helper that stringifies the first full path that we find. */
static int
path_to_string(
const char *mntpt,
const struct path_list *path,
void *arg)
{
struct gather_path_info *gpi = arg;
int mntpt_len = strlen(mntpt);
int ret;
/* Trim trailing slashes from the mountpoint */
while (mntpt_len > 0 && mntpt[mntpt_len - 1] == '/')
mntpt_len--;
ret = snprintf(gpi->buf, gpi->len, "%.*s", mntpt_len, mntpt);
if (ret != mntpt_len)
return ENAMETOOLONG;
gpi->written += ret;
ret = path_list_to_string(path, gpi->buf + ret, gpi->len - ret);
if (ret < 0)
return ENAMETOOLONG;
gpi->written += ret;
return ECANCELED;
}
/*
* Return any eligible path to this file handle. Returns 0 for success or
* positive errno.
*/
int
handle_to_path(
const void *hanp,
size_t hlen,
size_t ioctl_bufsize,
char *path,
size_t pathlen)
{
struct gather_path_info gpi = { .buf = path, .len = pathlen };
int ret;
ret = handle_walk_paths(hanp, hlen, ioctl_bufsize, path_to_string,
&gpi);
if (ret && ret != ECANCELED)
return ret;
if (!gpi.written)
return ENODATA;
path[gpi.written] = 0;
return 0;
}
/*
* Return any eligible path to this file description. Returns 0 for success
* or positive errno.
*/
int
fd_to_path(
int fd,
size_t ioctl_bufsize,
char *path,
size_t pathlen)
{
struct gather_path_info gpi = { .buf = path, .len = pathlen };
int ret;
ret = fd_walk_paths(fd, ioctl_bufsize, path_to_string, &gpi);
if (ret && ret != ECANCELED)
return ret;
if (!gpi.written)
return ENODATA;
path[gpi.written] = 0;
return 0;
}