NTFS: Add bmap address space operation needed for FIBMAP ioctl.
authorAnton Altaparmakov <anton@tuxera.com>
Thu, 16 Oct 2014 11:50:52 +0000 (12:50 +0100)
committerAnton Altaparmakov <anton@tuxera.com>
Thu, 16 Oct 2014 11:50:52 +0000 (12:50 +0100)
Signed-off-by: Anton Altaparmakov <anton@tuxera.com>
fs/ntfs/aops.c

index 6f0f98176b10f66269022802fb7fe916ffaeb6ad..7521e11db728f880e6067aecac7f97f40eac511d 100644 (file)
@@ -1537,6 +1537,129 @@ err_out:
 
 #endif /* NTFS_RW */
 
+/**
+ * ntfs_bmap - map logical file block to physical device block
+ * @mapping:   address space mapping to which the block to be mapped belongs
+ * @block:     logical block to map to its physical device block
+ *
+ * For regular, non-resident files (i.e. not compressed and not encrypted), map
+ * the logical @block belonging to the file described by the address space
+ * mapping @mapping to its physical device block.
+ *
+ * The size of the block is equal to the @s_blocksize field of the super block
+ * of the mounted file system which is guaranteed to be smaller than or equal
+ * to the cluster size thus the block is guaranteed to fit entirely inside the
+ * cluster which means we do not need to care how many contiguous bytes are
+ * available after the beginning of the block.
+ *
+ * Return the physical device block if the mapping succeeded or 0 if the block
+ * is sparse or there was an error.
+ *
+ * Note: This is a problem if someone tries to run bmap() on $Boot system file
+ * as that really is in block zero but there is nothing we can do.  bmap() is
+ * just broken in that respect (just like it cannot distinguish sparse from
+ * not available or error).
+ */
+static sector_t ntfs_bmap(struct address_space *mapping, sector_t block)
+{
+       s64 ofs, size;
+       loff_t i_size;
+       LCN lcn;
+       unsigned long blocksize, flags;
+       ntfs_inode *ni = NTFS_I(mapping->host);
+       ntfs_volume *vol = ni->vol;
+       unsigned delta;
+       unsigned char blocksize_bits, cluster_size_shift;
+
+       ntfs_debug("Entering for mft_no 0x%lx, logical block 0x%llx.",
+                       ni->mft_no, (unsigned long long)block);
+       if (ni->type != AT_DATA || !NInoNonResident(ni) || NInoEncrypted(ni)) {
+               ntfs_error(vol->sb, "BMAP does not make sense for %s "
+                               "attributes, returning 0.",
+                               (ni->type != AT_DATA) ? "non-data" :
+                               (!NInoNonResident(ni) ? "resident" :
+                               "encrypted"));
+               return 0;
+       }
+       /* None of these can happen. */
+       BUG_ON(NInoCompressed(ni));
+       BUG_ON(NInoMstProtected(ni));
+       blocksize = vol->sb->s_blocksize;
+       blocksize_bits = vol->sb->s_blocksize_bits;
+       ofs = (s64)block << blocksize_bits;
+       read_lock_irqsave(&ni->size_lock, flags);
+       size = ni->initialized_size;
+       i_size = i_size_read(VFS_I(ni));
+       read_unlock_irqrestore(&ni->size_lock, flags);
+       /*
+        * If the offset is outside the initialized size or the block straddles
+        * the initialized size then pretend it is a hole unless the
+        * initialized size equals the file size.
+        */
+       if (unlikely(ofs >= size || (ofs + blocksize > size && size < i_size)))
+               goto hole;
+       cluster_size_shift = vol->cluster_size_bits;
+       down_read(&ni->runlist.lock);
+       lcn = ntfs_attr_vcn_to_lcn_nolock(ni, ofs >> cluster_size_shift, false);
+       up_read(&ni->runlist.lock);
+       if (unlikely(lcn < LCN_HOLE)) {
+               /*
+                * Step down to an integer to avoid gcc doing a long long
+                * comparision in the switch when we know @lcn is between
+                * LCN_HOLE and LCN_EIO (i.e. -1 to -5).
+                *
+                * Otherwise older gcc (at least on some architectures) will
+                * try to use __cmpdi2() which is of course not available in
+                * the kernel.
+                */
+               switch ((int)lcn) {
+               case LCN_ENOENT:
+                       /*
+                        * If the offset is out of bounds then pretend it is a
+                        * hole.
+                        */
+                       goto hole;
+               case LCN_ENOMEM:
+                       ntfs_error(vol->sb, "Not enough memory to complete "
+                                       "mapping for inode 0x%lx.  "
+                                       "Returning 0.", ni->mft_no);
+                       break;
+               default:
+                       ntfs_error(vol->sb, "Failed to complete mapping for "
+                                       "inode 0x%lx.  Run chkdsk.  "
+                                       "Returning 0.", ni->mft_no);
+                       break;
+               }
+               return 0;
+       }
+       if (lcn < 0) {
+               /* It is a hole. */
+hole:
+               ntfs_debug("Done (returning hole).");
+               return 0;
+       }
+       /*
+        * The block is really allocated and fullfils all our criteria.
+        * Convert the cluster to units of block size and return the result.
+        */
+       delta = ofs & vol->cluster_size_mask;
+       if (unlikely(sizeof(block) < sizeof(lcn))) {
+               block = lcn = ((lcn << cluster_size_shift) + delta) >>
+                               blocksize_bits;
+               /* If the block number was truncated return 0. */
+               if (unlikely(block != lcn)) {
+                       ntfs_error(vol->sb, "Physical block 0x%llx is too "
+                                       "large to be returned, returning 0.",
+                                       (long long)lcn);
+                       return 0;
+               }
+       } else
+               block = ((lcn << cluster_size_shift) + delta) >>
+                               blocksize_bits;
+       ntfs_debug("Done (returning block 0x%llx).", (unsigned long long)lcn);
+       return block;
+}
+
 /**
  * ntfs_normal_aops - address space operations for normal inodes and attributes
  *
@@ -1549,6 +1672,7 @@ const struct address_space_operations ntfs_normal_aops = {
        .writepage      = ntfs_writepage,
        .set_page_dirty = __set_page_dirty_buffers,
 #endif /* NTFS_RW */
+       .bmap           = ntfs_bmap,
        .migratepage    = buffer_migrate_page,
        .is_partially_uptodate = block_is_partially_uptodate,
        .error_remove_page = generic_error_remove_page,