blob: 67fd40c34b76b41e92ef2dc71e62eeee27a72b5d [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2023 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 "libfrog/paths.h"
#include "handle.h"
#include "libfrog/pptrs.h"
/* Allocate a buffer large enough for some parent pointer records. */
static inline struct xfs_getparents *
alloc_pptr_buf(
size_t bufsize)
{
struct xfs_getparents *pi;
pi = calloc(bufsize, 1);
if (!pi)
return NULL;
pi->gp_bufsize = bufsize;
return pi;
}
/*
* Walk all parents of the given file handle. Returns 0 on success or positive
* errno.
*/
static int
handle_walk_parents(
int fd,
struct xfs_handle *handle,
walk_pptr_fn fn,
void *arg)
{
struct xfs_getparents *pi;
struct xfs_getparents_rec *p;
unsigned int i;
ssize_t ret = -1;
pi = alloc_pptr_buf(XFS_XATTR_LIST_MAX);
if (!pi)
return errno;
if (handle) {
memcpy(&pi->gp_handle, handle, sizeof(struct xfs_handle));
pi->gp_flags = XFS_GETPARENTS_IFLAG_HANDLE;
}
ret = ioctl(fd, XFS_IOC_GETPARENTS, pi);
while (!ret) {
if (pi->gp_flags & XFS_GETPARENTS_OFLAG_ROOT) {
ret = fn(pi, NULL, arg);
goto out_pi;
}
for (i = 0; i < pi->gp_count; i++) {
p = xfs_getparents_rec(pi, i);
ret = fn(pi, p, arg);
if (ret)
goto out_pi;
}
if (pi->gp_flags & XFS_GETPARENTS_OFLAG_DONE)
break;
ret = ioctl(fd, XFS_IOC_GETPARENTS, pi);
}
if (ret)
ret = errno;
out_pi:
free(pi);
return ret;
}
/* Walk all parent pointers of this handle. Returns 0 or positive errno. */
int
handle_walk_pptrs(
void *hanp,
size_t hlen,
walk_pptr_fn fn,
void *arg)
{
char *mntpt;
int fd;
if (hlen != sizeof(struct xfs_handle))
return EINVAL;
fd = handle_to_fsfd(hanp, &mntpt);
if (fd < 0)
return errno;
return handle_walk_parents(fd, hanp, fn, arg);
}
/* Walk all parent pointers of this fd. Returns 0 or positive errno. */
int
fd_walk_pptrs(
int fd,
walk_pptr_fn fn,
void *arg)
{
return handle_walk_parents(fd, NULL, fn, arg);
}
struct walk_ppaths_info {
walk_ppath_fn fn;
void *arg;
char *mntpt;
struct path_list *path;
int fd;
};
struct walk_ppath_level_info {
struct xfs_handle newhandle;
struct path_component *pc;
struct walk_ppaths_info *wpi;
};
static int handle_walk_parent_paths(struct walk_ppaths_info *wpi,
struct xfs_handle *handle);
static int
handle_walk_parent_path_ptr(
struct xfs_getparents *pi,
struct xfs_getparents_rec *p,
void *arg)
{
struct walk_ppath_level_info *wpli = arg;
struct walk_ppaths_info *wpi = wpli->wpi;
int ret = 0;
if (pi->gp_flags & XFS_GETPARENTS_OFLAG_ROOT)
return wpi->fn(wpi->mntpt, wpi->path, wpi->arg);
ret = path_component_change(wpli->pc, p->gpr_name,
strlen((char *)p->gpr_name), p->gpr_ino);
if (ret)
return ret;
wpli->newhandle.ha_fid.fid_ino = p->gpr_ino;
wpli->newhandle.ha_fid.fid_gen = p->gpr_gen;
path_list_add_parent_component(wpi->path, wpli->pc);
ret = handle_walk_parent_paths(wpi, &wpli->newhandle);
path_list_del_component(wpi->path, wpli->pc);
return ret;
}
/*
* Recursively walk all parents of the given file handle; if we hit the
* fs root then we call the associated function with the constructed path.
* Returns 0 for success or positive errno.
*/
static int
handle_walk_parent_paths(
struct walk_ppaths_info *wpi,
struct xfs_handle *handle)
{
struct walk_ppath_level_info *wpli;
int ret;
wpli = malloc(sizeof(struct walk_ppath_level_info));
if (!wpli)
return errno;
wpli->pc = path_component_init("", 0);
if (!wpli->pc) {
ret = errno;
free(wpli);
return ret;
}
wpli->wpi = wpi;
memcpy(&wpli->newhandle, handle, sizeof(struct xfs_handle));
ret = handle_walk_parents(wpi->fd, handle, handle_walk_parent_path_ptr,
wpli);
path_component_free(wpli->pc);
free(wpli);
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_ppaths(
void *hanp,
size_t hlen,
walk_ppath_fn fn,
void *arg)
{
struct walk_ppaths_info wpi;
ssize_t ret;
if (hlen != sizeof(struct xfs_handle))
return EINVAL;
wpi.fd = handle_to_fsfd(hanp, &wpi.mntpt);
if (wpi.fd < 0)
return errno;
wpi.path = path_list_init();
if (!wpi.path)
return errno;
wpi.fn = fn;
wpi.arg = arg;
ret = handle_walk_parent_paths(&wpi, hanp);
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_ppaths(
int fd,
walk_ppath_fn fn,
void *arg)
{
struct walk_ppaths_info wpi;
void *hanp;
size_t hlen;
int fsfd;
int ret;
ret = fd_to_handle(fd, &hanp, &hlen);
if (ret)
return errno;
fsfd = handle_to_fsfd(hanp, &wpi.mntpt);
if (fsfd < 0)
return errno;
wpi.fd = fd;
wpi.path = path_list_init();
if (!wpi.path)
return errno;
wpi.fn = fn;
wpi.arg = arg;
ret = handle_walk_parent_paths(&wpi, hanp);
path_list_free(wpi.path);
return ret;
}
struct path_walk_info {
char *buf;
size_t len;
};
/* Helper that stringifies the first full path that we find. */
static int
handle_to_path_walk(
const char *mntpt,
struct path_list *path,
void *arg)
{
struct path_walk_info *pwi = 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(pwi->buf, pwi->len, "%.*s", mntpt_len, mntpt);
if (ret != mntpt_len)
return ENAMETOOLONG;
ret = path_list_to_string(path, pwi->buf + ret, pwi->len - ret);
if (ret < 0)
return ENAMETOOLONG;
return ECANCELED;
}
/*
* Return any eligible path to this file handle. Returns 0 for success or
* positive errno.
*/
int
handle_to_path(
void *hanp,
size_t hlen,
char *path,
size_t pathlen)
{
struct path_walk_info pwi;
int ret;
pwi.buf = path;
pwi.len = pathlen;
ret = handle_walk_ppaths(hanp, hlen, handle_to_path_walk, &pwi);
if (ret == ECANCELED)
return 0;
return ret;
}
/*
* Return any eligible path to this file description. Returns 0 for success
* or positive errno.
*/
int
fd_to_path(
int fd,
char *path,
size_t pathlen)
{
struct path_walk_info pwi;
int ret;
pwi.buf = path;
pwi.len = pathlen;
ret = fd_walk_ppaths(fd, handle_to_path_walk, &pwi);
if (ret == ECANCELED)
return 0;
return ret;
}