home *** CD-ROM | disk | FTP | other *** search
/ Acorn RISC PD-CD 1 / Acorn RISC PD-CD 1.iso / languages / perl / examples / extar next >
Encoding:
Text File  |  1991-11-17  |  11.5 KB  |  496 lines

  1. #!/usr/perl
  2.  
  3. # Get the standard option parser
  4. require 'Getopts';
  5.  
  6. # Get the personalised file name mangler (if any)
  7. eval "require 'Personal'" || eval <<'END';
  8. sub personalise    { $_[0]; }
  9. sub preformat    { $_[0]; }
  10. END
  11.  
  12. # Usage is
  13. #    Tar [-tvx] [-L logfile] -f Tarfile
  14. &Getopts('f:L:tvx');
  15.  
  16. die "No tar file specified\n" unless $opt_f ne '';
  17. $tar = $opt_f;
  18. $logfile = $opt_L;
  19.  
  20. # Initialisation
  21. # --------------
  22. #
  23. # Tar file block size, and header format.
  24. # Array of month names, for date conversion.
  25.  
  26. @months = ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
  27.        'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec');
  28.  
  29. $blocksize = 512;
  30. $template = 'A100 A8 A8 A8 A12 A12 A8 a1 A100 A8 A32 A32 A8 A8';
  31.  
  32. # Pre-allocate file block buffers
  33. $header = "\0" x $blocksize + 1;
  34. $block  = "\0" x $blocksize + 1;
  35.  
  36. # In formatted output, break on slashes (see report_file).
  37. $: = '/';
  38.  
  39. # Main processing
  40. # ---------------
  41.  
  42. open(TAR,$tar) || die "Cannot open $tar: $!\n";
  43.  
  44. if ($logfile ne '')
  45. {
  46.     open(LOG,">$logfile") || die "Cannot open log file $logfile: $!\n";
  47. }
  48.  
  49. FILE: {
  50.     $bytes = read(TAR,$header,$blocksize);
  51.     die "Tar: Header block too short\n" unless ($bytes == $blocksize);
  52.  
  53.     # A null header block marks the end
  54.     last FILE if $header eq "\0" x $blocksize;
  55.  
  56.     # Decode the information in the file header
  57.     &process_header();
  58.  
  59.     # If we are to produce a table of contents, do so now.
  60.     &report_file() if $opt_t;
  61.  
  62.     # Open the output file
  63.     &open_file($name, $type) if $opt_x;
  64.  
  65.     # Skip through the file data blocks
  66.     while ($size > 0)
  67.     {
  68.         $bytes = read(TAR, $block, $blocksize);
  69.         die "End of file during file $name\n" if $bytes < $blocksize;
  70.  
  71.         # Write the data block to the output file
  72.         &write_file($block,$bytes,$size) if $opt_x;
  73.  
  74.         # Keep track of the number of bytes still to read
  75.         $size -= $bytes;
  76.     }
  77.  
  78.     # Close the output file
  79.     &close_file() if $opt_x;
  80.  
  81.     # Next file
  82.     redo FILE;
  83. }
  84.  
  85. close LOG if ($logfile ne '');
  86.  
  87. # Header block processing. Perform some validity checks, and decode the
  88. # header fields into global variables for later use. Also checks that
  89. # the header checksum is valid.
  90.  
  91. sub process_header
  92. {
  93.     # Store the header fields in global variables.
  94.     ($name, $mode, $uid, $gid, $size, $mtime, $check, $type, $link,
  95.      $magic, $uname, $gname, $major, $minor) = unpack($template,$header);
  96.  
  97.     # Check the format of the various fields
  98.     $mode  = &oct($mode,'mode');
  99.     $uid   = &oct($uid,'user id');
  100.     $gid   = &oct($gid,'group id');
  101.     $size  = &oct($size,'size');
  102.     $mtime = &oct($mtime,'modification time');
  103.     $check = &oct($check,'checksum');
  104.     $major = &oct($major,'major device');
  105.     $minor = &oct($minor,'minor device');
  106.  
  107.     # Test the checksum
  108.     substr($header, 148, 8) = ' ' x 8;
  109.     $checksum = unpack('%32C*', $header);
  110.     die "Invalid checksum for file $name header\n" if $check != $checksum;
  111. }
  112.  
  113. # Report the details of the current file
  114.  
  115. format =
  116. @<<<<<<<<< @>>>>>>>> @>>>>>>> @<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
  117. $modestr,  $ids,     $sz,     $date,            $temp_name
  118. ~~                                              ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
  119.                                                 $temp_name
  120. .
  121.  
  122. sub report_file
  123. {
  124.     print($name,"\n"), return unless $opt_v;
  125.  
  126.     # Format the user and group IDs
  127.     local($ids) = sprintf("%d/%d",$uid,$gid);
  128.  
  129.     # Format the date for output
  130.     local ($sec,$min,$hr,$dd,$mon,$yy) = localtime($mtime);
  131.     local ($mm) = $months[$mon];
  132.  
  133.     $yy += 1900;
  134.     local ($date) = sprintf("%s %.2d %.2d:%.2d %.4d",$mm,$dd,$hr,$min,$yy);
  135.  
  136.     local($modestr) = &desc($mode,$type);
  137.     local($temp_name) = $name;
  138.     local($sz) = $size;
  139.  
  140.     write;
  141. }
  142.  
  143. # Produce a mode description string (eg "-rw-rw-r--") from the numeric mode
  144. # and the tar file type.
  145.  
  146. sub desc
  147. {
  148.     local($mode, $type) = @_;
  149.     local($own, $grp, $oth);
  150.  
  151.     $oth = $mode & 07;
  152.     $grp = ($mode >> 3) & 07;
  153.     $own = ($mode >> 6) & 07;
  154.  
  155.     ($type eq '5' ? 'd' : '-') . &mode($own) . &mode($grp) . &mode($oth);
  156. }
  157.  
  158. # Handle a single group of permissions.
  159.  
  160. sub mode
  161. {
  162.     local ($num) = @_;
  163.     local ($str) = '---';
  164.  
  165.     substr($str,0,1) = 'r' if ($num & 04);
  166.     substr($str,1,1) = 'w' if ($num & 02);
  167.     substr($str,2,1) = 'x' if ($num & 01);
  168.  
  169.     $str;
  170. }
  171.  
  172. # Convert an octal string from the tar file header into a numeric value.
  173. # If the string is not in the correct format (whitespace followed by a
  174. # string of octal digits), report the error and stop.
  175.  
  176. sub oct
  177. {
  178.     local ($str,$field) = @_;
  179.  
  180.     unless ($str =~ /^\s*([0-7]*)$/)
  181.     {
  182.         die "Header $field field is not in the correct format\n";
  183.     }
  184.  
  185.     oct($1);
  186. }
  187.  
  188. # Open the output file.
  189.  
  190. sub open_file
  191. {
  192.     local ($name, $type) = @_;
  193.     local ($in_name, $out_name);
  194.  
  195.     $skip = 0;
  196.     $in_name = &preformat($name);
  197.     $out_name = &munge_filename($in_name, $type);
  198.     print LOG "$name\t$in_name\t$out_name\n" if ($opt_L);
  199.  
  200.     if ($skip == 0)
  201.     {
  202.         open(OUT, ">$out_name") || die "Cannot open $out_name: $!\n";
  203.     }
  204. }
  205.  
  206. # Write the next block of data to the output file.
  207.  
  208. sub write_file
  209. {
  210.     local ($block, $bytes, $size) = @_;
  211.  
  212.     if ($skip == 0)
  213.     {
  214.         print OUT ($bytes <= $size) ? $block : substr($block,0,$size);
  215.     }
  216. }
  217.  
  218. # Close the output file.
  219.  
  220. sub close_file
  221. {
  222.     close OUT if $skip == 0;
  223. }
  224.  
  225. # ******************* Os-dependent file name handling *******************
  226.  
  227. # This is the big OS-dependent bit. Take a file name from the tar header,
  228. # and convert it so that it conforms to Archimedes file naming conventions.
  229. # This process is somewhat adhoc, and should be modified if necessary to
  230. # handle the names used in particular tar files.
  231.  
  232. sub munge_filename
  233. {
  234.     local ($name,$type) = @_;
  235.     local ($out, @names);
  236.  
  237.     # We don't support CONTIG - treat as NORMAL.
  238.     $type = '0' if $type eq '7';
  239.  
  240.     # NORMAL files ending with a slash are directories
  241.     $type = '5' if ($name =~ m#/$#) && ($type eq '0' || $type eq "\0");
  242.  
  243.     # Split the path into pathname elements
  244.     @names = split(/\//, $name);
  245.  
  246.     # Don't allow rooted pathnames - treat as relative to the
  247.     # current directory
  248.     shift(@names) if $names[0] eq '';
  249.  
  250.     # We are going to treat the final part specially, so extract it.
  251.     # If the file is a directory, we don't do this.
  252.     $file = pop(@names) unless $type eq '5';
  253.  
  254.     if ($#names == -1)
  255.     {
  256.         $dir = '';
  257.     }
  258.     else
  259.     {
  260.         # Clean up each part of the pathname.
  261.         grep(&cleanup, @names);
  262.  
  263.         # Build the directory name.
  264.         $dir = join('.',@names);
  265.  
  266.         # If the file type is DIR, make the directory and quit.
  267.         if ($type eq '5')
  268.         {
  269.             $skip = 1;
  270.             mkdir($dir) || &continue("Cannot make directory '$dir' ($!)");
  271.             return $dir;
  272.         }
  273.  
  274.         # Othewise, the directory must exist. We'll have one go
  275.         # at making it if not, then give up and moan at the user.
  276.         unless (-d $dir)
  277.         {
  278.             if (-e _)
  279.             {
  280.                 # If it's already a file, complain.
  281.                 &continue("Directory '$dir' already there as a file");
  282.             }
  283.             elsif (mkdir($dir) == 0)
  284.             {
  285.                 # If we can't make it, complain.
  286.                 &continue("Cannot make directory '$dir' ($!)");
  287.             }
  288.         }
  289.     }
  290.  
  291.     # OK, now we sort out the basename. We'll pass this over to another
  292.     # subroutine. It needs to know the directory to create the file in,
  293.     # and the basename. It returns the filename to use, or undef on an
  294.     # error, or if we have nothing to extract (special files).
  295.  
  296.     # First, though, we allow the file to be 'personalised'.
  297.     $file = &personalise($file);
  298.     &handle_file($dir,$file);
  299. }
  300.  
  301. # Clean up filenames.
  302. #
  303. # The rules are
  304. #     Replace dots with commas.
  305. #     Remove all special characters :*#$&@^%\
  306. #     Cut down to 10 characters by removing non-alphanumerics, if necessary.
  307. #     As a last resort, truncate to 10 characters.
  308.  
  309. sub cleanup
  310. {
  311.     local ($ch);
  312.  
  313.     # Capitalise the name.
  314.     tr/A-Z/a-z/;
  315.     s/\b(\w)/(($ch = $1) =~ tr:a-z:A-Z:), $ch/eg;
  316.  
  317.     # Replace dots with commas.
  318.     tr/./,/;
  319.  
  320.     # Delete magic characters.
  321.     # I quoted everything here, just out of paranoia.
  322.     tr/\:\*\#\$\&\@\^\%\\//d;
  323.  
  324.     # If we are longer than 10 characters, delete punctuation.
  325.     tr/a-zA-Z0-9//cd if length($_) > 10;
  326.  
  327.     # If we are still too long, truncate.
  328.     $_ = substr($_,0,10) if length($_) > 10;
  329. }
  330.  
  331. # Handle a standard file (ie, everything except a directory). We take the
  332. # file name, to be created in directory $dir. First, we decide what to
  333. # call it, creating any new sub-directories we need. Then, if it's a
  334. # special file, we create it containing a comment about the type, and
  335. # then return 'undef' (which signals to the caller that it should not
  336. # write anything to the file). Otherwise, we simply return the name to
  337. # use. We use $_ for the filename, as we will be doing a lot of pattern
  338. # matching, etc here!
  339.  
  340. sub handle_file
  341. {
  342.     local ($dir, $_) = @_;
  343.     local ($dots, $pre, $suf);
  344.  
  345.     # Before we start, replace any initial and final dots
  346.     # with exclamation marks.
  347.     s/^\.+/'!' x length($&)/e;
  348.     s/\.+$/'!' x length($&)/e;
  349.  
  350.     # Our main problem is dots. Count how many there are in the
  351.     # supplied filename.
  352.     $dots = tr/././;
  353.  
  354.     if ($dots == 0 || ($dots == 1 && m#\.[0-9]+$#))
  355.     {
  356.         # If we have no dots, or simply a dot followed by a number,
  357.         # we can simply clean up the filename and ensure that we can
  358.         # write OK. We do this for every case, so there is nothing
  359.         # more to do here.
  360.     }
  361.     else
  362.     {
  363.         # Split the filename into prefix.suffix, and add the suffix
  364.         # to the directory name, using the prefix as the filename.
  365.  
  366.         ($pre, $suf) = m/^(.*)\.([^.]*)/;
  367.  
  368.         $_ = $suf;
  369.         &cleanup;
  370.         $dir = ($dir eq '') ? $_ : $dir . '.' . $_;
  371.  
  372.         # If the prefix has dots, replace them with commas.
  373.         $pre =~ tr/./,/;
  374.         $_ = $pre;
  375.     }
  376.  
  377.     &cleanup;
  378.     $dir = &check_write($dir);
  379.  
  380.     ($dir eq '') ? $_ : "$dir.$_";
  381. }
  382.  
  383. # Complain about an error, and ask the user if he wishes to continue.
  384.  
  385. sub continue
  386. {
  387.     local ($ch);
  388.     &oswrstr($_[0]);
  389.     &oswrstr(" Continue? (y/n) ");
  390.     $ch = &osrdch();
  391.     $ch =~ tr/a-z/A-Z/;
  392.     $ch = 'N' unless $ch eq 'Y';
  393.     &oswrstr("$ch\r\n");
  394.     exit(1) unless $ch eq 'Y';
  395. }
  396.  
  397. # Check that we can write a new file into the specified directory. If we
  398. # can't, return the name of a newly generated directory where we can. This
  399. # subroutine uses the array %dir_subst to remember how to handle the
  400. # directories it has seen.
  401.  
  402. # It also ensures that the file $_ is not already in the directory. If it is,
  403. # it gives the user the opportunity to rename the file, skip it, or stop.
  404.  
  405. sub check_write
  406. {
  407.     local ($dir) = @_;
  408.     local ($res, $base, $newdir);
  409.  
  410.     # If the directory name is empty, just return.
  411.     return $dir if ($dir eq '');
  412.  
  413.     # If the directory doesn't exist yet, create it (we will certainly
  414.     # have no problems writing in a new directory).
  415.     unless (-e $dir)
  416.     {
  417.         mkdir($dir) || ($skip = 1);
  418.         return $dir;
  419.     }
  420.  
  421.     # If the directory already exists as a file, offer the user the
  422.     # chance to rename it.
  423.     unless (-d $dir)
  424.     {
  425.         print "Directory $dir clashes with a file\n";
  426.         print "New name (CR to skip): ";
  427.         chop($newdir = <STDIN>);
  428.         $skip = 1 unless $newdir;
  429.         return $newdir ? &check_write($newdir) : $dir;
  430.     }
  431.  
  432.     if (!defined $dir_subst{$dir})
  433.     {
  434.         $res = &check_dir($dir);
  435.  
  436.         $skip = 1 if ($res == -1);
  437.         return $dir if $res;
  438.  
  439.         $base = (($dir =~ /\.([^.]*)$/) ? $1 : $dir);
  440.         if (length($base) < 9)
  441.         {
  442.             $newdir = $dir . '-A';
  443.         }
  444.         else
  445.         {
  446.             $newdir = $` . '.' . substr($base,0,8) . '-A';
  447.         }
  448.         mkdir($newdir) || ($skip = 1);
  449.     }
  450.     else
  451.     {
  452.         $newdir = $dir_subst{$dir};
  453.         $res = &check_dir($newdir);
  454.  
  455.         $skip = 1 if ($res == -1);
  456.         return $newdir if $res;
  457.  
  458.         ++$newdir;
  459.         mkdir($newdir) || ($skip = 1);
  460.     }
  461.  
  462.     $dir_subst{$dir} = $newdir;
  463.     $newdir;
  464. }
  465.  
  466.  
  467. # Check the directory for the file $_, and for less than 77 entries.
  468. # Return 1 for OK, 0 for no room, -1 for skip this file.
  469. sub check_dir
  470. {
  471.     local ($dir) = @_;
  472.     local ($file, *DIR);
  473.     local (@files);
  474.  
  475.     opendir(DIR,$dir);
  476.     @files = readdir(DIR);
  477.     closedir(DIR);
  478.  
  479.     for $file (@files)
  480.     {
  481.         next unless $_ eq $file;
  482.         print '-' x 70, "\n";
  483.         system("Cat $dir");
  484.         print "\nFile $_ already exists\n";
  485.         print "New name (CR to skip): ";
  486.         chop($file = <STDIN>);
  487.         return -1 if $file eq '';
  488.         $_ = $file;
  489.         last;
  490.     }
  491.  
  492.     # Maximum 77 files in a directory.
  493.     # Remember $#arr is last index, not count!
  494.     $#files < 76 ? $dir : undef;
  495. }
  496.