|
| 1 | +#!/usr/bin/env perl |
| 2 | +use strict; |
| 3 | +use warnings; |
| 4 | + |
| 5 | +use FindBin qw(); |
| 6 | + |
| 7 | +# By default capture both legacy firmware (alpine) and UFI (default) usage |
| 8 | +@ARGV = qw(alpine default) unless @ARGV; |
| 9 | + |
| 10 | +# This script creates a tarball containing lima and qemu, plus all their |
| 11 | +# dependencies from /usr/local/**. |
| 12 | +# |
| 13 | +# New processes (with their command line arguments) have been captured by |
| 14 | +# `sudo dtrace -s /usr/bin/newproc.d` (on a system with SIP disabled, using lima 0.3.0): |
| 15 | +# `limactl start examples/alpine.yaml; limactl stop alpine; limactrl delete alpine`. |
| 16 | +# |
| 17 | +# 5680 <777> limactl start --tty=false examples/alpine.yaml |
| 18 | +# 5681 <5680> curl -fSL -o /Users/jan/Library/Caches/lima/download/by-url-sha256/21753<...> |
| 19 | +# 5683 <5680> qemu-img create -f qcow2 /Users/jan/.lima/alpine/diffdisk 107374182400 |
| 20 | +# 5684 <5680> /usr/local/bin/limactl hostagent --pidfile /Users/jan/.lima/alpine/ha.pid alpine |
| 21 | +# 5686 <5684> ssh-keygen -R [127.0.0.1]:60020 -R [localhost]:60020 |
| 22 | +# 5687 <5684> ssh -o ControlMaster=auto -o ControlPath=/Users/jan/.lima/alpine/ssh.sock -o <...> |
| 23 | +# 5685 <5684> /usr/local/bin/qemu-system-x86_64 -cpu Haswell-v4 -machine q35,accel=hvf -smp <...> |
| 24 | +# 5689 <5684> ssh -o ControlMaster=auto -o ControlPath=/Users/jan/.lima/alpine/ssh.sock -o <...> |
| 25 | +# ... many more ssh sub-processes like the one above ... |
| 26 | +# 5800 <777> limactl stop alpine |
| 27 | +# 5801 <5684> ssh -o ControlMaster=auto -o ControlPath=/Users/jan/.lima/alpine/ssh.sock -o <...> |
| 28 | +# 5896 <777> limactl delete alpine |
| 29 | +# |
| 30 | +# It shows the following binaries from /usr/local are called: |
| 31 | + |
| 32 | +my $install_dir = "/usr/local"; |
| 33 | +record("$install_dir/bin/limactl"); |
| 34 | +record("$install_dir/bin/qemu-img"); |
| 35 | +record("$install_dir/bin/qemu-system-x86_64"); |
| 36 | + |
| 37 | +# Capture any library and datafiles access with opensnoop |
| 38 | +my $opensnoop = "/tmp/opensnoop.log"; |
| 39 | +END { system("sudo pkill dtrace") } |
| 40 | +print "sudo may prompt for password to run opensnoop\n"; |
| 41 | +system("sudo -b opensnoop >$opensnoop 2>/dev/null"); |
| 42 | +sleep(1) until -s $opensnoop; |
| 43 | + |
| 44 | +my $repo_root = dirname($FindBin::Bin); |
| 45 | +for my $example (@ARGV) { |
| 46 | + my $config = "$repo_root/examples/$example.yaml", ; |
| 47 | + die "Config $config not found" unless -f $config; |
| 48 | + system("limactl delete -f $example") if -f "$ENV{HOME}/.lima/$example"; |
| 49 | + system("limactl start --tty=false $config"); |
| 50 | + system("limactl shell $example uname"); |
| 51 | + system("limactl stop $example"); |
| 52 | + system("limactl delete $example"); |
| 53 | +} |
| 54 | +system("sudo pkill dtrace"); |
| 55 | + |
| 56 | +open(my $fh, "<", $opensnoop) or die "Can't read $opensnoop: $!"; |
| 57 | +while (<$fh>) { |
| 58 | + # Only record files opened by limactl or qemu-* |
| 59 | + next unless /^\s*\d+\s+\d+\s+(limactl|qemu-)/; |
| 60 | + # Ignore files not under /usr/local |
| 61 | + next unless s|^.*($install_dir/\S+).*$|$1|s; |
| 62 | + # Skip files that don't exist |
| 63 | + next unless -f; |
| 64 | + record($_); |
| 65 | +} |
| 66 | + |
| 67 | +my %deps; |
| 68 | +print "$_ $deps{$_}\n" for sort keys %deps; |
| 69 | +print "\n"; |
| 70 | + |
| 71 | +my $dist = "lima-and-qemu"; |
| 72 | +system("rm -rf /tmp/$dist"); |
| 73 | + |
| 74 | +# Copy all files to /tmp tree and make all dylib references relative to the |
| 75 | +# /usr/local/bin directory using @executable_path/.. |
| 76 | +my %resign; |
| 77 | +for my $file (keys %deps) { |
| 78 | + my $copy = $file =~ s|^$install_dir|/tmp/$dist|r; |
| 79 | + system("mkdir -p " . dirname($copy)); |
| 80 | + system("cp -R $file $copy"); |
| 81 | + next if -l $file; |
| 82 | + next unless qx(file $copy) =~ /Mach-O/; |
| 83 | + |
| 84 | + open(my $fh, "otool -L $file |") or die "Failed to run 'otool -L $file': $!"; |
| 85 | + while (<$fh>) { |
| 86 | + my($dylib) = m|$install_dir/(\S+)| or next; |
| 87 | + my $grep = ""; |
| 88 | + if ($file =~ m|bin/qemu-system-x86_64$|) { |
| 89 | + # qemu-system-* is already signed with an entitlement to use the hypervisor framework |
| 90 | + $grep = "| grep -v 'will invalidate the code signature'"; |
| 91 | + $resign{$copy}++; |
| 92 | + } |
| 93 | + system "install_name_tool -change $install_dir/$dylib \@executable_path/../$dylib $copy 2>&1 $grep"; |
| 94 | + } |
| 95 | + close($fh); |
| 96 | +} |
| 97 | +# Replace invalidated signatures |
| 98 | +for my $file (keys %resign) { |
| 99 | + system("codesign --sign - --force --preserve-metadata=entitlements $file"); |
| 100 | +} |
| 101 | + |
| 102 | +unlink("$repo_root/$dist.tar.gz"); |
| 103 | +my $files = join(" ", map s|^$install_dir/||r, keys %deps); |
| 104 | +system("tar cvfz $repo_root/$dist.tar.gz -C /tmp/$dist $files"); |
| 105 | +exit; |
| 106 | + |
| 107 | +# File references may involve multiple symlinks that need to be recorded as well, e.g. |
| 108 | +# |
| 109 | +# /usr/local/opt/libssh/lib/libssh.4.dylib |
| 110 | +# |
| 111 | +# turns into 2 symlinks and one file: |
| 112 | +# |
| 113 | +# /usr/local/opt/libssh → ../Cellar/libssh/0.9.5_1 |
| 114 | +# /usr/local/Cellar/libssh/0.9.5_1/lib/libssh.4.dylib → libssh.4.8.6.dylib |
| 115 | +# /usr/local/Cellar/libssh/0.9.5_1/lib/libssh.4.8.6.dylib [394K] |
| 116 | + |
| 117 | +my %seen; |
| 118 | +sub record { |
| 119 | + my $dep = shift; |
| 120 | + return if $seen{$dep}++; |
| 121 | + $dep =~ s|^/|| or die "$dep is not an absolute path"; |
| 122 | + my $filename = ""; |
| 123 | + my @segments = split '/', $dep; |
| 124 | + while (@segments) { |
| 125 | + my $segment = shift @segments; |
| 126 | + my $name = "$filename/$segment"; |
| 127 | + my $link = readlink $name; |
| 128 | + if (defined $link) { |
| 129 | + # Record the symlink itself with the link target as the comment |
| 130 | + $deps{$name} = "→ $link"; |
| 131 | + if ($link =~ m|^/|) { |
| 132 | + # Can't support absolute links pointing outside /usr/local |
| 133 | + die "$name → $link" unless $link =~ m|^$install_dir/|; |
| 134 | + $link = join("/", $link, @segments); |
| 135 | + } else { |
| 136 | + $link = join("/", $filename, $link, @segments); |
| 137 | + } |
| 138 | + # Re-parse from the start because the link may contain ".." segments |
| 139 | + return record($link) |
| 140 | + } |
| 141 | + if ($segment eq "..") { |
| 142 | + $filename = dirname($filename); |
| 143 | + } else { |
| 144 | + $filename = $name; |
| 145 | + } |
| 146 | + } |
| 147 | + # Use human readable size of the file as the comment: |
| 148 | + # $ ls -lh /usr/local/Cellar/libssh/0.9.5_1/lib/libssh.4.8.6.dylib |
| 149 | + # -rw-r--r-- 1 jan staff 394K 5 Jan 11:04 /usr/local/Cellar/libssh/0.9.5_1/lib/libssh.4.8.6.dylib |
| 150 | + $deps{$filename} = sprintf "[%s]", (split / +/, qx(ls -lh $filename))[4]; |
| 151 | +} |
| 152 | + |
| 153 | +sub dirname { |
| 154 | + shift =~ s|/[^/]+$||r; |
| 155 | +} |
0 commit comments