#!/usr/bin/perl # Copyright Gerben Wierda, 2001 # See below for documentation. Or run with --help use Getopt::Long; GetOptions( "warnsize", "repair", "help", "checksize", "verbose"); %types = ( 1, 'named pipe (fifo)', 2, 'character special', 4, 'directory', 6, 'block special', 8, 'regular', 10, 'symbolic link', 12, 'socket', 14, 'whiteout', ); &help if ($opt_help); sub help { &helpmsg; exit 0; } sub helpmsg { warn <<"__HELP_INFO"; Help information for $0 Usage: $0 [--warnsize] [--repair] [--help] [--checksize] targetroot originalroot $0 is meant to check for or repair differences between a directory tree meant for inclusion in an Apple Mac OS X Installer package. When you turn a directory tree into an Installer package your (sub)directories may have different permissions from the original ones, because you have to create them yourself. Installing such a package may damage a Mac OS X System (say, if you remove permissions from a System directory through installing something in it). The following flags apply: --help, which displays this message and exits. --warnsize, which adds the display of warning message about directories that differ in size. --checksize, which checks that - for anything but directories - sizes should be equal. This is useful if your targetroot has in some way been created from your installroot and you want to check that everything is still ok. For instance, for some packages, first you install on your own system, then you create an installer package from your own system's installation. --repair, which turns on repairing the permission and ownership differences, which means, copying permissions from originalroot to targetroot. In normal mode, the program only reports the differences. --verbose, which makes the whole operation rather verbose (surprise!) Two arguments shoud be given. The first is the root of the installation this script is supposed to work on. The second is the root of the directory to which should be compared. The program dies if it encounters a senseless comparison, e.g. if the original is a file and the copy is a directory (or other type mismatches) or if the size differs for non-directories. Normal use: mkdir -p /tmp/installroot/usr/local/bin cp -R /usr/local/bin/myprog /tmp/installroot/usr/local/bin mkdir -p /tmp/installroot/Applications cp -R /Applications/MyProg.app /tmp/installroot/Applications $0 --repair /tmp/installroot / After which /tmp/installroot can be safely used as install root for an Installer package. NB. Use "cp -R" when copying files, even if you just copy normal files and not directories. This ensures that symbolic links are copied as is instead of followed (which would copy the original files and that is in most cases not what you want). cp has no way of copying symbolic links without also copying recursively. Whoever made that one up should be expelled from the society of information engineers. BTW. If you know how to do it, it is better to use cd/gnutar subshell commands than cp. This automatically keeps modes and ownership as is. $0 is also used by pkgcheck, see there for extra comments on the limits of $0. __HELP_INFO } walktree( $ARGV[0], $ARGV[1]); exit 0; sub walktree { my $copyornew = $_[0]; my $original = $_[1]; if (-d $copyornew and -d $original) { checkmod( "$copyornew", "$original"); warn "Walking tree $copyornew against $original\n" if $opt_verbose; opendir( DIR, $copyornew) || die "Can't open $copyornew"; my @filenames = readdir( DIR); closedir( DIR); for (@filenames) { next if $_ eq '.' or $_ eq '..'; chop( $copyornew) if $copyornew =~ /\/$/; chop( $original) if $original =~ /\/$/; if (-e "$original/$_") { # both files exist, thus: checkmod( "$copyornew/$_", "$original/$_"); if (-d "$copyornew/$_") { walktree( "$copyornew/$_", "$original/$_"); } } else { warn "File $original/$_ does not exist (OK)\n" if $opt_verbose; } } } else { &helpmsg; die "Target dir $copyornew and source dir $original should both exist\n"; } } sub checkmod { # This routine checks (and repairs) permission and ownership of files # and directories. Both files should exist if this routine is called my $copyornew = $_[0]; my $original = $_[1]; warn "Comparing $copyornew against $original\n" if $opt_verbose; my @copyornewstat = lstat( $copyornew); my @originalstat = lstat( $original); # Check type, die if they differ if (($copyornewstat[2]>>12) != ($originalstat[2]>>12)) { printf STDERR "Types differ between $copyornew (%s) and $original (%s)\n", $types{$copyornewstat[2] >> 12}, $types{$originalstat[2] >> 12}; &helpmsg; die "DANGER: Types differ, trees are incompatible. I cannot repair this.\n"; } # Check permissions, repair if asked for if (($copyornewstat[2]&07777) != ($originalstat[2]&07777)) { if ($opt_repair) { chmod( ($originalstat[2] & 07777), $copyornew); } else { printf STDERR "Mods differ between $copyornew (%04o) and $original (%04o)\n", $copyornewstat[2] & 07777, $originalstat[2] & 07777; } } # Check owner and group compatibility, repair if asked for if ($copyornewstat[4] != $originalstat[4] or $copyornewstat[5] != $originalstat[5]) { if ($opt_repair) { chown( $originalstat[4], $originalstat[5], $copyornew); } else { warn "Ownership differs between $copyornew ($copyornewstat[4].$copyornewstat[5]) and $original ($originalstat[4].$originalstat[5])\n"; } } if ($copyornewstat[7] != $originalstat[7]) { if ($opt_checksize and ($copyornewstat[2]>>12) != 4) { &helpmsg; die "DANGER: Copy $copyornew is not the same size as original\n"; } warn "Sizes differ between $copyornew ($copyornewstat[7]) and $original ($originalstat[7])\n" if $opt_warnsize; } }