tarfs: Fix support for large files

* When fast-forwarding through a zstd frame, we incorrectly used the
  min() inline function instead of the MIN() macro.  The function
  truncates the result to unsigned int, resulting in a decompression
  error when trying to seek more than 4 GB into the frame.

* POSIX states that a size extended header record overrides the size
  field in the header if present, and that one must be included if the
  size of the file exceeds 8 GB (the size field maxes out at 64 GB).

* Reduce repetition in the exthdr parser by deduplicating the syntax
  error handler.

MFC after:	1 week
Sponsored by:	Klara, Inc.
Fixes:		69d94f4c76 ("Add tarfs, a filesystem backed by tarballs.")
Reviewed by:	allanjude
Differential Revision:	https://reviews.freebsd.org/D53718
This commit is contained in:
Dag-Erling Smørgrav
2025-11-12 22:23:49 +01:00
parent 4c05ff1d5a
commit a34a57d4b4
3 changed files with 51 additions and 26 deletions
+1 -1
View File
@@ -444,7 +444,7 @@ tarfs_zread_zstd(struct tarfs_zio *zio, struct uio *uiop)
}
if (zio->opos < off) {
/* to be discarded */
zob.size = min(off - zio->opos, len);
zob.size = MIN(off - zio->opos, len);
zob.pos = 0;
} else {
zob.size = len;
+23 -25
View File
@@ -441,7 +441,7 @@ tarfs_alloc_one(struct tarfs_mount *tmp, size_t *blknump)
int endmarker = 0;
char *namep, *sep;
struct tarfs_node *parent, *tnp, *other;
size_t namelen = 0, linklen = 0, realsize = 0, sz;
size_t namelen = 0, linklen = 0, realsize = 0, extsize = 0, sz;
ssize_t res;
dev_t rdev;
gid_t gid;
@@ -588,10 +588,7 @@ tarfs_alloc_one(struct tarfs_mount *tmp, size_t *blknump)
char *eol, *key, *value, *sep;
size_t len = strtoul(line, &sep, 10);
if (len == 0 || sep == line || *sep != ' ') {
TARFS_DPF(ALLOC, "%s: exthdr syntax error\n",
__func__);
error = EINVAL;
goto bad;
goto syntax;
}
if ((uintptr_t)line + len < (uintptr_t)line ||
line + len > exthdr + sz) {
@@ -606,16 +603,18 @@ tarfs_alloc_one(struct tarfs_mount *tmp, size_t *blknump)
key = sep + 1;
sep = strchr(key, '=');
if (sep == NULL) {
TARFS_DPF(ALLOC, "%s: exthdr syntax error\n",
__func__);
error = EINVAL;
goto bad;
goto syntax;
}
*sep = '\0';
value = sep + 1;
TARFS_DPF(ALLOC, "%s: exthdr %s=%s\n", __func__,
key, value);
if (strcmp(key, "path") == 0) {
if (strcmp(key, "size") == 0) {
extsize = strtol(value, &sep, 10);
if (sep != eol) {
goto syntax;
}
} else if (strcmp(key, "path") == 0) {
name = value;
namelen = eol - value;
} else if (strcmp(key, "linkpath") == 0) {
@@ -625,47 +624,42 @@ tarfs_alloc_one(struct tarfs_mount *tmp, size_t *blknump)
sparse = true;
major = strtol(value, &sep, 10);
if (sep != eol) {
printf("exthdr syntax error\n");
error = EINVAL;
goto bad;
goto syntax;
}
} else if (strcmp(key, "GNU.sparse.minor") == 0) {
sparse = true;
minor = strtol(value, &sep, 10);
if (sep != eol) {
printf("exthdr syntax error\n");
error = EINVAL;
goto bad;
goto syntax;
}
} else if (strcmp(key, "GNU.sparse.name") == 0) {
sparse = true;
name = value;
namelen = eol - value;
if (namelen == 0) {
printf("exthdr syntax error\n");
error = EINVAL;
goto bad;
goto syntax;
}
} else if (strcmp(key, "GNU.sparse.realsize") == 0) {
sparse = true;
realsize = strtoul(value, &sep, 10);
if (sep != eol) {
printf("exthdr syntax error\n");
error = EINVAL;
goto bad;
goto syntax;
}
} else if (strcmp(key, "SCHILY.fflags") == 0) {
flags |= tarfs_strtofflags(value, &sep);
if (sep != eol) {
printf("exthdr syntax error\n");
error = EINVAL;
goto bad;
goto syntax;
}
}
}
goto again;
}
/* do we have a size from an exthdr? */
if (extsize > 0) {
sz = extsize;
}
/* sparse file consistency checks */
if (sparse) {
TARFS_DPF(ALLOC, "%s: %s: sparse %ld.%ld (%zu bytes)\n", __func__,
@@ -832,6 +826,10 @@ tarfs_alloc_one(struct tarfs_mount *tmp, size_t *blknump)
sbuf_delete(namebuf);
}
return (0);
syntax:
TARFS_DPF(ALLOC, "%s: exthdr syntax error\n", __func__);
error = EINVAL;
goto bad;
eof:
TARFS_DPF(IO, "%s: premature end of file\n", __func__);
error = EIO;
+27
View File
@@ -396,6 +396,32 @@ tarfs_git_archive_cleanup() {
tarfs_cleanup
}
atf_test_case tarfs_large cleanup
tarfs_large_head() {
atf_set "descr" "Test support for large files"
atf_set "require.user" "root"
atf_set "require.kmods" "tarfs"
atf_set "timeout" "600"
}
tarfs_large_body() {
tarfs_setup
local tarball="${PWD}/tarfs_test.tar.zst"
local exp off
for exp in 31 32 33 34 35 36 ; do
for off in 1 0 ; do
local size=$(((1<<exp)-off))
atf_check truncate -s ${size} file
atf_check bsdtar -cf "${tarball}" --no-read-sparse --zstd file
atf_check mount -rt tarfs "${tarball}" "${mnt}"
atf_check -o inline:"${size}\n" stat -f%z "${mnt}"/file
atf_check umount "${mnt}"
done
done
}
tarfs_large_cleanup() {
tarfs_cleanup
}
atf_init_test_cases() {
atf_add_test_case tarfs_basic
atf_add_test_case tarfs_basic_gnu
@@ -414,4 +440,5 @@ atf_init_test_cases() {
atf_add_test_case tarfs_long_names
atf_add_test_case tarfs_long_paths
atf_add_test_case tarfs_git_archive
atf_add_test_case tarfs_large
}