Skip to content

Commit b4ece2e

Browse files
committed
ZLFS: double-indirect blocks -- files to about 1 GB
Add the double-indirect tree (zi_ib[1]): ZLFS_MAXFILESZ rises from ~2 MB to NDADDR + NINDIR + NINDIR^2 blocks. The commit builds the tree lazily -- only L2 blocks whose entries changed are rewritten, so a splice deep in a large file writes one data block, one L2, the L1 and the inode; a shrink orphans whole L2 subtrees by zeroing their L1 entries. zi_blocks is computed arithmetically from the no-holes invariant. zlfs_bmap_read loses its caller-held indirect cache (the buffer cache serves repeat reads now that ZLFS blocks cache normally) and walks all three levels through one guarded helper. The cleaner marks the whole ib[1] tree and refuses reclaim only for ib[2]. Review fixes: directories stay bounded by the zn_data commit path's capacity (new ZLFS_MAXDIRSZ = direct + single indirect) -- the raised file limit would have let a directory grow past what its commit path can write, wedging every later commit with EFBIG; and the overlay pointer array now grows geometrically (sequential writes were O(n^2) at the new size). The runtime harness in review exercised the actual kernel functions through ~50 commits (splice, shrink, regrow, 14 injected failures) cleanly under ASan. Add zlfs-bigfile.sh: 16 MB file across 7 L2 blocks, a splice at 10 MB, cmp against an FFS template, re-checked over a remount.
1 parent a5ca4a7 commit b4ece2e

5 files changed

Lines changed: 331 additions & 97 deletions

File tree

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
#!/bin/sh
2+
set -eu
3+
4+
# ZLFS double-indirect test: a 16 MB file (4096 blocks -- direct +
5+
# single indirect + 7 L2 blocks of the double-indirect tree), a splice
6+
# at 10 MB (RMW of one double-range block; only that block, its L2, L1
7+
# and the inode may be rewritten), verified with cmp against an FFS
8+
# template, again after a remount.
9+
#
10+
# WARNING: this reformats the given disk with newfs_zlfs.
11+
12+
usage()
13+
{
14+
echo "usage: $0 disk [mount-point]" >&2
15+
echo "warning: this reformats the disk with newfs_zlfs" >&2
16+
exit 1
17+
}
18+
19+
die()
20+
{
21+
echo "$0: $*" >&2
22+
exit 1
23+
}
24+
25+
section()
26+
{
27+
echo
28+
echo "== $* =="
29+
}
30+
31+
disk=${1-}
32+
mnt=${2-/mnt/zlfs}
33+
34+
[ -n "$disk" ] && [ $# -le 2 ] || usage
35+
[ "$(id -u)" -eq 0 ] || die "must run as root"
36+
37+
dev=/dev/$disk
38+
tmpl=/tmp/zlfs-big-tmpl
39+
40+
section "newfs + mount"
41+
umount "$mnt" 2>/dev/null || true
42+
newfs_zlfs "$disk"
43+
mkdir -p "$mnt"
44+
mount_zlfs "$dev" "$mnt"
45+
46+
section "16 MB random file (4096 blocks, double indirect)"
47+
dd if=/dev/random of="$tmpl" bs=64k count=256 2>/dev/null
48+
cp "$tmpl" "$mnt/big"
49+
sync
50+
cmp "$tmpl" "$mnt/big" || die "initial contents differ"
51+
echo " ok: 16 MB written and committed"
52+
53+
section "splice at 10 MB (RMW deep in the double-indirect range)"
54+
printf 'DOUBLE-IND' | dd of="$mnt/big" bs=1 seek=10485770 conv=notrunc 2>/dev/null
55+
printf 'DOUBLE-IND' | dd of="$tmpl" bs=1 seek=10485770 conv=notrunc 2>/dev/null
56+
sync
57+
cmp "$tmpl" "$mnt/big" || die "splice differs"
58+
echo " ok: splice committed, rest intact"
59+
60+
section "intact after remount?"
61+
umount "$mnt"
62+
mount_zlfs "$dev" "$mnt"
63+
cmp "$tmpl" "$mnt/big" || die "contents differ after remount"
64+
echo " ok: byte-for-byte match after remount"
65+
66+
rm -f "$tmpl"
67+
section "PASS"

sys/zlfs/zlfs_alloc.c

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -137,16 +137,16 @@ zlfs_gc_mark(struct zlfs_mount *zmp, u_int64_t lba)
137137
static int
138138
zlfs_gc_mark_blocks(struct zlfs_mount *zmp, const struct zlfs_inode *zi)
139139
{
140-
struct buf *bp;
141-
u_int64_t j, nindir = ZLFS_NINDIR(zmp);
140+
struct buf *bp, *l2bp;
141+
u_int64_t j, k, l1, nindir = ZLFS_NINDIR(zmp);
142142
int error;
143143

144144
/*
145-
* This implementation never writes double/triple indirect blocks;
146-
* if some other writer of this format did, the blocks they reach
145+
* This implementation never writes triple indirect blocks; if
146+
* some other writer of this format did, the blocks they reach
147147
* would not be marked, so refuse to reclaim anything.
148148
*/
149-
if (zi->zi_ib[1] != 0 || zi->zi_ib[2] != 0)
149+
if (zi->zi_ib[2] != 0)
150150
return EFTYPE;
151151

152152
for (j = 0; j < ZLFS_NDADDR; j++)
@@ -161,6 +161,28 @@ zlfs_gc_mark_blocks(struct zlfs_mount *zmp, const struct zlfs_inode *zi)
161161
letoh64(((u_int64_t *)bp->b_data)[j]));
162162
brelse(bp);
163163
}
164+
if (zi->zi_ib[1] != 0) {
165+
zlfs_gc_mark(zmp, zi->zi_ib[1]);
166+
error = zlfs_bread_block(zmp, zi->zi_ib[1], &bp);
167+
if (error != 0)
168+
return error;
169+
for (j = 0; j < nindir; j++) {
170+
l1 = letoh64(((u_int64_t *)bp->b_data)[j]);
171+
if (l1 == 0)
172+
continue;
173+
zlfs_gc_mark(zmp, l1);
174+
error = zlfs_bread_block(zmp, l1, &l2bp);
175+
if (error != 0) {
176+
brelse(bp);
177+
return error;
178+
}
179+
for (k = 0; k < nindir; k++)
180+
zlfs_gc_mark(zmp,
181+
letoh64(((u_int64_t *)l2bp->b_data)[k]));
182+
brelse(l2bp);
183+
}
184+
brelse(bp);
185+
}
164186
return 0;
165187
}
166188

sys/zlfs/zlfs_var.h

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,10 +96,21 @@ struct zlfs_node {
9696
((zmp)->zm_super.zs_block_size / sizeof(u_int64_t)))
9797

9898
/*
99-
* Largest file the write path supports: the direct blocks plus one
100-
* single-indirect block. Double/triple indirect are not yet used.
99+
* Largest file the write path supports: the direct blocks, one
100+
* single-indirect block, and one double-indirect tree (about 1 GB at
101+
* a 4 KB block size). Triple indirect stays reserved.
101102
*/
102103
#define ZLFS_MAXFILESZ(zmp) \
104+
((ZLFS_NDADDR + ZLFS_NINDIR(zmp) + \
105+
ZLFS_NINDIR(zmp) * ZLFS_NINDIR(zmp)) * \
106+
(u_int64_t)(zmp)->zm_super.zs_block_size)
107+
108+
/*
109+
* Directories are committed through the whole-contents (zn_data) path,
110+
* which writes direct plus single-indirect blocks only, so their growth
111+
* must stop at that bound (16k+ entries at a 4 KB block).
112+
*/
113+
#define ZLFS_MAXDIRSZ(zmp) \
103114
((ZLFS_NDADDR + ZLFS_NINDIR(zmp)) * \
104115
(u_int64_t)(zmp)->zm_super.zs_block_size)
105116

@@ -144,8 +155,7 @@ void zlfs_clean(struct zlfs_mount *);
144155

145156
/* zlfs_write.c */
146157
int zlfs_write_block(struct zlfs_mount *, u_int64_t, const void *);
147-
int zlfs_bmap_read(struct zlfs_node *, u_int64_t, struct buf **,
148-
u_int64_t *);
158+
int zlfs_bmap_read(struct zlfs_node *, u_int64_t, u_int64_t *);
149159
int zlfs_node_load(struct zlfs_node *);
150160
int zlfs_node_resize(struct zlfs_node *, size_t);
151161
int zlfs_dblk_prepare(struct zlfs_node *, u_int64_t, int);

sys/zlfs/zlfs_vnops.c

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ zlfs_dir_add(struct zlfs_node *dnp, const char *name, int namlen,
114114
}
115115
}
116116
if (slot == (u_int64_t)-1) {
117-
if (dnp->zn_datalen + ZLFS_DIRENT_SIZE > ZLFS_MAXFILESZ(zmp))
117+
if (dnp->zn_datalen + ZLFS_DIRENT_SIZE > ZLFS_MAXDIRSZ(zmp))
118118
return ENOSPC;
119119
error = zlfs_node_resize(dnp, dnp->zn_datalen + ZLFS_DIRENT_SIZE);
120120
if (error != 0)
@@ -471,7 +471,7 @@ zlfs_setattr(void *v)
471471
/*
472472
* Read regular-file data block by block: a block with a dirty overlay
473473
* buffer is served from it, everything else from disk through
474-
* zlfs_bmap_read (direct and single-indirect pointers).
474+
* zlfs_bmap_read (direct, single- and double-indirect pointers).
475475
*/
476476
int
477477
zlfs_read(void *v)
@@ -482,7 +482,7 @@ zlfs_read(void *v)
482482
struct zlfs_node *znp = VTOZ(vp);
483483
struct zlfs_mount *zmp = znp->zn_zmp;
484484
struct zlfs_inode *zi = &znp->zn_dinode;
485-
struct buf *bp, *ind = NULL;
485+
struct buf *bp;
486486
u_int64_t blkno, boff, size;
487487
u_int32_t bsize = zmp->zm_super.zs_block_size;
488488
size_t n;
@@ -515,7 +515,7 @@ zlfs_read(void *v)
515515
continue;
516516
}
517517

518-
error = zlfs_bmap_read(znp, blkno, &ind, &lba);
518+
error = zlfs_bmap_read(znp, blkno, &lba);
519519
if (error == 0 && lba == 0)
520520
error = EIO;
521521
if (error != 0)
@@ -528,8 +528,6 @@ zlfs_read(void *v)
528528
if (error != 0)
529529
break;
530530
}
531-
if (ind != NULL)
532-
brelse(ind);
533531

534532
return error;
535533
}
@@ -938,7 +936,7 @@ zlfs_rename(void *v)
938936
goto out;
939937
}
940938
if (tnp == NULL && fdvp != tdvp) {
941-
if (tdnp->zn_datalen + ZLFS_DIRENT_SIZE > ZLFS_MAXFILESZ(zmp)) {
939+
if (tdnp->zn_datalen + ZLFS_DIRENT_SIZE > ZLFS_MAXDIRSZ(zmp)) {
942940
error = ENOSPC;
943941
goto out;
944942
}

0 commit comments

Comments
 (0)