diff options
-rw-r--r-- | TODO | 27 | ||||
-rwxr-xr-x | convert-etc-shells | 64 | ||||
-rwxr-xr-x | convert-usrmerge | 617 | ||||
-rwxr-xr-x | development/check-contents | 73 | ||||
-rwxr-xr-x | development/library_paths | 65 | ||||
-rwxr-xr-x | development/test | 30 |
6 files changed, 876 insertions, 0 deletions
@@ -0,0 +1,27 @@ +How to implement race-free replacement of a directory by a symlink: + +mount -o bind / /tmp/root + +mount -o bind /usr/bin /bin +mv /tmp/root/bin /tmp/root/bin.old +ln -s usr/bin /tmp/root/bin +umount /bin +rm -rf /tmp/root/bin.old + +umount /tmp/root + +Is this complexity justified just for convert_directory()? + +For some operations there are two possible implementations: +- cp/rename/symlink: slower but race-free +- rename/symlink: faster (only metadata operations, as long as / and /usr + are on the same file system) but racy + +Is it useful to keep the first implementation if we do not also fix +the directory-to-symlink races? + +How to handle the initramfs check in preinst? +Is asking a debconf question justified if we suspect that there is no +initramfs? +Or should we always ask for confirmation if /usr is a standalone filesystem? + diff --git a/convert-etc-shells b/convert-etc-shells new file mode 100755 index 0000000..824de4a --- /dev/null +++ b/convert-etc-shells @@ -0,0 +1,64 @@ +#!/usr/bin/perl +# vim: shiftwidth=4 tabstop=4 +# +# Copyright 2016-2022 by Marco d'Itri <md@Linux.IT> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +use warnings; +use strict; +use autodie; +use v5.16; + +convert($ARGV[0] || '/etc/shells'); +exit; + +sub convert { + my ($file) = @_; + + my (@shells, @nonusr_shells, %seen_in_usr); + + open(my $in, '<', $file); + while (<$in>) { + chomp; + push(@shells, $_); + $seen_in_usr{$1} = 1 if m#^/usr(/s?bin/.+)#; + push(@nonusr_shells, $_) if m#^/s?bin/#; + } + close $in; + + my @new_shells = # and add them to the list + map { "/usr$_" } # add /usr to their path + grep { not $seen_in_usr{$_} } # if they do not already exist in /usr + @nonusr_shells; # for each shell not in /usr + + return unless @new_shells; + + umask(0022); + open(my $out, '>', "$file.tmp"); + say $out $_ foreach @shells, @new_shells; + close $out; + + rename("$file.tmp", $file); + restore_context($file); +} + +sub safe_system { + my (@cmd) = @_; + + my $rc = system(@cmd); + die "Failed to execute @cmd: $!\n" if $rc == -1; + die "@cmd: rc=" . ($? >> 8) . "\n" if $rc; +} + +sub restore_context { + my ($file) = @_; + + return if not -x '/sbin/restorecon'; + + safe_system('restorecon', $file); +} + diff --git a/convert-usrmerge b/convert-usrmerge new file mode 100755 index 0000000..7567a01 --- /dev/null +++ b/convert-usrmerge @@ -0,0 +1,617 @@ +#!/usr/bin/perl +# vim: shiftwidth=4 tabstop=4 +# +# Copyright 2014-2022 by Marco d'Itri <md@Linux.IT> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +use warnings; +use strict; +use autodie; +use v5.16; + +use File::Find::Rule; +use Cwd qw(abs_path); +use Errno; + +my $Debug = 0; +my $Program_RC = 0; +my $Is_Hurd = is_hurd(); + +# If a file exists both in / and /usr then ignore (delete) the one in / +# Justification for these entries: +# /lib/udev/hwdb.bin -> systemd-hwdb --usr update [LP: #1930573, Ubuntu-specific] +my %Ignore_In_Root = map { $_ => 1 } qw( + /lib/udev/hwdb.bin +); + +$ENV{LC_ALL} = 'C'; + +check_free_space(); + +check_overlayfs(); + +check_uml(); + +go_faster(); + +# print the long error message if something fails due to autodie +$SIG{__DIE__} = sub { fatal($_[0]); }; + +{ + foreach my $name (early_conversion_files()) { + next if $name =~ m#^/usr/#; # already converted + convert_file($name); + } + + my @dirs = directories_to_merge(); + + # create any directory which is in / but not in /usr + umask(0022); + foreach my $dir (@dirs) { + next if -e "/usr$dir"; + next if not -d $dir; # but only if it actually exists in / + mkdir("/usr$dir"); + restore_context("/usr$dir"); + } + + my @later; + my $rule = File::Find::Rule->mindepth(1)->maxdepth(1)->start(@dirs); + while (defined (my $name = $rule->match)) { + convert_file($name, \@later); + } + + # symlinks must be converted after the rest to avoid races, because + # they may point to binaries which have not been converted yet + if ($Is_Hurd) { + while (@later) { + my @newlater; + convert_file($_, \@newlater) foreach @later; + @later = @newlater; + } + } else { + convert_file($_) foreach @later; + } + + verify_links_only($_) foreach @dirs; + + convert_directory($_) foreach @dirs; + + exit($Program_RC) if $Program_RC; + print "The system has been successfully converted.\n"; + exit; +} + +############################################################################## +sub convert_file { + my ($n, $later) = @_; + print "==== $n ====\n" if $Debug; + + # the source is a broken link + if (-l $n and not -e $n and -e "/usr$n") { + warn "WARNING: $n is a broken symlink and has been renamed!\n"; + rename($n, "$n.usrmerge-broken"); + return; + } + + # the destination is a broken link + if (-l "/usr$n" and not -e "/usr$n") { + warn "WARNING: /usr$n is a broken symlink and has been renamed!\n"; + rename("/usr$n", "/usr$n.usrmerge-broken"); + # continue and move the source as usual + } + + # is a directory and the destination does not exist + if (-d $n and not -e "/usr$n") { + mv($n, "/usr$n"); # XXX race + symlink("/usr$n", $n); + return; + } + + # is a link and the destination does not exist, but defer the conversion + if (-l $n and not -e "/usr$n" and $later and not $Is_Hurd) { + push(@$later, $n); + return; + } + + # the same, but only tested on Hurd systems (see #1020463 for details) + if (-l $n and not -e "/usr$n" and $later and $Is_Hurd) { + my $l = readlink($n); + my ($basedir) = $n =~ m#^(.+)/[^/]+$#; + if ($l !~ m#^/# and not -e "/usr$basedir/$l") { + fatal("Converted link /usr$n would point to non-existing" + . " /usr$basedir/$l but we are not deferring conversion") + if not $later; + push(@$later, $n); + return; + } + } + + # is a file or link and the destination does not exist + if (not -e "/usr$n") { + cp($n, "/usr$n"); + symlink("/usr$n", "$n~~tmp~usrmerge~~"); + rename("$n~~tmp~usrmerge~~", $n); + # XXX alternative implementation: + #mv($n, "/usr$n"); # XXX race + #symlink("/usr$n", $n); + return; + } + + # The other cases are more complex and there are some corner cases that + # we do not try to resolve automatically. + + # both source and dest are links + if (-l $n and -l "/usr$n") { + my $l1 = readlink($n); + my $l2 = readlink("/usr$n"); + my ($basedir) = $n =~ m#^(.+)/[^/]+$#; + return if $l1 eq $l2; # and they point to the same file + return if "$basedir/$l1" eq $l2; # same (the / link is relative) + return if $l1 eq "/usr$basedir/$l2";# same (the /usr link is relative) + return if $l1 eq "/usr$n"; # and the / link points to the /usr link + if ($l2 eq $n) { # and the /usr link points to the / link + # the target of the new link will be an absolute path, so it + # may be different from the original one even if both point to + # the same file + symlink(abs_path($n), "/usr$n~~tmp~usrmerge~~"); + rename("/usr$n~~tmp~usrmerge~~", "/usr$n"); + # convert the /bin link too to make the program idempotent + symlink(abs_path($n), "$n~~tmp~usrmerge~~"); + rename("$n~~tmp~usrmerge~~", "$n"); + return; + } + fatal("Both $n and /usr$n exist"); + } + + # the source is a link + if (-l $n) { + my $l = readlink($n); + return if $l eq "/usr$n"; # and it points to dest + fatal("Both $n and /usr$n exist"); + } + + # the destination is a link + if (-l "/usr$n") { + my $l = readlink("/usr$n"); + if ($l eq $n) { # and it points to source + cp($n, "/usr$n~~tmp~usrmerge~~"); + rename("/usr$n~~tmp~usrmerge~~", "/usr$n"); + symlink("/usr$n", "$n~~tmp~usrmerge~~"); + rename("$n~~tmp~usrmerge~~", $n); + # XXX alternative implementation: + #mv($n, "/usr$n"); # XXX race + #symlink("/usr$n", $n); + return; + } + fatal("Both $n and /usr$n exist"); + } + + # both source and dest are directories + # this is the second most common case + if (-d $n and -d "/usr$n") { + # so they have to be merged recursively + my $rule = File::Find::Rule->mindepth(1)->maxdepth(1)->start($n); + while (defined (my $name = $rule->match)) { + convert_file($name, $later); + } + return; + } + + fatal("$n is a directory and /usr$n is not") + if -d $n and -e "/usr$n"; + fatal("/usr$n is a directory and $n is not") + if -d "/usr$n"; + if (-e "/usr$n" and exists $Ignore_In_Root{$n}) { + rm($n); + return; + } + fatal("Both $n and /usr$n exist") + if -e "/usr$n"; + + fatal("The status of $n and /usr$n is really unexpected"); +} + +############################################################################## +# To prevent a failure later, the regular files of the libraries used by +# cp and mv must be converted before of the symlinks that point to them. +sub early_conversion_files { + no autodie qw(close); + + my $bin_cp = '/bin/cp'; + $bin_cp = '/usr/bin/cp' unless -x $bin_cp; + open(my $fh, '-|', "ldd $bin_cp"); + my @ldd = <$fh>; + close $fh or fatal("Failed to execute 'ldd $bin_cp'"); + + # the libraries + my @list = grep { $_ } map { /^\s+\S+ => (\/\S+) / and $1 } @ldd; + # the dynamic linker + push(@list, grep { $_ } map { /^\s+(\/\S+) \(/ and $1 } @ldd); + + # this is the equivalent of readlink --canonicalize + my @newlist; + foreach my $name (@list) { + my $newname = -l $name ? readlink($name) : $name; + if ($newname !~ m#^/#) { + my $dir = $name; + $dir =~ s#[^/]+$##; + $newname = $dir . $newname; + } + my $topdir = $newname; + $topdir =~ s#^(/[^/]+).*#$1#; + # this is needed to be idempotent after a complete conversion + next if -l $topdir; + push(@newlist, $newname); + } + + return @newlist; +} + +############################################################################## +# Safety check: verify that there are no regular files in the directories +# that will be deleted by the final pass. +sub verify_links_only { + my ($dir) = @_; + + my $link_or_dir = File::Find::Rule->or( + File::Find::Rule->symlink, + File::Find::Rule->directory, + ); + + my $rule = File::Find::Rule->mindepth(1)->not($link_or_dir)->start($dir); + while (defined (my $name = $rule->match)) { + print STDERR "$name is not a symlink!\n"; + print STDERR "\nSafety check: the conversion has failed!\n"; + exit 1; + } +} + +sub convert_directory { + my ($dir) = @_; + + return if -l $dir; + + if (not -d $dir) { + # do not create a broken symlink to /usr$dir if it does not exist + return if not -e "usr$dir"; + symlink("usr$dir", $dir); + restore_context($dir); + return; + } + + no autodie; + if (not rename($dir, "$dir~~delete~usrmerge~~")) { # XXX race + if ($!{EBUSY}) { + handle_ebusy($dir); + return; + } + die "Can't rename $dir: $!"; + } + use autodie; + symlink("usr$dir", $dir); + restore_context($dir); + + rm('-rf', "$dir~~delete~usrmerge~~"); +} + +sub restore_context { + my ($file) = @_; + + return if not (-x '/sbin/restorecon' or -x '/usr/sbin/restorecon'); + + safe_system('restorecon', $file); +} + +sub handle_ebusy { + my ($dir) = @_; + + print STDERR <<END; + +WARNING: renaming $dir/ (for the purpose of replacing it with a symlink +to /usr$dir/) has failed with the EBUSY error. +This is probably caused by a systemd service started with the +ProtectSystem option. Before running again this program you will need to +stop the relevant daemon(s) or reboot the system. +Do not install or update other Debian packages until the program +has been run successfully. (Removing packages is not harmful.) +END + + # continue, but have the program eventually return an error + $Program_RC = 1; + + # if systemd is running... + return if -d '/run/systemd/system'; + + # list the services with ProtectSystem enabled + my $cmd = q{ + for service in $(systemctl --no-legend --full list-units \ + --state=active --type=service | cut -d ' ' -f 1); do + if systemctl show $service | egrep -q "^ProtectSystem=(yes|full)"; then + echo $service + fi + done + }; + + my $cmd_output = qx{$cmd} || return; + print STDERR <<END; + +The following services are using the ProtectSystem feature: +$cmd_output +END +} + +############################################################################## +sub check_free_space { + no autodie qw(close); + + my $fh; + open($fh, '-|', 'stat --dereference --file-system --format="%i" /'); + my $root_id = <$fh>; + die "stat / failed" if not defined $root_id; + chomp $root_id; + + # beware: df(1) reports the value of %a, not of %f + open($fh, '-|', + 'stat --dereference --file-system --format="%f %S %i" /usr/'); + my $stat_output = <$fh>; + die "stat /usr failed" if not defined $stat_output; + chomp $stat_output; + my ($free_blocks, $bs, $usr_id) = split(/ /, $stat_output); + + return if $root_id eq $usr_id; + + my $free = $free_blocks * ($bs / 1024); + my @dirs = grep { -e $_ } directories_to_merge(); + + my $cmd = "du --summarize --no-dereference --total --block-size=1K @dirs"; + open($fh, '-|', $cmd); + my $needed; + while (<$fh>) { + ($needed) = /^(\d+)\s+total$/; + } + close $fh or fatal("Failed to execute $cmd"); + die "$cmd failed" if not defined $needed; + + say "Free space in /usr: $free KB." if $Debug; + say "The origin directories (@dirs) require $needed KB." if $Debug; + + die "$free KB in /usr but $needed KB are required!\n" if $needed > $free; +} + +# check if we are running under overlayfs, and if so, try and see if directories +# are movable - we might be running in a container, thus running under overlayfs +# but still able to do the conversion because the chroot is not overlayed. +sub check_overlayfs { + no autodie qw(close); + + my $fh; + open($fh, '-|', "stat --file-system --format=%T /"); + my $fs_type = <$fh>; + close $fh or fatal("Failed to execute stat --file-system --format=%T /"); + die "stat / failed" if not defined $fs_type; + chomp $fs_type; + return if $fs_type ne "overlayfs"; + + # We have detected overlayfs on /. Let's see if we can move the directories + # with a (hopefully) quick back-and-forth. This is not risk-free, but it's + # the best we can do. + my @dirs = directories_to_merge(); + foreach my $dir (@dirs) { + next if not -e "$dir"; + + no autodie qw(rename); + if ((not rename("$dir", "$dir~usrmerge~")) && $!{EXDEV}) { + die("Warning: overlayfs detected, /usr/lib/usrmerge/convert-usrmerge will not +be run automatically. See #1008202 for details. + +If this is a container then it can be converted by unpacking the image, +entering it with chroot(8), installing usrmerge and then repacking the +image again."); + } + use autodie qw(rename); + + rename("$dir~usrmerge~", "$dir"); + } +} + +sub is_hurd { + my $architecture = qx{dpkg --print-architecture}; + return 1 if $architecture =~ /^hurd-/; + return 0; +} + +sub is_mount_point { + my ($path) = @_; + + my @cmd = ('mountpoint', '-q', $path); + my $rc = system(@cmd); + die "Failed to execute @cmd: $!\n" if $rc == -1; + return 0 if $rc; + return 1; +} + +# Check if something is mounted on /lib/modules/ in User Mode Linux systems. +# See #1021180 for details. +sub check_uml { + return if not is_mount_point('/lib/modules/'); + + print STDERR <<END; + +FATAL ERROR: + +/lib/modules/ is a mount point. +Probably this system is using User Mode Linux or some variant of Xen. + +To continue the conversion please unmount /lib/modules/ (try the command +'umount -l /lib/modules/') and then try again. +Do not forget to reboot after the conversion is complete to have it +mounted again. + +END + exit(1); +} + +############################################################################## +# Try to avoid the inherent races by being as fast as possible. +sub go_faster { + system('/usr/bin/ionice', '--class=realtime', "--pid=$$") + if -x '/usr/bin/ionice'; + system('/usr/bin/chrt', '--rr', '-p', '99', '--pid', $$) + if -x '/usr/bin/chrt'; +} + +# We use cp from coreutils because it preserves extended attributes and +# handles sparse files. +sub cp { + my ($source, $dest) = @_; + + safe_system('cp', '--no-dereference', '--preserve=all', + '--reflink=auto', '--sparse=always', $source, $dest); +} + +# We use mv from coreutils because it supports moving directories across +# filesystems, which would be inconvenient to implement here. +sub mv { + my ($source, $dest) = @_; + + safe_system('mv', '--no-clobber', $source, $dest); +} + +# We use rm from coreutils because I cannot be bothered to implement -r. +sub rm { + safe_system('rm', @_); +} + +sub safe_system { + my (@cmd) = @_; + + my $rc = system(@cmd); + die "Failed to execute @cmd: $!\n" if $rc == -1; + die "@cmd: rc=" . ($? >> 8) . "\n" if $rc; +} + +sub fatal { + my ($msg) = @_; + + $msg .= ".\n" if $msg !~ /\n$/; + + print STDERR <<END; + +FATAL ERROR: +$msg +You can try correcting the errors reported and running again +$0 until it will complete without errors. +Do not install or update other Debian packages until the program +has been run successfully. + +END + exit(1); +} + +# Find out where the runtime dynamic linker and the shared libraries +# can be installed on each architecture: native and multilib. +sub directories_to_merge { + return qw(/bin /sbin /lib /lib32 /lib64 /libo32 /libx32); +} + +# check if the argument is one of the architectures enabled on the system +sub running_arch { + my ($wanted) = @_; + + state @system_arch; + if (not @system_arch) { + push(@system_arch, `dpkg --print-architecture`); + push(@system_arch, `dpkg --print-foreign-architectures`); + chomp @system_arch; + } + + return 1 if grep { $_ eq $wanted } @system_arch; + return 0; +} + +############################################################################## +__END__ + +=head1 NAME + +convert-usrmerge - converts the system to everything-in-usr + +=head1 SYNOPSIS + +convert-usrmerge + +=head1 DESCRIPTION + +This program will automatically convert the system to the +everything-in-usr directory scheme. + +There is no automatic method to restore the precedent configuration, so +there is no going back once the program has been run. + +The program is idempotent, unless the system crashes at a really bad time. + +The conflicts of all files in the Debian archive can be solved +automatically, but some corner cases of custom setups may require +manual changes. + +=head1 CONFLICTS RESOLUTION MATRIX + + s/d F D L B + F X X S Rd + D X D S Rd + L K K K? Rd + B Rs Rs Rs Rs + +I<Source> is / and I<destination> is /usr. + +Types of files: + +=over 4 + +=item F: file + +=item D: directory + +=item L: symbolic link + +=item B: broken symbolic link + +=back + +Actions: + +=over 4 + +=item X: unresolvable conflict + +=item D: recurse and compare the content of the directories + +=item K: keep the source (if the link matches the destination) + +=item S: swap source and destination (if the link matches the destination) + +=item R: rename (source or destination) + +=back + +=head1 BUGS + +Replacing a directory with a symlink is racy unless we do a complex dance +of bind mounts. We should decide if this is really needed. + +Conflicting relative symbolic links are not handled automatically. + +The libc6-i386 and libc6-x32 packages require to convert the /lib32 and +/libx32 directories as well, otherwise they would only contain the +symlink to the dynamic linker specified by the architecture ABI. + +=head1 AUTHOR + +The program and this man page have been written by Marco d'Itri +and are licensed under the terms of the GNU General Public License, +version 2 or higher. + diff --git a/development/check-contents b/development/check-contents new file mode 100755 index 0000000..01b1111 --- /dev/null +++ b/development/check-contents @@ -0,0 +1,73 @@ +#!/usr/bin/perl +# wget http://ftp.it.debian.org/debian/dists/unstable/main/Contents-amd64.gz + +use warnings; +use strict; +use autodie; +use feature qw(say); + +use Storable; + +my $data = read_data(); + +my (@l1, @l2); + +# check all files in /bin /sbin /lib +foreach (@{$data->{rootfiles}}) { + my ($name, $pkg) = @$_; + + # is there one in /usr too? + next unless exists $data->{files}->{'usr/' . $name}; + my $usrpkg = $data->{files}->{'usr/' . $name}; + + # and are they in the same package? + if ($pkg eq $usrpkg) { + push(@l1, [ $name, $pkg ]); + } else { + push(@l2, [ $name, $pkg, $usrpkg ]); + } +} + +say 'Single packages shipping the same file in / and /usr:'; +say "$_->[1]\t$_->[0]" foreach sort { $a->[1] cmp $b->[1] } @l1; + +say ''; +say 'Multiple packages shipping the same file in / and /usr:'; +say "$_->[1]\t$_->[0]\t$_->[2]" foreach sort { $a->[1] cmp $b->[1] } @l2; + +exit 0; + +############################################################################## +sub read_data { + my $cachefile = '/tmp/contents.storable'; + + return retrieve($cachefile) if -e $cachefile; + + my $data = read_contents(); + store($data, $cachefile); + return $data; +} + +sub read_contents { + my (@rootfiles, %files); + + open(my $fh, 'zcat Contents-amd64.gz |'); + while (<$fh>) { + my ($file, $package) = split; + $package =~ s#(^|,)[a-z]+/#$1#g; + + if ($file =~ m#^(?:s?bin|lib|libx?32|lib64)/#) { + push(@rootfiles, [$file, $package]); + } elsif ($file =~ m#^usr/#) { + if (exists $files{$file}) { + $files{$file} = $files{$file} . ',' . $package; + } else { + $files{$file} = $package; + } + } + } + close($fh) or die "close: $!"; + + return { rootfiles => \@rootfiles, files => \%files }; +} + diff --git a/development/library_paths b/development/library_paths new file mode 100755 index 0000000..129e04d --- /dev/null +++ b/development/library_paths @@ -0,0 +1,65 @@ +#!/usr/bin/perl +# This program will print the RTLD and shared libraries paths for all +# architectures. +# It provides the list of directories that need to be kept up to date +# in directories_to_merge() in convert-usrmerge and in setup_merged_usr() +# in debootstrap when new architectures are added to Debian. +# +# The command line argument is the path to the debian/sysdeps/ directory +# of the glibc package. It can be downloaded with the command: +# git clone --depth=1 https://salsa.debian.org/glibc-team/glibc.git + +use warnings; +use strict; +use autodie; +use v5.16; + +use File::Slurp; +use List::Util qw(uniq); + +my $sysdeps_dir = $ARGV[0] || 'glibc/debian/sysdeps'; +my @sysdeps_files = glob("$sysdeps_dir/*.mk") + or die "FATAL: $sysdeps_dir/*.mk is missing!\n"; + +my %directories; + +foreach my $file (@sysdeps_files) { + doit($file); +} + +print "\nconvert-usrmerge and debootstrap should list these directories:\n"; +foreach my $arch (sort keys %directories) { + my @list = + sort { $a cmp $b } + map { s#^/##; $_ } + grep { $_ ne '/lib' } + map { s#/\$\(DEB_HOST_MULTIARCH\)/.+##; $_ } + uniq + @{ $directories{$arch} }; + print "$arch\t@list\n" if @list; +} + +sub doit { + my ($file) = @_; + + say "==== $file ===="; + my @lines = grep { !/^#/ } read_file($file, chomp => 1); + my @multilib = map { /\s*\+=\s*(\S+)/; $1 } + grep { /^GLIBC_PASSES\b/ } @lines; + unshift(@multilib, 'libc'); + + foreach my $arch (@multilib) { + my ($rtlddir) = map { /=\s*(\S+)/; $1 } + grep { /^${arch}_rtlddir\b/ } @lines; + my ($slibdir) = map { /=\s*(\S+)/; $1 } + grep { /^${arch}_slibdir\b/ } @lines; + + next unless $rtlddir or $slibdir; + $rtlddir ||= ''; $slibdir ||= ''; + say "$arch\tRTLD: $rtlddir\t\tSLIBDIR: $slibdir"; + my ($dpkg_arch) = $file =~ m#/([^/]+)\.mk$#; + push(@{ $directories{$dpkg_arch} }, $rtlddir) if $rtlddir; + push(@{ $directories{$dpkg_arch} }, $slibdir) if $slibdir; + } +} + diff --git a/development/test b/development/test new file mode 100755 index 0000000..101f6a1 --- /dev/null +++ b/development/test @@ -0,0 +1,30 @@ +#!/bin/sh -e + +tmp_cleanup() { + [ -d "$BASE" ] || return + mountpoint -q "$BASE" || return + umount -l $BASE + rmdir $BASE +} + +trap "tmp_cleanup" 0 1 2 3 15 + +BASE=$(mktemp -d) +mount -t tmpfs tmpfs $BASE -o mode=755,size=401276k +mkdir $BASE/delta/ $BASE/.work/ $BASE/root/ + +if [ "$1" ]; then + base_dir="$1" +else + base_dir='/' +fi + +mount -t overlay overlay $BASE/root/ \ + -o workdir=$BASE/.work,lowerdir=$base_dir,upperdir=$BASE/delta + +systemd-nspawn --setenv=LANG=C.UTF-8 \ + --tmpfs=/tmp/:mode=1777 \ + --bind=/var/cache/apt/archives/ \ + --register=no \ + --directory=$BASE/root/ || true + |