📄 xfs_ioctl.c
字号:
/* * Copyright (c) 2000-2005 Silicon Graphics, Inc. * All Rights Reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation. * * This program is distributed in the hope that it would be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */#include "xfs.h"#include "xfs_fs.h"#include "xfs_bit.h"#include "xfs_log.h"#include "xfs_inum.h"#include "xfs_trans.h"#include "xfs_sb.h"#include "xfs_ag.h"#include "xfs_dir.h"#include "xfs_dir2.h"#include "xfs_alloc.h"#include "xfs_dmapi.h"#include "xfs_mount.h"#include "xfs_bmap_btree.h"#include "xfs_alloc_btree.h"#include "xfs_ialloc_btree.h"#include "xfs_dir_sf.h"#include "xfs_attr_sf.h"#include "xfs_dir2_sf.h"#include "xfs_dinode.h"#include "xfs_inode.h"#include "xfs_btree.h"#include "xfs_ialloc.h"#include "xfs_rtalloc.h"#include "xfs_itable.h"#include "xfs_error.h"#include "xfs_rw.h"#include "xfs_acl.h"#include "xfs_cap.h"#include "xfs_mac.h"#include "xfs_attr.h"#include "xfs_bmap.h"#include "xfs_buf_item.h"#include "xfs_utils.h"#include "xfs_dfrag.h"#include "xfs_fsops.h"#include <linux/dcache.h>#include <linux/mount.h>#include <linux/namei.h>#include <linux/pagemap.h>/* * xfs_find_handle maps from userspace xfs_fsop_handlereq structure to * a file or fs handle. * * XFS_IOC_PATH_TO_FSHANDLE * returns fs handle for a mount point or path within that mount point * XFS_IOC_FD_TO_HANDLE * returns full handle for a FD opened in user space * XFS_IOC_PATH_TO_HANDLE * returns full handle for a path */STATIC intxfs_find_handle( unsigned int cmd, void __user *arg){ int hsize; xfs_handle_t handle; xfs_fsop_handlereq_t hreq; struct inode *inode; struct vnode *vp; if (copy_from_user(&hreq, arg, sizeof(hreq))) return -XFS_ERROR(EFAULT); memset((char *)&handle, 0, sizeof(handle)); switch (cmd) { case XFS_IOC_PATH_TO_FSHANDLE: case XFS_IOC_PATH_TO_HANDLE: { struct nameidata nd; int error; error = user_path_walk_link((const char __user *)hreq.path, &nd); if (error) return error; ASSERT(nd.dentry); ASSERT(nd.dentry->d_inode); inode = igrab(nd.dentry->d_inode); path_release(&nd); break; } case XFS_IOC_FD_TO_HANDLE: { struct file *file; file = fget(hreq.fd); if (!file) return -EBADF; ASSERT(file->f_dentry); ASSERT(file->f_dentry->d_inode); inode = igrab(file->f_dentry->d_inode); fput(file); break; } default: ASSERT(0); return -XFS_ERROR(EINVAL); } if (inode->i_sb->s_magic != XFS_SB_MAGIC) { /* we're not in XFS anymore, Toto */ iput(inode); return -XFS_ERROR(EINVAL); } switch (inode->i_mode & S_IFMT) { case S_IFREG: case S_IFDIR: case S_IFLNK: break; default: iput(inode); return -XFS_ERROR(EBADF); } /* we need the vnode */ vp = LINVFS_GET_VP(inode); /* now we can grab the fsid */ memcpy(&handle.ha_fsid, vp->v_vfsp->vfs_altfsid, sizeof(xfs_fsid_t)); hsize = sizeof(xfs_fsid_t); if (cmd != XFS_IOC_PATH_TO_FSHANDLE) { xfs_inode_t *ip; bhv_desc_t *bhv; int lock_mode; /* need to get access to the xfs_inode to read the generation */ bhv = vn_bhv_lookup_unlocked(VN_BHV_HEAD(vp), &xfs_vnodeops); ASSERT(bhv); ip = XFS_BHVTOI(bhv); ASSERT(ip); lock_mode = xfs_ilock_map_shared(ip); /* fill in fid section of handle from inode */ handle.ha_fid.xfs_fid_len = sizeof(xfs_fid_t) - sizeof(handle.ha_fid.xfs_fid_len); handle.ha_fid.xfs_fid_pad = 0; handle.ha_fid.xfs_fid_gen = ip->i_d.di_gen; handle.ha_fid.xfs_fid_ino = ip->i_ino; xfs_iunlock_map_shared(ip, lock_mode); hsize = XFS_HSIZE(handle); } /* now copy our handle into the user buffer & write out the size */ if (copy_to_user(hreq.ohandle, &handle, hsize) || copy_to_user(hreq.ohandlen, &hsize, sizeof(__s32))) { iput(inode); return -XFS_ERROR(EFAULT); } iput(inode); return 0;}/* * Convert userspace handle data into vnode (and inode). * We [ab]use the fact that all the fsop_handlereq ioctl calls * have a data structure argument whose first component is always * a xfs_fsop_handlereq_t, so we can cast to and from this type. * This allows us to optimise the copy_from_user calls and gives * a handy, shared routine. * * If no error, caller must always VN_RELE the returned vp. */STATIC intxfs_vget_fsop_handlereq( xfs_mount_t *mp, struct inode *parinode, /* parent inode pointer */ xfs_fsop_handlereq_t *hreq, vnode_t **vp, struct inode **inode){ void __user *hanp; size_t hlen; xfs_fid_t *xfid; xfs_handle_t *handlep; xfs_handle_t handle; xfs_inode_t *ip; struct inode *inodep; vnode_t *vpp; xfs_ino_t ino; __u32 igen; int error; /* * Only allow handle opens under a directory. */ if (!S_ISDIR(parinode->i_mode)) return XFS_ERROR(ENOTDIR); hanp = hreq->ihandle; hlen = hreq->ihandlen; handlep = &handle; if (hlen < sizeof(handlep->ha_fsid) || hlen > sizeof(*handlep)) return XFS_ERROR(EINVAL); if (copy_from_user(handlep, hanp, hlen)) return XFS_ERROR(EFAULT); if (hlen < sizeof(*handlep)) memset(((char *)handlep) + hlen, 0, sizeof(*handlep) - hlen); if (hlen > sizeof(handlep->ha_fsid)) { if (handlep->ha_fid.xfs_fid_len != (hlen - sizeof(handlep->ha_fsid) - sizeof(handlep->ha_fid.xfs_fid_len)) || handlep->ha_fid.xfs_fid_pad) return XFS_ERROR(EINVAL); } /* * Crack the handle, obtain the inode # & generation # */ xfid = (struct xfs_fid *)&handlep->ha_fid; if (xfid->xfs_fid_len == sizeof(*xfid) - sizeof(xfid->xfs_fid_len)) { ino = xfid->xfs_fid_ino; igen = xfid->xfs_fid_gen; } else { return XFS_ERROR(EINVAL); } /* * Get the XFS inode, building a vnode to go with it. */ error = xfs_iget(mp, NULL, ino, 0, XFS_ILOCK_SHARED, &ip, 0); if (error) return error; if (ip == NULL) return XFS_ERROR(EIO); if (ip->i_d.di_mode == 0 || ip->i_d.di_gen != igen) { xfs_iput_new(ip, XFS_ILOCK_SHARED); return XFS_ERROR(ENOENT); } vpp = XFS_ITOV(ip); inodep = LINVFS_GET_IP(vpp); xfs_iunlock(ip, XFS_ILOCK_SHARED); *vp = vpp; *inode = inodep; return 0;}STATIC intxfs_open_by_handle( xfs_mount_t *mp, void __user *arg, struct file *parfilp, struct inode *parinode){ int error; int new_fd; int permflag; struct file *filp; struct inode *inode; struct dentry *dentry; vnode_t *vp; xfs_fsop_handlereq_t hreq; if (!capable(CAP_SYS_ADMIN)) return -XFS_ERROR(EPERM); if (copy_from_user(&hreq, arg, sizeof(xfs_fsop_handlereq_t))) return -XFS_ERROR(EFAULT); error = xfs_vget_fsop_handlereq(mp, parinode, &hreq, &vp, &inode); if (error) return -error; /* Restrict xfs_open_by_handle to directories & regular files. */ if (!(S_ISREG(inode->i_mode) || S_ISDIR(inode->i_mode))) { iput(inode); return -XFS_ERROR(EINVAL); }#if BITS_PER_LONG != 32 hreq.oflags |= O_LARGEFILE;#endif /* Put open permission in namei format. */ permflag = hreq.oflags; if ((permflag+1) & O_ACCMODE) permflag++; if (permflag & O_TRUNC) permflag |= 2; if ((!(permflag & O_APPEND) || (permflag & O_TRUNC)) && (permflag & FMODE_WRITE) && IS_APPEND(inode)) { iput(inode); return -XFS_ERROR(EPERM); } if ((permflag & FMODE_WRITE) && IS_IMMUTABLE(inode)) { iput(inode); return -XFS_ERROR(EACCES); } /* Can't write directories. */ if ( S_ISDIR(inode->i_mode) && (permflag & FMODE_WRITE)) { iput(inode); return -XFS_ERROR(EISDIR); } if ((new_fd = get_unused_fd()) < 0) { iput(inode); return new_fd; } dentry = d_alloc_anon(inode); if (dentry == NULL) { iput(inode); put_unused_fd(new_fd); return -XFS_ERROR(ENOMEM); } /* Ensure umount returns EBUSY on umounts while this file is open. */ mntget(parfilp->f_vfsmnt); /* Create file pointer. */ filp = dentry_open(dentry, parfilp->f_vfsmnt, hreq.oflags); if (IS_ERR(filp)) { put_unused_fd(new_fd); return -XFS_ERROR(-PTR_ERR(filp)); } if (inode->i_mode & S_IFREG) filp->f_op = &linvfs_invis_file_operations; fd_install(new_fd, filp); return new_fd;}STATIC intxfs_readlink_by_handle( xfs_mount_t *mp, void __user *arg, struct file *parfilp, struct inode *parinode){ int error; struct iovec aiov; struct uio auio; struct inode *inode; xfs_fsop_handlereq_t hreq; vnode_t *vp; __u32 olen; if (!capable(CAP_SYS_ADMIN)) return -XFS_ERROR(EPERM); if (copy_from_user(&hreq, arg, sizeof(xfs_fsop_handlereq_t))) return -XFS_ERROR(EFAULT); error = xfs_vget_fsop_handlereq(mp, parinode, &hreq, &vp, &inode); if (error) return -error; /* Restrict this handle operation to symlinks only. */ if (!S_ISLNK(inode->i_mode)) { VN_RELE(vp); return -XFS_ERROR(EINVAL); } if (copy_from_user(&olen, hreq.ohandlen, sizeof(__u32))) { VN_RELE(vp); return -XFS_ERROR(EFAULT); } aiov.iov_len = olen; aiov.iov_base = hreq.ohandle; auio.uio_iov = &aiov; auio.uio_iovcnt = 1; auio.uio_offset = 0; auio.uio_segflg = UIO_USERSPACE; auio.uio_resid = olen; VOP_READLINK(vp, &auio, IO_INVIS, NULL, error); VN_RELE(vp); return (olen - auio.uio_resid);}STATIC intxfs_fssetdm_by_handle( xfs_mount_t *mp, void __user *arg, struct file *parfilp, struct inode *parinode){ int error; struct fsdmidata fsd; xfs_fsop_setdm_handlereq_t dmhreq; struct inode *inode; bhv_desc_t *bdp; vnode_t *vp; if (!capable(CAP_MKNOD)) return -XFS_ERROR(EPERM); if (copy_from_user(&dmhreq, arg, sizeof(xfs_fsop_setdm_handlereq_t))) return -XFS_ERROR(EFAULT); error = xfs_vget_fsop_handlereq(mp, parinode, &dmhreq.hreq, &vp, &inode); if (error) return -error; if (IS_IMMUTABLE(inode) || IS_APPEND(inode)) { VN_RELE(vp); return -XFS_ERROR(EPERM); } if (copy_from_user(&fsd, dmhreq.data, sizeof(fsd))) { VN_RELE(vp); return -XFS_ERROR(EFAULT); } bdp = bhv_base_unlocked(VN_BHV_HEAD(vp)); error = xfs_set_dmattrs(bdp, fsd.fsd_dmevmask, fsd.fsd_dmstate, NULL); VN_RELE(vp); if (error) return -error; return 0;}STATIC intxfs_attrlist_by_handle( xfs_mount_t *mp, void __user *arg, struct file *parfilp, struct inode *parinode){ int error; attrlist_cursor_kern_t *cursor; xfs_fsop_attrlist_handlereq_t al_hreq; struct inode *inode; vnode_t *vp; char *kbuf; if (!capable(CAP_SYS_ADMIN)) return -XFS_ERROR(EPERM); if (copy_from_user(&al_hreq, arg, sizeof(xfs_fsop_attrlist_handlereq_t))) return -XFS_ERROR(EFAULT); if (al_hreq.buflen > XATTR_LIST_MAX) return -XFS_ERROR(EINVAL); error = xfs_vget_fsop_handlereq(mp, parinode, &al_hreq.hreq, &vp, &inode); if (error) goto out; kbuf = kmalloc(al_hreq.buflen, GFP_KERNEL); if (!kbuf) goto out_vn_rele; cursor = (attrlist_cursor_kern_t *)&al_hreq.pos; VOP_ATTR_LIST(vp, kbuf, al_hreq.buflen, al_hreq.flags, cursor, NULL, error); if (error) goto out_kfree; if (copy_to_user(al_hreq.buffer, kbuf, al_hreq.buflen)) error = -EFAULT; out_kfree: kfree(kbuf); out_vn_rele: VN_RELE(vp); out: return -error;}STATIC intxfs_attrmulti_attr_get( struct vnode *vp, char *name, char __user *ubuf, __uint32_t *len, __uint32_t flags){ char *kbuf; int error = EFAULT; if (*len > XATTR_SIZE_MAX) return EINVAL; kbuf = kmalloc(*len, GFP_KERNEL); if (!kbuf) return ENOMEM; VOP_ATTR_GET(vp, name, kbuf, len, flags, NULL, error); if (error) goto out_kfree; if (copy_to_user(ubuf, kbuf, *len)) error = EFAULT; out_kfree: kfree(kbuf); return error;}STATIC intxfs_attrmulti_attr_set( struct vnode *vp, char *name, const char __user *ubuf, __uint32_t len, __uint32_t flags){ char *kbuf; int error = EFAULT; if (IS_IMMUTABLE(&vp->v_inode) || IS_APPEND(&vp->v_inode)) return EPERM; if (len > XATTR_SIZE_MAX) return EINVAL; kbuf = kmalloc(len, GFP_KERNEL); if (!kbuf) return ENOMEM; if (copy_from_user(kbuf, ubuf, len)) goto out_kfree; VOP_ATTR_SET(vp, name, kbuf, len, flags, NULL, error); out_kfree: kfree(kbuf); return error;}STATIC intxfs_attrmulti_attr_remove( struct vnode *vp, char *name, __uint32_t flags){ int error; if (IS_IMMUTABLE(&vp->v_inode) || IS_APPEND(&vp->v_inode)) return EPERM; VOP_ATTR_REMOVE(vp, name, flags, NULL, error); return error;}STATIC intxfs_attrmulti_by_handle( xfs_mount_t *mp, void __user *arg, struct file *parfilp, struct inode *parinode){ int error; xfs_attr_multiop_t *ops; xfs_fsop_attrmulti_handlereq_t am_hreq; struct inode *inode; vnode_t *vp; unsigned int i, size; char *attr_name; if (!capable(CAP_SYS_ADMIN)) return -XFS_ERROR(EPERM); if (copy_from_user(&am_hreq, arg, sizeof(xfs_fsop_attrmulti_handlereq_t))) return -XFS_ERROR(EFAULT); error = xfs_vget_fsop_handlereq(mp, parinode, &am_hreq.hreq, &vp, &inode); if (error) goto out; error = E2BIG; size = am_hreq.opcount * sizeof(attr_multiop_t); if (!size || size > 16 * PAGE_SIZE) goto out_vn_rele; error = ENOMEM; ops = kmalloc(size, GFP_KERNEL); if (!ops) goto out_vn_rele; error = EFAULT; if (copy_from_user(ops, am_hreq.ops, size)) goto out_kfree_ops; attr_name = kmalloc(MAXNAMELEN, GFP_KERNEL); if (!attr_name) goto out_kfree_ops; error = 0; for (i = 0; i < am_hreq.opcount; i++) { ops[i].am_error = strncpy_from_user(attr_name, ops[i].am_attrname, MAXNAMELEN); if (ops[i].am_error == 0 || ops[i].am_error == MAXNAMELEN) error = -ERANGE; if (ops[i].am_error < 0) break; switch (ops[i].am_opcode) { case ATTR_OP_GET: ops[i].am_error = xfs_attrmulti_attr_get(vp, attr_name, ops[i].am_attrvalue, &ops[i].am_length, ops[i].am_flags); break; case ATTR_OP_SET: ops[i].am_error = xfs_attrmulti_attr_set(vp, attr_name, ops[i].am_attrvalue, ops[i].am_length, ops[i].am_flags); break; case ATTR_OP_REMOVE: ops[i].am_error = xfs_attrmulti_attr_remove(vp, attr_name, ops[i].am_flags); break; default: ops[i].am_error = EINVAL; } } if (copy_to_user(am_hreq.ops, ops, size)) error = XFS_ERROR(EFAULT); kfree(attr_name); out_kfree_ops: kfree(ops); out_vn_rele: VN_RELE(vp); out: return -error;}/* prototypes for a few of the stack-hungry cases that have * their own functions. Functions are defined after their use * so gcc doesn't get fancy and inline them with -03 */STATIC intxfs_ioc_space( bhv_desc_t *bdp, vnode_t *vp, struct file *filp, int flags, unsigned int cmd, void __user *arg);STATIC intxfs_ioc_bulkstat( xfs_mount_t *mp, unsigned int cmd,
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -