blob: 333c21909007eedcf8a3f0b0b5224c6874e4eabb [file] [log] [blame]
// SPDX-License-Identifier: LGPL-2.1
/*
* Copyright (c) 1995, 2001-2003, 2005 Silicon Graphics, Inc.
* All Rights Reserved.
*/
#include <libgen.h>
#include "platform_defs.h"
#include "xfs.h"
#include "handle.h"
#include "parent.h"
/* just pick a value we know is more than big enough */
#define MAXHANSIZ 64
/*
* The actual content of a handle is supposed to be opaque here.
* But, to do handle_to_fshandle, we need to know what it is. Sigh.
* However we can get by with knowing only that the first 8 bytes of
* a file handle are the file system ID, and that a file system handle
* consists of only those 8 bytes.
*/
#define FSIDSIZE 8
typedef union {
int fd;
char *path;
} comarg_t;
static int obj_to_handle(char *, int, unsigned int, comarg_t, void**, size_t*);
static int handle_to_fsfd(void *, char **);
static char *path_to_fspath(char *path);
/*
* Filesystem Handle -> Open File Descriptor Cache
*
* Maps filesystem handles to a corresponding open file descriptor for that
* filesystem. We need this because we're doing handle operations via xfsctl
* and we need to remember the open file descriptor for each filesystem.
*/
struct fdhash {
int fsfd;
char fsh[FSIDSIZE];
struct fdhash *fnxt;
char fspath[MAXPATHLEN];
};
static struct fdhash *fdhash_head = NULL;
void
fshandle_destroy(void)
{
struct fdhash *nexth;
struct fdhash *h = fdhash_head;
while (h) {
nexth = h->fnxt;
free(h);
h = nexth;
}
fdhash_head = NULL;
}
int
path_to_fshandle(
char *path, /* input, path to convert */
void **fshanp, /* output, pointer to data */
size_t *fshlen) /* output, size of returned data */
{
int result;
int fd;
comarg_t obj;
struct fdhash *fdhp;
char *tmppath;
char *fspath;
fspath = path_to_fspath(path);
if (fspath == NULL)
return -1;
fd = open(fspath, O_RDONLY);
if (fd < 0)
return -1;
obj.path = path;
result = obj_to_handle(fspath, fd, XFS_IOC_PATH_TO_FSHANDLE,
obj, fshanp, fshlen);
if (result < 0) {
close(fd);
return result;
}
if (handle_to_fsfd(*fshanp, &tmppath) >= 0) {
/* this filesystem is already in the cache */
close(fd);
} else {
/* new filesystem. add it to the cache */
fdhp = malloc(sizeof(struct fdhash));
if (fdhp == NULL) {
free(*fshanp);
close(fd);
errno = ENOMEM;
return -1;
}
fdhp->fsfd = fd;
strncpy(fdhp->fspath, fspath, sizeof(fdhp->fspath) - 1);
fdhp->fspath[sizeof(fdhp->fspath) - 1] = 0;
memcpy(fdhp->fsh, *fshanp, FSIDSIZE);
fdhp->fnxt = fdhash_head;
fdhash_head = fdhp;
}
return result;
}
int
path_to_handle(
char *path, /* input, path to convert */
void **hanp, /* output, pointer to data */
size_t *hlen) /* output, size of returned data */
{
int fd;
int result;
comarg_t obj;
char *fspath;
fspath = path_to_fspath(path);
if (fspath == NULL)
return -1;
fd = open(fspath, O_RDONLY);
if (fd < 0)
return -1;
obj.path = path;
result = obj_to_handle(fspath, fd, XFS_IOC_PATH_TO_HANDLE,
obj, hanp, hlen);
close(fd);
return result;
}
/* Given a path, return a suitable "fspath" for use in obtaining
* an fd for xfsctl calls. For regular files and directories the
* input path is sufficient. For other types the parent directory
* is used to avoid issues with opening dangling symlinks and
* potentially blocking in an open on a named pipe. Also
* symlinks to files on other filesystems would be a problem,
* since an fd would be obtained for the wrong fs.
*/
static char *
path_to_fspath(char *path)
{
static char dirpath[MAXPATHLEN];
struct stat statbuf;
if (lstat(path, &statbuf) != 0)
return NULL;
if (S_ISREG(statbuf.st_mode) || S_ISDIR(statbuf.st_mode))
return path;
strncpy(dirpath, path, MAXPATHLEN);
dirpath[MAXPATHLEN-1] = '\0';
return dirname(dirpath);
}
int
fd_to_handle (
int fd, /* input, file descriptor */
void **hanp, /* output, pointer to data */
size_t *hlen) /* output, size of returned data */
{
comarg_t obj;
obj.fd = fd;
return obj_to_handle(NULL, fd, XFS_IOC_FD_TO_HANDLE, obj, hanp, hlen);
}
int
handle_to_fshandle(
void *hanp,
size_t hlen,
void **fshanp,
size_t *fshlen)
{
if (hlen < FSIDSIZE) {
errno = EINVAL;
return -1;
}
*fshanp = malloc(FSIDSIZE);
if (*fshanp == NULL) {
errno = ENOMEM;
return -1;
}
*fshlen = FSIDSIZE;
memcpy(*fshanp, hanp, FSIDSIZE);
return 0;
}
static int
handle_to_fsfd(void *hanp, char **path)
{
struct fdhash *fdhp;
/*
* Look in cache for matching fsid field in the handle
* (which is at the start of the handle).
* When found return the file descriptor and path that
* we have in the cache.
*/
for (fdhp = fdhash_head; fdhp != NULL; fdhp = fdhp->fnxt) {
if (memcmp(fdhp->fsh, hanp, FSIDSIZE) == 0) {
*path = fdhp->fspath;
return fdhp->fsfd;
}
}
errno = EBADF;
return -1;
}
static int
obj_to_handle(
char *fspath,
int fsfd,
unsigned int opcode,
comarg_t obj,
void **hanp,
size_t *hlen)
{
char hbuf [MAXHANSIZ];
int ret;
uint32_t handlen = 0;
struct xfs_fsop_handlereq hreq = { };
memset(hbuf, 0, MAXHANSIZ);
if (opcode == XFS_IOC_FD_TO_HANDLE) {
hreq.fd = obj.fd;
hreq.path = NULL;
} else {
hreq.fd = 0;
hreq.path = obj.path;
}
hreq.oflags = O_LARGEFILE;
hreq.ihandle = NULL;
hreq.ihandlen = 0;
hreq.ohandle = hbuf;
hreq.ohandlen = &handlen;
ret = xfsctl(fspath, fsfd, opcode, &hreq);
if (ret)
return ret;
*hanp = malloc(handlen);
if (*hanp == NULL) {
errno = ENOMEM;
return -1;
}
memcpy(*hanp, hbuf, handlen);
*hlen = handlen;
return 0;
}
int
open_by_fshandle(
void *fshanp,
size_t fshlen,
int rw)
{
int fsfd;
char *path;
struct xfs_fsop_handlereq hreq = { };
if ((fsfd = handle_to_fsfd(fshanp, &path)) < 0)
return -1;
hreq.fd = 0;
hreq.path = NULL;
hreq.oflags = rw | O_LARGEFILE;
hreq.ihandle = fshanp;
hreq.ihandlen = fshlen;
hreq.ohandle = NULL;
hreq.ohandlen = NULL;
return xfsctl(path, fsfd, XFS_IOC_OPEN_BY_HANDLE, &hreq);
}
int
open_by_handle(
void *hanp,
size_t hlen,
int rw)
{
int fsfd;
char *path;
xfs_fsop_handlereq_t hreq;
if ((fsfd = handle_to_fsfd(hanp, &path)) < 0)
return -1;
hreq.fd = 0;
hreq.path = NULL;
hreq.oflags = rw | O_LARGEFILE;
hreq.ihandle = hanp;
hreq.ihandlen = hlen;
hreq.ohandle = NULL;
hreq.ohandlen = NULL;
return xfsctl(path, fsfd, XFS_IOC_OPEN_BY_HANDLE, &hreq);
}
int
readlink_by_handle(
void *hanp,
size_t hlen,
void *buf,
size_t bufsiz)
{
int fd;
__u32 buflen = (__u32)bufsiz;
char *path;
xfs_fsop_handlereq_t hreq;
if ((fd = handle_to_fsfd(hanp, &path)) < 0)
return -1;
hreq.fd = 0;
hreq.path = NULL;
hreq.oflags = O_LARGEFILE;
hreq.ihandle = hanp;
hreq.ihandlen = hlen;
hreq.ohandle = buf;
hreq.ohandlen = &buflen;
return xfsctl(path, fd, XFS_IOC_READLINK_BY_HANDLE, &hreq);
}
/*ARGSUSED4*/
int
attr_multi_by_handle(
void *hanp,
size_t hlen,
void *buf,
int rtrvcnt,
int flags)
{
int fd;
char *path;
xfs_fsop_attrmulti_handlereq_t amhreq;
if ((fd = handle_to_fsfd(hanp, &path)) < 0)
return -1;
amhreq.hreq.fd = 0;
amhreq.hreq.path = NULL;
amhreq.hreq.oflags = O_LARGEFILE;
amhreq.hreq.ihandle = hanp;
amhreq.hreq.ihandlen = hlen;
amhreq.hreq.ohandle = NULL;
amhreq.hreq.ohandlen = NULL;
amhreq.opcount = rtrvcnt;
amhreq.ops = buf;
return xfsctl(path, fd, XFS_IOC_ATTRMULTI_BY_HANDLE, &amhreq);
}
int
attr_list_by_handle(
void *hanp,
size_t hlen,
void *buf,
size_t bufsize,
int flags,
struct attrlist_cursor *cursor)
{
int error, fd;
char *path;
struct xfs_fsop_attrlist_handlereq alhreq = { };
if ((fd = handle_to_fsfd(hanp, &path)) < 0)
return -1;
alhreq.hreq.fd = 0;
alhreq.hreq.path = NULL;
alhreq.hreq.oflags = O_LARGEFILE;
alhreq.hreq.ihandle = hanp;
alhreq.hreq.ihandlen = hlen;
alhreq.hreq.ohandle = NULL;
alhreq.hreq.ohandlen = NULL;
memcpy(&alhreq.pos, cursor, sizeof(alhreq.pos));
alhreq.flags = flags;
alhreq.buffer = buf;
alhreq.buflen = bufsize;
/* prevent needless EINVAL from the kernel */
if (alhreq.buflen > XFS_XATTR_LIST_MAX)
alhreq.buflen = XFS_XATTR_LIST_MAX;
error = xfsctl(path, fd, XFS_IOC_ATTRLIST_BY_HANDLE, &alhreq);
memcpy(cursor, &alhreq.pos, sizeof(alhreq.pos));
return error;
}
int
parents_by_handle(
void *hanp,
size_t hlen,
parent_t *buf,
size_t bufsiz,
unsigned int *count)
{
errno = EOPNOTSUPP;
return -1;
}
int
parentpaths_by_handle(
void *hanp,
size_t hlen,
parent_t *buf,
size_t bufsiz,
unsigned int *count)
{
errno = EOPNOTSUPP;
return -1;
}
/* Deprecated in kernel */
int
fssetdm_by_handle(
void *hanp,
size_t hlen,
struct fsdmidata *fsdmidata)
{
errno = EOPNOTSUPP;
return -1;
}
/*ARGSUSED1*/
void
free_handle(
void *hanp,
size_t hlen)
{
free(hanp);
}