int error;
if (ITOZ(ip)->z_atime_dirty)
- mark_inode_dirty(ip);
+ zfs_mark_inode_dirty(ip);
crhold(cr);
error = -zfs_close(ip, filp->f_flags, cr);
return (error);
}
+static int
+zpl_aio_fsync(struct kiocb *kiocb, int datasync)
+{
+ struct file *filp = kiocb->ki_filp;
+ return (zpl_fsync(filp, filp->f_path.dentry, datasync));
+}
#elif defined(HAVE_FSYNC_WITHOUT_DENTRY)
/*
* Linux 2.6.35 - 3.0 API,
return (error);
}
+static int
+zpl_aio_fsync(struct kiocb *kiocb, int datasync)
+{
+ return (zpl_fsync(kiocb->ki_filp, datasync));
+}
#elif defined(HAVE_FSYNC_RANGE)
/*
* Linux 3.1 - 3.x API,
return (error);
}
+
+static int
+zpl_aio_fsync(struct kiocb *kiocb, int datasync)
+{
+ return (zpl_fsync(kiocb->ki_filp, kiocb->ki_pos,
+ kiocb->ki_pos + kiocb->ki_nbytes, datasync));
+}
#else
#error "Unsupported fops->fsync() implementation"
#endif
-ssize_t
-zpl_read_common(struct inode *ip, const char *buf, size_t len, loff_t pos,
- uio_seg_t segment, int flags, cred_t *cr)
+static inline ssize_t
+zpl_read_common_iovec(struct inode *ip, const struct iovec *iovp, size_t count,
+ unsigned long nr_segs, loff_t *ppos, uio_seg_t segment,
+ int flags, cred_t *cr)
{
- int error;
ssize_t read;
- struct iovec iov;
uio_t uio;
+ int error;
- iov.iov_base = (void *)buf;
- iov.iov_len = len;
-
- uio.uio_iov = &iov;
- uio.uio_resid = len;
- uio.uio_iovcnt = 1;
- uio.uio_loffset = pos;
+ uio.uio_iov = (struct iovec *)iovp;
+ uio.uio_resid = count;
+ uio.uio_iovcnt = nr_segs;
+ uio.uio_loffset = *ppos;
uio.uio_limit = MAXOFFSET_T;
uio.uio_segflg = segment;
if (error < 0)
return (error);
- read = len - uio.uio_resid;
+ read = count - uio.uio_resid;
+ *ppos += read;
task_io_account_read(read);
return (read);
}
+inline ssize_t
+zpl_read_common(struct inode *ip, const char *buf, size_t len, loff_t *ppos,
+ uio_seg_t segment, int flags, cred_t *cr)
+{
+ struct iovec iov;
+
+ iov.iov_base = (void *)buf;
+ iov.iov_len = len;
+
+ return (zpl_read_common_iovec(ip, &iov, len, 1, ppos, segment,
+ flags, cr));
+}
+
static ssize_t
zpl_read(struct file *filp, char __user *buf, size_t len, loff_t *ppos)
{
ssize_t read;
crhold(cr);
- read = zpl_read_common(filp->f_mapping->host, buf, len, *ppos,
+ read = zpl_read_common(filp->f_mapping->host, buf, len, ppos,
UIO_USERSPACE, filp->f_flags, cr);
crfree(cr);
- if (read < 0)
- return (read);
+ return (read);
+}
+
+static ssize_t
+zpl_aio_read(struct kiocb *kiocb, const struct iovec *iovp,
+ unsigned long nr_segs, loff_t pos)
+{
+ cred_t *cr = CRED();
+ struct file *filp = kiocb->ki_filp;
+ size_t count = kiocb->ki_nbytes;
+ ssize_t read;
+ size_t alloc_size = sizeof (struct iovec) * nr_segs;
+ struct iovec *iov_tmp = kmem_alloc(alloc_size, KM_SLEEP);
+ bcopy(iovp, iov_tmp, alloc_size);
+
+ ASSERT(iovp);
+
+ crhold(cr);
+ read = zpl_read_common_iovec(filp->f_mapping->host, iov_tmp, count,
+ nr_segs, &kiocb->ki_pos, UIO_USERSPACE, filp->f_flags, cr);
+ crfree(cr);
+
+ kmem_free(iov_tmp, alloc_size);
- *ppos += read;
return (read);
}
-ssize_t
-zpl_write_common(struct inode *ip, const char *buf, size_t len, loff_t pos,
- uio_seg_t segment, int flags, cred_t *cr)
+static inline ssize_t
+zpl_write_common_iovec(struct inode *ip, const struct iovec *iovp, size_t count,
+ unsigned long nr_segs, loff_t *ppos, uio_seg_t segment,
+ int flags, cred_t *cr)
{
- int error;
ssize_t wrote;
- struct iovec iov;
uio_t uio;
+ int error;
- iov.iov_base = (void *)buf;
- iov.iov_len = len;
-
- uio.uio_iov = &iov;
- uio.uio_resid = len,
- uio.uio_iovcnt = 1;
- uio.uio_loffset = pos;
+ uio.uio_iov = (struct iovec *)iovp;
+ uio.uio_resid = count;
+ uio.uio_iovcnt = nr_segs;
+ uio.uio_loffset = *ppos;
uio.uio_limit = MAXOFFSET_T;
uio.uio_segflg = segment;
if (error < 0)
return (error);
- wrote = len - uio.uio_resid;
+ wrote = count - uio.uio_resid;
+ *ppos += wrote;
task_io_account_write(wrote);
return (wrote);
}
+inline ssize_t
+zpl_write_common(struct inode *ip, const char *buf, size_t len, loff_t *ppos,
+ uio_seg_t segment, int flags, cred_t *cr)
+{
+ struct iovec iov;
+
+ iov.iov_base = (void *)buf;
+ iov.iov_len = len;
+
+ return (zpl_write_common_iovec(ip, &iov, len, 1, ppos, segment,
+ flags, cr));
+}
static ssize_t
zpl_write(struct file *filp, const char __user *buf, size_t len, loff_t *ppos)
ssize_t wrote;
crhold(cr);
- wrote = zpl_write_common(filp->f_mapping->host, buf, len, *ppos,
+ wrote = zpl_write_common(filp->f_mapping->host, buf, len, ppos,
UIO_USERSPACE, filp->f_flags, cr);
crfree(cr);
- if (wrote < 0)
- return (wrote);
+ return (wrote);
+}
+
+static ssize_t
+zpl_aio_write(struct kiocb *kiocb, const struct iovec *iovp,
+ unsigned long nr_segs, loff_t pos)
+{
+ cred_t *cr = CRED();
+ struct file *filp = kiocb->ki_filp;
+ size_t count = kiocb->ki_nbytes;
+ ssize_t wrote;
+ size_t alloc_size = sizeof (struct iovec) * nr_segs;
+ struct iovec *iov_tmp = kmem_alloc(alloc_size, KM_SLEEP);
+ bcopy(iovp, iov_tmp, alloc_size);
+
+ ASSERT(iovp);
+
+ crhold(cr);
+ wrote = zpl_write_common_iovec(filp->f_mapping->host, iov_tmp, count,
+ nr_segs, &kiocb->ki_pos, UIO_USERSPACE, filp->f_flags, cr);
+ crfree(cr);
+
+ kmem_free(iov_tmp, alloc_size);
- *ppos += wrote;
return (wrote);
}
}
#endif /* SEEK_HOLE && SEEK_DATA */
- return generic_file_llseek(filp, offset, whence);
+ return (generic_file_llseek(filp, offset, whence));
}
/*
}
unlock_page(pp);
- return error;
+ return (error);
}
/*
zpl_putpage(struct page *pp, struct writeback_control *wbc, void *data)
{
struct address_space *mapping = data;
+ fstrans_cookie_t cookie;
ASSERT(PageLocked(pp));
ASSERT(!PageWriteback(pp));
- ASSERT(!(current->flags & PF_NOFS));
- /*
- * Annotate this call path with a flag that indicates that it is
- * unsafe to use KM_SLEEP during memory allocations due to the
- * potential for a deadlock. KM_PUSHPAGE should be used instead.
- */
- current->flags |= PF_NOFS;
+ cookie = spl_fstrans_mark();
(void) zfs_putpage(mapping->host, pp, wbc);
- current->flags &= ~PF_NOFS;
+ spl_fstrans_unmark(cookie);
return (0);
}
if (sync_mode != wbc->sync_mode) {
ZFS_ENTER(zsb);
ZFS_VERIFY_ZP(zp);
- zil_commit(zsb->z_log, zp->z_id);
+ if (zsb->z_log != NULL)
+ zil_commit(zsb->z_log, zp->z_id);
ZFS_EXIT(zsb);
/*
/*
* The only flag combination which matches the behavior of zfs_space()
- * is FALLOC_FL_PUNCH_HOLE. This flag was introduced in the 2.6.38 kernel.
+ * is FALLOC_FL_KEEP_SIZE | FALLOC_FL_PUNCH_HOLE. The FALLOC_FL_PUNCH_HOLE
+ * flag was introduced in the 2.6.38 kernel.
*/
+#if defined(HAVE_FILE_FALLOCATE) || defined(HAVE_INODE_FALLOCATE)
long
zpl_fallocate_common(struct inode *ip, int mode, loff_t offset, loff_t len)
{
- cred_t *cr = CRED();
int error = -EOPNOTSUPP;
- if (mode & FALLOC_FL_KEEP_SIZE)
- return (-EOPNOTSUPP);
+#if defined(FALLOC_FL_PUNCH_HOLE) && defined(FALLOC_FL_KEEP_SIZE)
+ cred_t *cr = CRED();
+ flock64_t bf;
+ loff_t olen;
+
+ if (mode != (FALLOC_FL_KEEP_SIZE | FALLOC_FL_PUNCH_HOLE))
+ return (error);
crhold(cr);
-#ifdef FALLOC_FL_PUNCH_HOLE
- if (mode & FALLOC_FL_PUNCH_HOLE) {
- flock64_t bf;
+ if (offset < 0 || len <= 0)
+ return (-EINVAL);
- bf.l_type = F_WRLCK;
- bf.l_whence = 0;
- bf.l_start = offset;
- bf.l_len = len;
- bf.l_pid = 0;
+ spl_inode_lock(ip);
+ olen = i_size_read(ip);
- error = -zfs_space(ip, F_FREESP, &bf, FWRITE, offset, cr);
+ if (offset > olen) {
+ spl_inode_unlock(ip);
+ return (0);
}
-#endif /* FALLOC_FL_PUNCH_HOLE */
+ if (offset + len > olen)
+ len = olen - offset;
+ bf.l_type = F_WRLCK;
+ bf.l_whence = 0;
+ bf.l_start = offset;
+ bf.l_len = len;
+ bf.l_pid = 0;
+
+ error = -zfs_space(ip, F_FREESP, &bf, FWRITE, offset, cr);
+ spl_inode_unlock(ip);
crfree(cr);
+#endif /* defined(FALLOC_FL_PUNCH_HOLE) && defined(FALLOC_FL_KEEP_SIZE) */
ASSERT3S(error, <=, 0);
return (error);
}
+#endif /* defined(HAVE_FILE_FALLOCATE) || defined(HAVE_INODE_FALLOCATE) */
#ifdef HAVE_FILE_FALLOCATE
static long
}
#endif /* HAVE_FILE_FALLOCATE */
+/*
+ * Map zfs file z_pflags (xvattr_t) to linux file attributes. Only file
+ * attributes common to both Linux and Solaris are mapped.
+ */
+static int
+zpl_ioctl_getflags(struct file *filp, void __user *arg)
+{
+ struct inode *ip = filp->f_dentry->d_inode;
+ unsigned int ioctl_flags = 0;
+ uint64_t zfs_flags = ITOZ(ip)->z_pflags;
+ int error;
+
+ if (zfs_flags & ZFS_IMMUTABLE)
+ ioctl_flags |= FS_IMMUTABLE_FL;
+
+ if (zfs_flags & ZFS_APPENDONLY)
+ ioctl_flags |= FS_APPEND_FL;
+
+ if (zfs_flags & ZFS_NODUMP)
+ ioctl_flags |= FS_NODUMP_FL;
+
+ ioctl_flags &= FS_FL_USER_VISIBLE;
+
+ error = copy_to_user(arg, &ioctl_flags, sizeof (ioctl_flags));
+
+ return (error);
+}
+
+/*
+ * fchange() is a helper macro to detect if we have been asked to change a
+ * flag. This is ugly, but the requirement that we do this is a consequence of
+ * how the Linux file attribute interface was designed. Another consequence is
+ * that concurrent modification of files suffers from a TOCTOU race. Neither
+ * are things we can fix without modifying the kernel-userland interface, which
+ * is outside of our jurisdiction.
+ */
+
+#define fchange(f0, f1, b0, b1) ((((f0) & (b0)) == (b0)) != \
+ (((b1) & (f1)) == (f1)))
+
+static int
+zpl_ioctl_setflags(struct file *filp, void __user *arg)
+{
+ struct inode *ip = filp->f_dentry->d_inode;
+ uint64_t zfs_flags = ITOZ(ip)->z_pflags;
+ unsigned int ioctl_flags;
+ cred_t *cr = CRED();
+ xvattr_t xva;
+ xoptattr_t *xoap;
+ int error;
+
+ if (copy_from_user(&ioctl_flags, arg, sizeof (ioctl_flags)))
+ return (-EFAULT);
+
+ if ((ioctl_flags & ~(FS_IMMUTABLE_FL | FS_APPEND_FL | FS_NODUMP_FL)))
+ return (-EOPNOTSUPP);
+
+ if ((ioctl_flags & ~(FS_FL_USER_MODIFIABLE)))
+ return (-EACCES);
+
+ if ((fchange(ioctl_flags, zfs_flags, FS_IMMUTABLE_FL, ZFS_IMMUTABLE) ||
+ fchange(ioctl_flags, zfs_flags, FS_APPEND_FL, ZFS_APPENDONLY)) &&
+ !capable(CAP_LINUX_IMMUTABLE))
+ return (-EACCES);
+
+ if (!zpl_inode_owner_or_capable(ip))
+ return (-EACCES);
+
+ xva_init(&xva);
+ xoap = xva_getxoptattr(&xva);
+
+ XVA_SET_REQ(&xva, XAT_IMMUTABLE);
+ if (ioctl_flags & FS_IMMUTABLE_FL)
+ xoap->xoa_immutable = B_TRUE;
+
+ XVA_SET_REQ(&xva, XAT_APPENDONLY);
+ if (ioctl_flags & FS_APPEND_FL)
+ xoap->xoa_appendonly = B_TRUE;
+
+ XVA_SET_REQ(&xva, XAT_NODUMP);
+ if (ioctl_flags & FS_NODUMP_FL)
+ xoap->xoa_nodump = B_TRUE;
+
+ crhold(cr);
+ error = -zfs_setattr(ip, (vattr_t *)&xva, 0, cr);
+ crfree(cr);
+
+ return (error);
+}
+
static long
zpl_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
switch (cmd) {
- case ZFS_IOC_GETFLAGS:
- case ZFS_IOC_SETFLAGS:
- return (-EOPNOTSUPP);
+ case FS_IOC_GETFLAGS:
+ return (zpl_ioctl_getflags(filp, (void *)arg));
+ case FS_IOC_SETFLAGS:
+ return (zpl_ioctl_setflags(filp, (void *)arg));
default:
return (-ENOTTY);
}
static long
zpl_compat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
- return zpl_ioctl(filp, cmd, arg);
+ return (zpl_ioctl(filp, cmd, arg));
}
#endif /* CONFIG_COMPAT */
.readpages = zpl_readpages,
.readpage = zpl_readpage,
.writepage = zpl_writepage,
- .writepages = zpl_writepages,
+ .writepages = zpl_writepages,
};
const struct file_operations zpl_file_operations = {
.llseek = zpl_llseek,
.read = zpl_read,
.write = zpl_write,
+ .aio_read = zpl_aio_read,
+ .aio_write = zpl_aio_write,
.mmap = zpl_mmap,
.fsync = zpl_fsync,
+ .aio_fsync = zpl_aio_fsync,
#ifdef HAVE_FILE_FALLOCATE
- .fallocate = zpl_fallocate,
+ .fallocate = zpl_fallocate,
#endif /* HAVE_FILE_FALLOCATE */
- .unlocked_ioctl = zpl_ioctl,
+ .unlocked_ioctl = zpl_ioctl,
#ifdef CONFIG_COMPAT
- .compat_ioctl = zpl_compat_ioctl,
+ .compat_ioctl = zpl_compat_ioctl,
#endif
};