Compare commits
No commits in common. "7d7d757f00ece2ee5fbab4d4b43824b6071bbf82" and "3fcb125e3cc6c86c2a407f8d64d67c92d0ef260d" have entirely different histories.
7d7d757f00
...
3fcb125e3c
13 changed files with 180 additions and 223 deletions
|
@ -88,14 +88,12 @@ def main():
|
||||||
)
|
)
|
||||||
parser.add_argument("--mode", metavar="mode", help="only run tests with this mode")
|
parser.add_argument("--mode", metavar="mode", help="only run tests with this mode")
|
||||||
parser.add_argument("--dist", metavar="dist", help="only run tests with this dist")
|
parser.add_argument("--dist", metavar="dist", help="only run tests with this dist")
|
||||||
parser.add_argument(
|
|
||||||
"--variant", metavar="variant", help="only run tests with this variant"
|
|
||||||
)
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
# copy over files from git or as distributed
|
# copy over files from git or as distributed
|
||||||
for (git, dist, target) in [
|
for (git, dist, target) in [
|
||||||
("./mmdebstrap", "/usr/bin/mmdebstrap", "mmdebstrap"),
|
("./mmdebstrap", "/usr/bin/mmdebstrap", "mmdebstrap"),
|
||||||
|
("./taridshift", "/usr/bin/mmtaridshift", "taridshift"),
|
||||||
("./tarfilter", "/usr/bin/mmtarfilter", "tarfilter"),
|
("./tarfilter", "/usr/bin/mmtarfilter", "tarfilter"),
|
||||||
(
|
(
|
||||||
"./proxysolver",
|
"./proxysolver",
|
||||||
|
@ -225,7 +223,7 @@ def main():
|
||||||
)
|
)
|
||||||
print("time left: %s" % timeleft, file=sys.stderr)
|
print("time left: %s" % timeleft, file=sys.stderr)
|
||||||
if failed:
|
if failed:
|
||||||
print("failed: %d" % len(failed), file=sys.stderr)
|
print("failed: %d" % len(failed))
|
||||||
num_finished += 1
|
num_finished += 1
|
||||||
with open("tests/" + name) as fin, open("shared/test.sh", "w") as fout:
|
with open("tests/" + name) as fin, open("shared/test.sh", "w") as fout:
|
||||||
for line in fin:
|
for line in fin:
|
||||||
|
@ -259,9 +257,6 @@ def main():
|
||||||
if args.mode and args.mode != mode:
|
if args.mode and args.mode != mode:
|
||||||
print(f"skipping because of --mode={args.mode}", file=sys.stderr)
|
print(f"skipping because of --mode={args.mode}", file=sys.stderr)
|
||||||
continue
|
continue
|
||||||
if args.variant and args.variant != variant:
|
|
||||||
print(f"skipping because of --variant={args.variant}", file=sys.stderr)
|
|
||||||
continue
|
|
||||||
proc = subprocess.Popen(argv)
|
proc = subprocess.Popen(argv)
|
||||||
try:
|
try:
|
||||||
proc.wait()
|
proc.wait()
|
||||||
|
|
|
@ -52,7 +52,7 @@ Skip-If:
|
||||||
fmt == "squashfs" and dist == "oldstable" # squashfs-tools-ng is not available
|
fmt == "squashfs" and dist == "oldstable" # squashfs-tools-ng is not available
|
||||||
fmt == "ext2" and dist == "oldstable" # genext2fs does not support SOURCE_DATE_EPOCH
|
fmt == "ext2" and dist == "oldstable" # genext2fs does not support SOURCE_DATE_EPOCH
|
||||||
|
|
||||||
Test: tarfilter-idshift
|
Test: taridshift-utility
|
||||||
Needs-QEMU: true
|
Needs-QEMU: true
|
||||||
Skip-If: dist == "oldstable" # python3 tarfile module does not preserve xattrs
|
Skip-If: dist == "oldstable" # python3 tarfile module does not preserve xattrs
|
||||||
|
|
||||||
|
@ -286,10 +286,11 @@ Test: install-doc-debian
|
||||||
Modes: chrootless
|
Modes: chrootless
|
||||||
Variants: custom
|
Variants: custom
|
||||||
|
|
||||||
Test: chrootless-essential
|
Test: install-known-good-from-essential-yes
|
||||||
Variants: custom
|
Variants: custom
|
||||||
Modes: chrootless
|
Modes: chrootless
|
||||||
Skip-If:
|
Skip-If:
|
||||||
|
True # #1006692
|
||||||
dist in ["oldstable", "stable"]
|
dist in ["oldstable", "stable"]
|
||||||
|
|
||||||
Test: install-doc-debian-and-output-tarball
|
Test: install-doc-debian-and-output-tarball
|
||||||
|
@ -321,7 +322,3 @@ Modes: fakechroot
|
||||||
Test: dev-ptmx
|
Test: dev-ptmx
|
||||||
Modes: root unshare
|
Modes: root unshare
|
||||||
Needs-QEMU: true
|
Needs-QEMU: true
|
||||||
|
|
||||||
Test: error-if-stdout-is-tty
|
|
||||||
|
|
||||||
Test: variant-custom-timeout
|
|
||||||
|
|
94
mmdebstrap
94
mmdebstrap
|
@ -37,8 +37,7 @@ use Cwd qw(abs_path getcwd);
|
||||||
require "syscall.ph"; ## no critic (Modules::RequireBarewordIncludes)
|
require "syscall.ph"; ## no critic (Modules::RequireBarewordIncludes)
|
||||||
use Fcntl qw(S_IFCHR S_IFBLK FD_CLOEXEC F_GETFD F_SETFD);
|
use Fcntl qw(S_IFCHR S_IFBLK FD_CLOEXEC F_GETFD F_SETFD);
|
||||||
use List::Util qw(any none);
|
use List::Util qw(any none);
|
||||||
use POSIX
|
use POSIX qw(SIGINT SIGHUP SIGPIPE SIGTERM SIG_BLOCK SIG_UNBLOCK strftime);
|
||||||
qw(SIGINT SIGHUP SIGPIPE SIGTERM SIG_BLOCK SIG_UNBLOCK strftime isatty);
|
|
||||||
use Carp;
|
use Carp;
|
||||||
use Term::ANSIColor;
|
use Term::ANSIColor;
|
||||||
use Socket;
|
use Socket;
|
||||||
|
@ -979,59 +978,46 @@ sub run_apt_download_progress {
|
||||||
my $flags = fcntl($wfh, F_GETFD, 0) or error "fcntl F_GETFD: $!";
|
my $flags = fcntl($wfh, F_GETFD, 0) or error "fcntl F_GETFD: $!";
|
||||||
fcntl($wfh, F_SETFD, $flags & ~FD_CLOEXEC) or error "fcntl F_SETFD: $!";
|
fcntl($wfh, F_SETFD, $flags & ~FD_CLOEXEC) or error "fcntl F_SETFD: $!";
|
||||||
my $fd = fileno $wfh;
|
my $fd = fileno $wfh;
|
||||||
# run_apt_progress() can raise an exception which would leave this function
|
# 2022-05-02, #debian-apt on OFTC, times in UTC+2
|
||||||
# without cleaning up the other thread we started, making mmdebstrap hang
|
# 16:57 < josch> DonKult: how is -oDebug::pkgDpkgPm=1 -oDir::Log=/dev/null
|
||||||
# in case run_apt_progress() fails -- so wrap this in eval() instead
|
# a "fancy no-op"?
|
||||||
eval {
|
# 11:52 < DonKult> josch: "fancy no-op" in sofar as it does nothing to the
|
||||||
# 2022-05-02, #debian-apt on OFTC, times in UTC+2
|
# system even through its not in a special mode ala
|
||||||
# 16:57 < josch> DonKult: how is -oDebug::pkgDpkgPm=1
|
# simulation or download-only. It does all the things it
|
||||||
# -oDir::Log=/dev/null a "fancy no-op"?
|
# normally does, except that it just prints the dpkg calls
|
||||||
# 11:52 < DonKult> josch: "fancy no-op" in sofar as it does nothing to
|
# instead of execv() them which in practice amounts means
|
||||||
# the system even through its not in a special mode
|
# it does nothing (the Dir::Log just prevents libapt from
|
||||||
# ala simulation or download-only. It does all the
|
# creating the /var/log/apt directories. As the code
|
||||||
# things it normally does, except that it just prints
|
# creates them even if no logs will be placed there…). As
|
||||||
# the dpkg calls instead of execv() them which in
|
# said, midterm an apt --print-install-packages or
|
||||||
# practice amounts means it does nothing (the Dir::Log
|
# something would be nice to avoid running everything.
|
||||||
# just prevents libapt from creating the /var/log/apt
|
run_apt_progress({
|
||||||
# directories. As the code creates them even if no
|
ARGV => [
|
||||||
# logs will be placed there…). As said, midterm an apt
|
'apt-get',
|
||||||
# --print-install-packages or something would be nice
|
'--yes',
|
||||||
# to avoid running everything.
|
'-oDebug::pkgDpkgPm=1',
|
||||||
run_apt_progress({
|
'-oDir::Log=/dev/null',
|
||||||
ARGV => [
|
$options->{dryrun}
|
||||||
'apt-get',
|
? '-oAPT::Get::Simulate=true'
|
||||||
'--yes',
|
: (
|
||||||
'-oDebug::pkgDpkgPm=1',
|
"-oAPT::Keep-Fds::=$fd",
|
||||||
'-oDir::Log=/dev/null',
|
"-oDPkg::Tools::options::'cat >&$fd'::InfoFD=$fd",
|
||||||
$options->{dryrun}
|
"-oDpkg::Pre-Install-Pkgs::=cat >&$fd",
|
||||||
? '-oAPT::Get::Simulate=true'
|
# no need to lock the database if we are just downloading
|
||||||
: (
|
"-oDebug::NoLocking=1",
|
||||||
"-oAPT::Keep-Fds::=$fd",
|
# no need for pty magic if we write no log
|
||||||
"-oDPkg::Tools::options::'cat >&$fd'::InfoFD=$fd",
|
"-oDpkg::Use-Pty=0",
|
||||||
"-oDpkg::Pre-Install-Pkgs::=cat >&$fd",
|
),
|
||||||
# no need to lock the database if we are just downloading
|
@{ $options->{APT_ARGV} },
|
||||||
"-oDebug::NoLocking=1",
|
],
|
||||||
# no need for pty magic if we write no log
|
});
|
||||||
"-oDpkg::Use-Pty=0",
|
|
||||||
),
|
|
||||||
@{ $options->{APT_ARGV} },
|
|
||||||
],
|
|
||||||
});
|
|
||||||
};
|
|
||||||
my $err = '';
|
|
||||||
if ($@) {
|
|
||||||
$err = "apt download failed: $@";
|
|
||||||
}
|
|
||||||
# signal the child process that we are done
|
# signal the child process that we are done
|
||||||
close $wfh;
|
close $wfh;
|
||||||
# and then read from it what it got
|
# and then read from it what it got
|
||||||
my @listofdebs = <$fh>;
|
my @listofdebs = <$fh>;
|
||||||
close $fh;
|
close $fh;
|
||||||
if ($? != 0) {
|
if ($? != 0) {
|
||||||
$err = "status child failed";
|
error "status child failed";
|
||||||
}
|
|
||||||
if ($err) {
|
|
||||||
error $err;
|
|
||||||
}
|
}
|
||||||
# remove trailing newlines
|
# remove trailing newlines
|
||||||
chomp @listofdebs;
|
chomp @listofdebs;
|
||||||
|
@ -4163,7 +4149,7 @@ sub main() {
|
||||||
# this is like:
|
# this is like:
|
||||||
# lxc-usernsexec -- lxc-unshare -s 'MOUNT|PID|UTSNAME|IPC' ...
|
# lxc-usernsexec -- lxc-unshare -s 'MOUNT|PID|UTSNAME|IPC' ...
|
||||||
# but without needing lxc
|
# but without needing lxc
|
||||||
if (scalar @ARGV >= 1 && $ARGV[0] eq "--unshare-helper") {
|
if ($ARGV[0] eq "--unshare-helper") {
|
||||||
if ($EFFECTIVE_USER_ID != 0 && !test_unshare_userns(1)) {
|
if ($EFFECTIVE_USER_ID != 0 && !test_unshare_userns(1)) {
|
||||||
exit 1;
|
exit 1;
|
||||||
}
|
}
|
||||||
|
@ -4465,7 +4451,7 @@ sub main() {
|
||||||
);
|
);
|
||||||
close $fh;
|
close $fh;
|
||||||
if ( $? == 0
|
if ( $? == 0
|
||||||
and $content =~ /^apt (\d+\.\d+\.\d+)\w* \(\S+\)$/am) {
|
and $content =~ /^apt ([0-9]+\.[0-9]+\.[0-9]+)[a-z0-9]* \([a-z0-9-]+\)$/m) {
|
||||||
$aptversion = version->new($1);
|
$aptversion = version->new($1);
|
||||||
}
|
}
|
||||||
if ($aptversion < "2.3.14") {
|
if ($aptversion < "2.3.14") {
|
||||||
|
@ -5140,11 +5126,7 @@ sub main() {
|
||||||
$options->{sourceslists} = $sourceslists;
|
$options->{sourceslists} = $sourceslists;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($options->{target} eq '-') {
|
if ($options->{target} ne '-') {
|
||||||
if (POSIX::isatty STDOUT) {
|
|
||||||
error "stdout is a an interactive tty";
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
my $abs_path = abs_path($options->{target});
|
my $abs_path = abs_path($options->{target});
|
||||||
if (!defined $abs_path) {
|
if (!defined $abs_path) {
|
||||||
error "unable to get absolute path of target directory"
|
error "unable to get absolute path of target directory"
|
||||||
|
|
98
tarfilter
98
tarfilter
|
@ -43,53 +43,17 @@ class PaxFilterAction(argparse.Action):
|
||||||
setattr(namespace, "paxfilter", items)
|
setattr(namespace, "paxfilter", items)
|
||||||
|
|
||||||
|
|
||||||
class TransformAction(argparse.Action):
|
|
||||||
def __call__(self, parser, namespace, values, option_string=None):
|
|
||||||
items = getattr(namespace, "trans", [])
|
|
||||||
# This function mimics what src/transform.c from tar does
|
|
||||||
if not values.startswith("s"):
|
|
||||||
raise ValueError("regex must start with an 's'")
|
|
||||||
if len(values) <= 4:
|
|
||||||
# minimum regex: s/x//
|
|
||||||
raise ValueError("invalid regex (too short)")
|
|
||||||
d = values[1]
|
|
||||||
if values.startswith(f"s{d}{d}"):
|
|
||||||
raise ValueError("empty regex")
|
|
||||||
values = values.removeprefix(f"s{d}")
|
|
||||||
flags = 0
|
|
||||||
if values.endswith(f"{d}i"):
|
|
||||||
# trailing flags
|
|
||||||
flags = re.IGNORECASE
|
|
||||||
values = values.removesuffix(f"{d}i")
|
|
||||||
# This regex only finds non-empty tokens.
|
|
||||||
# Finding empty tokens would require a variable length look-behind
|
|
||||||
# or \K in order to find escaped delimiters which is not supported by
|
|
||||||
# the python re module.
|
|
||||||
tokens = re.findall(rf"(?:\\[\\{d}]|[^{d}])+", values)
|
|
||||||
match len(tokens):
|
|
||||||
case 0:
|
|
||||||
raise ValueError("invalid regex: not enough terms")
|
|
||||||
case 1:
|
|
||||||
repl = ""
|
|
||||||
case 2:
|
|
||||||
repl = tokens[1]
|
|
||||||
case _:
|
|
||||||
raise ValueError("invalid regex: too many terms: %s" % tokens)
|
|
||||||
items.append((re.compile(tokens[0], flags), repl))
|
|
||||||
setattr(namespace, "trans", items)
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
||||||
description="""\
|
description="""\
|
||||||
Filters a tarball on standard input by the same rules as the dpkg --path-exclude
|
Filters a tarball on standard input by the same rules as the dpkg --path-exclude
|
||||||
and --path-include options and writes resulting tarball to standard output. See
|
and --path-include options and writes resulting tarball to standard output. See
|
||||||
dpkg(1) for information on how these two options work in detail. To reuse the
|
dpkg(1) for information on how these two options work in detail. Since this is
|
||||||
exact same semantics as used by dpkg, paths must be given as /path and not as
|
meant for filtering tarballs storing a rootfs, notice that paths must be given
|
||||||
./path even though they might be stored as such in the tarball.
|
as /path and not as ./path even though they might be stored as such in the
|
||||||
|
tarball.
|
||||||
|
|
||||||
Secondly, filter out unwanted pax extended headers. This is useful in cases
|
Similarly, filter out unwanted pax extended headers. This is useful in cases
|
||||||
where a tool only accepts certain xattr prefixes. For example tar2sqfs only
|
where a tool only accepts certain xattr prefixes. For example tar2sqfs only
|
||||||
supports SCHILY.xattr.user.*, SCHILY.xattr.trusted.* and
|
supports SCHILY.xattr.user.*, SCHILY.xattr.trusted.* and
|
||||||
SCHILY.xattr.security.* but not SCHILY.xattr.system.posix_acl_default.*.
|
SCHILY.xattr.security.* but not SCHILY.xattr.system.posix_acl_default.*.
|
||||||
|
@ -101,65 +65,41 @@ Both types of options use Unix shell-style wildcards:
|
||||||
[seq] matches any character in seq
|
[seq] matches any character in seq
|
||||||
[!seq] matches any character not in seq
|
[!seq] matches any character not in seq
|
||||||
|
|
||||||
Thirdly, transform the path of tar members using a sed expression just as with
|
Thirdly, strip leading directory components off of tar members. Just as with
|
||||||
GNU tar --transform.
|
|
||||||
|
|
||||||
Fourthly, strip leading directory components off of tar members. Just as with
|
|
||||||
GNU tar --strip-components, tar members that have less or equal components in
|
GNU tar --strip-components, tar members that have less or equal components in
|
||||||
their path are not passed through.
|
their path are not passed through.
|
||||||
|
"""
|
||||||
Lastly, shift user id and group id of each entry by the value given by the
|
|
||||||
--idshift argument. The resulting uid or gid must not be negative.
|
|
||||||
""",
|
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--path-exclude",
|
"--path-exclude",
|
||||||
metavar="pattern",
|
metavar="pattern",
|
||||||
action=PathFilterAction,
|
action=PathFilterAction,
|
||||||
help="Exclude path matching the given shell pattern. "
|
help="Exclude path matching the given shell pattern.",
|
||||||
"This option can be specified multiple times.",
|
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--path-include",
|
"--path-include",
|
||||||
metavar="pattern",
|
metavar="pattern",
|
||||||
action=PathFilterAction,
|
action=PathFilterAction,
|
||||||
help="Re-include a pattern after a previous exclusion. "
|
help="Re-include a pattern after a previous exclusion.",
|
||||||
"This option can be specified multiple times.",
|
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--pax-exclude",
|
"--pax-exclude",
|
||||||
metavar="pattern",
|
metavar="pattern",
|
||||||
action=PaxFilterAction,
|
action=PaxFilterAction,
|
||||||
help="Exclude pax header matching the given globbing pattern. "
|
help="Exclude pax header matching the given globbing pattern.",
|
||||||
"This option can be specified multiple times.",
|
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--pax-include",
|
"--pax-include",
|
||||||
metavar="pattern",
|
metavar="pattern",
|
||||||
action=PaxFilterAction,
|
action=PaxFilterAction,
|
||||||
help="Re-include a pax header after a previous exclusion. "
|
help="Re-include a pax header after a previous exclusion.",
|
||||||
"This option can be specified multiple times.",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--transform",
|
|
||||||
"--xform",
|
|
||||||
metavar="EXPRESSION",
|
|
||||||
action=TransformAction,
|
|
||||||
help="Use sed replace EXPRESSION to transform file names. "
|
|
||||||
"This option can be specified multiple times.",
|
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--strip-components",
|
"--strip-components",
|
||||||
metavar="NUMBER",
|
metavar="number",
|
||||||
type=int,
|
type=int,
|
||||||
help="Strip NUMBER leading components from file names",
|
help="Strip NUMBER leading components from file names",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
|
||||||
"--idshift",
|
|
||||||
metavar="NUM",
|
|
||||||
type=int,
|
|
||||||
help="Integer value by which to shift the uid and gid of each entry",
|
|
||||||
)
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
if (
|
if (
|
||||||
not hasattr(args, "pathfilter")
|
not hasattr(args, "pathfilter")
|
||||||
|
@ -217,8 +157,6 @@ Lastly, shift user id and group id of each entry by the value given by the
|
||||||
continue
|
continue
|
||||||
if args.strip_components:
|
if args.strip_components:
|
||||||
comps = member.name.split("/")
|
comps = member.name.split("/")
|
||||||
# just as with GNU tar, archive members with less or equal
|
|
||||||
# number of components are not passed through at all
|
|
||||||
if len(comps) <= args.strip_components:
|
if len(comps) <= args.strip_components:
|
||||||
continue
|
continue
|
||||||
member.name = "/".join(comps[args.strip_components :])
|
member.name = "/".join(comps[args.strip_components :])
|
||||||
|
@ -227,18 +165,6 @@ Lastly, shift user id and group id of each entry by the value given by the
|
||||||
for k, v in member.pax_headers.items()
|
for k, v in member.pax_headers.items()
|
||||||
if not pax_filter_should_skip(k)
|
if not pax_filter_should_skip(k)
|
||||||
}
|
}
|
||||||
if args.idshift:
|
|
||||||
if args.idshift < 0 and -args.idshift > member.uid:
|
|
||||||
print("uid cannot be negative", file=sys.stderr)
|
|
||||||
exit(1)
|
|
||||||
if args.idshift < 0 and -args.idshift > member.gid:
|
|
||||||
print("gid cannot be negative", file=sys.stderr)
|
|
||||||
exit(1)
|
|
||||||
member.uid += args.idshift
|
|
||||||
member.gid += args.idshift
|
|
||||||
if hasattr(args, "trans"):
|
|
||||||
for r, s in args.trans:
|
|
||||||
member.name = r.sub(s, member.name)
|
|
||||||
if member.isfile():
|
if member.isfile():
|
||||||
with in_tar.extractfile(member) as file:
|
with in_tar.extractfile(member) as file:
|
||||||
out_tar.addfile(member, file)
|
out_tar.addfile(member, file)
|
||||||
|
|
67
taridshift
Executable file
67
taridshift
Executable file
|
@ -0,0 +1,67 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
#
|
||||||
|
# This script is in the public domain
|
||||||
|
#
|
||||||
|
# Author: Johannes Schauer Marin Rodrigues <josch@mister-muffin.de>
|
||||||
|
#
|
||||||
|
# This script accepts a tarball on standard input and prints a tarball on
|
||||||
|
# standard output with the same contents but all uid and gid ownership
|
||||||
|
# information shifted by the value given as first command line argument.
|
||||||
|
#
|
||||||
|
# A tool like this should be written in C but libarchive has issues:
|
||||||
|
# https://github.com/libarchive/libarchive/issues/587
|
||||||
|
# https://github.com/libarchive/libarchive/pull/1288/ (needs 3.4.1)
|
||||||
|
# Should these issues get fixed, then a good template is tarfilter.c in the
|
||||||
|
# examples directory of libarchive.
|
||||||
|
#
|
||||||
|
# We are not using Perl either, because Archive::Tar slurps the whole tarball
|
||||||
|
# into memory.
|
||||||
|
#
|
||||||
|
# We could also use Go but meh...
|
||||||
|
# https://stackoverflow.com/a/59542307/784669
|
||||||
|
|
||||||
|
import tarfile
|
||||||
|
import sys
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="""\
|
||||||
|
Accepts a tarball on standard input and prints a tarball on standard output
|
||||||
|
with the same contents but all uid and gid ownership information shifted by the
|
||||||
|
value given as first command line argument.
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"idshift",
|
||||||
|
metavar="NUM",
|
||||||
|
type=int,
|
||||||
|
help="Integer value by which to shift the uid and gid of each entry",
|
||||||
|
)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# starting with Python 3.8, the default format became PAX_FORMAT, so this
|
||||||
|
# is only for compatibility with older versions of Python 3
|
||||||
|
with tarfile.open(fileobj=sys.stdin.buffer, mode="r|*") as in_tar, tarfile.open(
|
||||||
|
fileobj=sys.stdout.buffer, mode="w|", format=tarfile.PAX_FORMAT
|
||||||
|
) as out_tar:
|
||||||
|
for member in in_tar:
|
||||||
|
if args.idshift < 0 and -args.idshift > member.uid:
|
||||||
|
print("uid cannot be negative", file=sys.stderr)
|
||||||
|
exit(1)
|
||||||
|
if args.idshift < 0 and -args.idshift > member.gid:
|
||||||
|
print("gid cannot be negative", file=sys.stderr)
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
member.uid += args.idshift
|
||||||
|
member.gid += args.idshift
|
||||||
|
if member.isfile():
|
||||||
|
with in_tar.extractfile(member) as file:
|
||||||
|
out_tar.addfile(member, file)
|
||||||
|
else:
|
||||||
|
out_tar.addfile(member)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
|
@ -16,7 +16,7 @@ echo "SOURCE_DATE_EPOCH=$SOURCE_DATE_EPOCH"
|
||||||
# mmdebstrap, resulting in different gid values
|
# mmdebstrap, resulting in different gid values
|
||||||
{{ CMD }} --variant={{ VARIANT }} --mode={{ MODE }} \
|
{{ CMD }} --variant={{ VARIANT }} --mode={{ MODE }} \
|
||||||
--essential-hook='if [ {{ VARIANT }} = - ]; then echo _apt:*:100:65534::/nonexistent:/usr/sbin/nologin >> "$1"/etc/passwd; fi' \
|
--essential-hook='if [ {{ VARIANT }} = - ]; then echo _apt:*:100:65534::/nonexistent:/usr/sbin/nologin >> "$1"/etc/passwd; fi' \
|
||||||
--essential-hook='if [ {{ VARIANT }} = - ] && [ {{ DIST }} = unstable -o {{ DIST }} = testing ]; then printf "systemd-journal:x:999:\nsystemd-network:x:998:\ncrontab:x:101:" >> "$1"/etc/group; fi' \
|
--essential-hook='if [ {{ VARIANT }} = - ] && [ {{ DIST }} = unstable -o {{ DIST }} = testing ]; then printf "systemd-journal:x:101:\nsystemd-network:x:102:\nsystemd-resolve:x:103:\ncrontab:x:104:" >> "$1"/etc/group; fi' \
|
||||||
$(case {{ DIST }} in oldstable|stable) : ;; *) echo --hook-dir=./hooks/merged-usr ;; esac) \
|
$(case {{ DIST }} in oldstable|stable) : ;; *) echo --hook-dir=./hooks/merged-usr ;; esac) \
|
||||||
{{ DIST }} /tmp/debian-{{ DIST }}-mm.tar {{ MIRROR }}
|
{{ DIST }} /tmp/debian-{{ DIST }}-mm.tar {{ MIRROR }}
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ tar --xattrs --xattrs-include='*' -C /tmp/debian-{{ DIST }}-debootstrap -xf "cac
|
||||||
tar -C /tmp/debian-{{ DIST }}-debootstrap -cf dev1.tar ./dev
|
tar -C /tmp/debian-{{ DIST }}-debootstrap -cf dev1.tar ./dev
|
||||||
tar -C /tmp/debian-{{ DIST }}-mm -cf dev2.tar ./dev
|
tar -C /tmp/debian-{{ DIST }}-mm -cf dev2.tar ./dev
|
||||||
ret=0
|
ret=0
|
||||||
cmp dev1.tar dev2.tar >&2 || ret=$?
|
cmp dev1.tar dev2.tar || ret=$?
|
||||||
if [ "$ret" -ne 0 ]; then
|
if [ "$ret" -ne 0 ]; then
|
||||||
if type diffoscope >/dev/null; then
|
if type diffoscope >/dev/null; then
|
||||||
diffoscope dev1.tar dev2.tar
|
diffoscope dev1.tar dev2.tar
|
||||||
|
@ -144,7 +144,7 @@ for f in "/var/lib/dpkg/triggers/File" "/etc/shells"; do
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
# the file must be different
|
# the file must be different
|
||||||
if cmp "$f1" "$f2" >&2; then
|
if cmp "$f1" "$f2"; then
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
# then sort both
|
# then sort both
|
||||||
|
@ -162,27 +162,15 @@ for f in shadow shadow-; do
|
||||||
done
|
done
|
||||||
# same as above but for cron and systemd groups
|
# same as above but for cron and systemd groups
|
||||||
for f in gshadow gshadow-; do
|
for f in gshadow gshadow-; do
|
||||||
for group in systemd-journal systemd-network crontab; do
|
for group in systemd-journal systemd-network systemd-resolve crontab; do
|
||||||
for password in "!" "!\\*"; do
|
if grep -q '^'"$group"':!:' /tmp/debian-{{ DIST }}-debootstrap/etc/$f; then
|
||||||
if grep -q '^'"$group"':'"$password"':' /tmp/debian-{{ DIST }}-debootstrap/etc/$f; then
|
sed -i 's/^'"$group"':x::/'"$group"':!::/' /tmp/debian-{{ DIST }}-mm/etc/$f
|
||||||
sed -i 's/^'"$group"':x::/'"$group"':'"$password"'::/' /tmp/debian-{{ DIST }}-mm/etc/$f
|
fi
|
||||||
fi
|
|
||||||
done
|
|
||||||
done
|
done
|
||||||
done
|
done
|
||||||
|
|
||||||
for log in faillog lastlog; do
|
|
||||||
if ! cmp /tmp/debian-{{ DIST }}-debootstrap/var/log/$log /tmp/debian-{{ DIST }}-mm/var/log/$log >&2;then
|
|
||||||
# if the files differ, make sure they are all zeroes
|
|
||||||
cmp -n $(stat -c %s /tmp/debian-{{ DIST }}-debootstrap/var/log/$log) /tmp/debian-{{ DIST }}-debootstrap/var/log/$log /dev/zero >&2
|
|
||||||
cmp -n $(stat -c %s /tmp/debian-{{ DIST }}-mm/var/log/$log) /tmp/debian-{{ DIST }}-mm/var/log/$log /dev/zero >&2
|
|
||||||
# then delete them
|
|
||||||
rm /tmp/debian-{{ DIST }}-debootstrap/var/log/$log /tmp/debian-{{ DIST }}-mm/var/log/$log
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# workaround for https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=917773
|
# workaround for https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=917773
|
||||||
if ! cmp /tmp/debian-{{ DIST }}-debootstrap/etc/shadow /tmp/debian-{{ DIST }}-mm/etc/shadow >&2; then
|
if ! cmp /tmp/debian-{{ DIST }}-debootstrap/etc/shadow /tmp/debian-{{ DIST }}-mm/etc/shadow; then
|
||||||
echo patching /etc/shadow on {{ DIST }} {{ VARIANT }} >&2
|
echo patching /etc/shadow on {{ DIST }} {{ VARIANT }} >&2
|
||||||
awk -v FS=: -v OFS=: -v SDE={{ SOURCE_DATE_EPOCH }} '{ print $1,$2,int(SDE/60/60/24),$4,$5,$6,$7,$8,$9 }' < /tmp/debian-{{ DIST }}-mm/etc/shadow > /tmp/debian-{{ DIST }}-mm/etc/shadow.bak
|
awk -v FS=: -v OFS=: -v SDE={{ SOURCE_DATE_EPOCH }} '{ print $1,$2,int(SDE/60/60/24),$4,$5,$6,$7,$8,$9 }' < /tmp/debian-{{ DIST }}-mm/etc/shadow > /tmp/debian-{{ DIST }}-mm/etc/shadow.bak
|
||||||
cat /tmp/debian-{{ DIST }}-mm/etc/shadow.bak > /tmp/debian-{{ DIST }}-mm/etc/shadow
|
cat /tmp/debian-{{ DIST }}-mm/etc/shadow.bak > /tmp/debian-{{ DIST }}-mm/etc/shadow
|
||||||
|
@ -190,7 +178,7 @@ if ! cmp /tmp/debian-{{ DIST }}-debootstrap/etc/shadow /tmp/debian-{{ DIST }}-mm
|
||||||
else
|
else
|
||||||
echo no difference for /etc/shadow on {{ DIST }} {{ VARIANT }} >&2
|
echo no difference for /etc/shadow on {{ DIST }} {{ VARIANT }} >&2
|
||||||
fi
|
fi
|
||||||
if ! cmp /tmp/debian-{{ DIST }}-debootstrap/etc/shadow- /tmp/debian-{{ DIST }}-mm/etc/shadow- >&2; then
|
if ! cmp /tmp/debian-{{ DIST }}-debootstrap/etc/shadow- /tmp/debian-{{ DIST }}-mm/etc/shadow-; then
|
||||||
echo patching /etc/shadow- on {{ DIST }} {{ VARIANT }} >&2
|
echo patching /etc/shadow- on {{ DIST }} {{ VARIANT }} >&2
|
||||||
awk -v FS=: -v OFS=: -v SDE={{ SOURCE_DATE_EPOCH }} '{ print $1,$2,int(SDE/60/60/24),$4,$5,$6,$7,$8,$9 }' < /tmp/debian-{{ DIST }}-mm/etc/shadow- > /tmp/debian-{{ DIST }}-mm/etc/shadow-.bak
|
awk -v FS=: -v OFS=: -v SDE={{ SOURCE_DATE_EPOCH }} '{ print $1,$2,int(SDE/60/60/24),$4,$5,$6,$7,$8,$9 }' < /tmp/debian-{{ DIST }}-mm/etc/shadow- > /tmp/debian-{{ DIST }}-mm/etc/shadow-.bak
|
||||||
cat /tmp/debian-{{ DIST }}-mm/etc/shadow-.bak > /tmp/debian-{{ DIST }}-mm/etc/shadow-
|
cat /tmp/debian-{{ DIST }}-mm/etc/shadow-.bak > /tmp/debian-{{ DIST }}-mm/etc/shadow-
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
set -eu
|
|
||||||
export LC_ALL=C.UTF-8
|
|
||||||
if [ "$(id -u)" -eq 0 ] && ! id -u user > /dev/null 2>&1; then
|
|
||||||
if [ ! -e /mmdebstrap-testenv ]; then
|
|
||||||
echo "this test modifies the system and should only be run inside a container" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
adduser --gecos user --disabled-password user
|
|
||||||
fi
|
|
||||||
prefix=
|
|
||||||
[ "$(id -u)" -eq 0 ] && prefix="runuser -u user --"
|
|
||||||
$prefix fakechroot fakeroot {{ CMD }} --mode=chrootless --variant=essential {{ DIST }} /dev/null {{ MIRROR }}
|
|
|
@ -13,10 +13,6 @@ homedir=$(runuser -u user -- sh -c 'cd && pwd')
|
||||||
# apt:test/integration/test-apt-key
|
# apt:test/integration/test-apt-key
|
||||||
TMPDIR_ADD="This is fü\$\$ing cràzy, \$(apt -v)\$!"
|
TMPDIR_ADD="This is fü\$\$ing cràzy, \$(apt -v)\$!"
|
||||||
runuser -u user -- mkdir "$homedir/$TMPDIR_ADD"
|
runuser -u user -- mkdir "$homedir/$TMPDIR_ADD"
|
||||||
# make sure the unshared user can traverse into the TMPDIR
|
|
||||||
chmod 711 "$homedir"
|
|
||||||
# set permissions and sticky bit like the real /tmp
|
|
||||||
chmod 1777 "$homedir/$TMPDIR_ADD"
|
|
||||||
runuser -u user -- env TMPDIR="$homedir/$TMPDIR_ADD" {{ CMD }} --mode=unshare --variant=apt \
|
runuser -u user -- env TMPDIR="$homedir/$TMPDIR_ADD" {{ CMD }} --mode=unshare --variant=apt \
|
||||||
--setup-hook='case "$1" in '"$(quote "$homedir/$TMPDIR_ADD/mmdebstrap.")"'??????????) exit 0;; *) echo "$1"; exit 1;; esac' \
|
--setup-hook='case "$1" in '"$(quote "$homedir/$TMPDIR_ADD/mmdebstrap.")"'??????????) exit 0;; *) echo "$1"; exit 1;; esac' \
|
||||||
{{ DIST }} /tmp/debian-chroot.tar {{ MIRROR }}
|
{{ DIST }} /tmp/debian-chroot.tar {{ MIRROR }}
|
||||||
|
|
|
@ -132,7 +132,6 @@ script -qfc "$prefix {{ CMD }} --mode={{ MODE }} --variant=apt \
|
||||||
{{ DIST }} /dev/null {{ MIRROR }}" /dev/null
|
{{ DIST }} /dev/null {{ MIRROR }}" /dev/null
|
||||||
|
|
||||||
fail=0
|
fail=0
|
||||||
[ -r /tmp/log ] || fail=1
|
|
||||||
grep '^E:' /tmp/log && fail=1
|
grep '^E:' /tmp/log && fail=1
|
||||||
grep 'Can not write log' /tmp/log && fail=1
|
grep 'Can not write log' /tmp/log && fail=1
|
||||||
grep 'posix_openpt' /tmp/log && fail=1
|
grep 'posix_openpt' /tmp/log && fail=1
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
|
|
||||||
set -eu
|
|
||||||
|
|
||||||
export LC_ALL=C.UTF-8
|
|
||||||
|
|
||||||
ret=0
|
|
||||||
script -qfec "{{ CMD }} --mode={{ MODE }} --variant=apt {{ DIST }} - {{ MIRROR }}" /dev/null || ret=$?
|
|
||||||
if [ "$ret" = 0 ]; then
|
|
||||||
echo expected failure but got exit $ret >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
43
tests/install-known-good-from-essential-yes
Normal file
43
tests/install-known-good-from-essential-yes
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
#!/bin/sh
|
||||||
|
#
|
||||||
|
# regularly check whether more packages work with chrootless:
|
||||||
|
# for p in $(grep-aptavail -F Essential yes -s Package -n | sort -u); do ./mmdebstrap -- mode=chrootless --variant=custom --include=bsdutils,coreutils,debianutils,diffutils,dpkg, findutils,grep,gzip,hostname,init-system-helpers,ncurses-base,ncurses-bin,perl-base,sed, sysvinit-utils,tar,$p unstable /dev/null; done
|
||||||
|
#
|
||||||
|
# see https://bugs.debian.org/cgi-bin/pkgreport.cgi?users=debian-dpkg@lists.debian.org;tag=dpkg- root-support
|
||||||
|
#
|
||||||
|
# base-files: #824594
|
||||||
|
# base-passwd: debconf
|
||||||
|
# bash: depends base-files
|
||||||
|
# bsdutils: ok
|
||||||
|
# coreutils: ok
|
||||||
|
# dash: debconf
|
||||||
|
# debianutils: ok
|
||||||
|
# diffutils: ok
|
||||||
|
# dpkg: ok
|
||||||
|
# findutils: ok
|
||||||
|
# grep: ok
|
||||||
|
# gzip: ok
|
||||||
|
# hostname: ok
|
||||||
|
# init-system-helpers: ok
|
||||||
|
# libc-bin: #983412
|
||||||
|
# login: debconf
|
||||||
|
# ncurses-base: ok
|
||||||
|
# ncurses-bin: ok
|
||||||
|
# perl-base: ok
|
||||||
|
# sed: ok
|
||||||
|
# sysvinit-utils: ok
|
||||||
|
# tar: ok
|
||||||
|
# util-linux: debconf
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
export LC_ALL=C.UTF-8
|
||||||
|
if [ "$(id -u)" -eq 0 ] && ! id -u user > /dev/null 2>&1; then
|
||||||
|
if [ ! -e /mmdebstrap-testenv ]; then
|
||||||
|
echo "this test modifies the system and should only be run inside a container" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
adduser --gecos user --disabled-password user
|
||||||
|
fi
|
||||||
|
prefix=
|
||||||
|
[ "$(id -u)" -eq 0 ] && prefix="runuser -u user --"
|
||||||
|
$prefix {{ CMD }} --mode=chrootless --variant=custom --include=bsdutils,coreutils,debianutils,diffutils,dpkg,findutils,grep,gzip,hostname,init-system-helpers,ncurses-base,ncurses-bin,perl-base,sed,sysvinit-utils,tar {{ DIST }} /dev/null {{ MIRROR }}
|
|
@ -10,12 +10,12 @@ adduser --gecos user --disabled-password user
|
||||||
echo user:100000:65536 | cmp /etc/subuid -
|
echo user:100000:65536 | cmp /etc/subuid -
|
||||||
echo user:100000:65536 | cmp /etc/subgid -
|
echo user:100000:65536 | cmp /etc/subgid -
|
||||||
sysctl -w kernel.unprivileged_userns_clone=1
|
sysctl -w kernel.unprivileged_userns_clone=1
|
||||||
# include iputils-ping so that we can verify that tarfilter does not remove
|
# include iputils-ping so that we can verify that taridshift does not remove
|
||||||
# extended attributes
|
# extended attributes
|
||||||
# run through tarshift no-op to create a tarball that should be bit-by-bit
|
# run through tarshift no-op to create a tarball that should be bit-by-bit
|
||||||
# identical to a round trip through "tarfilter --idshift X" and "tarfilter --idshift -X"
|
# identical to a round trip through "taridshift X" and "taridshift -X"
|
||||||
runuser -u user -- {{ CMD }} --mode=unshare --variant=apt --include=iputils-ping {{ DIST }} - {{ MIRROR }} \
|
runuser -u user -- {{ CMD }} --mode=unshare --variant=apt --include=iputils-ping {{ DIST }} - {{ MIRROR }} \
|
||||||
| ./tarfilter --idshift 0 > /tmp/debian-chroot.tar
|
| ./taridshift 0 > /tmp/debian-chroot.tar
|
||||||
# make sure that xattrs are set in the original tarball
|
# make sure that xattrs are set in the original tarball
|
||||||
mkdir /tmp/debian-chroot
|
mkdir /tmp/debian-chroot
|
||||||
tar --xattrs --xattrs-include='*' --directory /tmp/debian-chroot -xf /tmp/debian-chroot.tar ./bin/ping
|
tar --xattrs --xattrs-include='*' --directory /tmp/debian-chroot -xf /tmp/debian-chroot.tar ./bin/ping
|
||||||
|
@ -25,9 +25,9 @@ rm /tmp/debian-chroot/bin/ping
|
||||||
rmdir /tmp/debian-chroot/bin
|
rmdir /tmp/debian-chroot/bin
|
||||||
rmdir /tmp/debian-chroot
|
rmdir /tmp/debian-chroot
|
||||||
# shift the uid/gid forward by 100000 and backward by 100000
|
# shift the uid/gid forward by 100000 and backward by 100000
|
||||||
./tarfilter --idshift 100000 < /tmp/debian-chroot.tar > /tmp/debian-chroot-shifted.tar
|
./taridshift 100000 < /tmp/debian-chroot.tar > /tmp/debian-chroot-shifted.tar
|
||||||
./tarfilter --idshift -100000 < /tmp/debian-chroot-shifted.tar > /tmp/debian-chroot-shiftedback.tar
|
./taridshift -100000 < /tmp/debian-chroot-shifted.tar > /tmp/debian-chroot-shiftedback.tar
|
||||||
# the tarball before and after the roundtrip through tarfilter should be bit
|
# the tarball before and after the roundtrip through taridshift should be bit
|
||||||
# by bit identical
|
# by bit identical
|
||||||
cmp /tmp/debian-chroot.tar /tmp/debian-chroot-shiftedback.tar
|
cmp /tmp/debian-chroot.tar /tmp/debian-chroot-shiftedback.tar
|
||||||
# manually adjust uid/gid and compare "tar -t" output
|
# manually adjust uid/gid and compare "tar -t" output
|
|
@ -1,11 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
set -eu
|
|
||||||
export LC_ALL=C.UTF-8
|
|
||||||
|
|
||||||
# mmdebstrap used to hang forever if apt in custom mode failed to resolve
|
|
||||||
# dependencies because a process died without cleaning up its children.
|
|
||||||
# https://bugs.debian.org/1017795
|
|
||||||
ret=0
|
|
||||||
{{ CMD }} --mode={{ MODE }} --variant=custom \
|
|
||||||
--include=this-package-does-not-exist {{ DIST }} /dev/null {{ MIRROR }} || ret=1
|
|
||||||
[ $ret -eq 1 ]
|
|
Loading…
Reference in a new issue