Skip to content

Commit d88f0ac

Browse files
committed
Added lfs_file_truncate
As a copy-on-write filesystem, the truncate function is a very nice function to have, as it can take advantage of reusing the data already written out to disk.
1 parent 2ad435e commit d88f0ac

File tree

4 files changed

+195
-2
lines changed

4 files changed

+195
-2
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ size: $(OBJ)
3333
$(SIZE) -t $^
3434

3535
.SUFFIXES:
36-
test: test_format test_dirs test_files test_seek test_parallel \
36+
test: test_format test_dirs test_files test_seek test_truncate test_parallel \
3737
test_alloc test_paths test_orphan test_move test_corrupt
3838
test_%: tests/test_%.sh
3939
ifdef QUIET

lfs.c

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1664,6 +1664,57 @@ lfs_soff_t lfs_file_seek(lfs_t *lfs, lfs_file_t *file,
16641664
return file->pos;
16651665
}
16661666

1667+
int lfs_file_truncate(lfs_t *lfs, lfs_file_t *file, lfs_off_t size) {
1668+
if ((file->flags & 3) == LFS_O_RDONLY) {
1669+
return LFS_ERR_INVAL;
1670+
}
1671+
1672+
if (size < lfs_file_size(lfs, file)) {
1673+
// need to flush since directly changing metadata
1674+
int err = lfs_file_flush(lfs, file);
1675+
if (err) {
1676+
return err;
1677+
}
1678+
1679+
// lookup new head in ctz skip list
1680+
err = lfs_ctz_find(lfs, &file->cache, NULL,
1681+
file->head, file->size,
1682+
size, &file->head, &(lfs_off_t){0});
1683+
if (err) {
1684+
return err;
1685+
}
1686+
1687+
file->size = size;
1688+
file->flags |= LFS_F_DIRTY;
1689+
} else if (size > lfs_file_size(lfs, file)) {
1690+
lfs_off_t pos = file->pos;
1691+
1692+
// flush+seek if not already at end
1693+
if (file->pos != lfs_file_size(lfs, file)) {
1694+
int err = lfs_file_seek(lfs, file, 0, SEEK_END);
1695+
if (err) {
1696+
return err;
1697+
}
1698+
}
1699+
1700+
// fill with zeros
1701+
while (file->pos < size) {
1702+
lfs_ssize_t res = lfs_file_write(lfs, file, &(uint8_t){0}, 1);
1703+
if (res < 0) {
1704+
return res;
1705+
}
1706+
}
1707+
1708+
// restore pos
1709+
int err = lfs_file_seek(lfs, file, pos, LFS_SEEK_SET);
1710+
if (err < 0) {
1711+
return err;
1712+
}
1713+
}
1714+
1715+
return 0;
1716+
}
1717+
16671718
lfs_soff_t lfs_file_tell(lfs_t *lfs, lfs_file_t *file) {
16681719
return file->pos;
16691720
}
@@ -1678,7 +1729,11 @@ int lfs_file_rewind(lfs_t *lfs, lfs_file_t *file) {
16781729
}
16791730

16801731
lfs_soff_t lfs_file_size(lfs_t *lfs, lfs_file_t *file) {
1681-
return lfs_max(file->pos, file->size);
1732+
if (file->flags & LFS_F_WRITING) {
1733+
return lfs_max(file->pos, file->size);
1734+
} else {
1735+
return file->size;
1736+
}
16821737
}
16831738

16841739

lfs.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,11 @@ lfs_ssize_t lfs_file_write(lfs_t *lfs, lfs_file_t *file,
364364
lfs_soff_t lfs_file_seek(lfs_t *lfs, lfs_file_t *file,
365365
lfs_soff_t off, int whence);
366366

367+
// Truncates the size of the file to the specified size
368+
//
369+
// Returns a negative error code on failure.
370+
int lfs_file_truncate(lfs_t *lfs, lfs_file_t *file, lfs_off_t size);
371+
367372
// Return the position of the file
368373
//
369374
// Equivalent to lfs_file_seek(lfs, file, 0, LFS_SEEK_CUR)

tests/test_truncate.sh

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
#!/bin/bash
2+
set -eu
3+
4+
SMALLSIZE=32
5+
MEDIUMSIZE=2048
6+
LARGESIZE=8192
7+
8+
echo "=== Truncate tests ==="
9+
rm -rf blocks
10+
tests/test.py << TEST
11+
lfs_format(&lfs, &cfg) => 0;
12+
TEST
13+
14+
truncate_test() {
15+
STARTSIZES="$1"
16+
HOTSIZES="$2"
17+
COLDSIZES="$3"
18+
tests/test.py << TEST
19+
static const lfs_off_t startsizes[] = {$STARTSIZES};
20+
static const lfs_off_t hotsizes[] = {$HOTSIZES};
21+
22+
lfs_mount(&lfs, &cfg) => 0;
23+
24+
for (int i = 0; i < sizeof(startsizes)/sizeof(startsizes[0]); i++) {
25+
sprintf((char*)buffer, "hairyhead%d", i);
26+
lfs_file_open(&lfs, &file[0], (const char*)buffer,
27+
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0;
28+
29+
strcpy((char*)buffer, "hair");
30+
size = strlen((char*)buffer);
31+
for (int j = 0; j < startsizes[i]; j += size) {
32+
lfs_file_write(&lfs, &file[0], buffer, size) => size;
33+
}
34+
lfs_file_size(&lfs, &file[0]) => startsizes[i];
35+
36+
lfs_file_truncate(&lfs, &file[0], hotsizes[i]) => 0;
37+
lfs_file_size(&lfs, &file[0]) => hotsizes[i];
38+
39+
lfs_file_close(&lfs, &file[0]) => 0;
40+
}
41+
42+
lfs_unmount(&lfs) => 0;
43+
TEST
44+
tests/test.py << TEST
45+
static const lfs_off_t startsizes[] = {$STARTSIZES};
46+
static const lfs_off_t hotsizes[] = {$HOTSIZES};
47+
static const lfs_off_t coldsizes[] = {$COLDSIZES};
48+
49+
lfs_mount(&lfs, &cfg) => 0;
50+
51+
for (int i = 0; i < sizeof(startsizes)/sizeof(startsizes[0]); i++) {
52+
sprintf((char*)buffer, "hairyhead%d", i);
53+
lfs_file_open(&lfs, &file[0], (const char*)buffer, LFS_O_RDWR) => 0;
54+
lfs_file_size(&lfs, &file[0]) => hotsizes[i];
55+
56+
size = strlen("hair");
57+
int j = 0;
58+
for (; j < startsizes[i] && j < hotsizes[i]; j += size) {
59+
lfs_file_read(&lfs, &file[0], buffer, size) => size;
60+
memcmp(buffer, "hair", size) => 0;
61+
}
62+
63+
for (; j < hotsizes[i]; j += size) {
64+
lfs_file_read(&lfs, &file[0], buffer, size) => size;
65+
memcmp(buffer, "\0\0\0\0", size) => 0;
66+
}
67+
68+
lfs_file_truncate(&lfs, &file[0], coldsizes[i]) => 0;
69+
lfs_file_size(&lfs, &file[0]) => coldsizes[i];
70+
71+
lfs_file_close(&lfs, &file[0]) => 0;
72+
}
73+
74+
lfs_unmount(&lfs) => 0;
75+
TEST
76+
tests/test.py << TEST
77+
static const lfs_off_t startsizes[] = {$STARTSIZES};
78+
static const lfs_off_t hotsizes[] = {$HOTSIZES};
79+
static const lfs_off_t coldsizes[] = {$COLDSIZES};
80+
81+
lfs_mount(&lfs, &cfg) => 0;
82+
83+
for (int i = 0; i < sizeof(startsizes)/sizeof(startsizes[0]); i++) {
84+
sprintf((char*)buffer, "hairyhead%d", i);
85+
lfs_file_open(&lfs, &file[0], (const char*)buffer, LFS_O_RDONLY) => 0;
86+
lfs_file_size(&lfs, &file[0]) => coldsizes[i];
87+
88+
size = strlen("hair");
89+
int j = 0;
90+
for (; j < startsizes[i] && j < hotsizes[i] && j < coldsizes[i];
91+
j += size) {
92+
lfs_file_read(&lfs, &file[0], buffer, size) => size;
93+
memcmp(buffer, "hair", size) => 0;
94+
}
95+
96+
for (; j < coldsizes[i]; j += size) {
97+
lfs_file_read(&lfs, &file[0], buffer, size) => size;
98+
memcmp(buffer, "\0\0\0\0", size) => 0;
99+
}
100+
101+
lfs_file_close(&lfs, &file[0]) => 0;
102+
}
103+
104+
lfs_unmount(&lfs) => 0;
105+
TEST
106+
}
107+
108+
echo "--- Cold shrinking truncate ---"
109+
truncate_test \
110+
"2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE" \
111+
"2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE" \
112+
" 0, $SMALLSIZE, $MEDIUMSIZE, $LARGESIZE, 2*$LARGESIZE"
113+
114+
echo "--- Cold expanding truncate ---"
115+
truncate_test \
116+
" 0, $SMALLSIZE, $MEDIUMSIZE, $LARGESIZE, 2*$LARGESIZE" \
117+
" 0, $SMALLSIZE, $MEDIUMSIZE, $LARGESIZE, 2*$LARGESIZE" \
118+
"2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE"
119+
120+
echo "--- Warm shrinking truncate ---"
121+
truncate_test \
122+
"2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE" \
123+
" 0, $SMALLSIZE, $MEDIUMSIZE, $LARGESIZE, 2*$LARGESIZE" \
124+
" 0, 0, 0, 0, 0"
125+
126+
echo "--- Warm expanding truncate ---"
127+
truncate_test \
128+
" 0, $SMALLSIZE, $MEDIUMSIZE, $LARGESIZE, 2*$LARGESIZE" \
129+
"2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE" \
130+
"2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE"
131+
132+
echo "--- Results ---"
133+
tests/stats.py

0 commit comments

Comments
 (0)