mirror of
https://github.com/torvalds/linux.git
synced 2024-11-28 15:11:31 +00:00
c3b2da3148
Btrfs has to make sure we have space to allocate new blocks in order to modify the inode, so updating time can fail. We've gotten around this by having our own file_update_time but this is kind of a pain, and Christoph has indicated he would like to make xfs do something different with atime updates. So introduce ->update_time, where we will deal with i_version an a/m/c time updates and indicate which changes need to be made. The normal version just does what it has always done, updates the time and marks the inode dirty, and then filesystems can choose to do something different. I've gone through all of the users of file_update_time and made them check for errors with the exception of the fault code since it's complicated and I wasn't quite sure what to do there, also Jan is going to be pushing the file time updates into page_mkwrite for those who have it so that should satisfy btrfs and make it not a big deal to check the file_update_time() return code in the generic fault path. Thanks, Signed-off-by: Josef Bacik <josef@redhat.com>
299 lines
6.7 KiB
C
299 lines
6.7 KiB
C
/*
|
|
* file.c
|
|
*
|
|
* Copyright (C) 1995, 1996 by Volker Lendecke
|
|
* Modified 1997 Peter Waltenberg, Bill Hawes, David Woodhouse for 2.1 dcache
|
|
*
|
|
*/
|
|
|
|
#include <asm/uaccess.h>
|
|
|
|
#include <linux/time.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/fcntl.h>
|
|
#include <linux/stat.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/vmalloc.h>
|
|
#include <linux/sched.h>
|
|
|
|
#include "ncp_fs.h"
|
|
|
|
static int ncp_fsync(struct file *file, loff_t start, loff_t end, int datasync)
|
|
{
|
|
return filemap_write_and_wait_range(file->f_mapping, start, end);
|
|
}
|
|
|
|
/*
|
|
* Open a file with the specified read/write mode.
|
|
*/
|
|
int ncp_make_open(struct inode *inode, int right)
|
|
{
|
|
int error;
|
|
int access;
|
|
|
|
error = -EINVAL;
|
|
if (!inode) {
|
|
printk(KERN_ERR "ncp_make_open: got NULL inode\n");
|
|
goto out;
|
|
}
|
|
|
|
DPRINTK("ncp_make_open: opened=%d, volume # %u, dir entry # %u\n",
|
|
atomic_read(&NCP_FINFO(inode)->opened),
|
|
NCP_FINFO(inode)->volNumber,
|
|
NCP_FINFO(inode)->dirEntNum);
|
|
error = -EACCES;
|
|
mutex_lock(&NCP_FINFO(inode)->open_mutex);
|
|
if (!atomic_read(&NCP_FINFO(inode)->opened)) {
|
|
struct ncp_entry_info finfo;
|
|
int result;
|
|
|
|
/* tries max. rights */
|
|
finfo.access = O_RDWR;
|
|
result = ncp_open_create_file_or_subdir(NCP_SERVER(inode),
|
|
inode, NULL, OC_MODE_OPEN,
|
|
0, AR_READ | AR_WRITE, &finfo);
|
|
if (!result)
|
|
goto update;
|
|
/* RDWR did not succeeded, try readonly or writeonly as requested */
|
|
switch (right) {
|
|
case O_RDONLY:
|
|
finfo.access = O_RDONLY;
|
|
result = ncp_open_create_file_or_subdir(NCP_SERVER(inode),
|
|
inode, NULL, OC_MODE_OPEN,
|
|
0, AR_READ, &finfo);
|
|
break;
|
|
case O_WRONLY:
|
|
finfo.access = O_WRONLY;
|
|
result = ncp_open_create_file_or_subdir(NCP_SERVER(inode),
|
|
inode, NULL, OC_MODE_OPEN,
|
|
0, AR_WRITE, &finfo);
|
|
break;
|
|
}
|
|
if (result) {
|
|
PPRINTK("ncp_make_open: failed, result=%d\n", result);
|
|
goto out_unlock;
|
|
}
|
|
/*
|
|
* Update the inode information.
|
|
*/
|
|
update:
|
|
ncp_update_inode(inode, &finfo);
|
|
atomic_set(&NCP_FINFO(inode)->opened, 1);
|
|
}
|
|
|
|
access = NCP_FINFO(inode)->access;
|
|
PPRINTK("ncp_make_open: file open, access=%x\n", access);
|
|
if (access == right || access == O_RDWR) {
|
|
atomic_inc(&NCP_FINFO(inode)->opened);
|
|
error = 0;
|
|
}
|
|
|
|
out_unlock:
|
|
mutex_unlock(&NCP_FINFO(inode)->open_mutex);
|
|
out:
|
|
return error;
|
|
}
|
|
|
|
static ssize_t
|
|
ncp_file_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
|
|
{
|
|
struct dentry *dentry = file->f_path.dentry;
|
|
struct inode *inode = dentry->d_inode;
|
|
size_t already_read = 0;
|
|
off_t pos;
|
|
size_t bufsize;
|
|
int error;
|
|
void* freepage;
|
|
size_t freelen;
|
|
|
|
DPRINTK("ncp_file_read: enter %s/%s\n",
|
|
dentry->d_parent->d_name.name, dentry->d_name.name);
|
|
|
|
pos = *ppos;
|
|
|
|
if ((ssize_t) count < 0) {
|
|
return -EINVAL;
|
|
}
|
|
if (!count)
|
|
return 0;
|
|
if (pos > inode->i_sb->s_maxbytes)
|
|
return 0;
|
|
if (pos + count > inode->i_sb->s_maxbytes) {
|
|
count = inode->i_sb->s_maxbytes - pos;
|
|
}
|
|
|
|
error = ncp_make_open(inode, O_RDONLY);
|
|
if (error) {
|
|
DPRINTK(KERN_ERR "ncp_file_read: open failed, error=%d\n", error);
|
|
return error;
|
|
}
|
|
|
|
bufsize = NCP_SERVER(inode)->buffer_size;
|
|
|
|
error = -EIO;
|
|
freelen = ncp_read_bounce_size(bufsize);
|
|
freepage = vmalloc(freelen);
|
|
if (!freepage)
|
|
goto outrel;
|
|
error = 0;
|
|
/* First read in as much as possible for each bufsize. */
|
|
while (already_read < count) {
|
|
int read_this_time;
|
|
size_t to_read = min_t(unsigned int,
|
|
bufsize - (pos % bufsize),
|
|
count - already_read);
|
|
|
|
error = ncp_read_bounce(NCP_SERVER(inode),
|
|
NCP_FINFO(inode)->file_handle,
|
|
pos, to_read, buf, &read_this_time,
|
|
freepage, freelen);
|
|
if (error) {
|
|
error = -EIO; /* NW errno -> Linux errno */
|
|
break;
|
|
}
|
|
pos += read_this_time;
|
|
buf += read_this_time;
|
|
already_read += read_this_time;
|
|
|
|
if (read_this_time != to_read) {
|
|
break;
|
|
}
|
|
}
|
|
vfree(freepage);
|
|
|
|
*ppos = pos;
|
|
|
|
file_accessed(file);
|
|
|
|
DPRINTK("ncp_file_read: exit %s/%s\n",
|
|
dentry->d_parent->d_name.name, dentry->d_name.name);
|
|
outrel:
|
|
ncp_inode_close(inode);
|
|
return already_read ? already_read : error;
|
|
}
|
|
|
|
static ssize_t
|
|
ncp_file_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
|
|
{
|
|
struct dentry *dentry = file->f_path.dentry;
|
|
struct inode *inode = dentry->d_inode;
|
|
size_t already_written = 0;
|
|
off_t pos;
|
|
size_t bufsize;
|
|
int errno;
|
|
void* bouncebuffer;
|
|
|
|
DPRINTK("ncp_file_write: enter %s/%s\n",
|
|
dentry->d_parent->d_name.name, dentry->d_name.name);
|
|
if ((ssize_t) count < 0)
|
|
return -EINVAL;
|
|
pos = *ppos;
|
|
if (file->f_flags & O_APPEND) {
|
|
pos = i_size_read(inode);
|
|
}
|
|
|
|
if (pos + count > MAX_NON_LFS && !(file->f_flags&O_LARGEFILE)) {
|
|
if (pos >= MAX_NON_LFS) {
|
|
return -EFBIG;
|
|
}
|
|
if (count > MAX_NON_LFS - (u32)pos) {
|
|
count = MAX_NON_LFS - (u32)pos;
|
|
}
|
|
}
|
|
if (pos >= inode->i_sb->s_maxbytes) {
|
|
if (count || pos > inode->i_sb->s_maxbytes) {
|
|
return -EFBIG;
|
|
}
|
|
}
|
|
if (pos + count > inode->i_sb->s_maxbytes) {
|
|
count = inode->i_sb->s_maxbytes - pos;
|
|
}
|
|
|
|
if (!count)
|
|
return 0;
|
|
errno = ncp_make_open(inode, O_WRONLY);
|
|
if (errno) {
|
|
DPRINTK(KERN_ERR "ncp_file_write: open failed, error=%d\n", errno);
|
|
return errno;
|
|
}
|
|
bufsize = NCP_SERVER(inode)->buffer_size;
|
|
|
|
already_written = 0;
|
|
|
|
errno = file_update_time(file);
|
|
if (errno)
|
|
goto outrel;
|
|
|
|
bouncebuffer = vmalloc(bufsize);
|
|
if (!bouncebuffer) {
|
|
errno = -EIO; /* -ENOMEM */
|
|
goto outrel;
|
|
}
|
|
while (already_written < count) {
|
|
int written_this_time;
|
|
size_t to_write = min_t(unsigned int,
|
|
bufsize - (pos % bufsize),
|
|
count - already_written);
|
|
|
|
if (copy_from_user(bouncebuffer, buf, to_write)) {
|
|
errno = -EFAULT;
|
|
break;
|
|
}
|
|
if (ncp_write_kernel(NCP_SERVER(inode),
|
|
NCP_FINFO(inode)->file_handle,
|
|
pos, to_write, bouncebuffer, &written_this_time) != 0) {
|
|
errno = -EIO;
|
|
break;
|
|
}
|
|
pos += written_this_time;
|
|
buf += written_this_time;
|
|
already_written += written_this_time;
|
|
|
|
if (written_this_time != to_write) {
|
|
break;
|
|
}
|
|
}
|
|
vfree(bouncebuffer);
|
|
|
|
*ppos = pos;
|
|
|
|
if (pos > i_size_read(inode)) {
|
|
mutex_lock(&inode->i_mutex);
|
|
if (pos > i_size_read(inode))
|
|
i_size_write(inode, pos);
|
|
mutex_unlock(&inode->i_mutex);
|
|
}
|
|
DPRINTK("ncp_file_write: exit %s/%s\n",
|
|
dentry->d_parent->d_name.name, dentry->d_name.name);
|
|
outrel:
|
|
ncp_inode_close(inode);
|
|
return already_written ? already_written : errno;
|
|
}
|
|
|
|
static int ncp_release(struct inode *inode, struct file *file) {
|
|
if (ncp_make_closed(inode)) {
|
|
DPRINTK("ncp_release: failed to close\n");
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
const struct file_operations ncp_file_operations =
|
|
{
|
|
.llseek = generic_file_llseek,
|
|
.read = ncp_file_read,
|
|
.write = ncp_file_write,
|
|
.unlocked_ioctl = ncp_ioctl,
|
|
#ifdef CONFIG_COMPAT
|
|
.compat_ioctl = ncp_compat_ioctl,
|
|
#endif
|
|
.mmap = ncp_mmap,
|
|
.release = ncp_release,
|
|
.fsync = ncp_fsync,
|
|
};
|
|
|
|
const struct inode_operations ncp_file_inode_operations =
|
|
{
|
|
.setattr = ncp_notify_change,
|
|
};
|