home *** CD-ROM | disk | FTP | other *** search
/ Source Code 1992 March / Source_Code_CD-ROM_Walnut_Creek_March_1992.iso / usenet / altsrcs / 2 / 2594 / mtf.icn < prev    next >
Text File  |  1991-01-22  |  17KB  |  524 lines

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