Newsgroups: comp.sources.unix From: madler@cco.caltech.edu (Mark Adler) Subject: v25i143: zip - file compression/archive tool, Part02/07 Sender: unix-sources-moderator@pa.dec.com Approved: vixie@pa.dec.com Submitted-By: madler@cco.caltech.edu (Mark Adler) Posting-Number: Volume 25, Issue 143 Archive-Name: zip/part02 #! /bin/sh # This is a shell archive. Remove anything before this line, then unpack # it by saving it into a file and typing "sh file". To overwrite existing # files, type "sh file -c". You can also feed this as standard input via # unshar, or by typing "sh 'im_lm.asm' <<'END_OF_FILE' X; X; Copyright (C) 1990,1991 Mark Adler, Richard B. Wales, and Jean-loup Gailly. X; Permission is granted to any individual or institution to use, copy, or X; redistribute this software so long as all of the original files are included X; unmodified, that it is not sold for profit, and that this copyright notice X; is retained. X; X X; im_lm.asm by Jean-loup Gailly. X X;im_lm.asm, optimized version of longest_match() and lm_process() in im_lmat.c X; Must be assembled with masm -ml. To be used only with C small model or X; compact model. This file is only optional. If you don't have masm, use the X; C version (add -DNO_ASM to CFLAGS in makefile.dos and remove im_lm.obj X; from OBJI). X; X; Turbo C 2.0 does not support static allocation of far data in X; the small model. So TC 2.0 users must assemble this with: X; tasm -ml -DDYN_ALLOC im_lm; X; OS/2 users must assemble this with -DOS2 -ml. To simplify the code, X; the option -DDYN_ALLOC is not supported for OS/2. X name im_lm X ifndef DYN_ALLOC X extrn _prev : word X extrn _next : word X prev equ _prev X next equ _next load_es_prev macro X mov cx,seg _prev X mov es,cx endm load_ds_next macro X mov si,seg _next X mov ds,si endm endif X X_BSS segment word public 'BSS' X extrn _window : byte X extrn _match_length : word X extrn _start_length : word X extrn _strstart : word X extrn _strsize : word X extrn _ins_h : word X extrn _h_shift : byte X extrn _checkpoint : word X extrn _bufsize : word X extrn _max_chain_length : word X extrn _min_match_length : word ifdef DYN_ALLOC X extrn _prev : word X extrn _next : word X prev equ 0 ; offset normalized to zero (except for OS/2) X next equ 0 ; offset normalized to zero (except for OS/2) load_es_prev macro X mov es,_prev[2] ; warning: the offset should be added under OS/2 endm load_ds_next macro X mov ds,_next[2] ; warning: the offset should be added under OS/2 endm endif X cur_best dw 1 dup(?) ; best match within longest_match X_BSS ends X DGROUP group _BSS X X_TEXT segment word public 'CODE' X assume cs: _TEXT, ds: DGROUP, ss: DGROUP X X extrn _write_match : near X X BSZ equ 4096 ; keep consistent with tailor.h X WSIZE equ 8192 ; keep consistent with im_lmat.c X NIL equ (WSIZE+BSZ) X MAX_DIST equ NIL X MAX_LENGTH equ 320 ; keep consistent with im_lmat.c X HASH_MASK equ 16383 ; (1<<14)-1 X X; Process a block of characters already inserted in the window X; IN assertion: count > 0 X public _lm_process X_lm_process proc near X push bp X mov bp,sp X push di X push si X X count equ word ptr [bp+4] X; delete_point equ ax X; ins_h equ bx X; h_shift equ cl X; count equ dx X; cur_match equ si X; strstart equ di X; min_match_length equ bp X X mov dx,count X mov bx,_ins_h X mov di,_strstart X lea ax,[di+MAX_LENGTH-1] X sub ax,_bufsize X jae del_ok X add ax,MAX_DIST del_ok: shl ax,1 ;delete_point as word index X load_es_prev X mov cl,_h_shift X load_ds_next X assume ds: nothing X jmp short insert X start_overflow: X sub di,di ;strstart = 0 X sub ss:_checkpoint,MAX_DIST X jmp short check_count del_overflow: X mov ax,-2 ;delete_point = 0 main_loop: X add ax,2 ;delete_point++ (word index) X cmp ax,2*MAX_DIST X je del_overflow X xchg ax,si ;si=2*delete_point X mov bp,next[si] X shl bp,1 X mov es:prev[bp],NIL X xchg ax,si ;ax=2*delete_point X X inc di ;strstart++ X cmp di,MAX_DIST X je start_overflow check_count: X dec dx ;count-- X jz end_proc_ok insert: X shl bx,cl ;ins_h <<= h_shift X mov bp,ss:_min_match_length X xor bl,_window[di+bp-1] ;ins_h ^= window[s+min_match_length-1] X and bx,HASH_MASK X add bx,MAX_DIST+1 X shl bx,1 ;word index X mov si,es:prev[bx] ;cur_match = match_head[ins_h] X mov es:prev[bx],di ;prev[ins_h+MAX_DIST+1] = strstart X shr bx,1 ;bx = ins_h + MAX_DIST + 1 X shl di,1 ;di = strstart as word index X mov next[di],bx X sub bx,MAX_DIST+1 ;bx = ins_h X mov es:prev[di],si ;prev[s] = cur_match X shr di,1 ;di = strstart X shl si,1 X mov next[si],di ;next[cur_match] = strstart X cmp di,ss:_checkpoint X jne main_loop X X push ax X push bx X push dx X mov ax,ss X mov ds,ax X assume ds: DGROUP X mov _match_length,0 X mov _strstart,di X shr si,1 ;si = cur_match X cmp si,NIL ;cur_match == NIL ? X je call_write X call _longest_match ;returns best_match in si call_write: X push _match_length X push si ;best_match or NIL X call _write_match X add sp,4 X pop dx X pop bx X or al,al X jnz failure X pop ax X load_es_prev X mov cl,_h_shift X load_ds_next X assume ds: nothing X jmp main_loop end_proc_ok: X mov ax,ss X mov ds,ax X assume ds: DGROUP X sub ax,ax X mov _strstart,di end_proc: X mov _ins_h,bx X pop si X pop di X pop bp X ret failure: X add sp,2 ;skip pushed ax X jmp short end_proc X X_lm_process endp X X; Find the longest match starting at the given string. Return its position X; and set its length in match_length. Matches shorter or equal to X; start_length are discarded, in which case match_length is unchanged X; and the result position is NIL. X; IN assertions: cur_match is the head of the hash chain for the current X; string (strstart) and is not NIL, start_length >= 1. X; registers: es points to the base of the prev array (preserved) X; si = cur_match and return value X; di = strstart X X; IPos longest_match(cur_match) X X public _longest_match X_longest_match proc near X X push bp X push di X X; match equ si X; scan equ di X; chain_count equ bp X; ma_length equ bx X X lea di,_window[di+2] ; di = window + strstart + 2 X mov cur_best,NIL X mov bp,_max_chain_length ; chain_count = max_chain_length X mov bx,_start_length ; ma_length = start_length X mov ax,[bx+di-3] ; ax = scan[ma_length-1..ma_length] X mov cx,[di-2] ; cx = scan[0..1] X jmp short do_scan X X even ; align destination of branch long_loop: X; at this point, di == scan+2, si = cur_match X mov ax,[bx+di-3] ; ax = scan[ma_length-1..ma_length] X mov cx,[di-2] ; cx = scan[0..1] short_loop: X dec bp ; --chain_count X jz the_end X; at this point, di == scan+2, si = cur_match, X; ax = scan[ma_length-1..ma_length] and cx = scan[0..1] X shl si,1 ; cur_match as word index X mov si,es:prev[si] ; cur_match = prev[cur_match] X cmp si,NIL X je the_end do_scan: X cmp ax,word ptr _window[bx+si-1] ; check match at ma_length-1 X jne short_loop X cmp cx,word ptr _window[si] ; check min_match_length match X jne short_loop X mov dx,si ; dx = cur_match X lea si,_window[si+2] ; si = match X mov ax,di ; ax = scan+2 X mov cx,ds X mov es,cx X mov cx,(MAX_LENGTH-2)/2 ; scan for at most MAX_LENGTH bytes X repe cmpsw ; loop until mismatch X load_es_prev ; reset es to address the prev array X mov cl,[di-2] ; mismatch on first or second byte? X sub cl,[si-2] ; dl = 0 if first bytes equal X xchg ax,di ; di = scan+2, ax = end of scan X sub ax,di ; ax = len X sub cl,1 ; set carry if cl == 0 (can't use DEC) X adc ax,0 ; ax = carry ? len+1 : len X mov si,dx ; si = cur_match X cmp ax,bx ; len > ma_length ? X jle long_loop X mov cur_best,si ; cur_best = cur_match X mov bx,ax ; bx = ma_length = len X cmp ax,_strsize ; len >= strsize ? X jle long_loop the_end: X cmp bx,_start_length ; ma_length > start_length ? X jle return X mov _match_length,bx ; match_length = ma_length return: X mov si,cur_best ; result = cur_best X pop di X pop bp X ret X X_longest_match endp X X_TEXT ends end END_OF_FILE if test 9944 -ne `wc -c <'im_lm.asm'`; then echo shar: \"'im_lm.asm'\" unpacked with wrong size! fi # end of 'im_lm.asm' fi if test -f 'implode.c' -a "${1}" != "-c" ; then echo shar: Will not clobber existing file \"'implode.c'\" else echo shar: Extracting \"'implode.c'\" \(8838 characters\) sed "s/^X//" >'implode.c' <<'END_OF_FILE' X/* X X Copyright (C) 1990,1991 Mark Adler, Richard B. Wales, and Jean-loup Gailly. X Permission is granted to any individual or institution to use, copy, or X redistribute this software so long as all of the original files are included X unmodified, that it is not sold for profit, and that this copyright notice X is retained. X X*/ X X/* X * implode.c by Richard B. Wales. X * X * PURPOSE X * X * Compress an input file using the ZIP "implosion" method. X * X * DISCUSSION X * X * The "implosion" algorithm is a composite of (1) OPM/L compres- X * sion within a sliding window, and (2) variable-length binary X * encoding of various parts of the OPM/L output. X * X * For a detailed treatment of OPM/L compression, see the source X * file "im_lmat.c". X * X * For a detailed treatment of variable-length binary encoding, X * see the source file "im_ctree.c". X * X * Since Pass Two (binary encoding) depends on a statistical anal- X * ysis of the entire output of Pass One (OPM/L compression), the X * Pass One output is saved in a temporary file and re-read for X * Pass Two. Implosion is thus a two-pass algorithm. X * X * An outline of the algorithm follows: X * X * (1) The entire input file is read and compressed via the OPM/L X * technique. The OPM/L output is saved in a temporary file. X * X * (2) The compressed info from (1) is analyzed, and various fre- X * quency counts are tallied. X * X * (3) Based on the frequency counts, a decision is made as to X * whether or not the "literal" characters (those not matched X * in earlier parts of the input) should be represented via a X * binary code tree or left "as is". If there are not very X * many literal characters, or if their frequency distribution X * is fairly even, the number of bits saved through coding may X * be exceeded by the amount of info which must be included to X * describe the tree. X * X * (4) The temporary file from (1) is re-read. The information is X * further compressed via the binary code trees, and the result X * is sent to the ZIP output file. X * X * REFERENCES X * X * APPNOTE.TXT documentation file in PKZIP 1.10 distribution. X * X * See also references in the "im_lmat.c" and "im_ctree.c" source X * files. X * X * INTERFACE X * X * int imp_setup (long filesize, int pack_level) X * Initialize the "implode" routines for a new file. X * X * int imp_p1 (char *buf, int count) X * Process "count" input characters pointed to by "buf". This X * routine is called as many times as needed to process the X * entire input file. There is no upper limit on "count". X * X * int imp_size (long *size, char *opts) X * Terminate processing of the input file, and return the com- X * pressed size ("size") and implosion option flags ("opts"). X * X * int imp_p2 (FILE *outfp) X * Output the entire compressed file -- including S-F trees and X * data -- to the ZIP output file "outfp". X * X * int imp_clear (void) X * Clean up after processing and outputting a file. This rou- X * tine may be called at any time to abort further processing. X * X * All the above routines return zero if successful, or a non-zero X * value if there was an error. X */ X X#include "implode.h" X#include "ziperr.h" X X X/*********************************************************************** X * X * Error return codes for the main ZIP program. X * Most of these are in "ziperr.h". X */ X X#define ZE_MAP(err) \ X ( ((err) == IM_OK) ? ZE_OK \ X : ((err) == IM_NOMEM) ? ZE_MEM \ X : ((err) == IM_IOERR) ? ZE_TEMP \ X : (fprintf(stderr,"\nZE_MAP(%d)",(err)), \ X ZE_LOGIC)) X X X/*********************************************************************** X * X * State variable for the implosion routines. X */ X X#define IMP_SETUP 0 /* need to do "imp_setup" */ X#define IMP_P1 1 /* setup done, ready for Pass 1 */ X#define IMP_P2 2 /* Pass 1 done, ready for Pass 2 */ X#define IMP_CLEAR 3 /* Pass 2 done, ready for "imp_clear" */ X local int imp_state = IMP_SETUP; X X X/*********************************************************************** X * X * Global variables. X */ X XFDATA fd; X X X/*********************************************************************** X * X * Set up for performing implosion on a new file. X */ X int imp_setup (filesize, pack_level) X long filesize; /* input file size */ X int pack_level; /* 0 = best speed, 9 = best compression, other = default */ X{ X ImpErr retcode; X extern char *tempname(); X X if (imp_state != IMP_SETUP) X { fprintf (stderr, "\nimp_setup called with wrong state %d", X imp_state); X return ZE_LOGIC; X } X imp_state = IMP_P1; X X /* Set up the initial parameters. Use an 8K window if the input X * file is greater than or equal to 5.5K, a 4K window otherwise. X */ X fd.fd_bufsize = 8192; X if (filesize < 5632) fd.fd_bufsize = 4096; X X fd.fd_strsize = 320; X fd.fd_nbits = (fd.fd_bufsize == 4096) ? 6 : 7; X X /* Create a temporary file for the Pass One output. */ X fd.fd_temp = topen ('I'); X if (fd.fd_temp == NULL) X return ZE_MEM; X X /* X * Initialize the "longest match" routines. X * "ct_init" is called at this point because the "lm_input" routine X * (called in "imp_p1") calls the "ct_tally" routine. X */ X retcode = lm_init (pack_level); X if (retcode != IM_OK) return ZE_MAP (retcode); X retcode = ct_init (); X return ZE_MAP (retcode); X} X X X/*********************************************************************** X * X * Feed characters to the implosion computation. X * This routine is called as many times as needed, until the entire X * input file has been processed. X */ X int imp_p1 (buf, count) X char *buf; /* input characters */ X int count; /* character count */ X{ ImpErr retcode; X X if (imp_state != IMP_P1) X { fprintf (stderr, "\nimp_p1 called with wrong state %d", X imp_state); X return ZE_LOGIC; X } X X if (buf == NULL || count < 0) X { fprintf (stderr, "\nimp_p1 called with bad arguments"); X return ZE_LOGIC; X } X retcode = lm_input ((U_CHAR *) buf, (U_INT) count); X return ZE_MAP (retcode); X} X X X/*********************************************************************** X * X * Terminate processing of input for this file, and determine the size X * this file will have if it is imploded. Also, find out whether two X * or three S-F trees will be used (needed for directory entries). X */ X int imp_size (size, opts) X long *size; /* imploded size */ X char *opts; /* implosion option info */ X{ ImpErr retcode; X X if (imp_state != IMP_P1) X { fprintf (stderr, "\nimp_size called with wrong state %d", X imp_state); X return ZE_LOGIC; X } X imp_state = IMP_P2; X X if (size == NULL || opts == NULL) X { fprintf (stderr, "\nimp_size called with bad arguments"); X return ZE_LOGIC; X } X retcode = lm_windup (); X if (retcode != IM_OK) return ZE_MAP (retcode); X retcode = ct_mktrees (); X if (retcode != IM_OK) return ZE_MAP (retcode); X *size = fd.fd_clen; X *opts = (char)(((fd.fd_bufsize == 8192) ? 0x02 : 0) X | ((fd.fd_method == LITERAL_TREE) ? 0x04 : 0)); X return ZE_OK; X} X X X/*********************************************************************** X * X * Output the imploded version of the file (but not the file directory X * info) to the ZIP file. X */ X int imp_p2 (outfp) X FILE *outfp; /* output (ZIP) file */ X{ ImpErr retcode; X X if (imp_state != IMP_P2) X { fprintf (stderr, "\nimp_p2 called with wrong state %d", X imp_state); X return ZE_LOGIC; X } X imp_state = IMP_CLEAR; X X if (outfp == NULL) X { fprintf (stderr, "\nimp_p2 called with bad arguments"); X return ZE_LOGIC; X } X retcode = ct_wrtrees (outfp); X if (retcode != IM_OK) return ZE_MAP (retcode); X retcode = ct_wrdata (outfp); X if (retcode != IM_OK) return ZE_MAP (retcode); X fflush (outfp); X if (ferror (outfp)) return ZE_TEMP; X return ZE_OK; X} X X X/*********************************************************************** X * X * Clean up after doing an implosion. X * This routine may also be called at any time to abort an implosion. X */ X int imp_clear () X{ (void) lm_windup (); X if (fd.fd_temp != NULL) X (void) tclose (fd.fd_temp); X (void) ct_windup (); X imp_state = IMP_SETUP; X return ZE_OK; X} X X X/**********************************************************************/ END_OF_FILE if test 8838 -ne `wc -c <'implode.c'`; then echo shar: \"'implode.c'\" unpacked with wrong size! fi # end of 'implode.c' fi if test -f 'shrink.c' -a "${1}" != "-c" ; then echo shar: Will not clobber existing file \"'shrink.c'\" else echo shar: Extracting \"'shrink.c'\" \(10431 characters\) sed "s/^X//" >'shrink.c' <<'END_OF_FILE' X/* X X Copyright (C) 1990,1991 Mark Adler, Richard B. Wales, and Jean-loup Gailly. X Permission is granted to any individual or institution to use, copy, or X redistribute this software so long as all of the original files are included X unmodified, that it is not sold for profit, and that this copyright notice X is retained. X X*/ X X/* X * shrink.c by Mark Adler. X */ X X#include "zip.h" X#include "tempf.h" X X X/* X Shrink.Pas version 1.2 by R. P. Byrne, 1989 (in Pascal and assembler). X We here heartily acknowledge R. P. Byrne's contribution to this project. X The existence of this program really triggered our efforts to write a X portable Unix zip. What little remains of Byrne's program lies in this X source file, and those remnants are mostly in the variable and routine X names, since it has been translated and extensively modified and rewritten. X X Stolen, translated into C, and modified by Mark Adler, 11 October 1990. X Severely modified again by Mark Adler, 11 July 1991, to remove the X unnecessary FreeList and ClearTable arrays, and to replace the recursive X Prune() routine with a non-recursive method. X As Stravinsky once said: "Mediocre composers plagiarize. X Great composers steal." X*/ X X/* Compress a set of input files into a Zip file using Lempel-Ziv-Welch */ X/* (LZW) compression techniques (the "shrink" method). */ X X X#define MINBITS 9 /* Starting code size of 9 bits */ X#define MAXBITS 13 /* Maximum code size of 13 bits */ X#define TABLESIZE 8191 /* We'll need 4K entries in table */ X#define SPECIAL 256 /* Special function code */ X#define INCSIZE 1 /* Code for a jump in code size */ X#define CLEARCODE 2 /* Code for code table has been cleared */ X#define FIRSTENTRY 257 /* First available table entry */ X X X/* Define data types needed to implement a code table for LZW compression */ X typedef struct CodeRec { X /* Code Table record format... */ X short Child; /* Addr of 1st suffix for this prefix */ X short Sibling; /* Addr of next suffix in chain */ X uch Suffix; /* Suffix character */ X} CodeRec; X typedef CodeRec CodeArray[TABLESIZE + 1]; /* Define the code table */ X X X X/* Private globals */ local CodeRec far *CodeTable; /* Points to code table for LZW compression */ X local int NextFree; /* Next free table entry */ X local int CodeSize; /* Size of codes (in bits) currently being written */ local int MaxCode; /* Largest code that can be written in CodeSize bits */ X local int FirstCh; /* Flag indicating the START of a shrink operation */ X local tFILE *tempf = NULL; /* Temporary file */ local ulg count; /* Count of bytes written */ X X X/* Local functions */ X#ifdef PROTO X local void PutCode(int); X local int Build_Data_Structures(void); X local void Destroy_Data_Structures(void); X local void Initialize_Data_Structures(void); X local void Clear_Table(void); X local void Table_Add(int, int); X#endif /* PROTO */ X X X/* Macro for PutCode() that writes to tempf and counts bytes in count */ X#define PUT(c) {tputc(c, tempf); count++;} X local void PutCode(c) int c; /* code to send */ X/* Write out the low CodeSize bits of c using the PUT macro. If c is -1, X then flush the bit buffer. Assumes CodeSize < 16. By Mark Adler. */ X{ X static int b = 0; /* current bits waiting to go out */ X static int n = 0; /* number of bits in b */ X /* masks for all bit values */ X static int x[] = {0, 1, 3, 7, 0xf, 0x1f, 0x3f, 0x7f, 0xff, 0x1ff, X 0x3ff, 0x7ff, 0xfff, 0x1fff, 0x3fff, 0x7fff, 0xffff}; X X if (c == -1) X { X if (n) X { X if (n > 8) X { X PUT((char)b) X PUT((char)((b >> 8) & x[n - 8])) X } X else X PUT((char)(b & x[n])) X b = n = 0; X } X } X else X { X b |= (c & x[CodeSize]) << n; X n += CodeSize; X if (n >= 16) X { X PUT((char)b) X PUT((char)(b >> 8)) X if (n == 16) X b = n = 0; X else X { X n -= 16; X b = (c >> (CodeSize - n)) & x[n]; X } X } X } X} X X X local int Build_Data_Structures() X/* Allocate tables for shrinking. Return true on failure. */ X{ X return (CodeTable = (CodeRec far *)farmalloc(sizeof(CodeArray))) == NULL; X} X X X local void Destroy_Data_Structures() X/* Deallocate tables for shrinking. */ X{ X if (CodeTable != NULL) X { X farfree((voidp far *)CodeTable); X CodeTable = NULL; X } X} X X X local void Initialize_Data_Structures() X/* Clear tables for shrinking. */ X{ X int i; /* counter for table entries */ X CodeRec far *t; /* pointer to current table entry */ X X /* Initialize parent symbols */ X for (i = 0, t = CodeTable; i <= 255; i++, t++) X { X t->Child = -1; X t->Suffix = (uch)i; X } X X /* Build free list */ X NextFree = FIRSTENTRY; X for (i = FIRSTENTRY, t = CodeTable + FIRSTENTRY; i < TABLESIZE; i++, t++) X t->Child = i + 1; X t->Child = -1; X} X X X local void Clear_Table() X/* Clear the leaves of the tree--assume all entries used (NextFree == -1) */ X{ X int n; /* node counter */ X CodeRec far *p; /* pointer to next node to look at */ X short far *q; /* pointer to node child or sibling entry */ X X /* Mark leaf nodes */ X p = CodeTable + TABLESIZE; X n = TABLESIZE + 1 - FIRSTENTRY; X do { X if (p->Child == -1) X p->Child = -2; X p--; X } while (--n); X X /* Shake leaves from tree */ X p = CodeTable; X n = 256; X do { X q = &p->Child; X while (*q != -1 && CodeTable[*q].Child == -2) X *q = CodeTable[*q].Sibling; X p++; X } while (--n); X p = CodeTable + FIRSTENTRY; X n = TABLESIZE + 1 - FIRSTENTRY; X do { X if (p->Child != -2) X { X q = &p->Child; X while (*q != -1 && CodeTable[*q].Child == -2) X *q = CodeTable[*q].Sibling; X q = &p->Sibling; X while (*q != -1 && CodeTable[*q].Child == -2) X *q = CodeTable[*q].Sibling; X } X p++; X } while (--n); X X /* Build the list of free table entries */ X NextFree = -1; X p = CodeTable + TABLESIZE; X n = TABLESIZE + 1 - FIRSTENTRY; X do { X if (p->Child == -2) X { X p->Child = NextFree; X NextFree = n + FIRSTENTRY - 1; X } X p--; X } while (--n); X} X X X local void Table_Add(p, s) int p; /* prefix to add to */ int s; /* suffix to add to it */ X/* Add an entry to the table. */ X{ X int f; /* next free node */ X X if ((f = NextFree) != -1) X { X NextFree = CodeTable[f].Child; X CodeTable[f].Child = -1; X CodeTable[f].Sibling = -1; X CodeTable[f].Suffix = (uch)s; X if (CodeTable[p].Child == -1) X CodeTable[p].Child = f; X else X { X p = CodeTable[p].Child; X while (CodeTable[p].Sibling != -1) X p = CodeTable[p].Sibling; X CodeTable[p].Sibling = f; X } X } X} X X local int lastcode; X int shr_setup() X/* Initialize shrink() routines. Return an error code in the ZE_ class. */ X{ X if (Build_Data_Structures()) X return ZE_MEM; X Initialize_Data_Structures(); X FirstCh = 1; X lastcode = -1; X if ((tempf = topen('S')) == NULL) X return ZE_MEM; X count = 0; X return ZE_OK; X} X X int shr_p1(b, n) uch *b; /* buffer with bytes to shrink */ extent n; /* number of bytes in buffer */ X/* Shrink n bytes at *b. Return an error code in the ZE_ class. */ X{ X int f; /* result of Table_Lookup */ X int s; /* byte to shrink */ X X if (FirstCh && n) X { /* If just getting started ... */ X CodeSize = MINBITS; /* Initialize code size to minimum */ X MaxCode = (1 << CodeSize) - 1; X lastcode = *b++; n--; /* get first character from input, */ X FirstCh = 0; /* and reset the first char flag. */ X } X while (NextFree == -1 && n) X { X /* Ok, lets clear the code table (adaptive reset) */ X PutCode(lastcode); X PutCode(SPECIAL); X PutCode(CLEARCODE); X Clear_Table(); X Table_Add(lastcode, s = *b++); n--; X lastcode = s; X } X while (n) X { X s = *b++; n--; X f = CodeTable[lastcode].Child; X while (f != -1 && CodeTable[f].Suffix != (uch)s) X f = CodeTable[f].Sibling; X if (f != -1) X /* If lastcode:s pair is found in the code table, then ... */ X /* ... set lastcode to the entry where the pair is located */ X lastcode = f; X else X { X /* Not in table */ X PutCode(lastcode); /* Write current lastcode */ X Table_Add(lastcode, s); /* Attempt to add to code table */ X lastcode = s; /* Reset lastcode for new char */ X if (NextFree > MaxCode && CodeSize < MAXBITS) X { X /* Time to increase the code size and change the max. code */ X PutCode(SPECIAL); X PutCode(INCSIZE); X CodeSize++; X MaxCode = (1 << CodeSize) - 1; X } X while (NextFree == -1 && n) X { X /* Ok, lets clear the code table (adaptive reset) */ X PutCode(lastcode); X PutCode(SPECIAL); X PutCode(CLEARCODE); X Clear_Table(); X Table_Add(lastcode, s = *b++); n--; X lastcode = s; X } X } X } X return ZE_OK; X} X X int shr_size(s) ulg *s; /* return value: size of shrunk data */ X/* End shrink and return size of shrunk data in *s. Return an error code in X the ZE_ class. */ X{ X PutCode(lastcode); /* Write last prefix code */ X PutCode(-1); /* Tell putcode to flush remaining bits */ X Destroy_Data_Structures(); X *s = count; X return tflush(tempf) || terror(tempf) ? ZE_TEMP : ZE_OK; X} X X int shr_p2(f) XFILE *f; /* file to write shrunk data to */ X/* Copy shrunk data from temporary file to zip file *f. Return an error X code in the ZE_ class. */ X{ X char *b; /* malloc'ed buffer for copying */ X extent k; /* holds result of fread */ X X if ((b = malloc(BSZ)) == NULL) X return ZE_MEM; X trewind(tempf); X while ((k = tread(b, 1, BSZ, tempf)) > 0) X if (zfwrite(b, 1, k, f) != k) X { X free((voidp *)b); X return ZE_TEMP; X } X free((voidp *)b); X if (terror(tempf)) X return ZE_TEMP; X tclose(tempf); X tempf = NULL; X return ZE_OK; X} X X int shr_clear() X/* Terminate shrink procedure (at any time). Return an error code in X the ZE_ class (always ZE_OK). */ X{ X Destroy_Data_Structures(); X if (tempf != NULL) X { X tclose(tempf); X tempf = NULL; X } X return ZE_OK; X} END_OF_FILE if test 10431 -ne `wc -c <'shrink.c'`; then echo shar: \"'shrink.c'\" unpacked with wrong size! fi # end of 'shrink.c' fi if test -f 'util.c' -a "${1}" != "-c" ; then echo shar: Will not clobber existing file \"'util.c'\" else echo shar: Extracting \"'util.c'\" \(10800 characters\) sed "s/^X//" >'util.c' <<'END_OF_FILE' X/* X X Copyright (C) 1990,1991 Mark Adler, Richard B. Wales, and Jean-loup Gailly. X Permission is granted to any individual or institution to use, copy, or X redistribute this software so long as all of the original files are included X unmodified, that it is not sold for profit, and that this copyright notice X is retained. X X*/ X X/* X * util.c by Mark Adler. X */ X X#include "zip.h" X X/* Local functions */ X#ifdef PROTO X local int recmatch(char *, char *); X#endif /* PROTO */ X X char *isshexp(p) char *p; /* candidate sh expression */ X/* If p is a sh expression, a pointer to the first special character is X returned. Otherwise, NULL is returned. */ X{ X int c; X X c = -1; X for (; *p; p++) X#ifdef VMS X if (c != '\\' && (*p == '%' || *p == '*')) X#else /* !VMS */ X if (c != '\\' && (*p == '?' || *p == '*' || *p == '[')) X#endif /* ?VMS */ X return p; X return NULL; X} X X local int recmatch(p, s) char *p; /* sh pattern to match */ char *s; /* string to match it to */ X/* Recursively compare the sh pattern p with the string s and return 1 if X they match, and 0 or 2 if they don't or if there is a syntax error in the X pattern. This routine recurses on itself no deeper than the number of X characters in the pattern. */ X{ X int c; /* pattern char or start of range in [-] loop */ X X /* Get first character, the pattern for new recmatch calls follows */ X c = *p++; X X /* If that was the end of the pattern, match if string empty too */ X if (c == 0) X return *s == 0; X X /* '?' (or '%') matches any character (but not an empty string) */ X#ifdef VMS X if (c == '%') X#else /* !VMS */ X if (c == '?') X#endif /* ?VMS */ X return *s ? recmatch(p, s + 1) : 0; X X /* '*' matches any number of characters, including zero */ X if (c == '*') X { X if (*p == 0) X return 1; X for (; *s; s++) X if ((c = recmatch(p, s)) != 0) X return c; X return 2; /* 2 means give up--shmatch will return false */ X } X X#ifndef VMS /* No bracket matching in VMS */ X /* Parse and process the list of characters and ranges in brackets */ X if (c == '[') X { X int e; /* flag true if next char to be taken literally */ X char *q; /* pointer to end of [-] group */ X int r; /* flag true to match anything but the range */ X X if (*s == 0) /* need a character to match */ X return 0; X p += (r = *p == '!'); /* see if reverse */ X for (q = p, e = 0; *q; q++) /* find closing bracket */ X if (e) X e = 0; X else X if (*q == '\\') X e = 1; X else if (*q == ']') X break; X if (*q != ']') /* nothing matches if bad syntax */ X return 0; X for (c = 0, e = *p == '-'; p < q; p++) /* go through the list */ X { X if (e == 0 && *p == '\\') /* set escape flag if \ */ X e = 1; X else if (e == 0 && *p == '-') /* set start of range if - */ X c = *(p-1); X else X { X if (*(p+1) != '-') X for (c = c ? c : *p; c <= *p; c++) /* compare range */ X#ifdef OS2 X if (tolower(c) == tolower(*s)) X#else /* !OS2 */ X if (c == *s) X#endif /* ?OS2 */ X return r ? 0 : recmatch(q + 1, s + 1); X c = e = 0; /* clear range, escape flags */ X } X } X return r ? recmatch(q + 1, s + 1) : 0; /* bracket match failed */ X } X#endif /* !VMS */ X X /* If escape ('\'), just compare next character */ X if (c == '\\') X if ((c = *p++) == 0) /* if \ at end, then syntax error */ X return 0; X X /* Just a character--compare it */ X#ifdef OS2 X return tolower(c) == tolower(*s) ? recmatch(p, ++s) : 0; X#else /* !OS2 */ X return c == *s++ ? recmatch(p, s) : 0; /* compare one character */ X#endif /* ?OS2 */ X} X X int shmatch(p, s) char *p; /* sh pattern to match */ char *s; /* string to match it to */ X/* Compare the sh pattern p with the string s and return true if they match, X false if they don't or if there is a syntax error in the pattern. */ X{ X return recmatch(p, s) == 1; X} X X X#ifdef MSDOS X int dosmatch(p, s) char *p; /* dos pattern to match */ char *s; /* string to match it to */ X/* Break the pattern and string into name and extension parts and match X each separately using shmatch(). */ X{ X char *p1, *p2; /* pattern sections */ X char *s1, *s2; /* string sections */ X int r; /* result */ X X if ((p1 = malloc(strlen(p) + 1)) == NULL || X (s1 = malloc(strlen(s) + 1)) == NULL) X { X if (p1 != NULL) X free((voidp *)p1); X return 0; X } X strcpy(p1, p); X strcpy(s1, s); X if ((p2 = strrchr(p1, '.')) != NULL) X *p2++ = 0; X else X p2 = ""; X if ((s2 = strrchr(s1, '.')) != NULL) X *s2++ = 0; X else X s2 = ""; X r = shmatch(p2, s2) && shmatch(p1, s1); X free((voidp *)p1); X free((voidp *)s1); X return r; X} X X#endif /* MSDOS */ X X voidp far **search(b, a, n, cmp) voidp *b; /* pointer to value to search for */ voidp far **a; /* table of pointers to values, sorted */ extent n; /* number of pointers in a[] */ int (*cmp) OF((voidp *, voidp far *)); /* comparison function for search */ X/* Search for b in the pointer list a[0..n-1] using the compare function X cmp(b, c) where c is an element of a[i] and cmp() returns negative if X *b < *c, zero if *b == *c, or positive if *b > *c. If *b is found, X search returns a pointer to the entry in a[], else search() returns X NULL. The nature and size of *b and *c (they can be different) are X left up to the cmp() function. A binary search is used, and it is X assumed that the list is sorted in ascending order. */ X{ X voidp far **i; /* pointer to midpoint of current range */ X voidp far **l; /* pointer to lower end of current range */ X int r; /* result of (*cmp)() call */ X voidp far **u; /* pointer to upper end of current range */ X X l = (voidp far **)a; u = l + (n-1); X while (u >= l) X if ((r = (*cmp)(b, *(i = l + ((u - l) >> 1)))) < 0) X u = i - 1; X else if (r > 0) X l = i + 1; X else X return (voidp far **)i; X return NULL; /* If b were in list, it would belong at l */ X} X X X X/* Table of CRC-32's of all single byte values (made by makecrc.c) */ local ulg crctab[] = { X 0x00000000L, 0x77073096L, 0xee0e612cL, 0x990951baL, 0x076dc419L, X 0x706af48fL, 0xe963a535L, 0x9e6495a3L, 0x0edb8832L, 0x79dcb8a4L, X 0xe0d5e91eL, 0x97d2d988L, 0x09b64c2bL, 0x7eb17cbdL, 0xe7b82d07L, X 0x90bf1d91L, 0x1db71064L, 0x6ab020f2L, 0xf3b97148L, 0x84be41deL, X 0x1adad47dL, 0x6ddde4ebL, 0xf4d4b551L, 0x83d385c7L, 0x136c9856L, X 0x646ba8c0L, 0xfd62f97aL, 0x8a65c9ecL, 0x14015c4fL, 0x63066cd9L, X 0xfa0f3d63L, 0x8d080df5L, 0x3b6e20c8L, 0x4c69105eL, 0xd56041e4L, X 0xa2677172L, 0x3c03e4d1L, 0x4b04d447L, 0xd20d85fdL, 0xa50ab56bL, X 0x35b5a8faL, 0x42b2986cL, 0xdbbbc9d6L, 0xacbcf940L, 0x32d86ce3L, X 0x45df5c75L, 0xdcd60dcfL, 0xabd13d59L, 0x26d930acL, 0x51de003aL, X 0xc8d75180L, 0xbfd06116L, 0x21b4f4b5L, 0x56b3c423L, 0xcfba9599L, X 0xb8bda50fL, 0x2802b89eL, 0x5f058808L, 0xc60cd9b2L, 0xb10be924L, X 0x2f6f7c87L, 0x58684c11L, 0xc1611dabL, 0xb6662d3dL, 0x76dc4190L, X 0x01db7106L, 0x98d220bcL, 0xefd5102aL, 0x71b18589L, 0x06b6b51fL, X 0x9fbfe4a5L, 0xe8b8d433L, 0x7807c9a2L, 0x0f00f934L, 0x9609a88eL, X 0xe10e9818L, 0x7f6a0dbbL, 0x086d3d2dL, 0x91646c97L, 0xe6635c01L, X 0x6b6b51f4L, 0x1c6c6162L, 0x856530d8L, 0xf262004eL, 0x6c0695edL, X 0x1b01a57bL, 0x8208f4c1L, 0xf50fc457L, 0x65b0d9c6L, 0x12b7e950L, X 0x8bbeb8eaL, 0xfcb9887cL, 0x62dd1ddfL, 0x15da2d49L, 0x8cd37cf3L, X 0xfbd44c65L, 0x4db26158L, 0x3ab551ceL, 0xa3bc0074L, 0xd4bb30e2L, X 0x4adfa541L, 0x3dd895d7L, 0xa4d1c46dL, 0xd3d6f4fbL, 0x4369e96aL, X 0x346ed9fcL, 0xad678846L, 0xda60b8d0L, 0x44042d73L, 0x33031de5L, X 0xaa0a4c5fL, 0xdd0d7cc9L, 0x5005713cL, 0x270241aaL, 0xbe0b1010L, X 0xc90c2086L, 0x5768b525L, 0x206f85b3L, 0xb966d409L, 0xce61e49fL, X 0x5edef90eL, 0x29d9c998L, 0xb0d09822L, 0xc7d7a8b4L, 0x59b33d17L, X 0x2eb40d81L, 0xb7bd5c3bL, 0xc0ba6cadL, 0xedb88320L, 0x9abfb3b6L, X 0x03b6e20cL, 0x74b1d29aL, 0xead54739L, 0x9dd277afL, 0x04db2615L, X 0x73dc1683L, 0xe3630b12L, 0x94643b84L, 0x0d6d6a3eL, 0x7a6a5aa8L, X 0xe40ecf0bL, 0x9309ff9dL, 0x0a00ae27L, 0x7d079eb1L, 0xf00f9344L, X 0x8708a3d2L, 0x1e01f268L, 0x6906c2feL, 0xf762575dL, 0x806567cbL, X 0x196c3671L, 0x6e6b06e7L, 0xfed41b76L, 0x89d32be0L, 0x10da7a5aL, X 0x67dd4accL, 0xf9b9df6fL, 0x8ebeeff9L, 0x17b7be43L, 0x60b08ed5L, X 0xd6d6a3e8L, 0xa1d1937eL, 0x38d8c2c4L, 0x4fdff252L, 0xd1bb67f1L, X 0xa6bc5767L, 0x3fb506ddL, 0x48b2364bL, 0xd80d2bdaL, 0xaf0a1b4cL, X 0x36034af6L, 0x41047a60L, 0xdf60efc3L, 0xa867df55L, 0x316e8eefL, X 0x4669be79L, 0xcb61b38cL, 0xbc66831aL, 0x256fd2a0L, 0x5268e236L, X 0xcc0c7795L, 0xbb0b4703L, 0x220216b9L, 0x5505262fL, 0xc5ba3bbeL, X 0xb2bd0b28L, 0x2bb45a92L, 0x5cb36a04L, 0xc2d7ffa7L, 0xb5d0cf31L, X 0x2cd99e8bL, 0x5bdeae1dL, 0x9b64c2b0L, 0xec63f226L, 0x756aa39cL, X 0x026d930aL, 0x9c0906a9L, 0xeb0e363fL, 0x72076785L, 0x05005713L, X 0x95bf4a82L, 0xe2b87a14L, 0x7bb12baeL, 0x0cb61b38L, 0x92d28e9bL, X 0xe5d5be0dL, 0x7cdcefb7L, 0x0bdbdf21L, 0x86d3d2d4L, 0xf1d4e242L, X 0x68ddb3f8L, 0x1fda836eL, 0x81be16cdL, 0xf6b9265bL, 0x6fb077e1L, X 0x18b74777L, 0x88085ae6L, 0xff0f6a70L, 0x66063bcaL, 0x11010b5cL, X 0x8f659effL, 0xf862ae69L, 0x616bffd3L, 0x166ccf45L, 0xa00ae278L, X 0xd70dd2eeL, 0x4e048354L, 0x3903b3c2L, 0xa7672661L, 0xd06016f7L, X 0x4969474dL, 0x3e6e77dbL, 0xaed16a4aL, 0xd9d65adcL, 0x40df0b66L, X 0x37d83bf0L, 0xa9bcae53L, 0xdebb9ec5L, 0x47b2cf7fL, 0x30b5ffe9L, X 0xbdbdf21cL, 0xcabac28aL, 0x53b39330L, 0x24b4a3a6L, 0xbad03605L, X 0xcdd70693L, 0x54de5729L, 0x23d967bfL, 0xb3667a2eL, 0xc4614ab8L, X 0x5d681b02L, 0x2a6f2b94L, 0xb40bbe37L, 0xc30c8ea1L, 0x5a05df1bL, X 0x2d02ef8dL X}; X X ulg crc32(c, b) ulg c; /* current contents of crc shift register */ int b; /* byte (eight bits) to run through */ X/* Return the CRC-32 c updated with the eight bits in b. */ X{ X return crctab[((int)c ^ b) & 0xff] ^ (c >> 8); X} X X ulg updcrc(s, n) char *s; /* pointer to bytes to pump through */ extent n; /* number of bytes in s[] */ X/* Run a set of bytes through the crc shift register. If s is a NULL X pointer, then initialize the crc shift register contents instead. X Return the current crc in either case. */ X{ X register ulg c; /* temporary variable */ X X static ulg crc = 0xffffffffL; /* shift register contents */ X X if (s == NULL) X c = 0xffffffffL; X else X { X c = crc; X while (n--) X c = crctab[((int)c ^ (*s++)) & 0xff] ^ (c >> 8); X } X crc = c; X return c ^ 0xffffffffL; /* (instead of ~c for 64-bit machines) */ X} END_OF_FILE if test 10800 -ne `wc -c <'util.c'`; then echo shar: \"'util.c'\" unpacked with wrong size! fi # end of 'util.c' fi if test -f 'zip.h' -a "${1}" != "-c" ; then echo shar: Will not clobber existing file \"'zip.h'\" else echo shar: Extracting \"'zip.h'\" \(8017 characters\) sed "s/^X//" >'zip.h' <<'END_OF_FILE' X/* X X Copyright (C) 1990,1991 Mark Adler, Richard B. Wales, and Jean-loup Gailly. X Permission is granted to any individual or institution to use, copy, or X redistribute this software so long as all of the original files are included X unmodified, that it is not sold for profit, and that this copyright notice X is retained. X X*/ X X/* X * zip.h by Mark Adler. X */ X X X/* Set up portability */ X#include "tailor.h" X X/* Define malloc() and string functions */ X#ifdef MODERN X# include X#else /* !MODERN */ X voidp *malloc(); X char *getenv(); X long atol(); X char *strcpy(); X char *strcat(); X char *strchr(); X char *strrchr(); X# ifndef ZMEM X char *memset(); X char *memcpy(); X# endif /* !ZMEM */ X#endif /* ?MODERN */ X X X/* Define fseek() commands */ X#ifndef SEEK_SET X# define SEEK_SET 0 X#endif /* !SEEK_SET */ X X#ifndef SEEK_CUR X# define SEEK_CUR 1 X#endif /* !SEEK_CUR */ X X X/* Forget FILENAME_MAX (incorrectly = 14 on some System V) */ X#ifdef MSDOS X# define FNMAX 256 X#else /* !MSDOS */ X# define FNMAX 1024 X#endif /* ?MSDOS */ X X X/* Types centralized here for easy modification */ X#define local static /* More meaningful outside functions */ typedef unsigned char uch; /* unsigned 8-bit value */ typedef unsigned short ush; /* unsigned 16-bit value */ typedef unsigned long ulg; /* unsigned 32-bit value */ X X X/* Lengths of headers after signatures in bytes */ X#define LOCHEAD 26 X#define CENHEAD 42 X#define ENDHEAD 18 X X X/* Structures for in-memory file information */ struct zlist { X /* See central header in zipfile.c for what vem..off are */ X ush vem, ver, flg, how; X ulg tim, crc, siz, len; X extent nam, ext, cext, com; /* offset of ext must be >= LOCHEAD */ X ush dsk, att, lflg; /* offset of lflg must be >= LOCHEAD */ X ulg atx, off; X char *name; /* File name in zip file */ X char *extra; /* Extra field (set only if ext != 0) */ X char *cextra; /* Extra in central (set only if cext != 0) */ X char *comment; /* Comment (set only if com != 0) */ X char *zname; /* Name for new zip file header */ X int mark; /* Marker for files to operate on */ X int trash; /* Marker for files to delete */ X struct zlist far *nxt; /* Pointer to next header in list */ X}; struct flist { X char *name; /* Pointer to zero-delimited name */ X char *zname; /* Name used for zip file headers */ X struct flist far * far *lst; /* Pointer to link pointing here */ X struct flist far *nxt; /* Link to next name */ X}; X X X/* Error return codes and PERR macro */ X#include "ziperr.h" X X X/* Public globals */ extern char errbuf[]; /* Handy place to build error messages */ extern int recurse; /* Recurse into directories encountered */ extern int pathput; /* Store path with name */ X#define BEST -1 /* Use best method */ X#define STORE 0 /* Store method */ X#define SHRINK 1 /* Use shrink or store only */ X#define IMPLODE 6 /* Use implode or store only */ extern int method; /* Restriction on compression method */ extern int dosify; /* Make new entries look like MSDOS */ extern char *special; /* Don't compress special suffixes */ extern int verbose; /* Report oddities in zip file structure */ extern int level; /* Compression level */ X#ifdef VMS X extern int vmsver; /* Append VMS version number to file names */ X#endif /* VMS */ extern int linkput; /* Store symbolic links as such */ extern int noisy; /* False for quiet operation */ extern char *key; /* Scramble password or NULL */ extern char *tempath; /* Path for temporary files */ extern char *zipfile; /* New or existing zip archive (zip file) */ extern ulg zipbeg; /* Starting offset of zip structures */ extern ulg cenbeg; /* Starting offset of central directory */ extern struct zlist far *zfiles;/* Pointer to list of files in zip file */ extern extent zcount; /* Number of files in zip file */ extern extent zcomlen; /* Length of zip file comment */ extern char *zcomment; /* Zip file comment (not zero-terminated) */ extern struct zlist far **zsort;/* List of files sorted by name */ extern struct flist far *found; /* List of names found */ extern struct flist far * far *fnxt; /* Where to put next in found list */ extern extent fcount; /* Count of names in found list */ extern int shract; /* Shrink active */ extern int impact; /* Implosion active */ X X X/* Diagnostic function */ X#ifdef DEBUG X# define diag(where) fprintf(stderr, "zip diagnostic: %s\n", where) X#else /* !DEBUG */ X# define diag(where) X#endif /* ?DEBUG */ X X X/* Public function prototypes */ X X /* in zip.c, zipcloak.c, or zipsplit.c */ void warn OF((char *, char *)); X X /* in zipup.c */ int zipcopy OF((struct zlist far *, FILE *, FILE *)); X#ifndef UTIL X int percent OF((ulg, ulg)); X int zipup OF((struct zlist far *, FILE *)); X#endif /* !UTIL */ X X /* in zipfile.c */ X#ifndef UTIL X struct zlist far *zsearch OF((char *)); X int trash OF((void)); X#endif /* !UTIL */ char *ziptyp OF((char *)); int readzipfile OF((void)); int putlocal OF((struct zlist far *, FILE *)); int putcentral OF((struct zlist far *, FILE *)); int putend OF((int, ulg, ulg, extent, char *, FILE *)); X X /* in fileio.c */ X#ifndef UTIL X# ifdef MSDOS X int wild OF((char *)); X# endif /* MSDOS */ X char *getnam OF((char *)); X struct flist far *fexpel OF((struct flist far *)); X char *in2ex OF((char *)); X int exclude OF((void)); X int procname OF((char *)); X void stamp OF((char *, ulg)); X ulg dostime OF((int, int, int, int, int, int)); X ulg filetime OF((char *, ulg *, long *)); X int issymlnk OF((ulg a)); X# ifdef S_IFLNK X# define rdsymlnk(p,b,n) readlink(p,b,n) X extern int readlink OF((char *, char *, int)); X# else /* !S_IFLNK */ X# define rdsymlnk(p,b,n) (0) X# endif /* !S_IFLNK */ X int deletedir OF((char *)); X#endif /* !UTIL */ int destroy OF((char *)); int replace OF((char *, char *)); int getfileattr OF((char *)); int setfileattr OF((char *, int)); char *tempname OF((int)); int fcopy OF((FILE *, FILE *, ulg)); X#ifndef EXPORT X# ifndef MSVMS X void echoff OF((int)); X void echon OF((void)); X# endif /* !MSVMS */ X char *getp OF((char *, char *, int)); X#endif /* !EXPORT */ X#ifdef ZMEM X char *memset OF((char *, int, unsigned int)); X char *memcpy OF((char *, char *, unsigned int)); X int memcmp OF((char *, char *, unsigned int)); X#endif /* ZMEM */ X X /* in crypt.c */ X#ifdef EXPORT X# define zfwrite fwrite X#else /* !EXPORT */ X void crypthead OF((char *, ulg, FILE *)); X# ifdef UTIL X int zipcloak OF ((struct zlist far *, FILE *, FILE *, char *)); X int zipbare OF ((struct zlist far *, FILE *, FILE *, char *)); X# else /* !UTIL */ X int zfwrite OF((voidp *, extent, extent, FILE *)); X int zfputc OF((int, FILE *)); X# endif /* ?UTIL */ X#endif /* ?EXPORT */ X X /* in util.c */ char *isshexp OF((char *)); int shmatch OF((char *, char *)); X#ifdef MSDOS X int dosmatch OF((char *, char *)); X#endif /* MSDOS */ voidp far **search OF((voidp *, voidp far **, extent, X int (*)(voidp *, voidp far *))); ulg crc32 OF((ulg, int)); ulg updcrc OF((char *, extent)); X X /* in shrink.c */ X#ifndef UTIL X int shr_setup OF((void)); X int shr_p1 OF((uch *, extent)); X int shr_size OF((ulg *)); X int shr_p2 OF((FILE *)); X int shr_clear OF((void)); X X /* in implode.c */ X# ifndef NOIMPLODE X int imp_setup OF((long, int)); X int imp_p1 OF((char *, int)); X int imp_size OF((ulg *, uch *)); X int imp_p2 OF((FILE *)); X int imp_clear OF((void)); X# endif /* !NOIMPLODE */ X#endif /* !UTIL */ X X X/* end of zip.h */ END_OF_FILE if test 8017 -ne `wc -c <'zip.h'`; then echo shar: \"'zip.h'\" unpacked with wrong size! fi # end of 'zip.h' fi if test -f 'zipnote.c' -a "${1}" != "-c" ; then echo shar: Will not clobber existing file \"'zipnote.c'\" else echo shar: Extracting \"'zipnote.c'\" \(9810 characters\) sed "s/^X//" >'zipnote.c' <<'END_OF_FILE' X/* X X Copyright (C) 1990,1991 Mark Adler, Richard B. Wales, and Jean-loup Gailly. X Permission is granted to any individual or institution to use, copy, or X redistribute this software so long as all of the original files are included X unmodified, that it is not sold for profit, and that this copyright notice X is retained. X X*/ X X/* X * zipnote.c by Mark Adler. X */ X X#define UTIL X#include "revision.h" X#include "zip.h" X#include X X X/* Character to mark zip entry names in the comment file */ X#define MARK '@' X X/* Temporary zip file name and file pointer */ local char *tempzip; local FILE *tempzf; X X X/* Local functions */ X#ifdef PROTO X local void err(int, char *); X local void handler(int); X local void license(void); X local void help(void); X local void putclean(char *); X local int catalloc(char * far *, char *); X void main(int, char **); X#endif /* PROTO */ X X X local void err(c, h) int c; /* error code from the ZE_ class */ char *h; /* message about how it happened */ X/* Issue a message for the error, clean up files and memory, and exit. */ X{ X if (PERR(c)) X perror("zipnote error"); X fprintf(stderr, "zipnote error: %s (%s)\n", errors[c-1], h); X if (tempzf != NULL) X fclose(tempzf); X if (tempzip != NULL) X { X destroy(tempzip); X free((voidp *)tempzip); X } X if (zipfile != NULL) X free((voidp *)zipfile); X#ifdef VMS X exit(0); X#else /* !VMS */ X exit(c); X#endif /* ?VMS */ X} X X local void handler(s) int s; /* signal number (ignored) */ X/* Upon getting a user interrupt, abort cleanly using err(). */ X{ X#ifndef MSDOS X putc('\n', stderr); X#endif /* !MSDOS */ X err(ZE_ABORT, "aborting"); X s++; /* keep some compilers happy */ X} X X void warn(a, b) char *a, *b; /* message strings juxtaposed in output */ X/* Print a warning message to stderr and return. */ X{ X fprintf(stderr, "zipnote warning: %s%s\n", a, b); X} X X local void license() X/* Print license information to stdout. */ X{ X extent i; /* counter for copyright array */ X X for (i = 0; i < sizeof(copyright)/sizeof(char *); i++) X puts(copyright[i]); X for (i = 0; i < sizeof(disclaimer)/sizeof(char *); i++) X puts(disclaimer[i]); X} X X local void help() X/* Print help (along with license info) to stdout. */ X{ X extent i; /* counter for help array */ X X /* help array */ X static char *text[] = { X"", X"ZipNote %d.%d (%s)", X"Usage: zipnote [-w] [-b path] zipfile", X" the default action is to write the comments in zipfile to stdout", X" -w write the zipfile comments from stdin", X" -b use \"path\" for the temporary zip file", X" -h show this help -l show software license", X"", X"Example:", X#ifdef VMS X" define/user sys$output foo.tmp", X" zipnote foo.zip", X" edit foo.tmp", X" ... then you edit the comments, save, and exit ...", X" define/user sys$input foo.tmp", X" zipnote -w foo.zip" X#else /* !VMS */ X" zipnote foo.zip > foo.tmp", X" ed foo.tmp", X" ... then you edit the comments, save, and exit ...", X" zipnote -w foo.zip < foo.tmp" X#endif /* ?VMS */ X }; X X for (i = 0; i < sizeof(copyright)/sizeof(char *); i++) X puts(copyright[i]); X for (i = 0; i < sizeof(text)/sizeof(char *); i++) X { X printf(text[i], REVISION / 10, REVISION % 10, REVDATE); X putchar('\n'); X } X} X X local void putclean(s) char *s; /* string to write to stdout */ X/* Write the string s to stdout, filtering out control characters that are X not tab or newline (mainly to remove carriage returns), and prefix MARK's X and backslashes with a backslash. Also, terminate with a newline if X needed. */ X{ X int c; /* next character in string */ X int e; /* last character written */ X X e = '\n'; /* if empty, write nothing */ X while ((c = *s++) != 0) X { X if (c == MARK || c == '\\') X putchar('\\'); X if (c >= ' ' || c == '\t' || c == '\n') X putchar(e = c); X } X if (e != '\n') X putchar('\n'); X} X X local int catalloc(a, s) char * far *a; /* pointer to a pointer to a malloc'ed string */ char *s; /* string to concatenate on a */ X/* Concatentate the string s to the malloc'ed string pointed to by a. X Preprocess s by removing backslash escape characters. */ X{ X char *p; /* temporary pointer */ X char *q; /* temporary pointer */ X X for (p = q = s; *q; *p++ = *q++) X if (*q == '\\' && *(q+1)) X q++; X *p = 0; X if ((p = malloc(strlen(*a) + strlen(s) + 3)) == NULL) X return ZE_MEM; X strcat(strcat(strcpy(p, *a), **a ? "\r\n" : ""), s); X free((voidp *)*a); X *a = p; X return ZE_OK; X} X X void main(argc, argv) int argc; /* number of tokens in command line */ char **argv; /* command line tokens */ X/* Write the comments in the zipfile to stdout, or read them from stdin. */ X{ X char a[FNMAX+1]; /* input line buffer */ X ulg c; /* start of central directory */ X int k; /* next argument type */ X char *q; /* steps through option arguments */ X int r; /* arg counter, temporary variable */ X ulg s; /* length of central directory */ X int t; /* attributes of zip file */ X int w; /* true if updating zip file from stdin */ X FILE *x, *y; /* input and output zip files */ X struct zlist far *z; /* steps through zfiles linked list */ X X X /* If no args, show help */ X if (argc == 1) X { X help(); X exit(0); X } X X /* Go through args */ X zipfile = tempzip = NULL; X tempzf = NULL; X signal(SIGINT, handler); X signal(SIGTERM, handler); X k = w = 0; X for (r = 1; r < argc; r++) X if (*argv[r] == '-') X if (argv[r][1]) X for (q = argv[r]+1; *q; q++) X switch(*q) X { X case 'b': /* Specify path for temporary file */ X if (k) X err(ZE_PARMS, "use -b before zip file name"); X else X k = 1; /* Next non-option is path */ X break; X case 'h': /* Show help */ X help(); exit(0); X case 'l': /* Show copyright and disclaimer */ X license(); exit(0); X case 'w': X w = 1; break; X default: X err(ZE_PARMS, "unknown option"); X } X else X err(ZE_PARMS, "zip file cannot be stdin"); X else X if (k == 0) X if (zipfile == NULL) X { X if ((zipfile = ziptyp(argv[r])) == NULL) X err(ZE_MEM, "was processing arguments"); X } X else X err(ZE_PARMS, "can only specify one zip file"); X else X { X tempath = argv[r]; X k = 0; X } X if (zipfile == NULL) X err(ZE_PARMS, "need to specify zip file"); X X /* Read zip file */ X if ((r = readzipfile()) != ZE_OK) X err(r, zipfile); X if (zfiles == NULL) X err(ZE_NAME, zipfile); X X /* Put comments to stdout, if not -u */ X if (!w) X { X for (z = zfiles; z != NULL; z = z->nxt) X { X printf("%c %s\n", MARK, z->zname); X if (z->com) X putclean(z->comment); X putchar(MARK); putchar('\n'); X } X putchar(MARK); putchar('\n'); X if (zcomlen) X putclean(zcomment); X exit(ZE_OK); X } X X /* If updating comments, make sure zip file is writeable */ X if ((x = fopen(zipfile, "a")) == NULL) X err(ZE_CREAT, zipfile); X fclose(x); X t = getfileattr(zipfile); X X /* Process stdin, replacing comments */ X for (z = zfiles; z != NULL; z = z->nxt) X { X if (gets(a) == NULL || a[0] != MARK || a[1] != ' ' || X strcmp(a + 2, z->zname)) X err(ZE_NOTE, "missing entry name"); X if (z->com) X free((voidp *)z->comment); X z->comment = malloc(1); *(z->comment) = 0; X while (gets(a) != NULL && *a != MARK) X if ((r = catalloc(&(z->comment), a)) != ZE_OK) X err(r, "was building new comments"); X if (a[1]) X err(ZE_NOTE, "missing comment end line"); X z->com = strlen(z->comment); X } X if (gets(a) == NULL || a[0] != MARK || a[1]) X err(ZE_NOTE, "missing zip file comment marker line"); X zcomment = malloc(1); *zcomment = 0; X while (gets(a) != NULL) X if ((r = catalloc(&zcomment, a)) != ZE_OK) X err(r, "was building new comments"); X zcomlen = strlen(zcomment); X X /* Open output zip file for writing */ X if ((tempzf = y = fopen(tempzip = tempname('Z'), FOPW)) == NULL) X err(ZE_TEMP, tempzip); X X /* Open input zip file again, copy preamble if any */ X if ((x = fopen(zipfile, FOPR)) == NULL) X err(ZE_NAME, zipfile); X if (zipbeg && (r = fcopy(x, y, zipbeg)) != ZE_OK) X err(r, r == ZE_TEMP ? tempzip : zipfile); X X /* Go through local entries, copying them over as is */ X for (z = zfiles; z != NULL; z = z->nxt) X if ((r = zipcopy(z, x, y)) != ZE_OK) X err(r, "was copying an entry"); X fclose(x); X X /* Write central directory and end of central directory with new comments */ X if ((c = ftell(y)) == -1L) /* get start of central */ X err(ZE_TEMP, tempzip); X for (z = zfiles; z != NULL; z = z->nxt) X if ((r = putcentral(z, y)) != ZE_OK) X err(r, tempzip); X if ((s = ftell(y)) == -1L) /* get end of central */ X err(ZE_TEMP, tempzip); X s -= c; /* compute length of central */ X if ((r = putend((int)zcount, s, c, zcomlen, zcomment, y)) != ZE_OK) X err(r, tempzip); X tempzf = NULL; X if (fclose(y)) X err(ZE_TEMP, tempzip); X if ((r = replace(zipfile, tempzip)) != ZE_OK) X { X warn("new zip file left as: ", tempzip); X free((voidp *)tempzip); X tempzip = NULL; X err(r, "was replacing the original zip file"); X } X free((voidp *)tempzip); X tempzip = NULL; X setfileattr(zipfile, t); X free((voidp *)zipfile); X zipfile = NULL; X X /* Done! */ X exit(ZE_OK); X} END_OF_FILE if test 9810 -ne `wc -c <'zipnote.c'`; then echo shar: \"'zipnote.c'\" unpacked with wrong size! fi # end of 'zipnote.c' fi if test -f 'zipup.c' -a "${1}" != "-c" ; then echo shar: Will not clobber existing file \"'zipup.c'\" else echo shar: Extracting \"'zipup.c'\" \(10409 characters\) sed "s/^X//" >'zipup.c' <<'END_OF_FILE' X/* X X Copyright (C) 1990,1991 Mark Adler, Richard B. Wales, and Jean-loup Gailly. X Permission is granted to any individual or institution to use, copy, or X redistribute this software so long as all of the original files are included X unmodified, that it is not sold for profit, and that this copyright notice X is retained. X X*/ X X/* X * zipup.c by Mark Adler. X */ X X#define NOCPYRT /* this is not a main module */ X#include "zip.h" X#include "revision.h" X X/* Use the raw functions for MSDOS and Unix to save on buffer space. X They're not used for VMS since it doesn't work (raw is weird on VMS). X (This sort of stuff belongs in fileio.c, but oh well.) */ X#ifdef VMS X typedef FILE *ftype; X# define fhow FOPR X# define fbad NULL X# define zopen(n,p) fopen(n,p) X# define zread(f,b,n) fread(b,1,n,f) X# define zclose(f) fclose(f) X# define zerr(f) ferror(f) X# define zrew(f) rewind(f) X#else /* !VMS */ X# ifdef MSDOS X# include X# include X# define fhow (O_RDONLY|O_BINARY) X# else /* !MSDOS */ X int open OF((char *, int)); X int read OF((int, char *, int)); X int close OF((int)); X int lseek OF((int,long,int)); X# define fhow 0 X# endif /* ?MSDOS */ X typedef int ftype; X# define fbad (-1) X# define zopen(n,p) open(n,p) X# define zread(f,b,n) read(f,b,n) X# define zclose(f) close(f) X# define zerr(f) (k<0) X# define zrew(f) lseek(f,0L,0) X#endif /* ?VMS */ X X X/* Local functions */ X#ifdef PROTO X# ifndef UTIL X local int suffixes(char *, char *); X# endif /* !UTIL */ X#endif /* PROTO */ X X X/* Note: a zip "entry" includes a local header (which includes the file X name), an encryption header if encrypting, and the compressed data. */ X X int zipcopy(z, x, y) struct zlist far *z; /* zip entry to copy */ XFILE *x, *y; /* source and destination files */ X/* Copy the zip entry described by *z from file *x to file *y. Return an X error code in the ZE_ class. */ X{ X ulg n; /* holds local header offset */ X X if (fseek(x, z->off, SEEK_SET)) X return ferror(x) ? ZE_READ : ZE_EOF; X if ((n = ftell(y)) == -1L) X return ZE_TEMP; X z->off = n; X n = 4 + LOCHEAD + (long)z->nam + (long)z->ext + z->siz; X return fcopy(x, y, n); X} X X X#ifndef UTIL X int percent(n, m) ulg n, m; /* n is the original size, m is the new size */ X/* Return the percentage compression from n to m using only integer X operations */ X{ X if (n > 0xffffffL) /* If n >= 16M */ X { /* then divide n and m by 256 */ X n += 0x80; n >>= 8; X m += 0x80; m >>= 8; X } X return n ? (int)(1 + (200 * (n - m))/n) >> 1 : 0; X} X X local int suffixes(a, s) char *a; /* name to check suffix of */ char *s; /* list of suffixes separated by : or ; */ X/* Return true if a ends in any of the suffixes in the list s. */ X{ X int m; /* true if suffix matches so far */ X char *p; /* pointer into special */ X char *q; /* pointer into name a */ X X m = 1; X q = a + strlen(a) - 1; X for (p = s + strlen(s) - 1; p >= s; p--) X if (*p == ':' || *p == ';') X if (m) X return 1; X else X { X m = 1; X q = a + strlen(a) - 1; X } X else X#ifdef OS2 X { X m = m && q >= a && tolower(*p) == tolower(*q); X q--; X } X#else /* !OS2 */ X m = m && q >= a && *p == *q--; X#endif /* ?OS2 */ X return m; X} X X int zipup(z, y) struct zlist far *z; /* zip entry to compress */ XFILE *y; /* output file */ X/* Compress the file z->name into the zip entry described by *z and write X it to the file *y. Determine the best method (store, shrink, or implode) X and use it. Encrypt if requested. Return an error code in the X ZE_ class. */ X{ X ulg a; /* attributes returned by filetime() */ X char *b; /* malloc'ed file buffer */ X ulg c; /* crc on uncompressed file data */ X ftype f; /* file to compress */ X uch g; /* flags returned by implode */ X int h; /* compression method chosen (how) */ X#ifndef NOIMPLODE X ulg i; /* size of imploded data */ X#endif /* !NOIMPLODE */ X extent k; /* result of zread */ X int l; /* true if this file is a symbolic link */ X int m; /* method for this entry */ X ulg n; /* size of uncompressed file */ X long q; /* size returned by filetime */ X int r; /* temporary variable */ X ulg s; /* size of shrunk or compressed data */ X X /* Open file to zip up */ X if ((z->tim = filetime(z->name, &a, &q)) == 0 || q < 0) X return ZE_OPEN; X l = issymlnk(a); X if (l) X f = fbad; X else if ((f = zopen(z->name, fhow)) == fbad) X return ZE_OPEN; X X /* Select method based on the suffix and the global method */ X m = special != NULL && suffixes(z->name, special) ? STORE : method; X X /* Don't bother with shrink for "large" files (320 is what PKZIP uses--the X number used here must be <= BSZ) */ X if (m == BEST && q > 512) X m = IMPLODE; X if (q == 0) X m = STORE; X X /* Make first pass on the file, computing the crc and length, and running X shrink and implode on it. */ X n = 0; X c = updcrc((char *)NULL, 0); X if ((b = malloc(BSZ)) == NULL) X return ZE_MEM; X if (m == BEST || m == SHRINK) X if ((r = shr_setup()) != ZE_OK) X return r; X else X shract = 1; X while ((k = l ? rdsymlnk(z->name, b, BSZ) : zread(f, b, BSZ)) > 0) X { X n += k; X c = updcrc(b, k); X#ifdef MINIX X if (l) X q = k; X#endif /* MINIX */ X if ((m == BEST || m == SHRINK) && (r = shr_p1((uch *)b, k)) != ZE_OK) X { X free((voidp *)b); X shr_clear(); X shract = 0; X return r; X } X if (m == BEST) /* free up shrink data structures */ X { X if ((!l && zread(f, b, BSZ) != 0) X#ifndef VMS X || n != (ulg)q X#endif /* !VMS */ X ) X return ZE_READ; X if ((r = shr_size(&s)) != ZE_OK) X { X shr_clear(); X shract = 0; X return r; X } X } X#ifndef NOIMPLODE X if (m == BEST || m == IMPLODE) X { X if (!impact) X if ((r = imp_setup(q, level)) != ZE_OK) X return r; X else X impact = 1; X if ((r = imp_p1(b, k)) != ZE_OK) X { X free((voidp *)b); X imp_clear(); X impact = 0; X return r; X } X } X#endif /* !NOIMPLODE */ X if (m == BEST || l) X break; X } X free((voidp *)b); X if (!l && zerr(f)) X return ZE_READ; X X /* Determine the best method to use */ X g = 0; X if (noisy && verbose) X printf(" (n=%lu)", n); X if (m == SHRINK && (r = shr_size(&s)) != ZE_OK) X { X shr_clear(); X shract = 0; X return r; X } X if (noisy && verbose && (m == BEST || m == SHRINK)) X printf(" (s=%lu)", s); X#ifndef NOIMPLODE X if ((m == BEST || m == IMPLODE) && (r = imp_size(&i, &g)) != ZE_OK) X { X imp_clear(); X impact = 0; X return r; X } X if (noisy && verbose && (m == BEST || m == IMPLODE)) X printf(" (i=%lu)", i); X if ((m == BEST || m == IMPLODE) && i < n && (m == IMPLODE || i < s)) X { X if (noisy) X { X printf(" (imploded %d%%)", percent(n, i)); X fflush(stdout); X } X h = IMPLODE; X s = i; X if (m == BEST) X { X shr_clear(); X shract = 0; X } X if (!l) X zclose(f); X } X else X#endif /* !NOIMPLODE */ X if ((m == BEST || m == SHRINK) && s < n) X { X if (noisy) X { X printf(" (shrunk %d%%)", percent(n, s)); X fflush(stdout); X } X h = SHRINK; X#ifndef NOIMPLODE X if (m == BEST) X { X imp_clear(); X impact = 0; X } X#endif /* !NOIMPLODE */ X if (!l) X zclose(f); X } X else X { X if (noisy) X { X printf(" (stored 0%%)"); X fflush(stdout); X } X h = STORE; X s = n; X#ifndef NOIMPLODE X if (m == BEST || m == IMPLODE) X { X imp_clear(); X impact = 0; X } X#endif /* !NOIMPLODE */ X if (m == BEST || m == SHRINK) X { X shr_clear(); X shract = 0; X } X } X X#ifndef VMS X /* Check size (but not in VMS--variable record lengths mess it up) */ X if (n != (ulg)q) X return ZE_READ; X#endif /* !VMS */ X X /* Fill in header information and write local header to zip file */ X#ifdef OS2 X if ( dosify < 2 ) X dosify = IsFileSystemFAT(z->name); X#endif /* OS2 */ X X /* (Assume ext, cext, com, and zname already filled in.) */ X z->vem = dosify ? 11 : /* Made under MSDOS by PKZIP 1.1 */ X#ifdef VMS X 0x200 + REVISION; /* Made under VMS by this Zip */ X#else /* !VMS */ X#ifdef OS2 X 0x600 + REVISION; /* Made under OS/2 by this Zip */ X#else /* !OS2 */ X 0x300 + REVISION; /* Made under Unix by this Zip */ X#endif /* ?OS2 */ X#endif /* ?VMS */ X z->ver = 10; /* Need PKUNZIP 1.0 */ X z->flg = (ush)g; X if (key != NULL) X z->flg |= 1; X z->lflg = z->flg; X z->how = h; X z->crc = c; X z->siz = s; X if (key != NULL) X z->siz += 12; X z->len = n; X z->nam = strlen(z->zname); X z->dsk = 0; X z->att = 0; /* Assume they're all binary */ X z->atx = dosify ? a & 0xff : a; /* Attributes from filetime() */ X if ((z->off = ftell(y)) == -1L) X return ZE_WRITE; X if ((r = putlocal(z, y)) != ZE_OK) X return r; X X /* Write stored, shrunk, or imploded file to zip file */ X#ifndef EXPORT X if (key != NULL) X crypthead(key, z->crc, y); X#endif /* !EXPORT */ X n = ftell(y); /* Save offset for logic check */ X#ifndef NOIMPLODE X if (h == IMPLODE) X { X if ((r = imp_p2(y)) != ZE_OK) X return r; X imp_clear(); X impact = 0; X } X else X#endif /* !NOIMPLODE */ X if (h == SHRINK) X { X if ((r = shr_p2(y)) != ZE_OK) X return r; X shr_clear(); X shract = 0; X } X else X { X if (!l) X zrew(f); X if ((b = malloc(BSZ)) == NULL) X return ZE_MEM; X while ((k = l ? rdsymlnk(z->name, b, BSZ) : zread(f, b, BSZ)) > 0) X { X if (zfwrite(b, 1, k, y) != k) X { X free((voidp *)b); X return ZE_TEMP; X } X if (l) X break; X } X free((voidp *)b); X if (!l && zerr(f)) X return ZE_READ; X if (!l) X zclose(f); X } X X /* Check length of compressed data */ X if (ftell(y) != n + s) X return ZE_LOGIC; X X /* Done--clean up and leave */ X if (noisy) X { X putchar('\n'); X fflush(stdout); X } X return ZE_OK; X} X X#endif /* !UTIL */ END_OF_FILE if test 10409 -ne `wc -c <'zipup.c'`; then echo shar: \"'zipup.c'\" unpacked with wrong size! fi # end of 'zipup.c' fi echo shar: End of archive 2 \(of 7\). cp /dev/null ark2isdone MISSING="" for I in 1 2 3 4 5 6 7 ; do if test ! -f ark${I}isdone ; then MISSING="${MISSING} ${I}" fi done if test "${MISSING}" = "" ; then echo You have unpacked all 7 archives. rm -f ark[1-9]isdone else echo You still need to unpack the following archives: echo " " ${MISSING} fi ## End of shell archive. exit 0