aboutsummaryrefslogtreecommitdiffstats
path: root/fs/xfs/xfs_reflink.c
diff options
context:
space:
mode:
authorDarrick J. Wong <darrick.wong@oracle.com>2016-10-03 12:11:41 -0400
committerDarrick J. Wong <darrick.wong@oracle.com>2016-10-05 19:26:26 -0400
commitcc714660bb8b14dd897cd805bbcd8b76a7606289 (patch)
tree7c55c2dba94c899cc23f07e2333c99d2b9dc9313 /fs/xfs/xfs_reflink.c
parent9fe26045e98f8787999f6aa45aec35d16565c1bd (diff)
xfs: add dedupe range vfs function
Define a VFS function which allows userspace to request that the kernel reflink a range of blocks between two files if the ranges' contents match. The function fits the new VFS ioctl that standardizes the checking for the btrfs EXTENT SAME ioctl. Signed-off-by: Darrick J. Wong <darrick.wong@oracle.com> Reviewed-by: Christoph Hellwig <hch@lst.de>
Diffstat (limited to 'fs/xfs/xfs_reflink.c')
-rw-r--r--fs/xfs/xfs_reflink.c127
1 files changed, 126 insertions, 1 deletions
diff --git a/fs/xfs/xfs_reflink.c b/fs/xfs/xfs_reflink.c
index c1e98a43a937..6b22669421b2 100644
--- a/fs/xfs/xfs_reflink.c
+++ b/fs/xfs/xfs_reflink.c
@@ -1150,6 +1150,111 @@ err:
1150} 1150}
1151 1151
1152/* 1152/*
1153 * Read a page's worth of file data into the page cache. Return the page
1154 * locked.
1155 */
1156static struct page *
1157xfs_get_page(
1158 struct inode *inode,
1159 xfs_off_t offset)
1160{
1161 struct address_space *mapping;
1162 struct page *page;
1163 pgoff_t n;
1164
1165 n = offset >> PAGE_SHIFT;
1166 mapping = inode->i_mapping;
1167 page = read_mapping_page(mapping, n, NULL);
1168 if (IS_ERR(page))
1169 return page;
1170 if (!PageUptodate(page)) {
1171 put_page(page);
1172 return ERR_PTR(-EIO);
1173 }
1174 lock_page(page);
1175 return page;
1176}
1177
1178/*
1179 * Compare extents of two files to see if they are the same.
1180 */
1181static int
1182xfs_compare_extents(
1183 struct inode *src,
1184 xfs_off_t srcoff,
1185 struct inode *dest,
1186 xfs_off_t destoff,
1187 xfs_off_t len,
1188 bool *is_same)
1189{
1190 xfs_off_t src_poff;
1191 xfs_off_t dest_poff;
1192 void *src_addr;
1193 void *dest_addr;
1194 struct page *src_page;
1195 struct page *dest_page;
1196 xfs_off_t cmp_len;
1197 bool same;
1198 int error;
1199
1200 error = -EINVAL;
1201 same = true;
1202 while (len) {
1203 src_poff = srcoff & (PAGE_SIZE - 1);
1204 dest_poff = destoff & (PAGE_SIZE - 1);
1205 cmp_len = min(PAGE_SIZE - src_poff,
1206 PAGE_SIZE - dest_poff);
1207 cmp_len = min(cmp_len, len);
1208 ASSERT(cmp_len > 0);
1209
1210 trace_xfs_reflink_compare_extents(XFS_I(src), srcoff, cmp_len,
1211 XFS_I(dest), destoff);
1212
1213 src_page = xfs_get_page(src, srcoff);
1214 if (IS_ERR(src_page)) {
1215 error = PTR_ERR(src_page);
1216 goto out_error;
1217 }
1218 dest_page = xfs_get_page(dest, destoff);
1219 if (IS_ERR(dest_page)) {
1220 error = PTR_ERR(dest_page);
1221 unlock_page(src_page);
1222 put_page(src_page);
1223 goto out_error;
1224 }
1225 src_addr = kmap_atomic(src_page);
1226 dest_addr = kmap_atomic(dest_page);
1227
1228 flush_dcache_page(src_page);
1229 flush_dcache_page(dest_page);
1230
1231 if (memcmp(src_addr + src_poff, dest_addr + dest_poff, cmp_len))
1232 same = false;
1233
1234 kunmap_atomic(dest_addr);
1235 kunmap_atomic(src_addr);
1236 unlock_page(dest_page);
1237 unlock_page(src_page);
1238 put_page(dest_page);
1239 put_page(src_page);
1240
1241 if (!same)
1242 break;
1243
1244 srcoff += cmp_len;
1245 destoff += cmp_len;
1246 len -= cmp_len;
1247 }
1248
1249 *is_same = same;
1250 return 0;
1251
1252out_error:
1253 trace_xfs_reflink_compare_extents_error(XFS_I(dest), error, _RET_IP_);
1254 return error;
1255}
1256
1257/*
1153 * Link a range of blocks from one file to another. 1258 * Link a range of blocks from one file to another.
1154 */ 1259 */
1155int 1260int
@@ -1158,12 +1263,14 @@ xfs_reflink_remap_range(
1158 xfs_off_t srcoff, 1263 xfs_off_t srcoff,
1159 struct xfs_inode *dest, 1264 struct xfs_inode *dest,
1160 xfs_off_t destoff, 1265 xfs_off_t destoff,
1161 xfs_off_t len) 1266 xfs_off_t len,
1267 unsigned int flags)
1162{ 1268{
1163 struct xfs_mount *mp = src->i_mount; 1269 struct xfs_mount *mp = src->i_mount;
1164 xfs_fileoff_t sfsbno, dfsbno; 1270 xfs_fileoff_t sfsbno, dfsbno;
1165 xfs_filblks_t fsblen; 1271 xfs_filblks_t fsblen;
1166 int error; 1272 int error;
1273 bool is_same;
1167 1274
1168 if (!xfs_sb_version_hasreflink(&mp->m_sb)) 1275 if (!xfs_sb_version_hasreflink(&mp->m_sb))
1169 return -EOPNOTSUPP; 1276 return -EOPNOTSUPP;
@@ -1175,6 +1282,9 @@ xfs_reflink_remap_range(
1175 if (XFS_IS_REALTIME_INODE(src) || XFS_IS_REALTIME_INODE(dest)) 1282 if (XFS_IS_REALTIME_INODE(src) || XFS_IS_REALTIME_INODE(dest))
1176 return -EINVAL; 1283 return -EINVAL;
1177 1284
1285 if (flags & ~XFS_REFLINK_ALL)
1286 return -EINVAL;
1287
1178 trace_xfs_reflink_remap_range(src, srcoff, len, dest, destoff); 1288 trace_xfs_reflink_remap_range(src, srcoff, len, dest, destoff);
1179 1289
1180 /* Lock both files against IO */ 1290 /* Lock both files against IO */
@@ -1186,6 +1296,21 @@ xfs_reflink_remap_range(
1186 xfs_lock_two_inodes(src, dest, XFS_MMAPLOCK_EXCL); 1296 xfs_lock_two_inodes(src, dest, XFS_MMAPLOCK_EXCL);
1187 } 1297 }
1188 1298
1299 /*
1300 * Check that the extents are the same.
1301 */
1302 if (flags & XFS_REFLINK_DEDUPE) {
1303 is_same = false;
1304 error = xfs_compare_extents(VFS_I(src), srcoff, VFS_I(dest),
1305 destoff, len, &is_same);
1306 if (error)
1307 goto out_error;
1308 if (!is_same) {
1309 error = -EBADE;
1310 goto out_error;
1311 }
1312 }
1313
1189 error = xfs_reflink_set_inode_flag(src, dest); 1314 error = xfs_reflink_set_inode_flag(src, dest);
1190 if (error) 1315 if (error)
1191 goto out_error; 1316 goto out_error;