]> Pileus Git - ~andy/linux/blobdiff - fs/xfs/xfs_file.c
Merge tag 'sound-3.5' of git://git.kernel.org/pub/scm/linux/kernel/git/tiwai/sound
[~andy/linux] / fs / xfs / xfs_file.c
index 54a67dd9ac0a5fbe5a7caf4271d14c67798fd872..9f7ec15a65222e2fe318e0ab81ac9cca0a664b4a 100644 (file)
@@ -17,9 +17,7 @@
  */
 #include "xfs.h"
 #include "xfs_fs.h"
-#include "xfs_bit.h"
 #include "xfs_log.h"
-#include "xfs_inum.h"
 #include "xfs_sb.h"
 #include "xfs_ag.h"
 #include "xfs_trans.h"
@@ -396,114 +394,96 @@ xfs_file_splice_write(
 }
 
 /*
- * This routine is called to handle zeroing any space in the last
- * block of the file that is beyond the EOF.  We do this since the
- * size is being increased without writing anything to that block
- * and we don't want anyone to read the garbage on the disk.
+ * This routine is called to handle zeroing any space in the last block of the
+ * file that is beyond the EOF.  We do this since the size is being increased
+ * without writing anything to that block and we don't want to read the
+ * garbage on the disk.
  */
 STATIC int                             /* error (positive) */
 xfs_zero_last_block(
-       xfs_inode_t     *ip,
-       xfs_fsize_t     offset,
-       xfs_fsize_t     isize)
+       struct xfs_inode        *ip,
+       xfs_fsize_t             offset,
+       xfs_fsize_t             isize)
 {
-       xfs_fileoff_t   last_fsb;
-       xfs_mount_t     *mp = ip->i_mount;
-       int             nimaps;
-       int             zero_offset;
-       int             zero_len;
-       int             error = 0;
-       xfs_bmbt_irec_t imap;
-
-       ASSERT(xfs_isilocked(ip, XFS_ILOCK_EXCL));
-
-       zero_offset = XFS_B_FSB_OFFSET(mp, isize);
-       if (zero_offset == 0) {
-               /*
-                * There are no extra bytes in the last block on disk to
-                * zero, so return.
-                */
-               return 0;
-       }
+       struct xfs_mount        *mp = ip->i_mount;
+       xfs_fileoff_t           last_fsb = XFS_B_TO_FSBT(mp, isize);
+       int                     zero_offset = XFS_B_FSB_OFFSET(mp, isize);
+       int                     zero_len;
+       int                     nimaps = 1;
+       int                     error = 0;
+       struct xfs_bmbt_irec    imap;
 
-       last_fsb = XFS_B_TO_FSBT(mp, isize);
-       nimaps = 1;
+       xfs_ilock(ip, XFS_ILOCK_EXCL);
        error = xfs_bmapi_read(ip, last_fsb, 1, &imap, &nimaps, 0);
+       xfs_iunlock(ip, XFS_ILOCK_EXCL);
        if (error)
                return error;
+
        ASSERT(nimaps > 0);
+
        /*
         * If the block underlying isize is just a hole, then there
         * is nothing to zero.
         */
-       if (imap.br_startblock == HOLESTARTBLOCK) {
+       if (imap.br_startblock == HOLESTARTBLOCK)
                return 0;
-       }
-       /*
-        * Zero the part of the last block beyond the EOF, and write it
-        * out sync.  We need to drop the ilock while we do this so we
-        * don't deadlock when the buffer cache calls back to us.
-        */
-       xfs_iunlock(ip, XFS_ILOCK_EXCL);
 
        zero_len = mp->m_sb.sb_blocksize - zero_offset;
        if (isize + zero_len > offset)
                zero_len = offset - isize;
-       error = xfs_iozero(ip, isize, zero_len);
-
-       xfs_ilock(ip, XFS_ILOCK_EXCL);
-       ASSERT(error >= 0);
-       return error;
+       return xfs_iozero(ip, isize, zero_len);
 }
 
 /*
- * Zero any on disk space between the current EOF and the new,
- * larger EOF.  This handles the normal case of zeroing the remainder
- * of the last block in the file and the unusual case of zeroing blocks
- * out beyond the size of the file.  This second case only happens
- * with fixed size extents and when the system crashes before the inode
- * size was updated but after blocks were allocated.  If fill is set,
- * then any holes in the range are filled and zeroed.  If not, the holes
- * are left alone as holes.
+ * Zero any on disk space between the current EOF and the new, larger EOF.
+ *
+ * This handles the normal case of zeroing the remainder of the last block in
+ * the file and the unusual case of zeroing blocks out beyond the size of the
+ * file.  This second case only happens with fixed size extents and when the
+ * system crashes before the inode size was updated but after blocks were
+ * allocated.
+ *
+ * Expects the iolock to be held exclusive, and will take the ilock internally.
  */
-
 int                                    /* error (positive) */
 xfs_zero_eof(
-       xfs_inode_t     *ip,
-       xfs_off_t       offset,         /* starting I/O offset */
-       xfs_fsize_t     isize)          /* current inode size */
+       struct xfs_inode        *ip,
+       xfs_off_t               offset,         /* starting I/O offset */
+       xfs_fsize_t             isize)          /* current inode size */
 {
-       xfs_mount_t     *mp = ip->i_mount;
-       xfs_fileoff_t   start_zero_fsb;
-       xfs_fileoff_t   end_zero_fsb;
-       xfs_fileoff_t   zero_count_fsb;
-       xfs_fileoff_t   last_fsb;
-       xfs_fileoff_t   zero_off;
-       xfs_fsize_t     zero_len;
-       int             nimaps;
-       int             error = 0;
-       xfs_bmbt_irec_t imap;
-
-       ASSERT(xfs_isilocked(ip, XFS_ILOCK_EXCL|XFS_IOLOCK_EXCL));
+       struct xfs_mount        *mp = ip->i_mount;
+       xfs_fileoff_t           start_zero_fsb;
+       xfs_fileoff_t           end_zero_fsb;
+       xfs_fileoff_t           zero_count_fsb;
+       xfs_fileoff_t           last_fsb;
+       xfs_fileoff_t           zero_off;
+       xfs_fsize_t             zero_len;
+       int                     nimaps;
+       int                     error = 0;
+       struct xfs_bmbt_irec    imap;
+
+       ASSERT(xfs_isilocked(ip, XFS_IOLOCK_EXCL));
        ASSERT(offset > isize);
 
        /*
         * First handle zeroing the block on which isize resides.
+        *
         * We only zero a part of that block so it is handled specially.
         */
-       error = xfs_zero_last_block(ip, offset, isize);
-       if (error) {
-               ASSERT(xfs_isilocked(ip, XFS_ILOCK_EXCL|XFS_IOLOCK_EXCL));
-               return error;
+       if (XFS_B_FSB_OFFSET(mp, isize) != 0) {
+               error = xfs_zero_last_block(ip, offset, isize);
+               if (error)
+                       return error;
        }
 
        /*
-        * Calculate the range between the new size and the old
-        * where blocks needing to be zeroed may exist.  To get the
-        * block where the last byte in the file currently resides,
-        * we need to subtract one from the size and truncate back
-        * to a block boundary.  We subtract 1 in case the size is
-        * exactly on a block boundary.
+        * Calculate the range between the new size and the old where blocks
+        * needing to be zeroed may exist.
+        *
+        * To get the block where the last byte in the file currently resides,
+        * we need to subtract one from the size and truncate back to a block
+        * boundary.  We subtract 1 in case the size is exactly on a block
+        * boundary.
         */
        last_fsb = isize ? XFS_B_TO_FSBT(mp, isize - 1) : (xfs_fileoff_t)-1;
        start_zero_fsb = XFS_B_TO_FSB(mp, (xfs_ufsize_t)isize);
@@ -521,23 +501,18 @@ xfs_zero_eof(
        while (start_zero_fsb <= end_zero_fsb) {
                nimaps = 1;
                zero_count_fsb = end_zero_fsb - start_zero_fsb + 1;
+
+               xfs_ilock(ip, XFS_ILOCK_EXCL);
                error = xfs_bmapi_read(ip, start_zero_fsb, zero_count_fsb,
                                          &imap, &nimaps, 0);
-               if (error) {
-                       ASSERT(xfs_isilocked(ip, XFS_ILOCK_EXCL|XFS_IOLOCK_EXCL));
+               xfs_iunlock(ip, XFS_ILOCK_EXCL);
+               if (error)
                        return error;
-               }
+
                ASSERT(nimaps > 0);
 
                if (imap.br_state == XFS_EXT_UNWRITTEN ||
                    imap.br_startblock == HOLESTARTBLOCK) {
-                       /*
-                        * This loop handles initializing pages that were
-                        * partially initialized by the code below this
-                        * loop. It basically zeroes the part of the page
-                        * that sits on a hole and sets the page as P_HOLE
-                        * and calls remapf if it is a mapped file.
-                        */
                        start_zero_fsb = imap.br_startoff + imap.br_blockcount;
                        ASSERT(start_zero_fsb <= (end_zero_fsb + 1));
                        continue;
@@ -545,11 +520,7 @@ xfs_zero_eof(
 
                /*
                 * There are blocks we need to zero.
-                * Drop the inode lock while we're doing the I/O.
-                * We'll still have the iolock to protect us.
                 */
-               xfs_iunlock(ip, XFS_ILOCK_EXCL);
-
                zero_off = XFS_FSB_TO_B(mp, start_zero_fsb);
                zero_len = XFS_FSB_TO_B(mp, imap.br_blockcount);
 
@@ -557,22 +528,14 @@ xfs_zero_eof(
                        zero_len = offset - zero_off;
 
                error = xfs_iozero(ip, zero_off, zero_len);
-               if (error) {
-                       goto out_lock;
-               }
+               if (error)
+                       return error;
 
                start_zero_fsb = imap.br_startoff + imap.br_blockcount;
                ASSERT(start_zero_fsb <= (end_zero_fsb + 1));
-
-               xfs_ilock(ip, XFS_ILOCK_EXCL);
        }
 
        return 0;
-
-out_lock:
-       xfs_ilock(ip, XFS_ILOCK_EXCL);
-       ASSERT(error >= 0);
-       return error;
 }
 
 /*
@@ -593,35 +556,29 @@ xfs_file_aio_write_checks(
        struct xfs_inode        *ip = XFS_I(inode);
        int                     error = 0;
 
-       xfs_rw_ilock(ip, XFS_ILOCK_EXCL);
 restart:
        error = generic_write_checks(file, pos, count, S_ISBLK(inode->i_mode));
-       if (error) {
-               xfs_rw_iunlock(ip, XFS_ILOCK_EXCL);
+       if (error)
                return error;
-       }
 
        /*
         * If the offset is beyond the size of the file, we need to zero any
         * blocks that fall between the existing EOF and the start of this
         * write.  If zeroing is needed and we are currently holding the
-        * iolock shared, we need to update it to exclusive which involves
-        * dropping all locks and relocking to maintain correct locking order.
-        * If we do this, restart the function to ensure all checks and values
-        * are still valid.
+        * iolock shared, we need to update it to exclusive which implies
+        * having to redo all checks before.
         */
        if (*pos > i_size_read(inode)) {
                if (*iolock == XFS_IOLOCK_SHARED) {
-                       xfs_rw_iunlock(ip, XFS_ILOCK_EXCL | *iolock);
+                       xfs_rw_iunlock(ip, *iolock);
                        *iolock = XFS_IOLOCK_EXCL;
-                       xfs_rw_ilock(ip, XFS_ILOCK_EXCL | *iolock);
+                       xfs_rw_ilock(ip, *iolock);
                        goto restart;
                }
                error = -xfs_zero_eof(ip, *pos, i_size_read(inode));
+               if (error)
+                       return error;
        }
-       xfs_rw_iunlock(ip, XFS_ILOCK_EXCL);
-       if (error)
-               return error;
 
        /*
         * Updating the timestamps will grab the ilock again from
@@ -629,8 +586,11 @@ restart:
         * lock above.  Eventually we should look into a way to avoid
         * the pointless lock roundtrip.
         */
-       if (likely(!(file->f_mode & FMODE_NOCMTIME)))
-               file_update_time(file);
+       if (likely(!(file->f_mode & FMODE_NOCMTIME))) {
+               error = file_update_time(file);
+               if (error)
+                       return error;
+       }
 
        /*
         * If we're writing the file then make sure to clear the setuid and
@@ -638,7 +598,6 @@ restart:
         * people from modifying setuid and setgid binaries.
         */
        return file_remove_suid(file);
-
 }
 
 /*
@@ -1007,8 +966,149 @@ xfs_vm_page_mkwrite(
        return block_page_mkwrite(vma, vmf, xfs_get_blocks);
 }
 
+STATIC loff_t
+xfs_seek_data(
+       struct file             *file,
+       loff_t                  start,
+       u32                     type)
+{
+       struct inode            *inode = file->f_mapping->host;
+       struct xfs_inode        *ip = XFS_I(inode);
+       struct xfs_mount        *mp = ip->i_mount;
+       struct xfs_bmbt_irec    map[2];
+       int                     nmap = 2;
+       loff_t                  uninitialized_var(offset);
+       xfs_fsize_t             isize;
+       xfs_fileoff_t           fsbno;
+       xfs_filblks_t           end;
+       uint                    lock;
+       int                     error;
+
+       lock = xfs_ilock_map_shared(ip);
+
+       isize = i_size_read(inode);
+       if (start >= isize) {
+               error = ENXIO;
+               goto out_unlock;
+       }
+
+       fsbno = XFS_B_TO_FSBT(mp, start);
+
+       /*
+        * Try to read extents from the first block indicated
+        * by fsbno to the end block of the file.
+        */
+       end = XFS_B_TO_FSB(mp, isize);
+
+       error = xfs_bmapi_read(ip, fsbno, end - fsbno, map, &nmap,
+                              XFS_BMAPI_ENTIRE);
+       if (error)
+               goto out_unlock;
+
+       /*
+        * Treat unwritten extent as data extent since it might
+        * contains dirty data in page cache.
+        */
+       if (map[0].br_startblock != HOLESTARTBLOCK) {
+               offset = max_t(loff_t, start,
+                              XFS_FSB_TO_B(mp, map[0].br_startoff));
+       } else {
+               if (nmap == 1) {
+                       error = ENXIO;
+                       goto out_unlock;
+               }
+
+               offset = max_t(loff_t, start,
+                              XFS_FSB_TO_B(mp, map[1].br_startoff));
+       }
+
+       if (offset != file->f_pos)
+               file->f_pos = offset;
+
+out_unlock:
+       xfs_iunlock_map_shared(ip, lock);
+
+       if (error)
+               return -error;
+       return offset;
+}
+
+STATIC loff_t
+xfs_seek_hole(
+       struct file             *file,
+       loff_t                  start,
+       u32                     type)
+{
+       struct inode            *inode = file->f_mapping->host;
+       struct xfs_inode        *ip = XFS_I(inode);
+       struct xfs_mount        *mp = ip->i_mount;
+       loff_t                  uninitialized_var(offset);
+       loff_t                  holeoff;
+       xfs_fsize_t             isize;
+       xfs_fileoff_t           fsbno;
+       uint                    lock;
+       int                     error;
+
+       if (XFS_FORCED_SHUTDOWN(mp))
+               return -XFS_ERROR(EIO);
+
+       lock = xfs_ilock_map_shared(ip);
+
+       isize = i_size_read(inode);
+       if (start >= isize) {
+               error = ENXIO;
+               goto out_unlock;
+       }
+
+       fsbno = XFS_B_TO_FSBT(mp, start);
+       error = xfs_bmap_first_unused(NULL, ip, 1, &fsbno, XFS_DATA_FORK);
+       if (error)
+               goto out_unlock;
+
+       holeoff = XFS_FSB_TO_B(mp, fsbno);
+       if (holeoff <= start)
+               offset = start;
+       else {
+               /*
+                * xfs_bmap_first_unused() could return a value bigger than
+                * isize if there are no more holes past the supplied offset.
+                */
+               offset = min_t(loff_t, holeoff, isize);
+       }
+
+       if (offset != file->f_pos)
+               file->f_pos = offset;
+
+out_unlock:
+       xfs_iunlock_map_shared(ip, lock);
+
+       if (error)
+               return -error;
+       return offset;
+}
+
+STATIC loff_t
+xfs_file_llseek(
+       struct file     *file,
+       loff_t          offset,
+       int             origin)
+{
+       switch (origin) {
+       case SEEK_END:
+       case SEEK_CUR:
+       case SEEK_SET:
+               return generic_file_llseek(file, offset, origin);
+       case SEEK_DATA:
+               return xfs_seek_data(file, offset, origin);
+       case SEEK_HOLE:
+               return xfs_seek_hole(file, offset, origin);
+       default:
+               return -EINVAL;
+       }
+}
+
 const struct file_operations xfs_file_operations = {
-       .llseek         = generic_file_llseek,
+       .llseek         = xfs_file_llseek,
        .read           = do_sync_read,
        .write          = do_sync_write,
        .aio_read       = xfs_file_aio_read,