Document FreeBSD syscall mapping
This commit is contained in:
565
tests/system/freebsd-syscall-mapping.c
Normal file
565
tests/system/freebsd-syscall-mapping.c
Normal file
@@ -0,0 +1,565 @@
|
||||
#define _POSIX_C_SOURCE 200809L
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <spawn.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/param.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/statvfs.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/uio.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
#include <sched.h>
|
||||
|
||||
#ifdef __FreeBSD__
|
||||
#include <sys/jail.h>
|
||||
#include <sys/syscall.h>
|
||||
#endif
|
||||
|
||||
extern char **environ;
|
||||
|
||||
static int failures = 0;
|
||||
|
||||
static void report_value(const char *key, const char *value)
|
||||
{
|
||||
printf("%s=%s\n", key, value);
|
||||
}
|
||||
|
||||
static void report_ok(const char *key)
|
||||
{
|
||||
printf("%s=ok\n", key);
|
||||
}
|
||||
|
||||
static void report_fail(const char *key, const char *detail)
|
||||
{
|
||||
failures++;
|
||||
printf("%s=fail:%s\n", key, detail);
|
||||
}
|
||||
|
||||
static void report_errno_detail(const char *key, const char *context)
|
||||
{
|
||||
char buffer[256];
|
||||
snprintf(buffer, sizeof(buffer), "%s:%s", context, strerror(errno));
|
||||
report_fail(key, buffer);
|
||||
}
|
||||
|
||||
static void report_feature_macros(void)
|
||||
{
|
||||
#ifdef SYS_clone
|
||||
report_value("feature.SYS_clone", "yes");
|
||||
#else
|
||||
report_value("feature.SYS_clone", "no");
|
||||
#endif
|
||||
|
||||
#ifdef SYS_unshare
|
||||
report_value("feature.SYS_unshare", "yes");
|
||||
#else
|
||||
report_value("feature.SYS_unshare", "no");
|
||||
#endif
|
||||
|
||||
#ifdef SYS_setns
|
||||
report_value("feature.SYS_setns", "yes");
|
||||
#else
|
||||
report_value("feature.SYS_setns", "no");
|
||||
#endif
|
||||
|
||||
#ifdef SYS_pivot_root
|
||||
report_value("feature.SYS_pivot_root", "yes");
|
||||
#else
|
||||
report_value("feature.SYS_pivot_root", "no");
|
||||
#endif
|
||||
|
||||
#ifdef __FreeBSD__
|
||||
report_value("feature.FreeBSD_jail", "yes");
|
||||
#else
|
||||
report_value("feature.FreeBSD_jail", "no");
|
||||
#endif
|
||||
|
||||
#if defined(__has_include)
|
||||
# if __has_include(<sys/capsicum.h>)
|
||||
report_value("feature.Capsicum_headers", "yes");
|
||||
# else
|
||||
report_value("feature.Capsicum_headers", "no");
|
||||
# endif
|
||||
# if __has_include(<sys/prctl.h>)
|
||||
report_value("feature.sys_prctl_h", "yes");
|
||||
# else
|
||||
report_value("feature.sys_prctl_h", "no");
|
||||
# endif
|
||||
# if __has_include(<linux/close_range.h>)
|
||||
report_value("feature.linux_close_range_h", "yes");
|
||||
# else
|
||||
report_value("feature.linux_close_range_h", "no");
|
||||
# endif
|
||||
#else
|
||||
report_value("feature.Capsicum_headers", "unknown");
|
||||
report_value("feature.sys_prctl_h", "unknown");
|
||||
report_value("feature.linux_close_range_h", "unknown");
|
||||
#endif
|
||||
}
|
||||
|
||||
static void test_fork_waitpid(void)
|
||||
{
|
||||
int fds[2];
|
||||
pid_t pid;
|
||||
char buf[16] = {0};
|
||||
int status = 0;
|
||||
|
||||
if (pipe(fds) != 0) {
|
||||
report_errno_detail("runtime.fork_waitpid", "pipe");
|
||||
return;
|
||||
}
|
||||
|
||||
pid = fork();
|
||||
if (pid < 0) {
|
||||
close(fds[0]);
|
||||
close(fds[1]);
|
||||
report_errno_detail("runtime.fork_waitpid", "fork");
|
||||
return;
|
||||
}
|
||||
|
||||
if (pid == 0) {
|
||||
close(fds[0]);
|
||||
(void)write(fds[1], "child-ok", 8);
|
||||
close(fds[1]);
|
||||
_exit(0);
|
||||
}
|
||||
|
||||
close(fds[1]);
|
||||
if (read(fds[0], buf, sizeof(buf)) < 0) {
|
||||
close(fds[0]);
|
||||
report_errno_detail("runtime.fork_waitpid", "read");
|
||||
return;
|
||||
}
|
||||
close(fds[0]);
|
||||
|
||||
if (waitpid(pid, &status, 0) < 0) {
|
||||
report_errno_detail("runtime.fork_waitpid", "waitpid");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0 || strcmp(buf, "child-ok") != 0) {
|
||||
report_fail("runtime.fork_waitpid", "unexpected-child-result");
|
||||
return;
|
||||
}
|
||||
|
||||
report_ok("runtime.fork_waitpid");
|
||||
}
|
||||
|
||||
static void test_posix_spawn_closefrom(void)
|
||||
{
|
||||
posix_spawn_file_actions_t actions;
|
||||
pid_t pid = -1;
|
||||
int status = 0;
|
||||
|
||||
if (posix_spawn_file_actions_init(&actions) != 0) {
|
||||
report_fail("runtime.posix_spawn_addclosefrom_np", "file-actions-init");
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef __FreeBSD__
|
||||
if (posix_spawn_file_actions_addclosefrom_np(&actions, 3) != 0) {
|
||||
posix_spawn_file_actions_destroy(&actions);
|
||||
report_fail("runtime.posix_spawn_addclosefrom_np", "addclosefrom_np");
|
||||
return;
|
||||
}
|
||||
#else
|
||||
posix_spawn_file_actions_destroy(&actions);
|
||||
report_value("runtime.posix_spawn_addclosefrom_np", "skipped");
|
||||
return;
|
||||
#endif
|
||||
|
||||
if (posix_spawn(&pid, "/usr/bin/true", &actions, NULL,
|
||||
(char *const[]){(char *)"/usr/bin/true", NULL}, environ) != 0) {
|
||||
posix_spawn_file_actions_destroy(&actions);
|
||||
report_errno_detail("runtime.posix_spawn_addclosefrom_np", "posix_spawn");
|
||||
return;
|
||||
}
|
||||
|
||||
posix_spawn_file_actions_destroy(&actions);
|
||||
|
||||
if (waitpid(pid, &status, 0) < 0) {
|
||||
report_errno_detail("runtime.posix_spawn_addclosefrom_np", "waitpid");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
|
||||
report_fail("runtime.posix_spawn_addclosefrom_np", "child-nonzero");
|
||||
return;
|
||||
}
|
||||
|
||||
report_ok("runtime.posix_spawn_addclosefrom_np");
|
||||
}
|
||||
|
||||
static void test_close_range(void)
|
||||
{
|
||||
int fds[2];
|
||||
int fd3;
|
||||
|
||||
if (pipe(fds) != 0) {
|
||||
report_errno_detail("runtime.close_range", "pipe");
|
||||
return;
|
||||
}
|
||||
|
||||
fd3 = dup(fds[0]);
|
||||
if (fd3 < 0) {
|
||||
close(fds[0]);
|
||||
close(fds[1]);
|
||||
report_errno_detail("runtime.close_range", "dup");
|
||||
return;
|
||||
}
|
||||
|
||||
if (close_range((unsigned int)fds[0], (unsigned int)fd3, 0) != 0) {
|
||||
report_errno_detail("runtime.close_range", "close_range");
|
||||
return;
|
||||
}
|
||||
|
||||
if (fcntl(fds[0], F_GETFD) != -1 || errno != EBADF) {
|
||||
report_fail("runtime.close_range", "fd-not-closed");
|
||||
return;
|
||||
}
|
||||
|
||||
report_ok("runtime.close_range");
|
||||
}
|
||||
|
||||
static void test_posix_fallocate(void)
|
||||
{
|
||||
char path[] = "/tmp/fruix-posix-fallocate.XXXXXX";
|
||||
int fd;
|
||||
struct stat st;
|
||||
|
||||
fd = mkstemp(path);
|
||||
if (fd < 0) {
|
||||
report_errno_detail("runtime.posix_fallocate", "mkstemp");
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
int rc = posix_fallocate(fd, 0, 4096);
|
||||
if (rc != 0) {
|
||||
close(fd);
|
||||
unlink(path);
|
||||
if (rc == EOPNOTSUPP || rc == ENOTSUP) {
|
||||
report_value("runtime.posix_fallocate", "unsupported-on-tested-filesystem");
|
||||
return;
|
||||
}
|
||||
report_fail("runtime.posix_fallocate", strerror(rc));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (fstat(fd, &st) != 0) {
|
||||
close(fd);
|
||||
unlink(path);
|
||||
report_errno_detail("runtime.posix_fallocate", "fstat");
|
||||
return;
|
||||
}
|
||||
|
||||
close(fd);
|
||||
unlink(path);
|
||||
|
||||
if (st.st_size < 4096) {
|
||||
report_fail("runtime.posix_fallocate", "short-size");
|
||||
return;
|
||||
}
|
||||
|
||||
report_ok("runtime.posix_fallocate");
|
||||
}
|
||||
|
||||
static void test_lutimes(void)
|
||||
{
|
||||
char dir[] = "/tmp/fruix-lutimes.XXXXXX";
|
||||
char target[PATH_MAX];
|
||||
char linkpath[PATH_MAX];
|
||||
struct timeval times[2];
|
||||
|
||||
if (mkdtemp(dir) == NULL) {
|
||||
report_errno_detail("runtime.lutimes", "mkdtemp");
|
||||
return;
|
||||
}
|
||||
|
||||
snprintf(target, sizeof(target), "%s/target", dir);
|
||||
snprintf(linkpath, sizeof(linkpath), "%s/link", dir);
|
||||
|
||||
{
|
||||
int fd = open(target, O_CREAT | O_WRONLY, 0600);
|
||||
if (fd < 0) {
|
||||
report_errno_detail("runtime.lutimes", "open-target");
|
||||
return;
|
||||
}
|
||||
close(fd);
|
||||
}
|
||||
|
||||
if (symlink(target, linkpath) != 0) {
|
||||
report_errno_detail("runtime.lutimes", "symlink");
|
||||
unlink(target);
|
||||
rmdir(dir);
|
||||
return;
|
||||
}
|
||||
|
||||
times[0].tv_sec = 1;
|
||||
times[0].tv_usec = 0;
|
||||
times[1].tv_sec = 2;
|
||||
times[1].tv_usec = 0;
|
||||
|
||||
if (lutimes(linkpath, times) != 0) {
|
||||
report_errno_detail("runtime.lutimes", "lutimes");
|
||||
unlink(linkpath);
|
||||
unlink(target);
|
||||
rmdir(dir);
|
||||
return;
|
||||
}
|
||||
|
||||
unlink(linkpath);
|
||||
unlink(target);
|
||||
rmdir(dir);
|
||||
report_ok("runtime.lutimes");
|
||||
}
|
||||
|
||||
static void test_statvfs(void)
|
||||
{
|
||||
struct statvfs st;
|
||||
|
||||
if (statvfs("/tmp", &st) != 0) {
|
||||
report_errno_detail("runtime.statvfs", "statvfs");
|
||||
return;
|
||||
}
|
||||
|
||||
if (st.f_bsize == 0) {
|
||||
report_fail("runtime.statvfs", "zero-block-size");
|
||||
return;
|
||||
}
|
||||
|
||||
report_ok("runtime.statvfs");
|
||||
}
|
||||
|
||||
static void test_chroot_smoke(void)
|
||||
{
|
||||
char top[] = "/tmp/fruix-chroot.XXXXXX";
|
||||
char rootdir[PATH_MAX];
|
||||
char etcdir[PATH_MAX];
|
||||
char passwdpath[PATH_MAX];
|
||||
char outside[PATH_MAX];
|
||||
pid_t pid;
|
||||
int status = 0;
|
||||
|
||||
if (mkdtemp(top) == NULL) {
|
||||
report_errno_detail("root.chroot", "mkdtemp");
|
||||
return;
|
||||
}
|
||||
|
||||
snprintf(rootdir, sizeof(rootdir), "%s/root", top);
|
||||
snprintf(etcdir, sizeof(etcdir), "%s/etc", rootdir);
|
||||
snprintf(passwdpath, sizeof(passwdpath), "%s/passwd", etcdir);
|
||||
snprintf(outside, sizeof(outside), "%s/outside-sentinel", top);
|
||||
|
||||
if (mkdir(rootdir, 0755) != 0 || mkdir(etcdir, 0755) != 0) {
|
||||
report_errno_detail("root.chroot", "mkdir");
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
int fd = open(passwdpath, O_CREAT | O_WRONLY, 0644);
|
||||
if (fd < 0) {
|
||||
report_errno_detail("root.chroot", "open-passwd");
|
||||
return;
|
||||
}
|
||||
(void)write(fd, "root:x:0:0::/:/bin/sh\n", 22);
|
||||
close(fd);
|
||||
}
|
||||
|
||||
{
|
||||
int fd = open(outside, O_CREAT | O_WRONLY, 0644);
|
||||
if (fd < 0) {
|
||||
report_errno_detail("root.chroot", "open-outside");
|
||||
return;
|
||||
}
|
||||
close(fd);
|
||||
}
|
||||
|
||||
pid = fork();
|
||||
if (pid < 0) {
|
||||
report_errno_detail("root.chroot", "fork");
|
||||
return;
|
||||
}
|
||||
|
||||
if (pid == 0) {
|
||||
if (chdir(rootdir) != 0)
|
||||
_exit(10);
|
||||
if (chroot(".") != 0)
|
||||
_exit(11);
|
||||
if (access("/etc/passwd", F_OK) != 0)
|
||||
_exit(12);
|
||||
if (access("/outside-sentinel", F_OK) == 0)
|
||||
_exit(13);
|
||||
_exit(0);
|
||||
}
|
||||
|
||||
if (waitpid(pid, &status, 0) < 0) {
|
||||
report_errno_detail("root.chroot", "waitpid");
|
||||
return;
|
||||
}
|
||||
|
||||
unlink(outside);
|
||||
unlink(passwdpath);
|
||||
rmdir(etcdir);
|
||||
rmdir(rootdir);
|
||||
rmdir(top);
|
||||
|
||||
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
|
||||
report_fail("root.chroot", "child-failed");
|
||||
return;
|
||||
}
|
||||
|
||||
report_ok("root.chroot");
|
||||
}
|
||||
|
||||
#ifdef __FreeBSD__
|
||||
static void test_jail_smoke(void)
|
||||
{
|
||||
char top[] = "/tmp/fruix-jail.XXXXXX";
|
||||
struct jail j;
|
||||
pid_t pid;
|
||||
int status = 0;
|
||||
|
||||
if (mkdtemp(top) == NULL) {
|
||||
report_errno_detail("root.jail", "mkdtemp");
|
||||
return;
|
||||
}
|
||||
|
||||
pid = fork();
|
||||
if (pid < 0) {
|
||||
report_errno_detail("root.jail", "fork");
|
||||
rmdir(top);
|
||||
return;
|
||||
}
|
||||
|
||||
if (pid == 0) {
|
||||
char hostname[] = "fruix-jail";
|
||||
char jailname[] = "fruix-jail";
|
||||
char observed[256] = {0};
|
||||
|
||||
memset(&j, 0, sizeof(j));
|
||||
j.version = JAIL_API_VERSION;
|
||||
j.path = top;
|
||||
j.hostname = hostname;
|
||||
j.jailname = jailname;
|
||||
j.ip4s = 0;
|
||||
j.ip6s = 0;
|
||||
j.ip4 = NULL;
|
||||
j.ip6 = NULL;
|
||||
|
||||
if (jail(&j) < 0)
|
||||
_exit(20);
|
||||
if (gethostname(observed, sizeof(observed)) != 0)
|
||||
_exit(21);
|
||||
if (strcmp(observed, hostname) != 0)
|
||||
_exit(22);
|
||||
_exit(0);
|
||||
}
|
||||
|
||||
if (waitpid(pid, &status, 0) < 0) {
|
||||
report_errno_detail("root.jail", "waitpid");
|
||||
rmdir(top);
|
||||
return;
|
||||
}
|
||||
|
||||
rmdir(top);
|
||||
|
||||
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
|
||||
report_fail("root.jail", "child-failed");
|
||||
return;
|
||||
}
|
||||
|
||||
report_ok("root.jail");
|
||||
}
|
||||
#else
|
||||
static void test_jail_smoke(void)
|
||||
{
|
||||
report_value("root.jail", "skipped");
|
||||
}
|
||||
#endif
|
||||
|
||||
static void test_lchown_root(void)
|
||||
{
|
||||
char dir[] = "/tmp/fruix-lchown.XXXXXX";
|
||||
char target[PATH_MAX];
|
||||
char linkpath[PATH_MAX];
|
||||
|
||||
if (mkdtemp(dir) == NULL) {
|
||||
report_errno_detail("root.lchown", "mkdtemp");
|
||||
return;
|
||||
}
|
||||
|
||||
snprintf(target, sizeof(target), "%s/target", dir);
|
||||
snprintf(linkpath, sizeof(linkpath), "%s/link", dir);
|
||||
|
||||
{
|
||||
int fd = open(target, O_CREAT | O_WRONLY, 0600);
|
||||
if (fd < 0) {
|
||||
report_errno_detail("root.lchown", "open-target");
|
||||
return;
|
||||
}
|
||||
close(fd);
|
||||
}
|
||||
|
||||
if (symlink(target, linkpath) != 0) {
|
||||
report_errno_detail("root.lchown", "symlink");
|
||||
unlink(target);
|
||||
rmdir(dir);
|
||||
return;
|
||||
}
|
||||
|
||||
if (lchown(linkpath, getuid(), getgid()) != 0) {
|
||||
report_errno_detail("root.lchown", "lchown");
|
||||
unlink(linkpath);
|
||||
unlink(target);
|
||||
rmdir(dir);
|
||||
return;
|
||||
}
|
||||
|
||||
unlink(linkpath);
|
||||
unlink(target);
|
||||
rmdir(dir);
|
||||
report_ok("root.lchown");
|
||||
}
|
||||
|
||||
static int run_regular_tests(void)
|
||||
{
|
||||
report_feature_macros();
|
||||
test_fork_waitpid();
|
||||
test_posix_spawn_closefrom();
|
||||
test_close_range();
|
||||
test_posix_fallocate();
|
||||
test_lutimes();
|
||||
test_statvfs();
|
||||
return failures == 0 ? 0 : 1;
|
||||
}
|
||||
|
||||
static int run_root_tests(void)
|
||||
{
|
||||
if (geteuid() != 0) {
|
||||
fprintf(stderr, "root tests require euid 0\n");
|
||||
return 2;
|
||||
}
|
||||
|
||||
test_chroot_smoke();
|
||||
test_jail_smoke();
|
||||
test_lchown_root();
|
||||
return failures == 0 ? 0 : 1;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
if (argc > 1 && strcmp(argv[1], "--root-tests") == 0)
|
||||
return run_root_tests();
|
||||
|
||||
return run_regular_tests();
|
||||
}
|
||||
104
tests/system/run-freebsd-syscall-mapping.sh
Executable file
104
tests/system/run-freebsd-syscall-mapping.sh
Executable file
@@ -0,0 +1,104 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
cc_bin=${CC_BIN:-cc}
|
||||
source_file=${SOURCE_FILE:-tests/system/freebsd-syscall-mapping.c}
|
||||
|
||||
if ! command -v "$cc_bin" >/dev/null 2>&1; then
|
||||
echo "C compiler not found: $cc_bin" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ ! -f "$source_file" ]; then
|
||||
echo "Source file not found: $source_file" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cleanup=0
|
||||
if [ -n "${WORKDIR:-}" ]; then
|
||||
workdir=$WORKDIR
|
||||
mkdir -p "$workdir"
|
||||
else
|
||||
workdir=$(mktemp -d /tmp/fruix-freebsd-syscall-mapping.XXXXXX)
|
||||
cleanup=1
|
||||
fi
|
||||
|
||||
if [ "${KEEP_WORKDIR:-0}" -eq 1 ]; then
|
||||
cleanup=0
|
||||
fi
|
||||
|
||||
cleanup_workdir() {
|
||||
if [ "$cleanup" -eq 1 ]; then
|
||||
rm -rf "$workdir"
|
||||
fi
|
||||
}
|
||||
trap cleanup_workdir EXIT INT TERM
|
||||
|
||||
binary=$workdir/freebsd-syscall-mapping
|
||||
compile_log=$workdir/compile.log
|
||||
regular_out=$workdir/regular.out
|
||||
regular_err=$workdir/regular.err
|
||||
root_out=$workdir/root.out
|
||||
root_err=$workdir/root.err
|
||||
metadata_file=$workdir/freebsd-syscall-mapping-metadata.txt
|
||||
|
||||
printf 'Working directory: %s\n' "$workdir"
|
||||
printf 'Compiler: %s\n' "$cc_bin"
|
||||
|
||||
"$cc_bin" -Wall -Wextra -std=c11 -I/usr/local/include "$source_file" -o "$binary" >"$compile_log" 2>&1
|
||||
|
||||
set +e
|
||||
"$binary" >"$regular_out" 2>"$regular_err"
|
||||
regular_rc=$?
|
||||
set -e
|
||||
if [ "$regular_rc" -ne 0 ]; then
|
||||
echo "regular syscall mapping checks failed" >&2
|
||||
cat "$regular_out" >&2 || true
|
||||
cat "$regular_err" >&2 || true
|
||||
exit 1
|
||||
fi
|
||||
|
||||
set +e
|
||||
sudo "$binary" --root-tests >"$root_out" 2>"$root_err"
|
||||
root_rc=$?
|
||||
set -e
|
||||
if [ "$root_rc" -ne 0 ]; then
|
||||
echo "root syscall mapping checks failed" >&2
|
||||
cat "$root_out" >&2 || true
|
||||
cat "$root_err" >&2 || true
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cat >"$metadata_file" <<EOF
|
||||
source_file=$source_file
|
||||
binary=$binary
|
||||
compile_log=$compile_log
|
||||
regular_out=$regular_out
|
||||
regular_err=$regular_err
|
||||
regular_rc=$regular_rc
|
||||
root_out=$root_out
|
||||
root_err=$root_err
|
||||
root_rc=$root_rc
|
||||
regular_output_begin
|
||||
$(cat "$regular_out")
|
||||
regular_output_end
|
||||
root_output_begin
|
||||
$(cat "$root_out")
|
||||
root_output_end
|
||||
EOF
|
||||
|
||||
if [ -n "${METADATA_OUT:-}" ]; then
|
||||
mkdir -p "$(dirname "$METADATA_OUT")"
|
||||
cp "$metadata_file" "$METADATA_OUT"
|
||||
fi
|
||||
|
||||
printf 'PASS freebsd-syscall-mapping\n'
|
||||
printf 'Metadata file: %s\n' "$metadata_file"
|
||||
if [ -n "${METADATA_OUT:-}" ]; then
|
||||
printf 'Copied metadata to: %s\n' "$METADATA_OUT"
|
||||
fi
|
||||
printf '%s\n' '--- regular output ---'
|
||||
cat "$regular_out"
|
||||
printf '%s\n' '--- root output ---'
|
||||
cat "$root_out"
|
||||
printf '%s\n' '--- metadata ---'
|
||||
cat "$metadata_file"
|
||||
Reference in New Issue
Block a user