############################################################################ # # File: mszip.icn # # Subject: Program to ZIP a directory for MS-DOS use # # Author: Gregg M. Townsend # # Date: June 23, 2000 # ############################################################################ # # This file is in the public domain. # ############################################################################ # # usage: mszip [options] root-directory zip-file # -n no action: just report; zip-file may be omitted # -v verbose commentary: list individual file types # -i check filenames for ISO 9660 (CD-ROM) legality # # Mszip stuffs the contents of a directory into a ZIP archive file, # translating text files to CRLF form. Pipes are opened that # require find, sort, and zip in the search path. # # The default report gives an inventory of files by extension. This # can be useful even without creating a ZIP file ("mszip -n dir"). # # File types on the verbose report are: # x unreadable file # e empty file # b binary file # c text file with CRLFs # n text file with newlines # A file is "binary" if it contains more than 1% unexpected characters. # # Symlinks, FIFOs, device files etc. are reported and not archived. # Files with illegal MS-DOS names are reported but still archived. # ############################################################################ # # Requires: UNIX, zip program # ############################################################################ $define USAGE "[-n] [-v] [-i] root-directory zip-file" $define BTHRESH 0.01 # allowed fraction of wild bytes in text file $define BUFSIZ 65536 # size of buffer for checking binary/text # (bytes beyond this many are not checked) $define ZIPOPTS "-9 -X" # best compression; omit uid/gid link options global verbose global errorcount global allfiles, binlist, txtlist global extns procedure main(args) local opts, root, zipopts, zipname local pwd, pipe, fname, errmsg local nmproc # process options opts := options(args, "nvi") verbose := opts["v"] if \opts["i"] then nmproc := isoname else nmproc := msname root := args[1] | stop("usage: ", &progname, " ", USAGE) # get current directory name and prepend to zip file if necessary if /opts["n"] then { zipname := args[2] | stop("usage: ", &progname, USAGE) pipe := popen("pwd") pwd := read(pipe) | stop("can't read current directory") close(pipe) if not zipname ? ="/" then zipname := pwd || "/" || zipname } # change to source directory chdir(root) | stop("can't change to directory: ", root) # verify that zip file is writable if \zipname then { if not close(open(zipname, "w")) then stop(zipname, ": cannot write") remove(zipname) } # initialize errorcount := 0 extns := table("") allfiles := [] binlist := [] txtlist := [] # check for "bad" files: symlinks, fifos, etc. write(&errout, "finding files...") pipe := popen("find . ! -type d ! -type f -print | sort") while report(read(pipe), "bad file type") close(pipe) # get list of the rest pipe := popen("find . -type f -print | sort") while fname := read(pipe) do { put(allfiles, fname) if not nmproc(fname) then report(fname, "illegal filename") } close(pipe) # inspect files write(&errout, "inspecting files...") while inspect(get(allfiles)) # summarize file types by extension summary() # write zip file, if -n was not specified if \zipname then { zipopts := ZIPOPTS if /verbose then zipopts := ZIPOPTS || " -q" # create zip file and fill with text files write(&errout, "storing text files...") pipe := popen("zip -l " || zipopts || " " || zipname || " -@", "w") every write(pipe, !txtlist) close(pipe) # add binary files to zip file write() write(&errout, "storing binary files...") pipe := popen("zip -g " || zipopts || " " || zipname || " -@", "w") every write(pipe, !binlist) close(pipe) } # exit if errorcount > 0 then stop("\t", errorcount, " error(s)") else write("done.") end # popen(cmd, mode) -- open pipe, and abort on error procedure popen(cmd, mode) local f mode := "p" || (\mode | "r") f := open(cmd, mode) | stop("can't open pipe: ", cmd) return f end # census(s, c, lim) -- count occurrences of members of c in string s # # If lim is given, counting can stop early. procedure census(s, c, lim) local n /lim := *s n := 0 s ? { while n < lim & tab(upto(c)) do n +:= *tab(many(c)) } n >:= lim return n end # msname(fname) -- check filename for MS-DOS legality procedure msname(fname) local dir, base, ext static forbid initial forbid := &cset -- &letters -- &digits -- '/._^$~!#%&-{}()@\'`' fname ? { if upto(forbid) then fail # forbidden char while dir := tab(upto('/') + 1) do if *dir > 9 then fail # dir component too long if base := tab(upto('.')) then { move(1) if upto('.') then fail # two periods ext := tab(0) } else { base := tab(0) ext := "" } if (*base > 8) | (*ext > 3) then fail # component too long } return end # isoname(fname) -- check for ISO-9660 (CD-ROM) filename legality # # (disallows explicit version numbers) procedure isoname(fname) static legal initial legal := &lcase ++ &ucase ++ &digits ++ '_.' fname ? { while tab(upto('/') + 1) tab(many(legal)) if pos(0) then return msname(fname) else fail } end # inspect(fname) -- inspect one file and update lists procedure inspect(fname) local c fname ? { if ="./" then fname := tab(0) } c := ftype(fname) count(fname, c) if \verbose then write(c, " ", fname) if c == "x" then { report(fname, "unreadable file") return } if c == "n" then put(txtlist, fname) else put(binlist, fname) return end # ftype(fname) -- return file type character procedure ftype(fname) local f, s, lim static bset initial bset := # allows \a\b\t\n\v\f\r\^Z '\0\1\2\3\4\5\6\16\17\20\21\22\23\24\25\26\27\30\31\33\34\35\36\37' ++ &cset[128+:33] f := open(fname, "ru") | return "x" s := reads(f, BUFSIZ) close(f) if /s | (*s = 0) then return "e" lim := BTHRESH * *s if census(s, bset, lim) >= lim then return "b" else if census(s, '\l') > census(s, '\r') then return "n" else return "c" end # count(fname, typechar) -- count file extension procedure count(fname, tchar) local extn fname ? { while tab(upto('/') + 1) if tab(upto('.') + 1) then { while tab(upto('.') + 1) extn := tab(0) } else extn := "" } extns[extn] ||:= tchar return end # report(fname, errmsg) -- report error procedure report(fname, errmsg) write(&errout, "\t", errmsg, ": ", fname) errorcount +:= 1 return end # summary() -- generate summary of extension counts procedure summary() local tlist, ext, s, b, c, e, n, x, tb, tc, te, tn, tx write() write(" unrd empty bin crlf newln extension") tb := tc := te := tn := tx := 0 tlist := sort(extns, 3) while ext := get(tlist) do { s := get(tlist) tb +:= (b := census(s, 'b')) tc +:= (c := census(s, 'c')) te +:= (e := census(s, 'e')) tn +:= (n := census(s, 'n')) tx +:= (x := census(s, 'x')) write(r5(x), r5(e), r5(b), r5(c), r5(n), " .", ext) } write() write(r5(tx), r5(te), r5(tb), r5(tc), r5(tn), " TOTAL: ", tx+te+tb+tc+tn) write() return end # r5(n) -- show integer in 5-char field, if nonzero procedure r5(n) local s if n = 0 then return " " s := integer(n) if *s < 5 then return right(s, 5) else return " " || s end