#!/usr/bin/env perl # Clean all non-deterministric fields in a newc cpio file # # Items fixed: # Files are sorted by name # Inode numbers are based on the hash of the filename # File timestamp is set to 1970-01-01T00:00:00 # uid/gid are set to root # check field is zeroed # nlinks is set to zero, since the filesystem manages it # use warnings; use strict; use Data::Dumper; use Digest::MD5 'md5_hex'; # struct cpio_newc_header { # char c_magic[6]; -6 # char c_ino[8]; -- set to a monotonic value 0 # char c_mode[8]; 8 # char c_uid[8]; 16 # char c_gid[8]; 24 # char c_nlink[8]; 32 # char c_mtime[8]; 40 -- set to zero # char c_filesize[8]; 48 # char c_devmajor[8]; 56 # char c_devminor[8]; 64 # char c_rdevmajor[8]; 72 # char c_rdevminor[8]; 80 # char c_namesize[8]; 88 # char c_check[8]; 96 # }; // 104 # followed by namesize bytes of name (padded to be a multiple of 4) # followed dby filesize bytes of file (padded to be a multiple of 4) # Read the entire file at once undef $/; # Generate a map of all of the files in the cpio archive # This will also merge multiple cpio files my %entries; my $trailer; while(<>) { for(my $i = 0 ; $i < length $_ ; ) { my $magic = substr($_, $i, 6); if ($magic ne "070701") { die "$ARGV: offset $i: invalid magic '$magic'\n"; } my $namesize = substr($_, $i + 6+88, 8); my $filesize = substr($_, $i + 6+48, 8); if ($namesize =~ /[^0-9A-Fa-f]/) { die "$ARGV: offset $i: invalid characters in namesize '$namesize'\n"; } if ($filesize =~ /[^0-9A-Fa-f]/) { die "$ARGV: offset $i: invalid characters in filesize '$filesize'\n"; } # Convert them to hex $namesize = hex $namesize; $filesize = hex $filesize; #print STDERR "name: '$namesize', filesize: '$filesize'\n"; my $name = substr($_, $i + 6+104, $namesize); #print STDERR Dumper($name); # Align the header size to be a multiple of four bytes my $entry_size = (6+104 + $namesize + 3) & ~3; $entry_size += ($filesize + 3) & ~3; my $entry = substr($_, $i, $entry_size); $i += $entry_size; if ($name =~ /^TRAILER!!!/) { $trailer = $entry; last; } $entries{$name} = $entry; } die "$ARGV: No trailer!\n" unless $trailer; } # Apply the cleaning to each one for my $filename (sort keys %entries) { my $entry = $entries{$filename}; my $zero = sprintf "%08x", 0; # inodes are hashed to be deterministic # and hopefully not colliding my $md5 = md5_hex($filename); my $d0 = hex substr($md5, 0, 8) ; my $d1 = hex substr($md5, 8, 8) ; my $d2 = hex substr($md5, 16, 8) ; my $d3 = hex substr($md5, 24, 8) ; my $hash = sprintf "%08x", $d0 ^ $d1 ^ $d2 ^ $d3; #warn "$filename: $md5 -> $hash\n"; substr($entry, 6 + 0, 8) = $hash; # set timestamps to zero substr($entry, 6 + 40, 8) = $zero; # remove group/user permissions, leaving only # the owner bits intact. my $mode = hex substr($entry, 6 + 8, 8); $mode &= ~0077; #$mode |= $mode >> 3 | $mode >> 6; substr($entry, 6 + 8, 8) = sprintf "%08X", $mode; # set uid/gid to zero substr($entry, 6 + 16, 8) = $zero; substr($entry, 6 + 24, 8) = $zero; # zero out the nlinks, since it is managed by the real fs substr($entry, 6 + 32, 8) = $zero; # set the device major/minor to zero substr($entry, 6 + 56, 8) = $zero; substr($entry, 6 + 64, 8) = $zero; # set check to zero substr($entry, 6 + 96, 8) = $zero; $entries{$filename} = $entry; } # Output them in sorted order my $out = join '', map { $entries{$_} } sort keys %entries; #for my $filename (sort keys %entries) #{ #$out .= $entries{$filename}; #} # Output the trailer to mark the end of the archive $out .= $trailer; # Pad to 512-bytes for kernel initrd reasons my $unaligned = length($out) % 512; $out .= chr(0x00) x (512 - $unaligned) if $unaligned != 0; print $out; __END__