#! /usr/bin/perl -w # # atool - A script for managing file archives of various types. # # Copyright (C) 2001 Oskar Liljeblad # # 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. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, # MA 02111-1307 USA # # See the atool(1) manual page for usage details. # # This file uses tab stops with width 2. # use File::Basename; use File::Spec; use Getopt::Long; use POSIX; use locale; use strict; # Subroutine prototypes (needed for perl 5.6) sub runcmds($$;@); # sub getmode(); # sub multiarchivecmd($$$$@); # sub singlearchivecmd($$$$@); # sub maketarcmd($$$$@); # sub cmdexec($@); # sub parsefmt($$); # sub makeoutdir(); # sub makeoutfile(); # sub explain($); # sub extract(@); # sub shquotemeta($); # sub tailslash($); # sub de($); # sub makespec(@); # sub backticks(@); # sub readconfig($$); # sub formatext($); # sub stripext($); # sub findformat($$); # sub unlink_directory($); # sub find_comparable_file($); # sub makeabsolute($); # sub quote($); # sub shell_execute(@); # sub save_outdir($); # sub handle_empty_add(@); # # Configuration options and their built-in defaults $::cfg_args_diff = '-ru'; # arguments to pass to diff program $::cfg_default_verbosity = 1; # default verbosity level $::cfg_keep_compressed = 1; # keep compressed file after pack/unpack $::cfg_path_arc = 'arc'; # arc program $::cfg_path_arj = 'arj'; # arj program $::cfg_path_bzip = 'bzip'; # bzip program $::cfg_path_bzip2 = 'bzip2'; # bzip2 program $::cfg_path_cat = 'cat'; # cat program $::cfg_path_compress = 'compress'; # compress program $::cfg_path_cpio = 'cpio'; # cpio program $::cfg_path_diff = 'diff'; # diff program $::cfg_path_file = 'file'; # file program $::cfg_path_find = 'find'; # find program $::cfg_path_gzip = 'gzip'; # gzip program $::cfg_path_jar = 'jar'; # jar program $::cfg_path_lha = 'lha'; # lha program $::cfg_path_lzop = 'lzop'; # lzop program $::cfg_path_nomarch = 'nomarch'; # nomarch program $::cfg_path_pager = 'pager'; # pager program $::cfg_path_rar = 'rar'; # rar program $::cfg_path_rpm = 'rpm'; # rpm program $::cfg_path_rpm2cpio = 'rpm2cpio'; # rpm2cpio program $::cfg_path_syscfg = '/etc/atool.conf'; # system-wide configuration file $::cfg_path_tar = 'tar'; # tar program $::cfg_path_unace = 'unace'; # unace program $::cfg_path_unarj = 'unarj'; # unarj program $::cfg_path_unrar = 'unrar'; # unrar program $::cfg_path_unzip = 'unzip'; # unzip program $::cfg_path_usercfg = '.atoolrc'; # user configuration file $::cfg_path_xargs = 'xargs'; # xargs program $::cfg_path_zip = 'zip'; # zip program $::cfg_show_extracted = 1; # always show extracted file/directory $::cfg_strip_unknown_ext = 1; # strip unknown extensions $::cfg_tmpdir_name = 'Unpack-%04d'; # extraction directory name $::cfg_use_arc_for_unpack = 0; # use arc to unpack arc files? $::cfg_use_arj_for_unpack = 0; # use arj to unpack arj files? $::cfg_use_file = 1; # use file(1) for unknown extensions? $::cfg_use_find_cpio_print0 = 1; # use -print0/-0 find/cpio options? $::cfg_use_gzip_for_z = 1; # use gzip to decompress .Z files? $::cfg_use_jar = 0; # use jar or zip for .jar archives? $::cfg_use_rar_for_unpack = 0; # use rar to unpack rar files? $::cfg_use_tar_bzip2_option = 1; # does tar support --bzip2? $::cfg_use_tar_z_option = 1; # does tar support -z? # Global variables $::basename = quote(File::Basename::basename($0)); @::rmdirs = (); $::up = File::Spec->updir(); $::cur = File::Spec->curdir(); # Parse arguments Getopt::Long::config('bundling'); Getopt::Long::GetOptions( 'l|list' => \$::opt_cmd_list, 'x|extract' => \$::opt_cmd_extract, 'X|extract-to=s' => \$::opt_cmd_extract_to, 'a|add' => \$::opt_cmd_add, 'c|cat' => \$::opt_cmd_cat, 'd|diff' => \$::opt_cmd_diff, 'r|repack' => \$::opt_cmd_repack, 'q|quiet' => sub { $::opt_verbosity--; }, 'v|verbose' => sub { $::opt_verbosity++; }, 'V|verbosity=i' => \$::opt_verbosity, 'config=s' => \$::opt_config, 'help' => \$::opt_cmd_help, 'version' => \$::opt_cmd_version, 'F|format=s' => \$::opt_format, 'f|force' => \$::opt_force, 'p|page' => \$::opt_use_pager, 'e|each' => \$::opt_each, 'E|explain' => \$::opt_explain, 'S|simulate' => \$::opt_simulate, 'save-outdir=s' => \$::opt_save_outdir, 'D|subdir' => \$::opt_extract_subdir, '0|null' => \$::opt_null, ) or exit 1; # Display --version if ($::opt_cmd_version) { print "atool 0.27.0\ Written by Oskar Liljeblad .\ \ Copyright (C) 2001-2003 Oskar Liljeblad.\ This is free software; see the source for copying conditions. There is NO\ warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"; exit; } # Display --help if ($::opt_cmd_help) { print "Usage: atool [OPTION]... ARCHIVE [FILE]...\ atool -e [OPTION]... [ARCHIVE]... Managing file archives of various types.\ \ Commands:\ -l, --list list files in archive (als)\ -x, --extract extract files from archive (aunpack)\ -X, --extract-to=PATH extract archive to specified directory\ -a, --add create archive (apack)\ -c, --cat extract file to standard out (acat)\ -d, --diff generate a diff between two archives (adiff)\ --help display this help and exit\ --version output version information and exit\ \ Options:\ -e, --each execute command above for each file specified -F, --format=EXT override archive format (see below)\ -D, --subdir always create subdirectory when extracting\ -f, --force allow overwriting of local files\ -q, --quiet decrease verbosity level by one\ -v, --verbose increase verbosity level by one\ -V, --verbosity=LEVEL specify verbosity (0, 1 or 2)\ -p, --page send output through pager\ -0, --null filenames from standard in are null-byte separated\ -E, --explain explain what is being done by atool\ -S, --simulate simulation mode - no filesystem changes are made\ --config=FILE load configuration defaults from file\ \ Archive format (for --format) may be specified either as a\ file extension (\"tar.gz\") or as \"tar+gzip\".\ \ Report bugs to Oskar Liljeblad .\ "; exit; } # Read configuration files if (defined $::opt_config) { readconfig($::opt_config, 0); } else { readconfig($::cfg_path_syscfg, 1); if ($::cfg_path_usercfg !~ /^\//) { readconfig(File::Spec->catfile($ENV{HOME}, $::cfg_path_usercfg), 1); } else { readconfig($::cfg_path_usercfg, 1); } } # Verify option integrity $::opt_verbosity += $::cfg_default_verbosity; if ($::opt_explain && $::opt_simulate) { die "$::basename: --explain and --simulate options are mutually exclusive\n"; #OK } my $mode = getmode(); if (defined $::opt_save_outdir && $mode eq 'extract-to') { die "$::basename: --save-outdir cannot be used in extract-to mode\n"; } if ($::opt_extract_subdir && $mode ne 'extract') { die "$::basename: --subdir can only be used in extract mode\n"; } if ($mode eq 'diff') { die "$::basename: missing archive argument\n" if (@ARGV < 2); #OK my $use_pager = $::opt_use_pager; $::opt_verbosity--; $::opt_use_pager = 0; my $outfile1 = makeoutdir() || exit 1; my $outfile2 = makeoutdir() || exit 1; $::opt_cmd_extract_to = $outfile1; exit 1 if (!runcmds('extract-to', $ARGV[0])); $::opt_cmd_extract_to = $outfile2; exit 1 if (!runcmds('extract-to', $ARGV[1])); my $match1 = find_comparable_file($outfile1); my $match2 = find_comparable_file($outfile2); my @cmd = ($::cfg_path_diff, split(/ /, $::cfg_args_diff), $match1, $match2); push @cmd, ['|'], get_pager_program() if $use_pager; my $allok = cmdexec(1, @cmd); foreach my $file ($outfile1,$outfile2) { if (-e $file && -d $file) { #if (-e $file) { #print "$::basename: remove `$file'? "; #select((select(STDOUT), $| = 1)[0]); #my $line = ; #if (defined $line && $line =~ /^y/) { #if (-d $file) { unlink_directory($file); #} else { #unlink $file; #} #} } } exit ($allok ? 0 : 1); } elsif ($mode eq 'repack') { #FIXME: what if --each! die "$::basename: missing archive arguments\n" if (@ARGV < 1); #OK die "$::basename: missing archive argument\n" if (@ARGV < 2); #OK die "$::basename: cannot repack to same archive\n" if ($ARGV[0] eq $ARGV[1] || File::Spec->canonpath($ARGV[0]) eq File::Spec->canonpath($ARGV[1])); die "$::basename: ".quote($ARGV[1]).": destination file exists\n" if -e $ARGV[1]; my $outdir = makeoutdir() || exit 1; $::opt_cmd_extract_to = $outdir; exit 1 if (!runcmds('extract-to', $ARGV[0])); #OK????? my $newarchive = File::Spec->catdir($::up, $ARGV[1]); chdir($outdir) || die "$::basename: ".quote($outdir).": cannot change to - $!\n"; #OK????? exit 1 if (!runcmds('add', $newarchive, $::cur)); chdir($::up) || die "$::basename: ".$::up.": cannot change to - $!\n"; #OK????? unlink_directory($outdir); } elsif ($::opt_each) { my $allok = 1; if ($mode eq 'cat') { die "$::basename: --each can not be used with cat or add command\n"; #OK } if ($mode eq 'add') { if (!defined $::opt_format) { die "$::basename: specify a format with -F when using --each in add mode\n"; } my $format = findformat($::opt_format, 1); for (my $c = 0; $c < @ARGV; $c++) { my $archive = File::Spec->canonpath($ARGV[$c]) . formatext($format); warn quote($archive).":\n" if $::opt_verbosity > 1; runcmds($mode, $archive, $ARGV[$c]) or $allok = 0; } } else { for (my $c = 0; $c < @ARGV; $c++) { warn quote($ARGV[$c]).":\n" if $::opt_verbosity > 1; runcmds($mode, $ARGV[$c]) or $allok = 0; } } exit ($allok ? 0 : 1); } else { die "$::basename: missing archive argument\n" if (@ARGV == 0); #OK runcmds($mode, shift @ARGV, @ARGV) || exit 1; } # runcmds(mode, archive, args) # Execute an atool command. This is where it all happens. # If mode is 'extract', returns the directory (or only file) # which was extracted. sub runcmds($$;@) { my ($mode, $archive, @args) = @_; my $format; if (defined $::opt_format) { $format = findformat($::opt_format, 1); } else { $format = findformat($archive, 0); } return undef if !defined $format; my @cmd; my $outdir; if ($format eq 'tar+bzip2') { return undef if ($mode eq 'extract' && !defined ($outdir = makeoutdir())); if ($::cfg_use_tar_bzip2_option) { push @cmd, maketarcmd($archive, $outdir, $mode, 'f', '--bzip2'), @args; } else { push @cmd, $::cfg_path_bzip2, '-cd', $archive, ['|'] if $mode ne 'add'; push @cmd, maketarcmd(undef, $outdir, $mode, ''), @args; push @cmd, ['|'], $::cfg_path_bzip2, ['>'], $archive if $mode eq 'add'; } @cmd = handle_empty_add(@cmd) if ($mode eq 'add' && @args == 0); return multiarchivecmd($archive, $outdir, $mode, 1, 0, \@args, @cmd); } elsif ($format eq 'tar+gzip') { return undef if ($mode eq 'extract' && !defined ($outdir = makeoutdir())); if ($::cfg_use_tar_z_option) { push @cmd, maketarcmd($archive, $outdir, $mode, 'zf'), @args; } else { push @cmd, $::cfg_path_gzip, '-cd', $archive, ['|'] if $mode ne 'add'; push @cmd, maketarcmd(undef, $outdir, $mode, ''), @args; push @cmd, ['|'], $::cfg_path_gzip, ['>'], $archive if $mode eq 'add'; } @cmd = handle_empty_add(@cmd) if ($mode eq 'add' && @args == 0); return multiarchivecmd($archive, $outdir, $mode, 1, 0, \@args, @cmd); } elsif ($format eq 'tar+bzip') { return undef if ($mode eq 'extract' && !defined ($outdir = makeoutdir())); push @cmd, $::cfg_path_bzip, '-cd', $archive, ['|'] if $mode ne 'add'; push @cmd, maketarcmd(undef, $outdir, $mode, ''), @args; push @cmd, ['|'], $::cfg_path_bzip, ['>'], $archive if $mode eq 'add'; @cmd = handle_empty_add(@cmd) if ($mode eq 'add' && @args == 0); return multiarchivecmd($archive, $outdir, $mode, 1, 0, \@args, @cmd); } elsif ($format eq 'tar+compress') { return undef if ($mode eq 'extract' && !defined ($outdir = makeoutdir())); if ($::cfg_use_gzip_for_z) { push @cmd, $::cfg_path_gzip, '-cd', $archive, ['|'] if $mode ne 'add'; } else { push @cmd, $::cfg_path_compress, '-cd', $archive, ['|'] if $mode ne 'add'; } push @cmd, maketarcmd(undef, $outdir, $mode, ''), @args; push @cmd, ['|'], $::cfg_path_compress, ['>'], $archive if $mode eq 'add'; @cmd = handle_empty_add(@cmd) if ($mode eq 'add' && @args == 0); return multiarchivecmd($archive, $outdir, $mode, 1, 0, \@args, @cmd); } elsif ($format eq 'tar+lzop') { return undef if ($mode eq 'extract' && !defined ($outdir = makeoutdir())); push @cmd, $::cfg_path_lzop, '-Ucd', $archive, ['|'] if $mode ne 'add'; push @cmd, maketarcmd(undef, $outdir, $mode, ''), @args; push @cmd, ['|'], $::cfg_path_lzop, ['>'], $archive if $mode eq 'add'; @cmd = handle_empty_add(@cmd) if ($mode eq 'add' && @args == 0); return multiarchivecmd($archive, $outdir, $mode, 1, 0, \@args, @cmd); } elsif ($format eq 'tar') { return undef if ($mode eq 'extract' && !defined ($outdir = makeoutdir())); push @cmd, maketarcmd($archive, $outdir, $mode, 'f'), @args; @cmd = handle_empty_add(@cmd) if ($mode eq 'add' && @args == 0); return multiarchivecmd($archive, $outdir, $mode, 1, 0, \@args, @cmd); } elsif ($format eq 'jar' && $::cfg_use_jar) { return undef if ($mode eq 'extract' && !defined ($outdir = makeoutdir())); my $opts = ''; if ($mode eq 'add') { warn "$::basename: ".quote($archive).": $mode command not supported for $format archives\n"; return undef; } $opts .= 'v' if $::opt_verbosity >= 1; push @cmd, $::cfg_path_jar; push @cmd, "x$opts", '-C', $outdir if $mode eq 'extract'; push @cmd, "x$opts", '-C', $::opt_cmd_extract_to if $mode eq 'extract-to'; push @cmd, "t$opts" if $mode eq 'list'; push @cmd, "c$opts" if $mode eq 'add'; push @cmd, $archive, @args; @cmd = handle_empty_add(@cmd) if ($mode eq 'add' && @args == 0); return multiarchivecmd($archive, $outdir, $mode, 1, 0, \@args, @cmd); } elsif ($format eq 'jar' || $format eq 'zip') { return undef if ($mode eq 'extract' && !defined ($outdir = makeoutdir())); if ($mode eq 'add') { push @cmd, $::cfg_path_zip, '-r'; } else { push @cmd, $::cfg_path_unzip; push @cmd, '-p' if $mode eq 'cat'; push @cmd, '-l' if $mode eq 'list'; push @cmd, '-d', $outdir if $mode eq 'extract'; push @cmd, '-d', $::opt_cmd_extract_to if $mode eq 'extract-to'; } push @cmd, '-v' if $::opt_verbosity > 1; push @cmd, '-qq' if $::opt_verbosity < 0; push @cmd, '-q' if $::opt_verbosity == 0; push @cmd, $archive, @args; @cmd = handle_empty_add(@cmd) if ($mode eq 'add' && @args == 0); return multiarchivecmd($archive, $outdir, $mode, 0, 0, \@args, @cmd); } elsif ($format eq 'rar') { return undef if ($mode eq 'extract' && !defined ($outdir = makeoutdir())); if ($mode eq 'add' || $::cfg_use_rar_for_unpack) { push @cmd, $::cfg_path_rar; } else { push @cmd, $::cfg_path_unrar; } push @cmd, 'a' if $mode eq 'add'; push @cmd, 'vt' if $mode eq 'list' && $::opt_verbosity >= 3; push @cmd, 'v' if $mode eq 'list' && $::opt_verbosity == 2; push @cmd, 'l' if $mode eq 'list' && $::opt_verbosity <= 1; push @cmd, 'x' if ($mode eq 'extract' || $mode eq 'extract-to'); push @cmd, '-ierr', 'p' if $mode eq 'cat'; push @cmd, '-r' if ($mode eq 'add'); push @cmd, $archive, @args; push @cmd, tailslash($outdir) if $mode eq 'extract'; push @cmd, tailslash($::opt_cmd_extract_to) if $mode eq 'extract-to'; @cmd = handle_empty_add(@cmd) if ($mode eq 'add' && @args == 0); return multiarchivecmd($archive, $outdir, $mode, 0, 0, \@args, @cmd); } elsif ($format eq 'lha') { return undef if ($mode eq 'extract' && !defined ($outdir = makeoutdir())); push @cmd, $::cfg_path_lha; push @cmd, 'a' if $mode eq 'add'; push @cmd, 'v' if $mode eq 'list' && $::opt_verbosity >= 3; push @cmd, 'l' if $mode eq 'list' && $::opt_verbosity == 2; push @cmd, 'lq' if $mode eq 'list' && $::opt_verbosity <= 1; push @cmd, 'x' if ($mode eq 'extract' || $mode eq 'extract-to'); push @cmd, 'p' if $mode eq 'cat'; push @cmd, $archive, @args; push @cmd, tailslash($outdir) if $mode eq 'extract'; push @cmd, tailslash($::opt_cmd_extract_to) if $mode eq 'extract-to'; @cmd = handle_empty_add(@cmd) if ($mode eq 'add' && @args == 0); return multiarchivecmd($archive, $outdir, $mode, 0, 0, \@args, @cmd); } elsif ($format eq 'ace') { return undef if ($mode eq 'extract' && !defined ($outdir = makeoutdir())); push @cmd, $::cfg_path_unace; if ($mode eq 'add' || $mode eq 'cat') { warn "$::basename: ".quote($archive).": $mode command not supported for $format archives\n"; return undef; } push @cmd, 'v', '-c' if $mode eq 'list' && $::opt_verbosity >= 3; push @cmd, 'v' if $mode eq 'list' && $::opt_verbosity == 2; push @cmd, 'l' if $mode eq 'list' && $::opt_verbosity <= 1; push @cmd, 'x' if ($mode eq 'extract' || $mode eq 'extract-to'); push @cmd, $archive, @args; push @cmd, tailslash($outdir) if $mode eq 'extract'; push @cmd, tailslash($::opt_cmd_extract_to) if $mode eq 'extract-to'; @cmd = handle_empty_add(@cmd) if ($mode eq 'add' && @args == 0); return multiarchivecmd($archive, $outdir, $mode, 0, 0, \@args, @cmd); } elsif ($format eq 'arj') { return undef if ($mode eq 'extract' && !defined ($outdir = makeoutdir())); if ($mode eq 'cat') { warn "$::basename: ".quote($archive).": $mode command not supported for $format archives\n"; return undef; } if ($mode eq 'add' || $::cfg_use_arj_for_unpack) { push @cmd, $::cfg_path_arj; push @cmd, 'a' if $mode eq 'add'; push @cmd, 'v' if $mode eq 'list' && $::opt_verbosity == 2; push @cmd, 'l' if $mode eq 'list' && $::opt_verbosity <= 1; push @cmd, 'x' if ($mode eq 'extract' || $mode eq 'extract-to'); push @cmd, $archive, @args; push @cmd, tailslash($outdir) if $mode eq 'extract'; push @cmd, tailslash($::opt_cmd_extract_to) if $mode eq 'extract-to'; @cmd = handle_empty_add(@cmd) if ($mode eq 'add' && @args == 0); return multiarchivecmd($archive, $outdir, $mode, 0, 0, \@args, @cmd); } else { push @cmd, $::cfg_path_unarj; # XXX: cat mode might work for arj archives, but it extract to stderr! push @cmd, 'v' if $mode eq 'list' && $::opt_verbosity == 2; push @cmd, 'l' if $mode eq 'list' && $::opt_verbosity <= 1; push @cmd, 'x' if ($mode eq 'extract' || $mode eq 'extract-to'); push @cmd, $archive if ($mode ne 'extract' && $mode ne 'extract-to');; # we call makeabsolute here because needcwd=1 to the multiarchivecmd call push @cmd, makeabsolute($archive) if ($mode eq 'extract' || $mode eq 'extract-to'); push @cmd, @args; @cmd = handle_empty_add(@cmd) if ($mode eq 'add' && @args == 0); return multiarchivecmd($archive, $outdir, $mode, 0, 1, \@args, @cmd); } } elsif ($format eq 'arc') { return undef if ($mode eq 'extract' && !defined ($outdir = makeoutdir())); if ($mode eq 'add' || $::cfg_use_arc_for_unpack) { push @cmd, $::cfg_path_arc; push @cmd, 'a' if $mode eq 'add'; push @cmd, 'v' if $mode eq 'list' && $::opt_verbosity >= 3; push @cmd, 'l' if $mode eq 'list' && $::opt_verbosity == 2; push @cmd, 'ln' if $mode eq 'list' && $::opt_verbosity <= 1; push @cmd, 'x' if ($mode eq 'extract' || $mode eq 'extract-to'); push @cmd, 'p' if $mode eq 'cat'; } else { push @cmd, $::cfg_path_nomarch; push @cmd, '-lvU' if $mode eq 'list' && $::opt_verbosity >= 2; push @cmd, '-lU' if $mode eq 'list' && $::opt_verbosity <= 1; push @cmd, '-p' if $mode eq 'cat'; } push @cmd, $archive if ($mode ne 'extract' && $mode ne 'extract-to'); # we call makeabsolute here because needcwd=1 to the multiarchivecmd call push @cmd, makeabsolute($archive) if ($mode eq 'extract' || $mode eq 'extract-to'); push @cmd, @args; @cmd = handle_empty_add(@cmd) if ($mode eq 'add' && @args == 0); return multiarchivecmd($archive, $outdir, $mode, 0, 1, \@args, @cmd); } elsif ($format eq 'rpm') { return undef if ($mode eq 'extract' && !defined ($outdir = makeoutdir())); if ($mode eq 'list') { push @cmd, $::cfg_path_rpm; push @cmd, '-qlp' if $mode eq 'list'; push @cmd, '-v' if $::opt_verbosity >= 1; push @cmd, $archive, @args; return multiarchivecmd($archive, $outdir, $mode, 0, 0, \@args, @cmd); } elsif ($mode eq 'extract' || $mode eq 'extract-to') { push @cmd, $::cfg_path_rpm2cpio; push @cmd, makeabsolute($archive); push @cmd, ['|']; push @cmd, $::cfg_path_cpio, '-imd', '--quiet', @args; return multiarchivecmd($archive, $outdir, $mode, 0, 1, \@args, @cmd); } else { # add and cat # FIXME: I guess cat could work too, but it would require that we # extracted to a temporary dir, read and printed it, then removed it. warn "$::basename: ".quote($archive).": $mode command not supported for $format archives\n"; return undef; } } elsif ($format eq 'cpio') { return undef if ($mode eq 'extract' && !defined ($outdir = makeoutdir())); if ($mode eq 'list') { push @cmd, $::cfg_path_cat, $archive, ['|']; push @cmd, $::cfg_path_cpio, '-t'; push @cmd, '-v' if $::opt_verbosity >= 1; return multiarchivecmd($archive, $outdir, $mode, 0, 0, \@args, @cmd); } elsif ($mode eq 'extract' || $mode eq 'extract-to') { push @cmd, $::cfg_path_cat, makeabsolute($archive), ['|']; push @cmd, $::cfg_path_cpio, '-i'; push @cmd, '-v' if $::opt_verbosity >= 1; return multiarchivecmd($archive, $outdir, $mode, 0, 1, \@args, @cmd); } elsif ($mode eq 'add') { if (@args == 0) { push @cmd, $::cfg_path_cpio; push @cmd, '-0' if $::opt_null; push @cmd, '-o'; push @cmd, '-v' if $::opt_verbosity >= 1; push @cmd, ['>'], $archive; } else { push @cmd, $::cfg_path_find, @args; push @cmd, '-print0' if $::cfg_use_find_cpio_print0; push @cmd, ['|'], $::cfg_path_cpio; push @cmd, '-0' if $::cfg_use_find_cpio_print0; push @cmd, '-o'; push @cmd, '-v' if $::opt_verbosity >= 1; push @cmd, ['>'], $archive; } return multiarchivecmd($archive, $outdir, $mode, 1, 1, \@args, @cmd); } else { # cat warn "$::basename: ".quote($archive).": $mode command not supported for $format archives\n"; return undef; } } elsif ($format eq 'bzip2') { return singlearchivecmd($archive, $::cfg_path_bzip2, $format, $mode, @args); } elsif ($format eq 'bzip') { return singlearchivecmd($archive, $::cfg_path_bzip, $format, $mode, @args); } elsif ($format eq 'gzip') { return singlearchivecmd($archive, $::cfg_path_gzip, $format, $mode, @args); } elsif ($format eq 'compress') { if ($::cfg_use_gzip_for_z && $mode ne 'add') { return singlearchivecmd($archive, $::cfg_path_gzip, $format, $mode, @args); } else { return singlearchivecmd($archive, $::cfg_path_compress, $format, $mode, @args); } } elsif ($format eq 'lzop') { return singlearchivecmd($archive, $::cfg_path_lzop, $format, $mode, '-U', @args); } return undef; } # de(value): # Return 1 if value defined and is non-zero, 0 otherwise. sub de($) { my ($value) = @_; return defined $value && $value ? 1 : 0; } # getmode() # Identify the execution mode, and return it. # Possible modes are 'cat', 'extract', 'list', 'add' or 'extract-to'. sub getmode() { my $mode; if (de($::opt_cmd_list) + de($::opt_cmd_cat) + de($::opt_cmd_extract) + de($::opt_cmd_add) + de($::opt_cmd_extract_to) + de($::opt_cmd_diff) + de($::opt_cmd_repack) > 1) { die "$::basename: only one command may be specified\n"; #OK } $mode = 'cat' if ($::basename eq 'acat'); $mode = 'extract' if ($::basename eq 'aunpack'); $mode = 'list' if ($::basename eq 'als'); $mode = 'add' if ($::basename eq 'apack'); $mode = 'diff' if ($::basename eq 'adiff'); $mode = 'repack' if ($::basename eq 'arepack'); $mode = 'add' if ($::opt_cmd_add); $mode = 'cat' if ($::opt_cmd_cat); $mode = 'list' if ($::opt_cmd_list); $mode = 'extract' if ($::opt_cmd_extract); $mode = 'extract-to' if ($::opt_cmd_extract_to); $mode = 'diff' if ($::opt_cmd_diff); $mode = 'repack' if ($::opt_cmd_repack); if (!defined $mode) { die "$::basename: don't know what to do - no command specified\n"; #OK } return $mode; } # singlearchivecmd(archive, command, format, mode, args) # Execute a command for single-file archives. # The command parameter specifies what command to execute. # If mode is 'extract-to', returns the directory (or only file) # which was extracted. sub singlearchivecmd($$$$@) { my ($archive, $cmd, $format, $mode, @args) = @_; if ($mode eq 'add' && @ARGV > 1) { warn "$::basename: cannot add more than one file with this format\n"; return; } if ($mode eq 'add' && !$::opt_force && (-e $archive || -l $archive)) { warn "$::basename: ".quote($archive).": refusing to overwrite existing file\n"; return; } my $outfile; if ($mode eq 'extract-to') { $outfile = $::opt_cmd_extract_to; # undef if not extract-to if (-d $outfile) { my $base = File::Basename::basename($archive); $outfile = File::Spec->catfile($outfile, stripext($base)); } } elsif ($mode eq 'extract') { $outfile = stripext($archive); } if ($mode eq 'list') { warn "$::basename: ".quote($archive).": $mode command not supported for $format archives\n"; return; } if ($mode eq 'add' && !$::cfg_keep_compressed && stripext($archive) ne $args[0]) { warn "$::basename: ".quote($archive).": cannot create a $format archive with this name (use -X)\n"; return; } if ($mode eq 'extract' && -e $outfile) { $outfile = makeoutfile(); } my @cmd; push @cmd, $cmd; push @cmd, '-v' if $::opt_verbosity > 1; if ($::cfg_keep_compressed) { if ($mode eq 'add') { push @cmd, '-c', @args, ['>'], $archive; } elsif ($mode eq 'cat') { push @cmd, '-c', '-d', $archive, @args; } elsif ($mode eq 'extract' || $mode eq 'extract-to') { push @cmd, '-c', '-d', $archive, @args, ['>'], $outfile; } } else { if ($mode eq 'add') { push @cmd, @args; } elsif ($mode eq 'cat') { push @cmd, '-c', '-d', $archive, @args; } elsif ($mode eq 'extract') { push @cmd, '-d', $archive, @args; } elsif ($mode eq 'extract-to') { push @cmd, '-c', '-d', $archive, @args, ['>'], $outfile; } } push @cmd, ['|'], get_pager_program() if $::opt_use_pager; cmdexec(0, @cmd) || return; if (($mode eq 'extract' || $::cfg_show_extracted) && !$::opt_simulate) { my $archivebase = File::Basename::basename($archive); warn quote($archivebase).": extracted to `".quote($outfile)."'\n"; } return $outfile; } # maketarcmd(opts): # Create (partial) command line arguments for a tar command. # The parameter opts specifies additional arguments to add. sub maketarcmd($$$$@) { my ($archive, $outdir, $mode, $opts, @rest) = @_; $opts .= 'v' if $::opt_verbosity >= 1; my @cmd = ($::cfg_path_tar); push @cmd, "xO$opts" if $mode eq 'cat'; push @cmd, "x$opts" if ($mode eq 'extract' || $mode eq 'extract-to'); push @cmd, "t$opts" if $mode eq 'list'; push @cmd, "c$opts" if $mode eq 'add'; push @cmd, $archive if defined $archive; push @cmd, '-C', $outdir if $mode eq 'extract'; push @cmd, '-C', $::opt_cmd_extract_to if $mode eq 'extract-to'; push @cmd, @rest; return @cmd; } # cmdexec(ignore_return, cmdspec) # Execute a command specification. # The cmdspec parameter is a list of string arguments building # the command line. If there's a list reference instead of a # string, it is a shell meta character/string which shouldn't # be quoted. sub cmdexec($@) { my ($ignret, @cmd) = @_; if ($::opt_explain || $::opt_simulate) { my $spec = join(' ', map { ref $_ ? @{$_} : shquotemeta $_ } @cmd); explain quote($spec)."\n"; return 1 if ($::opt_simulate); } my $cmds = makespec(@cmd); if (!shell_execute(@cmd)) { warn "$::basename: ".quote($cmds).": cannot execute - $::errmsg\n"; return 0; } if ($? & 0xFF != 0) { warn "$::basename: ".quote($cmds).": abnormal exit (exit code $?)\n"; return 0; } if (!$ignret && $? >> 8 != 0) { warn "$::basename: ".quote($cmds).": non-zero return-code\n"; return 0; } return 1; } # makespec(@) # Make a command specification when printing errors. sub makespec(@) { my (@cmd) = @_; my $spec = $cmd[0].' ..'; my $lastref = 0; foreach (@cmd, '') { if ($lastref) { $spec .= " | $_ .."; $lastref = 0; } $lastref = 1 if (ref); } return $spec; } # makeoutfile() # Make a unique output file for extraction command. sub makeoutfile() { my $file; do { $file = sprintf $::cfg_tmpdir_name, int rand 10000; } while (-e $file); return $file; } # makeoutdir() # Make a temporary (unique) output directory for extraction command. sub makeoutdir() { my $dir; do { $dir = sprintf $::cfg_tmpdir_name, int rand 10000; } while (-e $dir); if (!$::opt_simulate) { if (!mkdir($dir, 0700)) { warn "$::basename: ".quote($dir).": cannot create directory - $!\n"; return undef; } push @::rmdirs, $dir; } return $dir; } # explain($) # Print on screen if $::opt_explain is true. sub explain($) { my ($msg) = @_; print STDERR $msg if ($::opt_explain || $::opt_simulate); } # tailslash($) # If specified filename does not end with a slash, # add one and return the new filename. sub tailslash($) { my ($file) = @_; return ($file =~ /\/$/ ? $file : "$file/"); } # shquotemeta($) # A more sophisticated quotemeta for bourne shells. # (This should be used for printing only.) sub shquotemeta($) { my ($str) = @_; $str =~ s/([^A-Za-z0-9_.+,\/:=@%^-])/\\$1/g; return $str; } # multiarchivecmd(archive, outdir, mode, create, argref, cmdspec) # Execute a command for multi-file archives. # The `create' argument controls whether the archive # will be created (1) or just added to (0) if mode is "add". # If mode is 'extract', returns the directory (or only file) # which was extracted. # If needcwd is true, the outdir must be changed to. sub multiarchivecmd($$$$@) { my ($archive, $outdir, $mode, $create, $needcwd, $argref, @cmd) = @_; my @args = @{$argref}; if ($mode eq 'cat' && @args == 0) { die "$::basename: missing file argument\n"; #OK } if ($mode eq 'add' && $create && !$::opt_force && (-e $archive || -l $archive)) { warn "$::basename: ".quote($archive).": refusing to overwrite existing file\n"; return undef; } push @cmd, ['|'], get_pager_program() if $::opt_use_pager; my $olddir = undef; if ($needcwd && !$::opt_simulate) { $olddir = getcwd(); if ($mode eq 'extract' && !chdir($outdir)) { warn "$::basename: ".quote($outdir).": cannot change to - $!\n"; return undef; } if ($mode eq 'extract-to' && !chdir($::opt_cmd_extract_to)) { warn "$::basename: ".quote($::opt_cmd_extract_to).": cannot change to - $!\n"; return undef; } } if ($mode ne 'extract') { cmdexec(0, @cmd) || return undef; if (defined $olddir && !chdir($olddir)) { warn "$::basename: ".quote($olddir).": cannot change to - $!\n"; return undef; } # XXX: can't save outdir with extract-to. return 1; } if (!cmdexec(0, @cmd)) { if (defined $olddir && !chdir($olddir)) { warn "$::basename: ".quote($olddir).": cannot change to - $!\n"; } return undef; } return undef if $::opt_simulate; if (defined $olddir && !chdir($olddir)) { warn "$::basename: ".quote($olddir).": cannot change to - $!\n"; return undef; } if (!opendir(DIR, $outdir)) { warn "$::basename: ".quote($outdir).": cannot list - $!\n"; return undef; } my @files = grep !/^\.\.?$/, readdir DIR; closedir DIR; my $archivebase = File::Basename::basename($archive); my $reason; my $adddir = 0; if (@files == 0) { warn quote($archivebase).": archive is empty\n"; rmdir $outdir; return undef; } elsif ($::opt_extract_subdir) { $reason = 'forced'; } elsif (@files == 1) { my $fromfile = File::Spec->catfile($outdir, $files[0]); my $tofile = $files[0]; unless ($::opt_force) { # Find a filename to unpack to that doesn't exist my $suffix = 1; my $try = $tofile; $try = "$tofile." . $suffix++ until !-l $try && !-e _; # We assume it will find a suitable filename eventually... $tofile = $try; } # If the file is a directory, it can only be moved if writable my $oldmode = undef; if (!-l $fromfile && -d $fromfile) { my @statinfo = stat($fromfile); if (!@statinfo) { warn quote($fromfile).": cannot get file info - $!\n"; return undef; } $oldmode = $statinfo[2]; if (!chmod(0700, $fromfile)) { warn quote($fromfile).": cannot change mode - $!\n"; return undef; } } if (!rename $fromfile, $tofile) { warn quote($fromfile).": cannot rename - $!\n"; return undef; } rmdir $outdir; # If we changed mode previously, restore that mode now if (defined $oldmode) { if (!chmod($oldmode, $tofile)) { warn quote($tofile).": cannot change mode - $!\n"; return undef; } } if ($::cfg_show_extracted) { my $file = ($tofile =~ /\// ? dirname($tofile) : $tofile); warn quote($archivebase).": extracted to `".quote($file)."'\n" ; } save_outdir($tofile); return $tofile; } else { $reason = 'multiple files in root'; } my $localoutdir = stripext($archivebase); if (!-e $localoutdir) { if (!rename $outdir, $localoutdir) { warn quote($outdir).": cannot rename - $!\n"; return undef; } $outdir = $localoutdir; } warn quote($archivebase).": extracted to `".quote($outdir)."' ($reason)\n"; save_outdir($adddir ? File::Spec->catfile($outdir, $files[0]) : $outdir); return $outdir; } # stripext(file) # Strip extension from the specified file. sub stripext($) { my ($file) = @_; return $file if ($file =~ s/(\.tar\.bz2|\.tbz2)$//); return $file if ($file =~ s/(\.tar\.bz|\.tbz)$//); return $file if ($file =~ s/(\.tar\.gz|\.tgz)$//); return $file if ($file =~ s/(\.tar\.Z|\.tZ)$//); return $file if ($file =~ s/\.tar$//); return $file if ($file =~ s/\.bz2$//); return $file if ($file =~ s/\.bz$//); return $file if ($file =~ s/\.gz$//); return $file if ($file =~ s/\.zip$//); return $file if ($file =~ s/\.jar$//); return $file if ($file =~ s/\.war$//); return $file if ($file =~ s/\.Z$//); return $file if ($file =~ s/\.rar$//); return $file if ($file =~ s/\.(lha|lzh)$//); return $file if ($file =~ s/\.ace$//); return $file if ($file =~ s/\.arj$//); return $file if ($file =~ s/\.rpm$//); return $file if ($file =~ s/\.cpio$//); return $file if ($::cfg_strip_unknown_ext && $file =~ s/\.[^.]+$//); return $file; } # formatext(format) # Return the usual extension for the specified file format sub formatext($) { my ($format) = @_; return '.tar.lzo' if $format eq 'tar+lzop'; return '.tar.bz2' if $format eq 'tar+bzip2'; return '.tar.bz' if $format eq 'tar+bzip'; return '.tar.gz' if $format eq 'tar+gzip'; return '.tar.Z' if $format eq 'tar+compress'; return '.tar' if $format eq 'tar'; return '.bz2' if $format eq 'bzip2'; return '.bz' if $format eq 'bzip'; return '.gz' if $format eq 'gzip'; return '.lzo' if $format eq 'lzop'; return '.zip' if $format eq 'zip'; return '.jar' if $format eq 'jar'; return '.Z' if $format eq 'compress'; return '.rar' if $format eq 'rar'; return '.ace' if $format eq 'ace'; return '.arj' if $format eq 'arj'; return '.lha' if $format eq 'lha'; return '.rpm' if $format eq 'rpm'; return '.cpio' if $format eq 'cpio'; die "$::basename: ".quote($format).": don't know file extension for format\n"; } # findformat(spec, manual) # Figure out format from specified file/string. # If manual is 0, spec is a filename, otherwise # it is a format description string. sub findformat($$) { my ($file, $manual) = @_; my $spec = lc $file; my @fileoutput = ( ['tar+bzip2', qr/^(GNU|POSIX) tar archive \(bzip2 compressed data(\W|$)/], ['tar+gzip', qr/^(GNU|POSIX) tar archive \(gzip compressed data(\W|$)/], ['tar+bzip', qr/^(GNU|POSIX) tar archive \(bzip compressed data(\W|$)/], ['tar+compress', qr/^(GNU|POSIX) tar archive \(compress'd data(\W|$)/], ['tar', qr/^(GNU|POSIX) tar archive(\W|$)/], ['zip', qr/^Zip archive data(\W|$)/], ['rar', qr/^RAR archive data(\W|$)/], ['lha', qr/^LHa \(2\.x\) archive data /], ['lha', qr/^LHa 2\.x\? archive data /], ['lha', qr/^LHarc 1\.x archive data /], ['arj', qr/^ARJ archive data(\W|$)/], ['arc', qr/^ARC archive data(\W|$)/], ['cpio', qr/^cpio archive$/], ['cpio', qr/^ASCII cpio archive /], ['rpm', qr/^RPM v/], ['bzip2', qr/ \(bzip2 compressed data(\W|$)/], ['bzip', qr/ \(bzip compressed data(\W|$)/], ['gzip', qr/ \(gzip compressed data(\W|$)/], ['compress', qr/ \(compress'd data(\W|$)/], ['lzop', qr/^lzop compressed data /], #['bzip2', qr/^bzip2 compressed data(\W|$)/], #['bzip', qr/^bzip compressed data(\W|$)/], #['gzip', qr/^gzip compressed data(\W|$)/], #['compress', qr/^compress'd data(\W|$)/], ); my @fileextensions = ( ['tar+bzip', qr/(\.tar\.bz|\.tbz)$/], ['tar+bzip2', qr/(\.tar\.bz2|\.tbz2)$/], ['tar+compress', qr/(\.tar\.[zZ]|\.t[zZ])$/], ['tar+gzip', qr/(\.tar\.gz|\.tgz)$/], ['tar+lzop', qr/(\.tar\.lzo|\.tzo)$/], ['arc', qr/\.arc$/], ['ace', qr/\.ace$/], ['arj', qr/\.arj$/], ['bzip', qr/\.bz$/], ['bzip2', qr/\.bz2$/], ['compress', qr/\.[zZ]$/], ['cpio', qr/\.cpio$/], ['gzip', qr/\.gz$/], ['jar', qr/\.(jar|war)$/], ['lha', qr/\.(lha|lzh)$/], ['lzop', qr/\.lzo$/], ['rar', qr/\.rar$/], ['rpm', qr/\.rpm$/], ['tar', qr/\.tar$/], ['zip', qr/\.zip$/], ); if ($manual) { $spec =~ tr/+/./; $spec =~ s/^\.*/\./; $spec =~ s/lzop/lzo/; $spec =~ s/bzip2/bz2/; $spec =~ s/bzip/bz/; $spec =~ s/gzip/gz/; $spec =~ s/compress/Z/; } foreach my $formatinfo (@fileextensions) { my ($format, $regex) = @{$formatinfo}; return $format if ($spec =~ $regex); } if (!$manual && $::cfg_use_file) { if (!-e $file) { warn "$::basename: ".quote($file).": no such file and cannot identify format from extension\n"; return; } if (!sysopen(TMP, $file, O_RDONLY)) { warn "$::basename: ".quote($file).": cannot open - $!\n"; return; } close TMP; if (!-f $file) { warn "$::basename: ".quote($file).": not a regular file\n"; return; } if ($::opt_verbosity >= 1) { warn "$::basename: ".quote($file).": format not known, identifying using file\n"; } my @cmd = ($::cfg_path_file, '-b', '-L', '-z', '--', $file); $spec = backticks(@cmd); if (!defined $spec) { warn "$::basename: $::errmsg\n"; return; } if ($? & 0xFF != 0) { warn "$::basename: ".quote($::cfg_path_file).": abnormal exit\n"; return; } if ($? >> 8 != 0) { warn "$::basename: ".quote($file).": unknown file format\n"; return; } chomp $spec; foreach my $formatinfo (@fileoutput) { my ($format, $regex) = @{$formatinfo}; if ($spec =~ $regex) { warn "$::basename: ".quote($file).": format is `$format'\n" if $::opt_verbosity >= 1; return $format; } } warn "$::basename: ".quote($file).": unsupported file format `$spec'\n"; return; } warn "$::basename: ".quote($file).": unrecognized file format\n"; return; } # backticks(cmdargs, ..) # An implementation of the backtick (qx//) operator. # The difference is that command STDERR output will still # be printed on STDERR, and the shell isn't used to parse # the command line. sub backticks(@) { if (!pipe(IN,OUT)) { $::errmsg = "pipe failed - $!"; return; } my $child = fork; if (!defined $child) { $::errmsg = "fork failed - $!"; return; } if ($child == 0) { close IN || exit 1; close STDOUT || exit 1; open(STDOUT, '>&OUT') || exit 1; close OUT || exit 1; $SIG{__WARN__} = sub {}; exec(@_) || exit 1; } close OUT; my $text = join('', ); close IN; if (waitpid($child,0) != $child) { $::errmsg = "waitpid failed - $!"; return; } return $text; } # readconfig(file) # Read and parse the specified configuration file. # If the file does not exist, just return. # If there is an error in the configuration file, # the program will be terminated. This could be a # problem when there are errors in the system-wide # configuration file. sub readconfig($$) { my ($file, $failok) = @_; my %optionmap = ( 'args_diff' => \$::cfg_args_diff, 'default_verbosity' => \$::cfg_default_verbosity, 'keep_compressed' => \$::cfg_keep_compressed, 'path_arc' => \$::cfg_path_arc, 'path_arj' => \$::cfg_path_arj, 'path_bzip' => \$::cfg_path_bzip, 'path_bzip2' => \$::cfg_path_bzip2, 'path_cat' => \$::cfg_path_cat, 'path_compress' => \$::cfg_path_compress, 'path_cpio' => \$::cfg_path_cpio, 'path_diff' => \$::cfg_path_diff, 'path_file' => \$::cfg_path_file, 'path_find' => \$::cfg_path_find, 'path_gzip' => \$::cfg_path_gzip, 'path_jar' => \$::cfg_path_jar, 'path_lha' => \$::cfg_path_lha, 'path_lzop' => \$::cfg_path_lzop, 'path_nomarch' => \$::cfg_path_nomarch, 'path_pager' => \$::cfg_path_pager, 'path_rar' => \$::cfg_path_rar, 'path_rpm' => \$::cfg_path_rpm, 'path_rpm2cpio' => \$::cfg_path_rpm2cpio, 'path_tar' => \$::cfg_path_tar, 'path_unace' => \$::cfg_path_unace, 'path_unarj' => \$::cfg_path_unarj, 'path_unrar' => \$::cfg_path_unrar, 'path_unzip' => \$::cfg_path_unzip, 'path_usercfg' => \$::cfg_path_usercfg, 'path_xargs' => \$::cfg_path_xargs, 'path_zip' => \$::cfg_path_zip, 'show_extracted' => \$::cfg_show_extracted, 'strip_unknown_ext' => \$::cfg_strip_unknown_ext, 'tmpdir_name' => \$::cfg_tmpdir_name, 'use_arc_for_unpack' => \$::cfg_use_arc_for_unpack, 'use_arj_for_unpack' => \$::cfg_use_arc_for_unpack, 'use_file' => \$::cfg_use_file, 'use_find_cpio_print0' => \$::cfg_use_find_cpio_print0, 'use_gzip_for_z' => \$::cfg_use_gzip_for_z, 'use_jar' => \$::cfg_use_jar, 'use_rar_for_unpack' => \$::cfg_use_rar_for_unpack, 'use_rar_for_unrar' => [ 'use_rar_for_unpack', \$::cfg_use_rar_for_unpack ], 'use_tar_bzip2_option' => \$::cfg_use_tar_bzip2_option, 'use_tar_j_option' => [ 'use_tar_bzip2_option', \$::cfg_use_tar_bzip2_option ], 'use_tar_z_option' => \$::cfg_use_tar_z_option, ); return if ($failok && !-e $file); sysopen(FILE, $file, O_RDONLY) || die "$::basename: ".quote($file).": cannot open for reading - $!\n"; #OK while () { chomp; next if /^\s*(#(.*))?$/; my ($var,$val) = /^(.*?)\s+([^\s].*)$/; my $varref = undef; if (exists $optionmap{$var}) { if (ref $optionmap{$var} eq 'ARRAY') { my ($newopt,$newref) = @{$optionmap{$var}}; warn quote($file).": $var is obsolete (use $newopt)\n"; ${$newref} = $val; } else { ${$optionmap{$var}} = $val; } } else { die "$::basename: ".quote($file).":$.: unrecognized directive\n"; } } close(FILE); } # Remove a directory recursively. This function used to change # the mode on the directories is traverses, but I now consider # that to be unsafe (what if there's a bug in atool and it # removes a file it shouldn't?). sub unlink_directory($) { my ($dir) = @_; die "$::basename: internal error 1 - please report this bug\n" if ($dir eq '/' || $dir eq $ENV{HOME}); # chmod 0700, $dir || die "$::basename: cannot chmod `".quote($dir)."': $!\n"; chdir $dir || die "$::basename: ".quote($dir).": cannot change to - $!\n"; opendir(DIR, $::cur) || die "$::basename: ".quote($dir).": cannot list - $!\n"; my @files = readdir(DIR); closedir(DIR); foreach my $file (@files) { next if $file eq $::cur || $file eq $::up; if (!-d $file) { unlink $file || die "$::basename: ".quote($file).": cannot remove - $!\n"; } else { unlink_directory($file); } } chdir $::up || die "$::basename: $::up: cannot change to - $!\n"; rmdir $dir || die "$::basename: ".quote($dir).": cannot remove - $!\n"; } # find_comparable_file(dir) # Assuming that the contents of some archive has been extracted to dir, # this function will determine the main file or directory in this # archive - the file or directory which will be compared when this # archive is compared to some other. sub find_comparable_file($) { my ($dir) = @_; my $result = $dir; if (opendir(DIR, $dir)) { my (@files) = map { readdir(DIR) } 0..3; if (@files == 3 && $files[0] eq $::cur && $files[1] eq $::up) { $result = File::Spec->catfile($dir, $files[2]); } closedir(DIR); } return $result; } # makeabsolute(file) # Return the absolute version of file. sub makeabsolute($) { my ($file) = @_; return $file if (substr($file, 0, 1) eq '/'); return File::Spec->catfile(getcwd(), $file); } # quote(string) # Quote a style like the GNU fileutils would do (`locale' # quoting style). sub quote($) { my ($in) = @_; my $out = ''; for (my $c = 0; $c < length($in); $c++) { my $ch = substr($in, $c, 1); if ($ch eq "\b") { $out .= "\\b"; } elsif ($ch eq "\f") { $out .= "\\f"; } elsif ($ch eq "\n") { $out .= "\\n"; } elsif ($ch eq "\r") { $out .= "\\r"; } elsif ($ch eq "\t") { $out .= "\\t"; } elsif (ord($ch) == 11) { # Vertical Tab, \v $out .= "\\v"; } elsif ($ch eq "\\") { $out .= "\\\\"; } elsif ($ch eq "'") { $out .= "\\'"; } elsif (!POSIX::isprint($ch)) { $out .= sprintf('\\%03o', ord($ch)); } else { $out .= $ch; } } return $out; } # shell_execute(@) # Execute a command with pipes and output redirection like the # shell does. Only difference is we do it without the shell. # This reason for this is because we don't have to quote # meta-characters - some meta-characters like LF and DEL are # unquotable! sub shell_execute(@) { my (@cmdspec) = @_; my @cmds = (); my $start = 0; my $redir_out = undef; for (my $c = 0; $c < @cmdspec; $c++) { if (ref $cmdspec[$c]) { push @cmds, [ @cmdspec[$start..$c-1] ]; $start = $c+1; $redir_out = $cmdspec[$c+1] if (${$cmdspec[$c]}[0] eq '>'); } } push @cmds, [ @cmdspec[$start..$#cmdspec] ] if !defined $redir_out; $SIG{INT} = 'IGNORE'; my @ip = (); my @op = (); my @children = (); for (my $c = 0; $c <= $#cmds; $c++) { if ($c != $#cmds) { @op = reverse POSIX::pipe(); if (!@op || !defined $op[0] || !defined $op[1]) { $::errmsg = "pipe failed - $!"; return 0; } } if ($c == $#cmds && defined $redir_out) { @_ = (); # XXX: necessary to overcome POSIX autoload bug! @op = (POSIX::open($redir_out, &POSIX::O_WRONLY | &POSIX::O_CREAT)); if (!@op || !defined $op[0]) { $::errmsg = quote($redir_out).": cannot open for writing - $!"; return 0; } } my $pid = fork(); die "fork failed - $!\n" if !defined $pid; if ($pid == 0) { $SIG{INT} = ''; if (@ip) { die "dup2 failed - $!\n" if POSIX::dup2($ip[1], 0) < 0; POSIX::close($_) foreach (@ip); } if (@op) { die "dup2 failed - $!\n" if POSIX::dup2($op[0], 1) < 0; POSIX::close($_) foreach (@op); } exec(@{$cmds[$c]}) || die ${$cmds[$c]}[0].": cannot execute - $!\n"; } POSIX::close($op[0]) if ($c == $#cmds && defined $redir_out); POSIX::close($_) foreach (@ip); @ip = @op; @op = (); push @children, $pid; } foreach (@children) { if (waitpid($_,0) < 0) { $::errmsg = "waitpid failed - $!"; return 0; } } $SIG{INT} = ''; return 1; } # Write dir to file indicated by $::opt_save_outdir. # sub save_outdir($) { my ($dir) = @_; if (defined $::opt_save_outdir && !-l $dir && -d $dir) { if (!sysopen(TMP, $::opt_save_outdir, O_WRONLY)) { warn die "$::basename: ".quote($::opt_save_outdir).": cannot open for writing - $!\n"; } else { print TMP $dir, "\n"; close(TMP); } } } # Somewhat stupid subroutine to add xargs to the command line. # sub handle_empty_add(@) { my @cmd = @_; unshift @cmd, '--'; unshift @cmd, '-0' if ($::opt_null); unshift @cmd, $::cfg_path_xargs; return @cmd; } # Return a suitable pager command # sub get_pager_program { return $ENV{PAGER} if (exists $ENV{PAGER}); return $::cfg_path_pager; } sub END { map (rmdir, @::rmdirs) if !$::opt_simulate; # Errors are ignored }