home *** CD-ROM | disk | FTP | other *** search
/ ftp.cs.arizona.edu / ftp.cs.arizona.edu.tar / ftp.cs.arizona.edu / icon / historic / v941.tgz / icon.v941src.tar / icon.v941src / ipl / progs / mtf3.icn < prev    next >
Text File  |  2000-07-29  |  17KB  |  537 lines

  1. ############################################################################
  2. #
  3. #    File:     mtf3.icn
  4. #
  5. #    Subject:  Program to map tar file
  6. #
  7. #    Author:   Richard Goerwitz
  8. #
  9. #    Date:     June 3, 1991
  10. #
  11. ############################################################################
  12. #
  13. #   This file is in the public domain.
  14. #
  15. ############################################################################
  16. #
  17. #    Version:  3.4
  18. #
  19. ############################################################################
  20. #
  21. #  PURPOSE: Maps 15+ char. filenames in a tar archive to 14 chars.
  22. #  Handles both header blocks and the archive itself.  Mtf is intended
  23. #  to facilitate installation of tar'd archives on systems subject to
  24. #  the System V 14-character filename limit.
  25. #
  26. #  USAGE:  mtf inputfile [-r reportfile] [-e .extensions] [-x exceptions]
  27. #
  28. #  "Inputfile" is a tar archive.  "Reportfile" is file containing a
  29. #  list of files already mapped by mtf in a previous run (used to
  30. #  avoid clashes with filenames in use outside the current archive).
  31. #  The -e switch precedes a list of filename .extensions which mtf is
  32. #  supposed to leave unscathed by the mapping process
  33. #  (single-character extensions such as .c and .o are automatically
  34. #  preserved; -e allows the user to specify additional extensions,
  35. #  such as .pxl, .cpi, and .icn).  The final switch, -x, precedes a
  36. #  list of strings which should not be mapped at all.  Use this switch
  37. #  if, say, you have a C file with a structure.field combination such
  38. #  as "thisisveryverybig.hashptr" in an archive that contains a file
  39. #  called "thisisveryverybig.h," and you want to avoid mapping that
  40. #  portion of the struct name which matches the name of the overlong
  41. #  file (to wit, "mtf inputfile -x thisisveryverybig.hashptr").  To
  42. #  prevent mapping of any string (including overlong filenames) begin-
  43. #  ning, say, with "thisisvery," use "mtf inputfile -x thisisvery."
  44. #  Be careful with this option, or you might end up defeating the
  45. #  whole point of using mtf in the first place.
  46. #
  47. #  OUTPUT FORMAT:  Mtf writes a mapped tar archive to the stdout.
  48. #  When finished, it leaves a file called "map.report" in the current
  49. #  directory which records what filenames were mapped and how.  Rename
  50. #  and save this file, and use it as the "reportfile" argument to any
  51. #  subsequent runs of mtf in this same directory.  Even if you don't
  52. #  plan to run mtf again, this file should still be examined, just to
  53. #  be sure that the new filenames are acceptable, and to see if
  54. #  perhaps additional .extensions and/or exceptions should be
  55. #  specified.
  56. #
  57. #  BUGS:  Mtf only maps filenames found in the main tar headers.
  58. #  Because of this, mtf cannot accept nested tar archives.  If you try
  59. #  to map a tar archive within a tar file, mtf will abort with a nasty
  60. #  message about screwing up your files.  Please note that, unless you
  61. #  give mtf a "reportfile" to consider, it knows nothing about files
  62. #  existing outside the archive.  Hence, if an input archive refers to
  63. #  an overlong filename in another archive, mtf naturally will not
  64. #  know to shorten it.  Mtf will, in fact, have no way of knowing that
  65. #  it is a filename, and not, say, an identifier in a C program.
  66. #  Final word of caution:  Try not to use mtf on binaries.  It cannot
  67. #  possibly preserve the correct format and alignment of strings in an
  68. #  executable.  Same goes for compressed files.  Mtf can't map
  69. #  filenames that it can't read!
  70. #
  71. ############################################################################
  72.  
  73.  
  74. global filenametbl, chunkset, short_chunkset   # see procedure mappiece(s)
  75. global extensions, no_nos                      # ditto
  76.  
  77. record hblock(name,junk,size,mtime,chksum,     # tar header struct;
  78.               linkflag,linkname,therest)       # see readtarhdr(s)
  79.  
  80.  
  81. procedure main(a)
  82.     local usage, intext, i, current_list
  83.  
  84.     usage := "usage:  mtf inputfile [-r reportfile] " ||
  85.          "[-e .extensions] [-x exceptions]"
  86.  
  87.     *a = 0 & stop(usage)
  88.  
  89.     intext := open_input_file(a[1]) & pop(a)
  90.  
  91.     i := 0
  92.     extensions := []; no_nos := []
  93.     while (i +:= 1) <= *a do {
  94.     case a[i] of {
  95.         "-r"    :    readin_old_map_report(a[i+:=1])
  96.         "-e"    :    current_list := extensions
  97.         "-x"    :    current_list := no_nos
  98.         default :    put(current_list,a[i])
  99.     }
  100.     }
  101.  
  102.     every !extensions ?:= (=".", tab(0))
  103.     
  104.     # Run through all the headers in the input file, filling
  105.     # (global) filenametbl with the names of overlong files;
  106.     # make_table_of_filenames fails if there are no such files.
  107.     make_table_of_filenames(intext) | {
  108.     write(&errout,"mtf:  no overlong path names to map") 
  109.     a[1] ? (tab(find(".tar")+4), pos(0)) |
  110.       write(&errout,"(Is ",a[1]," even a tar archive?)")
  111.      exit(1)
  112.     } 
  113.  
  114.     # Now that a table of overlong filenames exists, go back
  115.     # through the text, remapping all occurrences of these names
  116.     # to new, 14-char values; also, reset header checksums, and
  117.     # reformat text into correctly padded 512-byte blocks.  Ter-
  118.     # minate output with 512 nulls.
  119.     seek(intext,1)
  120.     every writes(output_mapped_headers_and_texts(intext))
  121.  
  122.     close(intext)
  123.     write_report()   # Record mapped file and dir names for future ref.
  124.     exit(0)
  125.     
  126. end
  127.  
  128.  
  129.  
  130. procedure open_input_file(s)
  131.     local intext
  132.  
  133.     intext := open("" ~== s,"r") |
  134.     stop("mtf:  can't open ",s)
  135.     find("UNIX",&features) |
  136.     stop("mtf:  I'm not tested on non-UNIX systems.")
  137.     s[-2:0] == ".Z" &
  138.         stop("mtf:  sorry, can't accept compressed files")
  139.     return intext
  140. end
  141.  
  142.  
  143.  
  144. procedure readin_old_map_report(s)
  145.     local mapfile, line, chunk, lchunk
  146.  
  147.     initial {
  148.     filenametbl := table()
  149.     chunkset := set()
  150.     short_chunkset := set()
  151.     }
  152.  
  153.     mapfile := open_input_file(s)
  154.     while line := read(mapfile) do {
  155.     line ? {    
  156.         if chunk := tab(many(~' \t')) & tab(upto(~' \t')) &
  157.         lchunk := move(14) & pos(0) then {
  158.         filenametbl[chunk] := lchunk
  159.         insert(chunkset,chunk)
  160.         insert(short_chunkset,chunk[1:16])
  161.         }
  162.     if /chunk | /lchunk
  163.     then stop("mtf:  report file, ",s," seems mangled.")
  164.     }
  165.     }
  166.  
  167. end
  168.  
  169.  
  170.  
  171. procedure make_table_of_filenames(intext)
  172.  
  173.     local header # chunkset is global
  174.  
  175.     # search headers for overlong filenames; for now
  176.     # ignore everything else
  177.     while header := readtarhdr(reads(intext,512)) do {
  178.     # tab upto the next header block
  179.     tab_nxt_hdr(intext,trim_str(header.size),1)
  180.     # record overlong filenames in several global tables, sets
  181.     fixpath(trim_str(header.name))
  182.     }
  183.     *\chunkset ~= 0 | fail
  184.     return &null
  185.  
  186. end
  187.  
  188.  
  189.  
  190. procedure output_mapped_headers_and_texts(intext)
  191.  
  192.     # Remember that filenametbl, chunkset, and short_chunkset
  193.     # (which are used by various procedures below) are global.
  194.     local header, newtext, full_block, block, lastblock
  195.  
  196.     # Read in headers, one at a time.
  197.     while header := readtarhdr(reads(intext,512)) do {
  198.  
  199.     # Replace overlong filenames with shorter ones, according to
  200.     # the conversions specified in the global hash table filenametbl
  201.     # (which were generated by fixpath() on the first pass).
  202.           header.name := left(map_filenams(header.name),100,"\x00")
  203.     header.linkname := left(map_filenams(header.linkname),100,"\x00")
  204.  
  205.     # Use header.size field to determine the size of the subsequent text.
  206.     # Read in the text as one string.  Map overlong filenames found in it
  207.      # to shorter names as specified in the global hash table filenamtbl.
  208.     newtext := map_filenams(tab_nxt_hdr(intext,trim_str(header.size)))
  209.  
  210.     # Now, find the length of newtext, and insert it into the size field.
  211.     header.size := right(exbase10(*newtext,8) || " ",12," ")
  212.  
  213.     # Calculate the checksum of the newly retouched header.
  214.     header.chksum := right(exbase10(get_checksum(header),8)||"\x00 ",8," ")
  215.  
  216.     # Finally, join all the header fields into a new block and write it out
  217.     full_block := ""; every full_block ||:= !header
  218.     suspend left(full_block,512,"\x00")
  219.  
  220.     # Now we're ready to write out the text, padding the final block
  221.     # out to an even 512 bytes if necessary; the next header must start
  222.     # right at the beginning of a 512-byte block.
  223.     newtext ? {
  224.         while block := move(512)
  225.         do suspend block
  226.         pos(0) & next
  227.             lastblock := left(tab(0),512,"\x00")
  228.         suspend lastblock
  229.     }
  230.     }
  231.     # Write out a final null-filled block.  Some tar programs will write
  232.     # out 1024 nulls at the end.  Dunno why.
  233.     return repl("\x00",512)
  234.  
  235. end
  236.  
  237.  
  238.  
  239. procedure trim_str(s)
  240.  
  241.     # Knock out spaces, nulls from those crazy tar header
  242.     # block fields (some of which end in a space and a null,
  243.     # some just a space, and some just a null [anyone know
  244.     # why?]).
  245.     return s ? {
  246.     (tab(many(' ')) | &null) &
  247.         trim(tab(find("\x00")|0))
  248.     }
  249.  
  250. end 
  251.  
  252.  
  253.  
  254. procedure tab_nxt_hdr(f,size_str,firstpass)
  255.  
  256.     # Tab upto the next header block.  Return the bypassed text
  257.     # as a string if not the first pass.
  258.  
  259.     local hs, next_header_offset
  260.  
  261.     hs := integer("8r" || size_str)
  262.     next_header_offset := (hs / 512) * 512
  263.     hs % 512 ~= 0 & next_header_offset +:= 512
  264.     if 0 = next_header_offset then return ""
  265.     else {
  266.     # if this is pass no. 1 don't bother returning a value; we're
  267.     # just collecting long filenames;
  268.     if \firstpass then {
  269.         seek(f,where(f)+next_header_offset)
  270.         return
  271.     }
  272.     else {
  273.         return reads(f,next_header_offset)[1:hs+1] |
  274.         stop("mtf:  error reading in ",
  275.              string(next_header_offset)," bytes.")
  276.     }
  277.     }
  278.  
  279. end
  280.  
  281.  
  282.  
  283. procedure fixpath(s)
  284.     local s2, piece
  285.  
  286.     # Fixpath is a misnomer of sorts, since it is used on
  287.     # the first pass only, and merely examines each filename
  288.     # in a path, using the procedure mappiece to record any
  289.     # overlong ones in the global table filenametbl and in
  290.     # the global sets chunkset and short_chunkset; no fixing
  291.     # is actually done here.
  292.  
  293.     s2 := ""
  294.     s ? {
  295.     while piece := tab(find("/")+1)
  296.     do s2 ||:= mappiece(piece) 
  297.     s2 ||:= mappiece(tab(0))
  298.     }
  299.     return s2
  300.  
  301. end
  302.  
  303.  
  304.  
  305. procedure mappiece(s)
  306.     local chunk, i, lchunk
  307.  
  308.     # Check s (the name of a file or dir as recorded in the tar header
  309.     # being examined) to see if it is over 14 chars long.  If so,
  310.     # generate a unique 14-char version of the name, and store
  311.     # both values in the global hashtable filenametbl.  Also store
  312.     # the original (overlong) file name in chunkset.  Store the
  313.     # first fifteen chars of the original file name in short_chunkset.
  314.     # Sorry about all of the tables and sets.  It actually makes for
  315.     # a reasonably efficient program.  Doing away with both sets,
  316.     # while possible, causes a tenfold drop in execution speed!
  317.     
  318.     # global filenametbl, chunkset, short_chunkset, extensions
  319.     local j, ending
  320.  
  321.     initial {
  322.     /filenametbl := table()
  323.     /chunkset := set()
  324.     /short_chunkset := set()
  325.     }
  326.    
  327.     chunk := trim(s,'/')
  328.     if chunk ? (tab(find(".tar")+4), pos(0)) then {
  329.     write(&errout, "mtf:  Sorry, I can't let you do this.\n",
  330.                    "      You've nested a tar archive within\n",
  331.                    "      another tar archive, which makes it\n",
  332.                    "      likely I'll f your filenames ubar.")
  333.     exit(2)
  334.     }
  335.     if *chunk > 14 then {
  336.     i := 0
  337.  
  338.     if /filenametbl[chunk] then {
  339.     # if we have not seen this file, then...
  340.         repeat {
  341.         # ...find a new unique 14-character name for it;
  342.         # preserve important suffixes like ".Z," ".c," etc.
  343.         # First, check to see if the original filename (chunk)
  344.         # ends in an important extension...
  345.         if chunk ?
  346.             (tab(find(".")),
  347.              ending := move(1) || tab(match(!extensions)|any(&ascii)),
  348.              pos(0)
  349.              )
  350.         # ...If so, then leave the extension alone; mess with the
  351.         # middle part of the filename (e.g. file.with.extension.c ->
  352.         # file.with001.c).
  353.         then {
  354.             j := (15 - *ending - 3)
  355.             lchunk:= chunk[1:j] || right(string(i+:=1),3,"0") || ending
  356.         }
  357.         # If no important extension is present, then reformat the
  358.         # end of the file (e.g. too.long.file.name -> too.long.fi01).
  359.         else lchunk := chunk[1:13] || right(string(i+:=1),2,"0")
  360.  
  361.         # If the resulting shorter file name has already been used...
  362.         if lchunk == !filenametbl
  363.         # ...then go back and find another (i.e. increment i & try
  364.         # again; else break from the repeat loop, and...
  365.         then next else break
  366.         }
  367.             # ...record both the old filename (chunk) and its new,
  368.         # mapped name (lchunk) in filenametbl.  Also record the
  369.         # mapped names in chunkset and short_chunkset.
  370.         filenametbl[chunk] := lchunk
  371.         insert(chunkset,chunk)
  372.         insert(short_chunkset,chunk[1:16])
  373.     }
  374.     }
  375.  
  376.     # If the filename is overlong, return lchunk (the shortened
  377.     # name), else return the original name (chunk).  If the name,
  378.     # as passed to the current function, contained a trailing /
  379.     # (i.e. if s[-1]=="/"), then put the / back.  This could be
  380.     # done more elegantly.
  381.     return (\lchunk | chunk) || ((s[-1] == "/") | "")
  382.  
  383. end
  384.  
  385.  
  386.  
  387. procedure readtarhdr(s)
  388.     local this_block
  389.  
  390.     # Read the silly tar header into a record.  Note that, as was
  391.     # complained about above, some of the fields end in a null, some
  392.     # in a space, and some in a space and a null.  The procedure
  393.     # trim_str() may (and in fact often _is_) used to remove this
  394.     # extra garbage.
  395.  
  396.     this_block := hblock()
  397.     s ? {
  398.     this_block.name     := move(100)    # <- to be looked at later
  399.     this_block.junk     := move(8+8+8)  # skip the permissions, uid, etc.
  400.     this_block.size     := move(12)     # <- to be looked at later
  401.     this_block.mtime    := move(12)
  402.     this_block.chksum   := move(8)      # <- to be looked at later
  403.     this_block.linkflag := move(1)
  404.     this_block.linkname := move(100)    # <- to be looked at later
  405.     this_block.therest  := tab(0)
  406.     }
  407.     integer(this_block.size) | fail  # If it's not an integer, we've hit
  408.                                      # the final (null-filled) block.
  409.     return this_block
  410.  
  411. end
  412.  
  413.  
  414.  
  415. procedure map_filenams(s)
  416.     local el, ch
  417.  
  418.     # Chunkset is global, and contains all the overlong filenames
  419.     # found in the first pass through the input file; here the aim
  420.     # is to map these filenames to the shortened variants as stored
  421.     # in filenametbl (GLOBAL).
  422.  
  423.     local s2, tmp_chunk_tbl, tmp_lst
  424.     static new_chunklist
  425.     initial {
  426.  
  427.         # Make sure filenames are sorted, longest first.  Say we
  428.         # have a file called long_file_name_here.1 and one called
  429.         # long_file_name_here.1a.  We want to check for the longer
  430.         # one first.  Otherwise the portion of the second file which
  431.         # matches the first file will get remapped.
  432.         tmp_chunk_tbl := table()
  433.         every el := !chunkset
  434.         do insert(tmp_chunk_tbl,el,*el)
  435.         tmp_lst := sort(tmp_chunk_tbl,4)
  436.         new_chunklist := list()
  437.         every put(new_chunklist,tmp_lst[*tmp_lst-1 to 1 by -2])
  438.  
  439.     }
  440.  
  441.     s2 := ""
  442.     s ? {
  443.     until pos(0) do {
  444.         # first narrow the possibilities, using short_chunkset
  445.         if member(short_chunkset,&subject[&pos:&pos+15])
  446.             # then try to map from a long to a shorter 14-char filename
  447.         then {
  448.         if match(ch := !new_chunklist) & not match(!no_nos)
  449.         then s2 ||:= filenametbl[=ch]
  450.         else s2 ||:= move(1)
  451.         }
  452.         else s2 ||:= move(1)
  453.     }
  454.     }
  455.     return s2
  456.  
  457. end
  458.  
  459.  
  460. #  From the IPL.  Thanks, Ralph -
  461. #  Author:  Ralph E. Griswold
  462. #  Date:  June 10, 1988
  463. #  exbase10(i,j) convert base-10 integer i to base j
  464. #  The maximum base allowed is 36.
  465.  
  466. procedure exbase10(i,j)
  467.  
  468.    static digits
  469.    local s, d, sign
  470.    initial digits := &digits || &lcase
  471.    if i = 0 then return 0
  472.    if i < 0 then {
  473.       sign := "-"
  474.       i := -i
  475.       }
  476.    else sign := ""
  477.    s := ""
  478.    while i > 0 do {
  479.       d := i % j
  480.       if d > 9 then d := digits[d + 1]
  481.       s := d || s
  482.       i /:= j
  483.       }
  484.    return sign || s
  485.  
  486. end
  487.  
  488. # end IPL material
  489.  
  490.  
  491. procedure get_checksum(r)
  492.     local sum, field
  493.  
  494.     # Calculates the new value of the checksum field for the
  495.     # current header block.  Note that the specification say
  496.     # that, when calculating this value, the chksum field must
  497.     # be blank-filled.
  498.  
  499.     sum := 0
  500.     r.chksum := "        "
  501.     every field := !r
  502.     do every sum +:= ord(!field)
  503.     return sum
  504.  
  505. end
  506.  
  507.  
  508.  
  509. procedure write_report()
  510.  
  511.     # This procedure writes out a list of filenames which were
  512.     # remapped (because they exceeded the SysV 14-char limit),
  513.     # and then notifies the user of the existence of this file.
  514.  
  515.     local outtext, stbl, i, j, mapfile_name
  516.  
  517.     # Get a unique name for the map.report (thereby preventing
  518.     # us from overwriting an older one).
  519.     mapfile_name := "map.report"; j := 1
  520.     until not close(open(mapfile_name,"r"))
  521.     do mapfile_name := (mapfile_name[1:11] || string(j+:=1))
  522.  
  523.     (outtext := open(mapfile_name,"w")) |
  524.     open(mapfile_name := "/tmp/map.report","w") |
  525.          stop("mtf:  Can't find a place to put map.report!")
  526.     stbl := sort(filenametbl,3)
  527.     every i := 1 to *stbl -1 by 2 do {
  528.     match(!no_nos,stbl[i]) |
  529.         write(outtext,left(stbl[i],35," ")," ",stbl[i+1])
  530.     }
  531.     write(&errout,"\nmtf:  ",mapfile_name," contains the list of changes.")
  532.     write(&errout,"      Please save this list!")
  533.     close(outtext)
  534.     return &null
  535.  
  536. end
  537.