summaryrefslogtreecommitdiffstats
path: root/drivers/staging/smbfs/file.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/staging/smbfs/file.c')
-rw-r--r--drivers/staging/smbfs/file.c453
1 files changed, 453 insertions, 0 deletions
diff --git a/drivers/staging/smbfs/file.c b/drivers/staging/smbfs/file.c
new file mode 100644
index 0000000..5dcd19c
--- /dev/null
+++ b/drivers/staging/smbfs/file.c
@@ -0,0 +1,453 @@
+/*
+ * file.c
+ *
+ * Copyright (C) 1995, 1996, 1997 by Paal-Kr. Engstad and Volker Lendecke
+ * Copyright (C) 1997 by Volker Lendecke
+ *
+ * Please add a note about your changes to smbfs in the ChangeLog file.
+ */
+
+#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/pagemap.h>
+#include <linux/smp_lock.h>
+#include <linux/net.h>
+#include <linux/aio.h>
+
+#include <asm/uaccess.h>
+#include <asm/system.h>
+
+#include "smbno.h"
+#include "smb_fs.h"
+#include "smb_debug.h"
+#include "proto.h"
+
+static int
+smb_fsync(struct file *file, int datasync)
+{
+ struct dentry *dentry = file->f_path.dentry;
+ struct smb_sb_info *server = server_from_dentry(dentry);
+ int result;
+
+ VERBOSE("sync file %s/%s\n", DENTRY_PATH(dentry));
+
+ /*
+ * The VFS will writepage() all dirty pages for us, but we
+ * should send a SMBflush to the server, letting it know that
+ * we want things synchronized with actual storage.
+ *
+ * Note: this function requires all pages to have been written already
+ * (should be ok with writepage_sync)
+ */
+ result = smb_proc_flush(server, SMB_I(dentry->d_inode)->fileid);
+ return result;
+}
+
+/*
+ * Read a page synchronously.
+ */
+static int
+smb_readpage_sync(struct dentry *dentry, struct page *page)
+{
+ char *buffer = kmap(page);
+ loff_t offset = (loff_t)page->index << PAGE_CACHE_SHIFT;
+ struct smb_sb_info *server = server_from_dentry(dentry);
+ unsigned int rsize = smb_get_rsize(server);
+ int count = PAGE_SIZE;
+ int result;
+
+ VERBOSE("file %s/%s, count=%d@%Ld, rsize=%d\n",
+ DENTRY_PATH(dentry), count, offset, rsize);
+
+ result = smb_open(dentry, SMB_O_RDONLY);
+ if (result < 0)
+ goto io_error;
+
+ do {
+ if (count < rsize)
+ rsize = count;
+
+ result = server->ops->read(dentry->d_inode,offset,rsize,buffer);
+ if (result < 0)
+ goto io_error;
+
+ count -= result;
+ offset += result;
+ buffer += result;
+ dentry->d_inode->i_atime =
+ current_fs_time(dentry->d_inode->i_sb);
+ if (result < rsize)
+ break;
+ } while (count);
+
+ memset(buffer, 0, count);
+ flush_dcache_page(page);
+ SetPageUptodate(page);
+ result = 0;
+
+io_error:
+ kunmap(page);
+ unlock_page(page);
+ return result;
+}
+
+/*
+ * We are called with the page locked and we unlock it when done.
+ */
+static int
+smb_readpage(struct file *file, struct page *page)
+{
+ int error;
+ struct dentry *dentry = file->f_path.dentry;
+
+ page_cache_get(page);
+ error = smb_readpage_sync(dentry, page);
+ page_cache_release(page);
+ return error;
+}
+
+/*
+ * Write a page synchronously.
+ * Offset is the data offset within the page.
+ */
+static int
+smb_writepage_sync(struct inode *inode, struct page *page,
+ unsigned long pageoffset, unsigned int count)
+{
+ loff_t offset;
+ char *buffer = kmap(page) + pageoffset;
+ struct smb_sb_info *server = server_from_inode(inode);
+ unsigned int wsize = smb_get_wsize(server);
+ int ret = 0;
+
+ offset = ((loff_t)page->index << PAGE_CACHE_SHIFT) + pageoffset;
+ VERBOSE("file ino=%ld, fileid=%d, count=%d@%Ld, wsize=%d\n",
+ inode->i_ino, SMB_I(inode)->fileid, count, offset, wsize);
+
+ do {
+ int write_ret;
+
+ if (count < wsize)
+ wsize = count;
+
+ write_ret = server->ops->write(inode, offset, wsize, buffer);
+ if (write_ret < 0) {
+ PARANOIA("failed write, wsize=%d, write_ret=%d\n",
+ wsize, write_ret);
+ ret = write_ret;
+ break;
+ }
+ /* N.B. what if result < wsize?? */
+#ifdef SMBFS_PARANOIA
+ if (write_ret < wsize)
+ PARANOIA("short write, wsize=%d, write_ret=%d\n",
+ wsize, write_ret);
+#endif
+ buffer += wsize;
+ offset += wsize;
+ count -= wsize;
+ /*
+ * Update the inode now rather than waiting for a refresh.
+ */
+ inode->i_mtime = inode->i_atime = current_fs_time(inode->i_sb);
+ SMB_I(inode)->flags |= SMB_F_LOCALWRITE;
+ if (offset > inode->i_size)
+ inode->i_size = offset;
+ } while (count);
+
+ kunmap(page);
+ return ret;
+}
+
+/*
+ * Write a page to the server. This will be used for NFS swapping only
+ * (for now), and we currently do this synchronously only.
+ *
+ * We are called with the page locked and we unlock it when done.
+ */
+static int
+smb_writepage(struct page *page, struct writeback_control *wbc)
+{
+ struct address_space *mapping = page->mapping;
+ struct inode *inode;
+ unsigned long end_index;
+ unsigned offset = PAGE_CACHE_SIZE;
+ int err;
+
+ BUG_ON(!mapping);
+ inode = mapping->host;
+ BUG_ON(!inode);
+
+ end_index = inode->i_size >> PAGE_CACHE_SHIFT;
+
+ /* easy case */
+ if (page->index < end_index)
+ goto do_it;
+ /* things got complicated... */
+ offset = inode->i_size & (PAGE_CACHE_SIZE-1);
+ /* OK, are we completely out? */
+ if (page->index >= end_index+1 || !offset)
+ return 0; /* truncated - don't care */
+do_it:
+ page_cache_get(page);
+ err = smb_writepage_sync(inode, page, 0, offset);
+ SetPageUptodate(page);
+ unlock_page(page);
+ page_cache_release(page);
+ return err;
+}
+
+static int
+smb_updatepage(struct file *file, struct page *page, unsigned long offset,
+ unsigned int count)
+{
+ struct dentry *dentry = file->f_path.dentry;
+
+ DEBUG1("(%s/%s %d@%lld)\n", DENTRY_PATH(dentry), count,
+ ((unsigned long long)page->index << PAGE_CACHE_SHIFT) + offset);
+
+ return smb_writepage_sync(dentry->d_inode, page, offset, count);
+}
+
+static ssize_t
+smb_file_aio_read(struct kiocb *iocb, const struct iovec *iov,
+ unsigned long nr_segs, loff_t pos)
+{
+ struct file * file = iocb->ki_filp;
+ struct dentry * dentry = file->f_path.dentry;
+ ssize_t status;
+
+ VERBOSE("file %s/%s, count=%lu@%lu\n", DENTRY_PATH(dentry),
+ (unsigned long) iocb->ki_left, (unsigned long) pos);
+
+ status = smb_revalidate_inode(dentry);
+ if (status) {
+ PARANOIA("%s/%s validation failed, error=%Zd\n",
+ DENTRY_PATH(dentry), status);
+ goto out;
+ }
+
+ VERBOSE("before read, size=%ld, flags=%x, atime=%ld\n",
+ (long)dentry->d_inode->i_size,
+ dentry->d_inode->i_flags, dentry->d_inode->i_atime.tv_sec);
+
+ status = generic_file_aio_read(iocb, iov, nr_segs, pos);
+out:
+ return status;
+}
+
+static int
+smb_file_mmap(struct file * file, struct vm_area_struct * vma)
+{
+ struct dentry * dentry = file->f_path.dentry;
+ int status;
+
+ VERBOSE("file %s/%s, address %lu - %lu\n",
+ DENTRY_PATH(dentry), vma->vm_start, vma->vm_end);
+
+ status = smb_revalidate_inode(dentry);
+ if (status) {
+ PARANOIA("%s/%s validation failed, error=%d\n",
+ DENTRY_PATH(dentry), status);
+ goto out;
+ }
+ status = generic_file_mmap(file, vma);
+out:
+ return status;
+}
+
+static ssize_t
+smb_file_splice_read(struct file *file, loff_t *ppos,
+ struct pipe_inode_info *pipe, size_t count,
+ unsigned int flags)
+{
+ struct dentry *dentry = file->f_path.dentry;
+ ssize_t status;
+
+ VERBOSE("file %s/%s, pos=%Ld, count=%lu\n",
+ DENTRY_PATH(dentry), *ppos, count);
+
+ status = smb_revalidate_inode(dentry);
+ if (status) {
+ PARANOIA("%s/%s validation failed, error=%Zd\n",
+ DENTRY_PATH(dentry), status);
+ goto out;
+ }
+ status = generic_file_splice_read(file, ppos, pipe, count, flags);
+out:
+ return status;
+}
+
+/*
+ * This does the "real" work of the write. The generic routine has
+ * allocated the page, locked it, done all the page alignment stuff
+ * calculations etc. Now we should just copy the data from user
+ * space and write it back to the real medium..
+ *
+ * If the writer ends up delaying the write, the writer needs to
+ * increment the page use counts until he is done with the page.
+ */
+static int smb_write_begin(struct file *file, struct address_space *mapping,
+ loff_t pos, unsigned len, unsigned flags,
+ struct page **pagep, void **fsdata)
+{
+ pgoff_t index = pos >> PAGE_CACHE_SHIFT;
+ *pagep = grab_cache_page_write_begin(mapping, index, flags);
+ if (!*pagep)
+ return -ENOMEM;
+ return 0;
+}
+
+static int smb_write_end(struct file *file, struct address_space *mapping,
+ loff_t pos, unsigned len, unsigned copied,
+ struct page *page, void *fsdata)
+{
+ int status;
+ unsigned offset = pos & (PAGE_CACHE_SIZE - 1);
+
+ lock_kernel();
+ status = smb_updatepage(file, page, offset, copied);
+ unlock_kernel();
+
+ if (!status) {
+ if (!PageUptodate(page) && copied == PAGE_CACHE_SIZE)
+ SetPageUptodate(page);
+ status = copied;
+ }
+
+ unlock_page(page);
+ page_cache_release(page);
+
+ return status;
+}
+
+const struct address_space_operations smb_file_aops = {
+ .readpage = smb_readpage,
+ .writepage = smb_writepage,
+ .write_begin = smb_write_begin,
+ .write_end = smb_write_end,
+};
+
+/*
+ * Write to a file (through the page cache).
+ */
+static ssize_t
+smb_file_aio_write(struct kiocb *iocb, const struct iovec *iov,
+ unsigned long nr_segs, loff_t pos)
+{
+ struct file * file = iocb->ki_filp;
+ struct dentry * dentry = file->f_path.dentry;
+ ssize_t result;
+
+ VERBOSE("file %s/%s, count=%lu@%lu\n",
+ DENTRY_PATH(dentry),
+ (unsigned long) iocb->ki_left, (unsigned long) pos);
+
+ result = smb_revalidate_inode(dentry);
+ if (result) {
+ PARANOIA("%s/%s validation failed, error=%Zd\n",
+ DENTRY_PATH(dentry), result);
+ goto out;
+ }
+
+ result = smb_open(dentry, SMB_O_WRONLY);
+ if (result)
+ goto out;
+
+ if (iocb->ki_left > 0) {
+ result = generic_file_aio_write(iocb, iov, nr_segs, pos);
+ VERBOSE("pos=%ld, size=%ld, mtime=%ld, atime=%ld\n",
+ (long) file->f_pos, (long) dentry->d_inode->i_size,
+ dentry->d_inode->i_mtime.tv_sec,
+ dentry->d_inode->i_atime.tv_sec);
+ }
+out:
+ return result;
+}
+
+static int
+smb_file_open(struct inode *inode, struct file * file)
+{
+ int result;
+ struct dentry *dentry = file->f_path.dentry;
+ int smb_mode = (file->f_mode & O_ACCMODE) - 1;
+
+ lock_kernel();
+ result = smb_open(dentry, smb_mode);
+ if (result)
+ goto out;
+ SMB_I(inode)->openers++;
+out:
+ unlock_kernel();
+ return result;
+}
+
+static int
+smb_file_release(struct inode *inode, struct file * file)
+{
+ lock_kernel();
+ if (!--SMB_I(inode)->openers) {
+ /* We must flush any dirty pages now as we won't be able to
+ write anything after close. mmap can trigger this.
+ "openers" should perhaps include mmap'ers ... */
+ filemap_write_and_wait(inode->i_mapping);
+ smb_close(inode);
+ }
+ unlock_kernel();
+ return 0;
+}
+
+/*
+ * Check whether the required access is compatible with
+ * an inode's permission. SMB doesn't recognize superuser
+ * privileges, so we need our own check for this.
+ */
+static int
+smb_file_permission(struct inode *inode, int mask)
+{
+ int mode = inode->i_mode;
+ int error = 0;
+
+ VERBOSE("mode=%x, mask=%x\n", mode, mask);
+
+ /* Look at user permissions */
+ mode >>= 6;
+ if (mask & ~mode & (MAY_READ | MAY_WRITE | MAY_EXEC))
+ error = -EACCES;
+ return error;
+}
+
+static loff_t smb_remote_llseek(struct file *file, loff_t offset, int origin)
+{
+ loff_t ret;
+ lock_kernel();
+ ret = generic_file_llseek_unlocked(file, offset, origin);
+ unlock_kernel();
+ return ret;
+}
+
+const struct file_operations smb_file_operations =
+{
+ .llseek = smb_remote_llseek,
+ .read = do_sync_read,
+ .aio_read = smb_file_aio_read,
+ .write = do_sync_write,
+ .aio_write = smb_file_aio_write,
+ .unlocked_ioctl = smb_ioctl,
+ .mmap = smb_file_mmap,
+ .open = smb_file_open,
+ .release = smb_file_release,
+ .fsync = smb_fsync,
+ .splice_read = smb_file_splice_read,
+};
+
+const struct inode_operations smb_file_inode_operations =
+{
+ .permission = smb_file_permission,
+ .getattr = smb_getattr,
+ .setattr = smb_notify_change,
+};
OpenPOWER on IntegriCloud