diff options
Diffstat (limited to 'tests/sys/fs/tarfs')
| -rw-r--r-- | tests/sys/fs/tarfs/Makefile | 10 | ||||
| -rw-r--r-- | tests/sys/fs/tarfs/mktar.c | 273 | ||||
| -rw-r--r-- | tests/sys/fs/tarfs/tarfs_test.sh | 417 | ||||
| -rw-r--r-- | tests/sys/fs/tarfs/tarsum.c | 128 |
4 files changed, 828 insertions, 0 deletions
diff --git a/tests/sys/fs/tarfs/Makefile b/tests/sys/fs/tarfs/Makefile new file mode 100644 index 000000000000..72355a08a158 --- /dev/null +++ b/tests/sys/fs/tarfs/Makefile @@ -0,0 +1,10 @@ +PACKAGE= tests + +TESTSDIR= ${TESTSBASE}/sys/fs/tarfs +BINDIR= ${TESTSDIR} + +PROGS+= mktar tarsum + +ATF_TESTS_SH+= tarfs_test + +.include <bsd.test.mk> diff --git a/tests/sys/fs/tarfs/mktar.c b/tests/sys/fs/tarfs/mktar.c new file mode 100644 index 000000000000..b4ec271c73a1 --- /dev/null +++ b/tests/sys/fs/tarfs/mktar.c @@ -0,0 +1,273 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2023 Klara, Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/stat.h> +#include <sys/wait.h> + +#include <err.h> +#include <fcntl.h> +#include <paths.h> +#include <stdarg.h> +#include <stdbool.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +#define PROGNAME "mktar" + +#define SUBDIRNAME "directory" +#define NORMALFILENAME "file" +#define SPARSEFILENAME "sparse_file" +#define HARDLINKNAME "hard_link" +#define SHORTLINKNAME "short_link" +#define LONGLINKNAME "long_link" + +static bool opt_g; +static bool opt_v; + +static void verbose(const char *fmt, ...) +{ + va_list ap; + + if (!opt_v) + return; + fprintf(stderr, "%s: ", PROGNAME); + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + fprintf(stderr, "\n"); +} + +static void +mknormalfile(const char *filename, mode_t mode) +{ + char buf[512]; + ssize_t res; + int fd; + + if ((fd = open(filename, O_RDWR|O_CREAT|O_EXCL, mode)) < 0) + err(1, "%s", filename); + for (unsigned int i = 0; i < sizeof(buf); i++) + buf[i] = 32 + i % 64; + res = write(fd, buf, sizeof(buf)); + if (res < 0) + err(1, "%s", filename); + if (res != sizeof(buf)) + errx(1, "%s: short write", filename); + close(fd); +} + +static void +mksparsefile(const char *filename, mode_t mode) +{ + char buf[511]; + ssize_t res; + int fd; + + if ((fd = open(filename, O_RDWR|O_CREAT|O_EXCL, mode)) < 0) + err(1, "%s", filename); + for (unsigned int i = 33; i <= 126; i++) { + memset(buf, i, sizeof(buf)); + if (lseek(fd, 1048576LU * (i - 32), SEEK_SET) < 0) + err(1, "%s", filename); + res = write(fd, buf, sizeof(buf)); + if (res < 0) + err(1, "%s", filename); + if (res != sizeof(buf)) + errx(1, "%s: short write", filename); + } + close(fd); +} + +static char * +mklonglinktarget(const char *dirname, const char *filename) +{ + char *piece, *target; + + if (asprintf(&piece, "%1$s/../%1$s/../%1$s/../%1$s/../", dirname) < 0) + err(1, "asprintf()"); + if (asprintf(&target, "%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%2$s", piece, filename) < 0) + err(1, "asprintf()"); + free(piece); + return target; +} + +static void +mktar(void) +{ + char *linktarget; + + /* create a subdirectory */ + verbose("mkdir %s", SUBDIRNAME); + if (mkdir(SUBDIRNAME, 0755) != 0) + err(1, "%s", SUBDIRNAME); + + /* create a normal file */ + verbose("creating %s", NORMALFILENAME); + mknormalfile(NORMALFILENAME, 0644); + + /* create a sparse file */ + verbose("creating %s", SPARSEFILENAME); + mksparsefile(SPARSEFILENAME, 0644); + chflags(SPARSEFILENAME, UF_NODUMP); + + /* create a hard link */ + verbose("link %s %s", SPARSEFILENAME, HARDLINKNAME); + if (link(SPARSEFILENAME, HARDLINKNAME) != 0) + err(1, "%s", HARDLINKNAME); + + /* create a symbolic link with a short target */ + verbose("symlink %s %s", SPARSEFILENAME, SHORTLINKNAME); + if (symlink(SPARSEFILENAME, SHORTLINKNAME) != 0) + err(1, "%s", SHORTLINKNAME); + + /* create a symbolic link with a long target */ + linktarget = mklonglinktarget(SUBDIRNAME, SPARSEFILENAME); + verbose("symlink %s %s", linktarget, LONGLINKNAME); + if (symlink(linktarget, LONGLINKNAME) != 0) + err(1, "%s", LONGLINKNAME); + free(linktarget); +} + +static void +usage(void) +{ + + fprintf(stderr, "usage: %s [-gv] tarfile\n", PROGNAME); + exit(EXIT_FAILURE); +} + +int +main(int argc, char *argv[]) +{ + const char *tarfilename; + char *dirname; + int opt, wstatus; + pid_t pid; + + while ((opt = getopt(argc, argv, "gv")) != -1) + switch (opt) { + case 'g': + opt_g = true; + break; + case 'v': + opt_v = true; + break; + default: + usage(); + } + + argc -= optind; + argv += optind; + + if (argc != 1) + usage(); + tarfilename = *argv; + + if (asprintf(&dirname, "%s%s.XXXXXXXX", _PATH_TMP, PROGNAME) < 0) + err(1, "asprintf()"); + if (mkdtemp(dirname) == NULL) + err(1, "%s", dirname); + verbose("mkdir %s", dirname); + + /* fork a child to create the files */ + if ((pid = fork()) < 0) + err(1, "fork()"); + if (pid == 0) { + verbose("cd %s", dirname); + if (chdir(dirname) != 0) + err(1, "%s", dirname); + verbose("umask 022"); + umask(022); + mktar(); + verbose("cd -"); + exit(0); + } + if (waitpid(pid, &wstatus, 0) < 0) + err(1, "waitpid()"); + if (!WIFEXITED(wstatus) || WEXITSTATUS(wstatus) != 0) + errx(1, "child failed"); + + /* fork a child to create the tarball */ + if ((pid = fork()) < 0) + err(1, "fork()"); + if (pid == 0) { + verbose("creating tarball"); + execlp(opt_g ? "gtar" : "tar", + "tar", + "-c", + "-f", tarfilename, + "-C", dirname, + "--posix", + "--zstd", +#if 0 + "--options", "zstd:frame-per-file", +#endif + "./" SUBDIRNAME "/../" NORMALFILENAME, + "./" SPARSEFILENAME, + "./" HARDLINKNAME, + "./" SHORTLINKNAME, + "./" SUBDIRNAME, + "./" LONGLINKNAME, + NULL); + err(1, "execlp()"); + } + if (waitpid(pid, &wstatus, 0) < 0) + err(1, "waitpid()"); + if (!WIFEXITED(wstatus) || WEXITSTATUS(wstatus) != 0) + errx(1, "child failed"); + + /* fork a child to delete everything */ + if ((pid = fork()) < 0) + err(1, "fork()"); + if (pid == 0) { + verbose("cd %s", dirname); + if (chdir(dirname) != 0) + err(1, "%s", dirname); + verbose("rm %s", LONGLINKNAME); + (void)unlink(LONGLINKNAME); + verbose("rm %s", SHORTLINKNAME); + (void)unlink(SHORTLINKNAME); + verbose("rm %s", HARDLINKNAME); + (void)unlink(HARDLINKNAME); + verbose("rm %s", SPARSEFILENAME); + (void)unlink(SPARSEFILENAME); + verbose("rmdir %s", SUBDIRNAME); + (void)rmdir(SUBDIRNAME); + verbose("cd -"); + exit(0); + } + if (waitpid(pid, &wstatus, 0) < 0) + err(1, "waitpid()"); + if (!WIFEXITED(wstatus) || WEXITSTATUS(wstatus) != 0) + errx(1, "child failed"); + verbose("rmdir %s", dirname); + (void)rmdir(dirname); + + exit(0); +} diff --git a/tests/sys/fs/tarfs/tarfs_test.sh b/tests/sys/fs/tarfs/tarfs_test.sh new file mode 100644 index 000000000000..d4de71271985 --- /dev/null +++ b/tests/sys/fs/tarfs/tarfs_test.sh @@ -0,0 +1,417 @@ +#!/bin/sh +#- +# SPDX-License-Identifier: BSD-2-Clause +# +# Copyright (c) 2023-2024 Klara, Inc. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# + +mnt="$(realpath ${TMPDIR:-/tmp})/mnt" + +# expected SHA256 checksum of file contained in test tarball +sum=4da2143234486307bb44eaa610375301781a577d1172f362b88bb4b1643dee62 + +tar() { + if [ -n "${TARFS_USE_GNU_TAR}" ] ; then + gtar --posix --absolute-names "$@" + else + bsdtar "$@" + fi +} + +mktar() { + "$(atf_get_srcdir)"/mktar ${TARFS_USE_GNU_TAR+-g} "$@" +} + +tarsum() { + "$(atf_get_srcdir)"/tarsum +} + +tarfs_setup() { + mkdir "${mnt}" +} + +tarfs_cleanup() { + umount -f "${mnt}" 2>/dev/null || true +} + +atf_test_case tarfs_basic cleanup +tarfs_basic_head() { + atf_set "descr" "Basic function test" + atf_set "require.user" "root" + atf_set "require.kmods" "tarfs" +} +tarfs_basic_body() { + tarfs_setup + local tarball="${PWD}/tarfs_test.tar.zst" + mktar "${tarball}" + atf_check mount -rt tarfs "${tarball}" "${mnt}" + atf_check -o match:"^${tarball} on ${mnt} \(tarfs," mount + atf_check test "${mnt}"/sparse_file -ef "${mnt}"/hard_link + atf_check test "${mnt}"/sparse_file -ef "${mnt}"/short_link + atf_check test "${mnt}"/sparse_file -ef "${mnt}"/long_link + atf_check -o inline:"${sum}\n" sha256 -q "${mnt}"/sparse_file + atf_check -o inline:"2,40755\n" stat -f%l,%p "${mnt}"/directory + atf_check -o inline:"1,100644\n" stat -f%l,%p "${mnt}"/file + atf_check -o inline:"2,100644\n" stat -f%l,%p "${mnt}"/hard_link + atf_check -o inline:"1,120755\n" stat -f%l,%p "${mnt}"/long_link + atf_check -o inline:"1,120755\n" stat -f%l,%p "${mnt}"/short_link + atf_check -o inline:"2,100644\n" stat -f%l,%p "${mnt}"/sparse_file + atf_check -o inline:"3,40755\n" stat -f%l,%p "${mnt}" +} +tarfs_basic_cleanup() { + tarfs_cleanup +} + +atf_test_case tarfs_basic_gnu cleanup +tarfs_basic_gnu_head() { + atf_set "descr" "Basic function test using GNU tar" + atf_set "require.user" "root" + atf_set "require.kmods" "tarfs" + atf_set "require.progs" "gtar" +} +tarfs_basic_gnu_body() { + TARFS_USE_GNU_TAR=true + tarfs_basic_body +} +tarfs_basic_gnu_cleanup() { + tarfs_basic_cleanup +} + +atf_test_case tarfs_notdir_device cleanup +tarfs_notdir_device_head() { + atf_set "descr" "Regression test for PR 269519 and 269561" + atf_set "require.user" "root" + atf_set "require.kmods" "tarfs" +} +tarfs_notdir_device_body() { + tarfs_setup + atf_check mknod d b 0xdead 0xbeef + tar -cf tarfs_notdir.tar d + rm d + mkdir d + echo "boom" >d/f + tar -rf tarfs_notdir.tar d/f + atf_check -s not-exit:0 -e match:"Invalid" \ + mount -rt tarfs tarfs_notdir.tar "${mnt}" +} +tarfs_notdir_device_cleanup() { + tarfs_cleanup +} + +atf_test_case tarfs_notdir_device_gnu cleanup +tarfs_notdir_device_gnu_head() { + atf_set "descr" "Regression test for PR 269519 and 269561 using GNU tar" + atf_set "require.user" "root" + atf_set "require.kmods" "tarfs" + atf_set "require.progs" "gtar" +} +tarfs_notdir_device_gnu_body() { + TARFS_USE_GNU_TAR=true + tarfs_notdir_device_body +} +tarfs_notdir_device_gnu_cleanup() { + tarfs_notdir_device_cleanup +} + +atf_test_case tarfs_notdir_dot cleanup +tarfs_notdir_dot_head() { + atf_set "descr" "Regression test for PR 269519 and 269561" + atf_set "require.user" "root" + atf_set "require.kmods" "tarfs" +} +tarfs_notdir_dot_body() { + tarfs_setup + echo "hello" >d + tar -cf tarfs_notdir.tar d + rm d + mkdir d + echo "world" >d/f + tar -rf tarfs_notdir.tar d/./f + atf_check -s not-exit:0 -e match:"Invalid" \ + mount -rt tarfs tarfs_notdir.tar "${mnt}" +} +tarfs_notdir_dot_cleanup() { + tarfs_cleanup +} + +atf_test_case tarfs_notdir_dot_gnu cleanup +tarfs_notdir_dot_gnu_head() { + atf_set "descr" "Regression test for PR 269519 and 269561 using GNU tar" + atf_set "require.user" "root" + atf_set "require.kmods" "tarfs" + atf_set "require.progs" "gtar" +} +tarfs_notdir_dot_gnu_body() { + TARFS_USE_GNU_TAR=true + tarfs_notdir_dot_body +} +tarfs_notdir_dot_gnu_cleanup() { + tarfs_notdir_dot_cleanup +} + +atf_test_case tarfs_notdir_dotdot cleanup +tarfs_notdir_dotdot_head() { + atf_set "descr" "Regression test for PR 269519 and 269561" + atf_set "require.user" "root" + atf_set "require.kmods" "tarfs" +} +tarfs_notdir_dotdot_body() { + tarfs_setup + echo "hello" >d + tar -cf tarfs_notdir.tar d + rm d + mkdir d + echo "world" >f + tar -rf tarfs_notdir.tar d/../f + atf_check -s not-exit:0 -e match:"Invalid" \ + mount -rt tarfs tarfs_notdir.tar "${mnt}" +} +tarfs_notdir_dotdot_cleanup() { + tarfs_cleanup +} + +atf_test_case tarfs_notdir_dotdot_gnu cleanup +tarfs_notdir_dotdot_gnu_head() { + atf_set "descr" "Regression test for PR 269519 and 269561 using GNU tar" + atf_set "require.user" "root" + atf_set "require.kmods" "tarfs" + atf_set "require.progs" "gtar" +} +tarfs_notdir_dotdot_gnu_body() { + TARFS_USE_GNU_TAR=true + tarfs_notdir_dotdot_body +} +tarfs_notdir_dotdot_gnu_cleanup() { + tarfs_notdir_dotdot_cleanup +} + +atf_test_case tarfs_notdir_file cleanup +tarfs_notdir_file_head() { + atf_set "descr" "Regression test for PR 269519 and 269561" + atf_set "require.user" "root" + atf_set "require.kmods" "tarfs" +} +tarfs_notdir_file_body() { + tarfs_setup + echo "hello" >d + tar -cf tarfs_notdir.tar d + rm d + mkdir d + echo "world" >d/f + tar -rf tarfs_notdir.tar d/f + atf_check -s not-exit:0 -e match:"Invalid" \ + mount -rt tarfs tarfs_notdir.tar "${mnt}" +} +tarfs_notdir_file_cleanup() { + tarfs_cleanup +} + +atf_test_case tarfs_notdir_file_gnu cleanup +tarfs_notdir_file_gnu_head() { + atf_set "descr" "Regression test for PR 269519 and 269561 using GNU tar" + atf_set "require.user" "root" + atf_set "require.kmods" "tarfs" + atf_set "require.progs" "gtar" +} +tarfs_notdir_file_gnu_body() { + TARFS_USE_GNU_TAR=true + tarfs_notdir_file_body +} +tarfs_notdir_file_gnu_cleanup() { + tarfs_notdir_file_cleanup +} + +atf_test_case tarfs_emptylink cleanup +tarfs_emptylink_head() { + atf_set "descr" "Regression test for PR 277360: empty link target" + atf_set "require.user" "root" + atf_set "require.kmods" "tarfs" +} +tarfs_emptylink_body() { + tarfs_setup + touch z + ln -f z hard + ln -fs z soft + tar -cf - z hard soft | dd bs=512 skip=1 | tr z '\0' | \ + tarsum >> tarfs_emptylink.tar + atf_check -s not-exit:0 -e match:"Invalid" \ + mount -rt tarfs tarfs_emptylink.tar "${mnt}" +} +tarfs_emptylink_cleanup() { + tarfs_cleanup +} + +atf_test_case tarfs_linktodir cleanup +tarfs_linktodir_head() { + atf_set "descr" "Regression test for PR 277360: link to directory" + atf_set "require.user" "root" + atf_set "require.kmods" "tarfs" +} +tarfs_linktodir_body() { + tarfs_setup + mkdir d + tar -cf - d | dd bs=512 count=1 > tarfs_linktodir.tar + rmdir d + touch d + ln -f d link + tar -cf - d link | dd bs=512 skip=1 >> tarfs_linktodir.tar + atf_check -s not-exit:0 -e match:"Invalid" \ + mount -rt tarfs tarfs_linktodir.tar "${mnt}" +} +tarfs_linktodir_cleanup() { + tarfs_cleanup +} + +atf_test_case tarfs_linktononexistent cleanup +tarfs_linktononexistent_head() { + atf_set "descr" "Regression test for PR 277360: link to nonexistent target" + atf_set "require.user" "root" + atf_set "require.kmods" "tarfs" +} +tarfs_linktononexistent_body() { + tarfs_setup + touch f + ln -f f link + tar -cf - f link | dd bs=512 skip=1 >> tarfs_linktononexistent.tar + atf_check -s not-exit:0 -e match:"Invalid" \ + mount -rt tarfs tarfs_linktononexistent.tar "${mnt}" +} +tarfs_linktononexistent_cleanup() { + tarfs_cleanup +} + +atf_test_case tarfs_checksum cleanup +tarfs_checksum_head() { + atf_set "descr" "Verify that the checksum covers header padding" + atf_set "require.user" "root" + atf_set "require.kmods" "tarfs" +} +tarfs_checksum_body() { + tarfs_setup + touch f + tar -cf tarfs_checksum.tar f + truncate -s 500 tarfs_checksum.tar + printf "\1\1\1\1\1\1\1\1\1\1\1\1" >> tarfs_checksum.tar + dd if=/dev/zero bs=512 count=2 >> tarfs_checksum.tar + hexdump -C tarfs_checksum.tar + atf_check -s not-exit:0 -e match:"Invalid" \ + mount -rt tarfs tarfs_checksum.tar "${mnt}" +} +tarfs_checksum_cleanup() { + tarfs_cleanup +} + +atf_test_case tarfs_long_names cleanup +tarfs_long_names_head() { + atf_set "descr" "Verify that tarfs supports long file names" + atf_set "require.user" "root" + atf_set "require.kmods" "tarfs" +} +tarfs_long_names_body() { + tarfs_setup + local a b c d e + a="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + b="bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" + c="cccccccccccccccccccccccccccccccccccccccc" + d="dddddddddddddddddddddddddddddddddddddddd" + e="eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" + mkdir -p "${a}" + touch "${a}/${b}_${c}_${d}_${e}_foo" + ln "${a}/${b}_${c}_${d}_${e}_foo" "${a}/${b}_${c}_${d}_${e}_bar" + ln -s "${b}_${c}_${d}_${e}_bar" "${a}/${b}_${c}_${d}_${e}_baz" + tar -cf tarfs_long_names.tar "${a}" + atf_check mount -rt tarfs tarfs_long_names.tar "${mnt}" +} +tarfs_long_names_cleanup() { + tarfs_cleanup +} + +atf_test_case tarfs_long_paths cleanup +tarfs_long_paths_head() { + atf_set "descr" "Verify that tarfs supports long paths" + atf_set "require.user" "root" + atf_set "require.kmods" "tarfs" +} +tarfs_long_paths_body() { + tarfs_setup + local a b c d e + a="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + b="bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" + c="cccccccccccccccccccccccccccccccccccccccc" + d="dddddddddddddddddddddddddddddddddddddddd" + e="eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" + mkdir -p "${a}/${b}/${c}/${d}/${e}" + touch "${a}/${b}/${c}/${d}/${e}/foo" + ln "${a}/${b}/${c}/${d}/${e}/foo" "${a}/${b}/${c}/${d}/${e}/bar" + ln -s "${b}/${c}/${d}/${e}/bar" "${a}/baz" + tar -cf tarfs_long_paths.tar "${a}" + atf_check mount -rt tarfs tarfs_long_paths.tar "${mnt}" +} +tarfs_long_paths_cleanup() { + tarfs_cleanup +} + +atf_test_case tarfs_git_archive cleanup +tarfs_git_archive_head() { + atf_set "descr" "Verify that tarfs supports archives created by git" + atf_set "require.user" "root" + atf_set "require.kmods" "tarfs" + atf_set "require.progs" "git" +} +tarfs_git_archive_body() { + tarfs_setup + mkdir foo + echo "Hello, world!" >foo/bar + git -C foo init --initial-branch=tarfs + git -C foo config user.name "File System" + git -C foo config user.email fs@freebsd.org + git -C foo add bar + git -C foo commit -m bar + git -C foo archive --output=../tarfs_git_archive.tar HEAD + atf_check mount -rt tarfs tarfs_git_archive.tar "${mnt}" + atf_check -o file:foo/bar cat "${mnt}"/bar +} +tarfs_git_archive_cleanup() { + tarfs_cleanup +} + +atf_init_test_cases() { + atf_add_test_case tarfs_basic + atf_add_test_case tarfs_basic_gnu + atf_add_test_case tarfs_notdir_device + atf_add_test_case tarfs_notdir_device_gnu + atf_add_test_case tarfs_notdir_dot + atf_add_test_case tarfs_notdir_dot_gnu + atf_add_test_case tarfs_notdir_dotdot + atf_add_test_case tarfs_notdir_dotdot_gnu + atf_add_test_case tarfs_notdir_file + atf_add_test_case tarfs_notdir_file_gnu + atf_add_test_case tarfs_emptylink + atf_add_test_case tarfs_linktodir + atf_add_test_case tarfs_linktononexistent + atf_add_test_case tarfs_checksum + atf_add_test_case tarfs_long_names + atf_add_test_case tarfs_long_paths + atf_add_test_case tarfs_git_archive +} diff --git a/tests/sys/fs/tarfs/tarsum.c b/tests/sys/fs/tarfs/tarsum.c new file mode 100644 index 000000000000..73ead2230a5e --- /dev/null +++ b/tests/sys/fs/tarfs/tarsum.c @@ -0,0 +1,128 @@ +/*- + * Copyright (c) 2024 Klara, Inc. + * + * SPDX-License-Identifier: BSD-2-Clause + * + * This program reads a tarball from stdin, recalculates the checksums of + * all ustar records within it, and writes the result to stdout. + */ + +#include <err.h> +#include <stdarg.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +static bool opt_v; + +static int +verbose(const char *fmt, ...) +{ + va_list ap; + int ret; + + if (!opt_v) + return (0); + va_start(ap, fmt); + ret = vfprintf(stderr, fmt, ap); + va_end(ap); + return (ret); +} + +static void +tarsum(FILE *in, const char *ifn, FILE *out, const char *ofn) +{ + union { + uint8_t bytes[512]; + struct { + uint8_t prelude[148]; + char checksum[8]; + uint8_t interlude[101]; + char magic[6]; + char version[2]; + char postlude[]; + }; + } ustar; + unsigned long sum; + off_t offset = 0; + ssize_t ret; + size_t i; + + for (;;) { + if ((ret = fread(&ustar, 1, sizeof(ustar), in)) < 0) + err(1, "%s", ifn); + else if (ret == 0) + break; + else if ((size_t)ret < sizeof(ustar)) + errx(1, "%s: Short read", ifn); + if (strcmp(ustar.magic, "ustar") == 0 && + ustar.version[0] == '0' && ustar.version[1] == '0') { + verbose("header found at offset %#lx\n", offset); + verbose("current checksum %.*s\n", + (int)sizeof(ustar.checksum), ustar.checksum); + memset(ustar.checksum, ' ', sizeof(ustar.checksum)); + for (sum = i = 0; i < sizeof(ustar); i++) + sum += ustar.bytes[i]; + verbose("calculated checksum %#lo\n", sum); + sprintf(ustar.checksum, "%#lo", sum); + } + if ((ret = fwrite(&ustar, 1, sizeof(ustar), out)) < 0) + err(1, "%s", ofn); + else if ((size_t)ret < sizeof(ustar)) + errx(1, "%s: Short write", ofn); + offset += sizeof(ustar); + } + verbose("%lu bytes processed\n", offset); +} + +static void +usage(void) +{ + fprintf(stderr, "usage: tarsum [-v] [-o output] [input]\n"); + exit(1); +} + +int +main(int argc, char *argv[]) +{ + const char *ifn, *ofn = NULL; + FILE *in, *out; + int opt; + + while ((opt = getopt(argc, argv, "o:v")) != -1) { + switch (opt) { + case 'o': + ofn = optarg; + break; + case 'v': + opt_v = true; + break; + default: + usage(); + } + } + argc -= optind; + argv += optind; + if (argc == 0 || strcmp(*argv, "-") == 0) { + ifn = "stdin"; + in = stdin; + } else if (argc == 1) { + ifn = *argv; + if ((in = fopen(ifn, "rb")) == NULL) + err(1, "%s", ifn); + } else { + usage(); + } + if (ofn == NULL || strcmp(ofn, "-") == 0) { + ofn = "stdout"; + out = stdout; + } else { + if ((out = fopen(ofn, "wb")) == NULL) + err(1, "%s", ofn); + } + tarsum(in, ifn, out, ofn); + return (0); +} |
