| // 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); |
| } |