Skip to content

Commit dec91e7

Browse files
plougherakpm00
authored andcommitted
Squashfs: add SEEK_DATA/SEEK_HOLE support
Add support for SEEK_DATA and SEEK_HOLE lseek() whence values. These allow much faster searches for holes and data in sparse files, which can significantly speed up file copying, e.g. before (GNU coreutils, Debian 13): cp --sparse=always big-file / took real 11m58s, user 0m5.764s, sys 11m48s after: real 0.047s, user 0.000s, sys 0.027s Where big-file has a 256 GB hole followed by 47 KB of data. Link: https://lkml.kernel.org/r/[email protected] Signed-off-by: Phillip Lougher <[email protected]> Signed-off-by: Andrew Morton <[email protected]>
1 parent 9ee94bf commit dec91e7

File tree

4 files changed

+130
-13
lines changed

4 files changed

+130
-13
lines changed

fs/squashfs/file.c

Lines changed: 126 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -307,7 +307,8 @@ static int fill_meta_index(struct inode *inode, int index,
307307
all_done:
308308
*index_block = cur_index_block;
309309
*index_offset = cur_offset;
310-
*data_block = cur_data_block;
310+
if (data_block)
311+
*data_block = cur_data_block;
311312

312313
/*
313314
* Scale cache index (cache slot entry) to index
@@ -324,17 +325,15 @@ static int fill_meta_index(struct inode *inode, int index,
324325
* Get the on-disk location and compressed size of the datablock
325326
* specified by index. Fill_meta_index() does most of the work.
326327
*/
327-
static int read_blocklist(struct inode *inode, int index, u64 *block)
328+
static int read_blocklist_ptrs(struct inode *inode, int index, u64 *start,
329+
int *offset, u64 *block)
328330
{
329-
u64 start;
330331
long long blks;
331-
int offset;
332332
__le32 size;
333-
int res = fill_meta_index(inode, index, &start, &offset, block);
333+
int res = fill_meta_index(inode, index, start, offset, block);
334334

335-
TRACE("read_blocklist: res %d, index %d, start 0x%llx, offset"
336-
" 0x%x, block 0x%llx\n", res, index, start, offset,
337-
*block);
335+
TRACE("read_blocklist: res %d, index %d, start 0x%llx, offset 0x%x, block 0x%llx\n",
336+
res, index, *start, *offset, block ? *block : 0);
338337

339338
if (res < 0)
340339
return res;
@@ -346,22 +345,31 @@ static int read_blocklist(struct inode *inode, int index, u64 *block)
346345
* extra block indexes needed.
347346
*/
348347
if (res < index) {
349-
blks = read_indexes(inode->i_sb, index - res, &start, &offset);
348+
blks = read_indexes(inode->i_sb, index - res, start, offset);
350349
if (blks < 0)
351350
return (int) blks;
352-
*block += blks;
351+
if (block)
352+
*block += blks;
353353
}
354354

355355
/*
356356
* Read length of block specified by index.
357357
*/
358-
res = squashfs_read_metadata(inode->i_sb, &size, &start, &offset,
358+
res = squashfs_read_metadata(inode->i_sb, &size, start, offset,
359359
sizeof(size));
360360
if (res < 0)
361361
return res;
362362
return squashfs_block_size(size);
363363
}
364364

365+
static inline int read_blocklist(struct inode *inode, int index, u64 *block)
366+
{
367+
u64 start;
368+
int offset;
369+
370+
return read_blocklist_ptrs(inode, index, &start, &offset, block);
371+
}
372+
365373
static bool squashfs_fill_page(struct folio *folio,
366374
struct squashfs_cache_entry *buffer, size_t offset,
367375
size_t avail)
@@ -658,7 +666,114 @@ static void squashfs_readahead(struct readahead_control *ractl)
658666
kfree(pages);
659667
}
660668

669+
static loff_t seek_hole_data(struct file *file, loff_t offset, int whence)
670+
{
671+
struct inode *inode = file->f_mapping->host;
672+
struct super_block *sb = inode->i_sb;
673+
struct squashfs_sb_info *msblk = sb->s_fs_info;
674+
u64 start, index = offset >> msblk->block_log;
675+
u64 file_end = (i_size_read(inode) + msblk->block_size - 1) >> msblk->block_log;
676+
int s_offset, length;
677+
__le32 *blist = NULL;
678+
679+
/* reject offset if negative or beyond file end */
680+
if ((unsigned long long)offset >= i_size_read(inode))
681+
return -ENXIO;
682+
683+
/* is offset within tailend and is tailend packed into a fragment? */
684+
if (index + 1 == file_end &&
685+
squashfs_i(inode)->fragment_block != SQUASHFS_INVALID_BLK) {
686+
if (whence == SEEK_DATA)
687+
return offset;
688+
689+
/* there is an implicit hole at the end of any file */
690+
return i_size_read(inode);
691+
}
692+
693+
length = read_blocklist_ptrs(inode, index, &start, &s_offset, NULL);
694+
if (length < 0)
695+
return length;
696+
697+
/* nothing more to do if offset matches desired whence value */
698+
if ((length == 0 && whence == SEEK_HOLE) ||
699+
(length && whence == SEEK_DATA))
700+
return offset;
701+
702+
/* skip scanning forwards if we're at file end */
703+
if (++ index == file_end)
704+
goto not_found;
705+
706+
blist = kmalloc(SQUASHFS_SCAN_INDEXES << 2, GFP_KERNEL);
707+
if (blist == NULL) {
708+
ERROR("%s: Failed to allocate block_list\n", __func__);
709+
return -ENOMEM;
710+
}
711+
712+
while (index < file_end) {
713+
int i, indexes = min(file_end - index, SQUASHFS_SCAN_INDEXES);
714+
715+
offset = squashfs_read_metadata(sb, blist, &start, &s_offset, indexes << 2);
716+
if (offset < 0)
717+
goto finished;
718+
719+
for (i = 0; i < indexes; i++) {
720+
length = squashfs_block_size(blist[i]);
721+
if (length < 0) {
722+
offset = length;
723+
goto finished;
724+
}
725+
726+
/* does this block match desired whence value? */
727+
if ((length == 0 && whence == SEEK_HOLE) ||
728+
(length && whence == SEEK_DATA)) {
729+
offset = (index + i) << msblk->block_log;
730+
goto finished;
731+
}
732+
}
733+
734+
index += indexes;
735+
}
736+
737+
not_found:
738+
/* whence value determines what happens */
739+
if (whence == SEEK_DATA)
740+
offset = -ENXIO;
741+
else
742+
/* there is an implicit hole at the end of any file */
743+
offset = i_size_read(inode);
744+
745+
finished:
746+
kfree(blist);
747+
return offset;
748+
}
749+
750+
static loff_t squashfs_llseek(struct file *file, loff_t offset, int whence)
751+
{
752+
struct inode *inode = file->f_mapping->host;
753+
754+
switch (whence) {
755+
default:
756+
return generic_file_llseek(file, offset, whence);
757+
case SEEK_DATA:
758+
case SEEK_HOLE:
759+
offset = seek_hole_data(file, offset, whence);
760+
break;
761+
}
762+
763+
if (offset < 0)
764+
return offset;
765+
766+
return vfs_setpos(file, offset, inode->i_sb->s_maxbytes);
767+
}
768+
661769
const struct address_space_operations squashfs_aops = {
662770
.read_folio = squashfs_read_folio,
663771
.readahead = squashfs_readahead
664772
};
773+
774+
const struct file_operations squashfs_file_operations = {
775+
.llseek = squashfs_llseek,
776+
.read_iter = generic_file_read_iter,
777+
.mmap_prepare = generic_file_readonly_mmap_prepare,
778+
.splice_read = filemap_splice_read
779+
};

fs/squashfs/inode.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ int squashfs_read_inode(struct inode *inode, long long ino)
168168
}
169169

170170
set_nlink(inode, 1);
171-
inode->i_fop = &generic_ro_fops;
171+
inode->i_fop = &squashfs_file_operations;
172172
inode->i_mode |= S_IFREG;
173173
inode->i_blocks = ((inode->i_size - 1) >> 9) + 1;
174174
squashfs_i(inode)->fragment_block = frag_blk;
@@ -222,7 +222,7 @@ int squashfs_read_inode(struct inode *inode, long long ino)
222222
xattr_id = le32_to_cpu(sqsh_ino->xattr);
223223
set_nlink(inode, le32_to_cpu(sqsh_ino->nlink));
224224
inode->i_op = &squashfs_inode_ops;
225-
inode->i_fop = &generic_ro_fops;
225+
inode->i_fop = &squashfs_file_operations;
226226
inode->i_mode |= S_IFREG;
227227
inode->i_blocks = (inode->i_size -
228228
le64_to_cpu(sqsh_ino->sparse) + 511) >> 9;

fs/squashfs/squashfs.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ extern const struct address_space_operations squashfs_aops;
107107

108108
/* inode.c */
109109
extern const struct inode_operations squashfs_inode_ops;
110+
extern const struct file_operations squashfs_file_operations;
110111

111112
/* namei.c */
112113
extern const struct inode_operations squashfs_dir_inode_ops;

fs/squashfs/squashfs_fs.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,7 @@ static inline int squashfs_block_size(__le32 raw)
208208
#define SQUASHFS_META_INDEXES (SQUASHFS_METADATA_SIZE / sizeof(unsigned int))
209209
#define SQUASHFS_META_ENTRIES 127
210210
#define SQUASHFS_META_SLOTS 8
211+
#define SQUASHFS_SCAN_INDEXES 1024
211212

212213
struct meta_entry {
213214
u64 data_block;

0 commit comments

Comments
 (0)