|  | /* | 
|  | *   fs/cifs/ioctl.c | 
|  | * | 
|  | *   vfs operations that deal with io control | 
|  | * | 
|  | *   Copyright (C) International Business Machines  Corp., 2005,2013 | 
|  | *   Author(s): Steve French (sfrench@us.ibm.com) | 
|  | * | 
|  | *   This library is free software; you can redistribute it and/or modify | 
|  | *   it under the terms of the GNU Lesser General Public License as published | 
|  | *   by the Free Software Foundation; either version 2.1 of the License, or | 
|  | *   (at your option) any later version. | 
|  | * | 
|  | *   This library is distributed in the hope that it will be useful, | 
|  | *   but WITHOUT ANY WARRANTY; without even the implied warranty of | 
|  | *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See | 
|  | *   the GNU Lesser General Public License for more details. | 
|  | * | 
|  | *   You should have received a copy of the GNU Lesser General Public License | 
|  | *   along with this library; if not, write to the Free Software | 
|  | *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | 
|  | */ | 
|  |  | 
|  | #include <linux/fs.h> | 
|  | #include <linux/file.h> | 
|  | #include <linux/mount.h> | 
|  | #include <linux/mm.h> | 
|  | #include <linux/pagemap.h> | 
|  | #include "cifspdu.h" | 
|  | #include "cifsglob.h" | 
|  | #include "cifsproto.h" | 
|  | #include "cifs_debug.h" | 
|  | #include "cifsfs.h" | 
|  | #include "cifs_ioctl.h" | 
|  | #include <linux/btrfs.h> | 
|  |  | 
|  | static long cifs_ioctl_copychunk(unsigned int xid, struct file *dst_file, | 
|  | unsigned long srcfd) | 
|  | { | 
|  | int rc; | 
|  | struct fd src_file; | 
|  | struct inode *src_inode; | 
|  |  | 
|  | cifs_dbg(FYI, "ioctl copychunk range\n"); | 
|  | /* the destination must be opened for writing */ | 
|  | if (!(dst_file->f_mode & FMODE_WRITE)) { | 
|  | cifs_dbg(FYI, "file target not open for write\n"); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | /* check if target volume is readonly and take reference */ | 
|  | rc = mnt_want_write_file(dst_file); | 
|  | if (rc) { | 
|  | cifs_dbg(FYI, "mnt_want_write failed with rc %d\n", rc); | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | src_file = fdget(srcfd); | 
|  | if (!src_file.file) { | 
|  | rc = -EBADF; | 
|  | goto out_drop_write; | 
|  | } | 
|  |  | 
|  | if (src_file.file->f_op->unlocked_ioctl != cifs_ioctl) { | 
|  | rc = -EBADF; | 
|  | cifs_dbg(VFS, "src file seems to be from a different filesystem type\n"); | 
|  | goto out_fput; | 
|  | } | 
|  |  | 
|  | src_inode = file_inode(src_file.file); | 
|  | rc = -EINVAL; | 
|  | if (S_ISDIR(src_inode->i_mode)) | 
|  | goto out_fput; | 
|  |  | 
|  | rc = cifs_file_copychunk_range(xid, src_file.file, 0, dst_file, 0, | 
|  | src_inode->i_size, 0); | 
|  | if (rc > 0) | 
|  | rc = 0; | 
|  | out_fput: | 
|  | fdput(src_file); | 
|  | out_drop_write: | 
|  | mnt_drop_write_file(dst_file); | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | static long smb_mnt_get_fsinfo(unsigned int xid, struct cifs_tcon *tcon, | 
|  | void __user *arg) | 
|  | { | 
|  | int rc = 0; | 
|  | struct smb_mnt_fs_info *fsinf; | 
|  |  | 
|  | fsinf = kzalloc(sizeof(struct smb_mnt_fs_info), GFP_KERNEL); | 
|  | if (fsinf == NULL) | 
|  | return -ENOMEM; | 
|  |  | 
|  | fsinf->version = 1; | 
|  | fsinf->protocol_id = tcon->ses->server->vals->protocol_id; | 
|  | fsinf->device_characteristics = | 
|  | le32_to_cpu(tcon->fsDevInfo.DeviceCharacteristics); | 
|  | fsinf->device_type = le32_to_cpu(tcon->fsDevInfo.DeviceType); | 
|  | fsinf->fs_attributes = le32_to_cpu(tcon->fsAttrInfo.Attributes); | 
|  | fsinf->max_path_component = | 
|  | le32_to_cpu(tcon->fsAttrInfo.MaxPathNameComponentLength); | 
|  | fsinf->vol_serial_number = tcon->vol_serial_number; | 
|  | fsinf->vol_create_time = le64_to_cpu(tcon->vol_create_time); | 
|  | fsinf->share_flags = tcon->share_flags; | 
|  | fsinf->share_caps = le32_to_cpu(tcon->capabilities); | 
|  | fsinf->sector_flags = tcon->ss_flags; | 
|  | fsinf->optimal_sector_size = tcon->perf_sector_size; | 
|  | fsinf->max_bytes_chunk = tcon->max_bytes_chunk; | 
|  | fsinf->maximal_access = tcon->maximal_access; | 
|  | fsinf->cifs_posix_caps = le64_to_cpu(tcon->fsUnixInfo.Capability); | 
|  |  | 
|  | if (copy_to_user(arg, fsinf, sizeof(struct smb_mnt_fs_info))) | 
|  | rc = -EFAULT; | 
|  |  | 
|  | kfree(fsinf); | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | long cifs_ioctl(struct file *filep, unsigned int command, unsigned long arg) | 
|  | { | 
|  | struct inode *inode = file_inode(filep); | 
|  | int rc = -ENOTTY; /* strange error - but the precedent */ | 
|  | unsigned int xid; | 
|  | struct cifs_sb_info *cifs_sb; | 
|  | struct cifsFileInfo *pSMBFile = filep->private_data; | 
|  | struct cifs_tcon *tcon; | 
|  | __u64	ExtAttrBits = 0; | 
|  | __u64   caps; | 
|  |  | 
|  | xid = get_xid(); | 
|  |  | 
|  | cifs_sb = CIFS_SB(inode->i_sb); | 
|  | cifs_dbg(FYI, "cifs ioctl 0x%x\n", command); | 
|  | switch (command) { | 
|  | case FS_IOC_GETFLAGS: | 
|  | if (pSMBFile == NULL) | 
|  | break; | 
|  | tcon = tlink_tcon(pSMBFile->tlink); | 
|  | caps = le64_to_cpu(tcon->fsUnixInfo.Capability); | 
|  | #ifdef CONFIG_CIFS_POSIX | 
|  | if (CIFS_UNIX_EXTATTR_CAP & caps) { | 
|  | __u64	ExtAttrMask = 0; | 
|  | rc = CIFSGetExtAttr(xid, tcon, | 
|  | pSMBFile->fid.netfid, | 
|  | &ExtAttrBits, &ExtAttrMask); | 
|  | if (rc == 0) | 
|  | rc = put_user(ExtAttrBits & | 
|  | FS_FL_USER_VISIBLE, | 
|  | (int __user *)arg); | 
|  | if (rc != EOPNOTSUPP) | 
|  | break; | 
|  | } | 
|  | #endif /* CONFIG_CIFS_POSIX */ | 
|  | rc = 0; | 
|  | if (CIFS_I(inode)->cifsAttrs & ATTR_COMPRESSED) { | 
|  | /* add in the compressed bit */ | 
|  | ExtAttrBits = FS_COMPR_FL; | 
|  | rc = put_user(ExtAttrBits & FS_FL_USER_VISIBLE, | 
|  | (int __user *)arg); | 
|  | } | 
|  | break; | 
|  | case FS_IOC_SETFLAGS: | 
|  | if (pSMBFile == NULL) | 
|  | break; | 
|  | tcon = tlink_tcon(pSMBFile->tlink); | 
|  | caps = le64_to_cpu(tcon->fsUnixInfo.Capability); | 
|  |  | 
|  | if (get_user(ExtAttrBits, (int __user *)arg)) { | 
|  | rc = -EFAULT; | 
|  | break; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * if (CIFS_UNIX_EXTATTR_CAP & caps) | 
|  | *	rc = CIFSSetExtAttr(xid, tcon, | 
|  | *		       pSMBFile->fid.netfid, | 
|  | *		       extAttrBits, | 
|  | *		       &ExtAttrMask); | 
|  | * if (rc != EOPNOTSUPP) | 
|  | *	break; | 
|  | */ | 
|  |  | 
|  | /* Currently only flag we can set is compressed flag */ | 
|  | if ((ExtAttrBits & FS_COMPR_FL) == 0) | 
|  | break; | 
|  |  | 
|  | /* Try to set compress flag */ | 
|  | if (tcon->ses->server->ops->set_compression) { | 
|  | rc = tcon->ses->server->ops->set_compression( | 
|  | xid, tcon, pSMBFile); | 
|  | cifs_dbg(FYI, "set compress flag rc %d\n", rc); | 
|  | } | 
|  | break; | 
|  | case CIFS_IOC_COPYCHUNK_FILE: | 
|  | rc = cifs_ioctl_copychunk(xid, filep, arg); | 
|  | break; | 
|  | case CIFS_IOC_SET_INTEGRITY: | 
|  | if (pSMBFile == NULL) | 
|  | break; | 
|  | tcon = tlink_tcon(pSMBFile->tlink); | 
|  | if (tcon->ses->server->ops->set_integrity) | 
|  | rc = tcon->ses->server->ops->set_integrity(xid, | 
|  | tcon, pSMBFile); | 
|  | else | 
|  | rc = -EOPNOTSUPP; | 
|  | break; | 
|  | case CIFS_IOC_GET_MNT_INFO: | 
|  | if (pSMBFile == NULL) | 
|  | break; | 
|  | tcon = tlink_tcon(pSMBFile->tlink); | 
|  | rc = smb_mnt_get_fsinfo(xid, tcon, (void __user *)arg); | 
|  | break; | 
|  | case CIFS_ENUMERATE_SNAPSHOTS: | 
|  | if (pSMBFile == NULL) | 
|  | break; | 
|  | if (arg == 0) { | 
|  | rc = -EINVAL; | 
|  | goto cifs_ioc_exit; | 
|  | } | 
|  | tcon = tlink_tcon(pSMBFile->tlink); | 
|  | if (tcon->ses->server->ops->enum_snapshots) | 
|  | rc = tcon->ses->server->ops->enum_snapshots(xid, tcon, | 
|  | pSMBFile, (void __user *)arg); | 
|  | else | 
|  | rc = -EOPNOTSUPP; | 
|  | break; | 
|  | default: | 
|  | cifs_dbg(FYI, "unsupported ioctl\n"); | 
|  | break; | 
|  | } | 
|  | cifs_ioc_exit: | 
|  | free_xid(xid); | 
|  | return rc; | 
|  | } |