| /* |
| * Copyright (C) Sistina Software, Inc. 1997-2003 All rights reserved. |
| * Copyright (C) 2004-2005 Red Hat, Inc. All rights reserved. |
| * |
| * This copyrighted material is made available to anyone wishing to use, |
| * modify, copy, or redistribute it subject to the terms and conditions |
| * of the GNU General Public License v.2. |
| */ |
| |
| #include <linux/sched.h> |
| #include <linux/slab.h> |
| #include <linux/spinlock.h> |
| #include <linux/completion.h> |
| #include <linux/buffer_head.h> |
| #include <asm/semaphore.h> |
| #include <asm/uaccess.h> |
| |
| #include "gfs2.h" |
| #include "bmap.h" |
| #include "inode.h" |
| #include "jdata.h" |
| #include "meta_io.h" |
| #include "trans.h" |
| |
| int gfs2_internal_read(struct gfs2_inode *ip, |
| struct file_ra_state *ra_state, |
| char *buf, loff_t *pos, unsigned size) |
| { |
| return gfs2_jdata_read_mem(ip, buf, *pos, size); |
| } |
| |
| int gfs2_jdata_get_buffer(struct gfs2_inode *ip, uint64_t block, int new, |
| struct buffer_head **bhp) |
| { |
| struct buffer_head *bh; |
| int error = 0; |
| |
| if (new) { |
| bh = gfs2_meta_new(ip->i_gl, block); |
| gfs2_trans_add_bh(ip->i_gl, bh, 1); |
| gfs2_metatype_set(bh, GFS2_METATYPE_JD, GFS2_FORMAT_JD); |
| gfs2_buffer_clear_tail(bh, sizeof(struct gfs2_meta_header)); |
| } else { |
| error = gfs2_meta_read(ip->i_gl, block, |
| DIO_START | DIO_WAIT, &bh); |
| if (error) |
| return error; |
| if (gfs2_metatype_check(ip->i_sbd, bh, GFS2_METATYPE_JD)) { |
| brelse(bh); |
| return -EIO; |
| } |
| } |
| |
| *bhp = bh; |
| |
| return 0; |
| } |
| |
| /** |
| * gfs2_copy2mem - Trivial copy function for gfs2_jdata_read() |
| * @bh: The buffer to copy from, or NULL meaning zero the buffer |
| * @buf: The buffer to copy/zero |
| * @offset: The offset in the buffer to copy from |
| * @size: The amount of data to copy/zero |
| * |
| * Returns: errno |
| */ |
| |
| int gfs2_copy2mem(struct buffer_head *bh, char **buf, unsigned int offset, |
| unsigned int size) |
| { |
| if (bh) |
| memcpy(*buf, bh->b_data + offset, size); |
| else |
| memset(*buf, 0, size); |
| *buf += size; |
| return 0; |
| } |
| |
| /** |
| * gfs2_copy2user - Copy bytes to user space for gfs2_jdata_read() |
| * @bh: The buffer |
| * @buf: The destination of the data |
| * @offset: The offset into the buffer |
| * @size: The amount of data to copy |
| * |
| * Returns: errno |
| */ |
| |
| int gfs2_copy2user(struct buffer_head *bh, char **buf, unsigned int offset, |
| unsigned int size) |
| { |
| int error; |
| |
| if (bh) |
| error = copy_to_user(*buf, bh->b_data + offset, size); |
| else |
| error = clear_user(*buf, size); |
| |
| if (error) |
| error = -EFAULT; |
| else |
| *buf += size; |
| |
| return error; |
| } |
| |
| static int jdata_read_stuffed(struct gfs2_inode *ip, char *buf, |
| unsigned int offset, unsigned int size, |
| read_copy_fn_t copy_fn) |
| { |
| struct buffer_head *dibh; |
| int error; |
| |
| error = gfs2_meta_inode_buffer(ip, &dibh); |
| if (!error) { |
| error = copy_fn(dibh, &buf, |
| offset + sizeof(struct gfs2_dinode), size); |
| brelse(dibh); |
| } |
| |
| return (error) ? error : size; |
| } |
| |
| /** |
| * gfs2_jdata_read - Read a jdata file |
| * @ip: The GFS2 Inode |
| * @buf: The buffer to place result into |
| * @offset: File offset to begin jdata_readng from |
| * @size: Amount of data to transfer |
| * @copy_fn: Function to actually perform the copy |
| * |
| * The @copy_fn only copies a maximum of a single block at once so |
| * we are safe calling it with int arguments. It is done so that |
| * we don't needlessly put 64bit arguments on the stack and it |
| * also makes the code in the @copy_fn nicer too. |
| * |
| * Returns: The amount of data actually copied or the error |
| */ |
| |
| int gfs2_jdata_read(struct gfs2_inode *ip, char __user *buf, uint64_t offset, |
| unsigned int size, read_copy_fn_t copy_fn) |
| { |
| struct gfs2_sbd *sdp = ip->i_sbd; |
| uint64_t lblock, dblock; |
| uint32_t extlen = 0; |
| unsigned int o; |
| int copied = 0; |
| int error = 0; |
| |
| if (offset >= ip->i_di.di_size) |
| return 0; |
| |
| if ((offset + size) > ip->i_di.di_size) |
| size = ip->i_di.di_size - offset; |
| |
| if (!size) |
| return 0; |
| |
| if (gfs2_is_stuffed(ip)) |
| return jdata_read_stuffed(ip, buf, (unsigned int)offset, size, |
| copy_fn); |
| |
| if (gfs2_assert_warn(sdp, gfs2_is_jdata(ip))) |
| return -EINVAL; |
| |
| lblock = offset; |
| o = do_div(lblock, sdp->sd_jbsize) + |
| sizeof(struct gfs2_meta_header); |
| |
| while (copied < size) { |
| unsigned int amount; |
| struct buffer_head *bh; |
| int new; |
| |
| amount = size - copied; |
| if (amount > sdp->sd_sb.sb_bsize - o) |
| amount = sdp->sd_sb.sb_bsize - o; |
| |
| if (!extlen) { |
| new = 0; |
| error = gfs2_block_map(ip, lblock, &new, |
| &dblock, &extlen); |
| if (error) |
| goto fail; |
| } |
| |
| if (extlen > 1) |
| gfs2_meta_ra(ip->i_gl, dblock, extlen); |
| |
| if (dblock) { |
| error = gfs2_jdata_get_buffer(ip, dblock, new, &bh); |
| if (error) |
| goto fail; |
| dblock++; |
| extlen--; |
| } else |
| bh = NULL; |
| |
| error = copy_fn(bh, &buf, o, amount); |
| brelse(bh); |
| if (error) |
| goto fail; |
| |
| copied += amount; |
| lblock++; |
| |
| o = sizeof(struct gfs2_meta_header); |
| } |
| |
| return copied; |
| |
| fail: |
| return (copied) ? copied : error; |
| } |
| |
| /** |
| * gfs2_copy_from_mem - Trivial copy function for gfs2_jdata_write() |
| * @bh: The buffer to copy to or clear |
| * @buf: The buffer to copy from |
| * @offset: The offset in the buffer to write to |
| * @size: The amount of data to write |
| * |
| * Returns: errno |
| */ |
| |
| int gfs2_copy_from_mem(struct gfs2_inode *ip, struct buffer_head *bh, |
| const char **buf, unsigned int offset, unsigned int size) |
| { |
| gfs2_trans_add_bh(ip->i_gl, bh, 1); |
| memcpy(bh->b_data + offset, *buf, size); |
| |
| *buf += size; |
| |
| return 0; |
| } |
| |
| /** |
| * gfs2_copy_from_user - Copy bytes from user space for gfs2_jdata_write() |
| * @bh: The buffer to copy to or clear |
| * @buf: The buffer to copy from |
| * @offset: The offset in the buffer to write to |
| * @size: The amount of data to write |
| * |
| * Returns: errno |
| */ |
| |
| int gfs2_copy_from_user(struct gfs2_inode *ip, struct buffer_head *bh, |
| const char __user **buf, unsigned int offset, unsigned int size) |
| { |
| int error = 0; |
| |
| gfs2_trans_add_bh(ip->i_gl, bh, 1); |
| if (copy_from_user(bh->b_data + offset, *buf, size)) |
| error = -EFAULT; |
| else |
| *buf += size; |
| |
| return error; |
| } |
| |
| static int jdata_write_stuffed(struct gfs2_inode *ip, char *buf, |
| unsigned int offset, unsigned int size, |
| write_copy_fn_t copy_fn) |
| { |
| struct buffer_head *dibh; |
| int error; |
| |
| error = gfs2_meta_inode_buffer(ip, &dibh); |
| if (error) |
| return error; |
| |
| error = copy_fn(ip, |
| dibh, &buf, |
| offset + sizeof(struct gfs2_dinode), size); |
| if (!error) { |
| if (ip->i_di.di_size < offset + size) |
| ip->i_di.di_size = offset + size; |
| ip->i_di.di_mtime = ip->i_di.di_ctime = get_seconds(); |
| gfs2_dinode_out(&ip->i_di, dibh->b_data); |
| } |
| |
| brelse(dibh); |
| |
| return (error) ? error : size; |
| } |
| |
| /** |
| * gfs2_jdata_write - Write bytes to a file |
| * @ip: The GFS2 inode |
| * @buf: The buffer containing information to be written |
| * @offset: The file offset to start writing at |
| * @size: The amount of data to write |
| * @copy_fn: Function to do the actual copying |
| * |
| * Returns: The number of bytes correctly written or error code |
| */ |
| |
| int gfs2_jdata_write(struct gfs2_inode *ip, const char __user *buf, uint64_t offset, |
| unsigned int size, write_copy_fn_t copy_fn) |
| { |
| struct gfs2_sbd *sdp = ip->i_sbd; |
| struct buffer_head *dibh; |
| uint64_t lblock, dblock; |
| uint32_t extlen = 0; |
| unsigned int o; |
| int copied = 0; |
| int error = 0; |
| |
| if (!size) |
| return 0; |
| |
| if (gfs2_is_stuffed(ip) && |
| offset + size <= sdp->sd_sb.sb_bsize - sizeof(struct gfs2_dinode)) |
| return jdata_write_stuffed(ip, buf, (unsigned int)offset, size, |
| copy_fn); |
| |
| if (gfs2_assert_warn(sdp, gfs2_is_jdata(ip))) |
| return -EINVAL; |
| |
| if (gfs2_is_stuffed(ip)) { |
| error = gfs2_unstuff_dinode(ip, NULL, NULL); |
| if (error) |
| return error; |
| } |
| |
| lblock = offset; |
| o = do_div(lblock, sdp->sd_jbsize) + sizeof(struct gfs2_meta_header); |
| |
| while (copied < size) { |
| unsigned int amount; |
| struct buffer_head *bh; |
| int new; |
| |
| amount = size - copied; |
| if (amount > sdp->sd_sb.sb_bsize - o) |
| amount = sdp->sd_sb.sb_bsize - o; |
| |
| if (!extlen) { |
| new = 1; |
| error = gfs2_block_map(ip, lblock, &new, |
| &dblock, &extlen); |
| if (error) |
| goto fail; |
| error = -EIO; |
| if (gfs2_assert_withdraw(sdp, dblock)) |
| goto fail; |
| } |
| |
| error = gfs2_jdata_get_buffer(ip, dblock, |
| (amount == sdp->sd_jbsize) ? 1 : new, |
| &bh); |
| if (error) |
| goto fail; |
| |
| error = copy_fn(ip, bh, &buf, o, amount); |
| brelse(bh); |
| if (error) |
| goto fail; |
| |
| copied += amount; |
| lblock++; |
| dblock++; |
| extlen--; |
| |
| o = sizeof(struct gfs2_meta_header); |
| } |
| |
| out: |
| error = gfs2_meta_inode_buffer(ip, &dibh); |
| if (error) |
| return error; |
| |
| if (ip->i_di.di_size < offset + copied) |
| ip->i_di.di_size = offset + copied; |
| ip->i_di.di_mtime = ip->i_di.di_ctime = get_seconds(); |
| |
| gfs2_trans_add_bh(ip->i_gl, dibh, 1); |
| gfs2_dinode_out(&ip->i_di, dibh->b_data); |
| brelse(dibh); |
| |
| return copied; |
| |
| fail: |
| if (copied) |
| goto out; |
| return error; |
| } |
| |