2 jartool.c - main functions for fastjar utility
3 Copyright (C) 1999, 2000 Bryan Burns
5 This program is free software; you can redistribute it and/or
6 modify it under the terms of the GNU General Public License
7 as published by the Free Software Foundation; either version 2
8 of the License, or (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20 /* $Id: jartool.c,v 1.3 2000/12/14 18:45:35 ghazi Exp $
23 Revision 1.3 2000/12/14 18:45:35 ghazi
26 * compress.c: Include stdlib.h and compress.h.
28 (report_str_error): Make static.
29 (ez_inflate_str): Delete unused variable. Add parens in if-stmt.
30 (hrd_inflate_str): Likewise.
32 * compress.h (init_compression, end_compression, init_inflation,
33 end_inflation): Prototype void arguments.
35 * dostime.c (rcsid): Delete.
37 * jargrep.c: Include ctype.h, stdlib.h, zlib.h and compress.h.
38 Make functions static. Cast ctype function argument to `unsigned
39 char'. Add parens in if-stmts. Constify.
40 (Usage): Change into a macro.
41 (jargrep): Remove unused parameter.
43 * jartool.c: Constify. Add parens in if-stmts. Align
44 signed/unsigned char pointers in functions calls using casts.
46 (list_jar): Fix printf format specifier.
47 (usage): Chop long string into bits. Reformat.
49 * pushback.c (rcsid): Delete.
51 Revision 1.2 2000/12/13 18:11:57 tromey
52 * jartool.c (extract_jar): Use strchr, not index.
54 Revision 1.1 2000/12/09 03:08:23 apbianco
55 2000-12-08 Alexandre Petit-Bianco <apbianco@cygnus.com>
59 Revision 1.5 2000/08/24 15:01:27 cory
60 Made certain that fastjar opened the jar file before trying to update it
63 Revision 1.4 2000/08/24 13:39:21 cory
64 Changed +'s to |'s in jartool.c to insure there was no confusion with sign
65 when byte swapping. Better safe than sorry.
67 Revision 1.3 2000/08/23 19:42:17 cory
68 Added support for more Unix platforms. The following code has been hacked
69 to work on AIX, Solaris, True 64, and HP-UX.
70 Added bigendian check. Probably works on most big and little endian platforms
73 Revision 1.2 1999/12/06 07:38:28 toast
74 fixed recursive archiving bug
76 Revision 1.1.1.1 1999/12/06 03:09:34 toast
81 Revision 1.22 1999/10/12 19:45:13 burnsbr
82 adding patch to fix compat problem
84 Revision 1.21 1999/05/10 09:15:49 burnsbr
85 fixed manifest file version info
87 Revision 1.20 1999/05/10 08:53:16 burnsbr
88 *** empty log message ***
90 Revision 1.19 1999/05/10 08:30:39 burnsbr
91 added extract / listing code
93 Revision 1.18 1999/04/28 04:24:29 burnsbr
96 Revision 1.17 1999/04/28 04:21:23 burnsbr
97 added support for -C dir-changing flag.. Updated total compression display
99 Revision 1.16 1999/04/27 10:28:22 burnsbr
100 updated version string
102 Revision 1.15 1999/04/27 10:04:06 burnsbr
105 Revision 1.14 1999/04/27 08:56:14 burnsbr
106 added -V flag, better error messages
108 Revision 1.13 1999/04/26 02:35:21 burnsbr
109 changed all sorts of stuff.. compression now works 100%
111 Revision 1.12 1999/04/23 12:00:45 burnsbr
112 90% done with compression code
114 Revision 1.11 1999/04/22 04:12:57 burnsbr
115 finished first round of Manifest file support..
116 might need to do more, digest etc..
118 Revision 1.10 1999/04/22 02:35:23 burnsbr
119 added more manifest support, about 75% done now. Replaced all the
120 redundant shifts and bit-logic with a macro or two, making the code
123 Revision 1.9 1999/04/21 09:55:16 burnsbr
126 Revision 1.8 1999/04/21 02:58:01 burnsbr
127 started manifest code
129 Revision 1.7 1999/04/20 23:15:28 burnsbr
130 added patch sent by John Bley <jbb6@acpub.duke.edu>
132 Revision 1.6 1999/04/20 08:56:02 burnsbr
135 Revision 1.5 1999/04/20 08:16:09 burnsbr
136 fixed verbose flag, did some optimization
138 Revision 1.4 1999/04/20 05:09:59 burnsbr
141 Revision 1.3 1999/04/20 05:08:54 burnsbr
159 #include <sys/stat.h>
160 #include <sys/types.h>
162 #ifdef HAVE_SYS_PARAM_H
163 #include <sys/param.h>
167 #define MAXPATHLEN 1024
181 #ifdef TM_IN_SYS_TIME
182 #include <sys/time.h>
190 #include "pushback.h"
191 #include "compress.h"
193 #ifdef WORDS_BIGENDIAN
195 #define L2BI(l) ((l & 0xff000000) >> 24) | \
196 ((l & 0x00ff0000) >> 8) | \
197 ((l & 0x0000ff00) << 8) | \
198 ((l & 0x000000ff) << 24);
200 #define L2BS(l) ((l & 0xff00) >> 8) | ((l & 0x00ff) << 8);
204 static char version_string
[] = VERSION
;
208 void usage(const char*);
209 void add_entry(struct zipentry
*);
210 void init_headers(void);
212 int consume(pb_file
*, int);
213 int list_jar(int, char**, int);
214 int extract_jar(int, char**, int);
215 int add_file_to_jar(int, int, const char*, struct stat
*);
216 int add_to_jar(int, const char*, const char*);
217 int create_central_header(int);
218 int make_manifest(int, const char*);
219 static void init_args(char **, int);
220 static char *get_next_arg (void);
222 /* global variables */
224 ub1 data_descriptor
[16];
230 /* If non zero, then don't recurse in directory. Instead, add the
231 directory entry and relie on an explicit list of files to populate
232 the archive. This option isn't supported by the original jar tool. */
233 int use_explicit_list_only
;
235 /* If non zero, then read the entry names from stdin. This option
236 isn't supported by the original jar tool. */
237 int read_names_from_stdin
;
239 zipentry
*ziplist
; /* linked list of entries */
240 zipentry
*ziptail
; /* tail of the linked list */
242 int number_of_entries
; /* number of entries in the linked list */
244 int main(int argc
, char **argv
){
248 int action
= ACTION_NONE
;
250 int manifest_file
= FALSE
;
252 int file_first
= FALSE
;
262 number_of_entries
= 0;
269 for(i
= 0; i
< j
; i
++){
272 action
= ACTION_CREATE
;
275 action
= ACTION_LIST
;
278 action
= ACTION_EXTRACT
;
281 action
= ACTION_UPDATE
;
287 printf("%s\n", version_string
);
297 manifest_file
= TRUE
;
307 /* The following options aren't supported by the original jar tool. */
309 use_explicit_list_only
= TRUE
;
312 read_names_from_stdin
= TRUE
;
315 fprintf(stderr
, "Illegal option: %c\n", argv
[1][i
]);
320 if(action
== ACTION_NONE
){
321 fprintf(stderr
, "One of options -{ctxu} must be specified.\n");
325 /* Verify unsupported combinations and warn of the use of non
327 if(verbose
&& use_explicit_list_only
)
328 fprintf (stderr
, "Warning: using non standard '-E' option\n");
329 if(verbose
&& read_names_from_stdin
)
330 fprintf (stderr
, "Warning: using non standard '-@' option\n");
331 if(read_names_from_stdin
332 && (action
!= ACTION_CREATE
&& action
!= ACTION_UPDATE
)){
333 fprintf(stderr
, "Option '-@' is supported only with '-c' or '-u'.\n");
339 /* get the jarfile and manifest file (if any) */
340 if(file
&& file_first
){
344 strncpy(jarfile
, argv
[i
++], 256);
350 strncpy(mfile
, argv
[i
++], 256);
353 if(file
&& !file_first
){
357 strncpy(jarfile
, argv
[i
++], 256);
360 /* create the jarfile */
361 if(action
== ACTION_CREATE
){
363 jarfd
= creat(jarfile
, S_IRUSR
| S_IWUSR
| S_IRGRP
| S_IROTH
);
366 fprintf(stderr
, "Error opening %s for writing!\n", jarfile
);
371 /* We assume that the file is seekable */
376 jarfd
= STDOUT_FILENO
; /* jarfd is stdout otherwise */
378 /* standard out is not seekable */
381 /* don't want our output to be part of the jar file.. figured this one
382 out the hard way.. =P */
385 } else if(action
== ACTION_LIST
|| action
== ACTION_EXTRACT
){
388 jarfd
= open(jarfile
, O_RDONLY
);
391 fprintf(stderr
, "Error opening %s for reading!\n", jarfile
);
398 jarfd
= STDIN_FILENO
; /* jarfd is standard in */
400 /* we assume that the stream isn't seekable for safety */
405 if(action
== ACTION_CREATE
|| action
== ACTION_UPDATE
){
409 if((action
== ACTION_UPDATE
) && file
) {
410 if((jarfd
= open(jarfile
, O_RDWR
)) < 0) {
411 fprintf(stderr
, "Error opening %s for reading!\n", jarfile
);
421 /* Add the META-INF/ directory and the manifest */
422 if(manifest
&& manifest_file
)
423 make_manifest(jarfd
, mfile
);
425 make_manifest(jarfd
, NULL
);
428 /* now we add the files to the archive */
429 while ((arg
= get_next_arg ())){
431 if(!strcmp(arg
, "-C")){
432 const char *dir_to_change
= get_next_arg ();
433 const char *file_to_add
= get_next_arg ();
436 || add_to_jar(jarfd
, dir_to_change
, file_to_add
)){
437 printf("Error adding %s to jar archive!\n", arg
);
441 if(add_to_jar(jarfd
, NULL
, arg
)){
442 printf("Error adding %s to jar archive!\n", arg
);
447 /* de-initialize the compression DS */
451 create_central_header(jarfd
);
453 if (close(jarfd
) != 0) {
454 fprintf(stderr
, "Error closing jar archive!\n");
456 } else if(action
== ACTION_LIST
){
457 list_jar(jarfd
, &argv
[i
], (argc
- i
));
458 } else if(action
== ACTION_EXTRACT
){
459 extract_jar(jarfd
, &argv
[i
], (argc
- i
));
465 static int args_current_g
;
466 static char **args_g
;
469 init_args(args
, current
)
473 if(!read_names_from_stdin
)
476 args_current_g
= current
;
483 static int reached_end
= 0;
490 if (!args_g
[args_current_g
])
495 return args_g
[args_current_g
++];
499 /* Read the name from stdin. Delimiters are '\n' and
500 '\r'. Reading EOF indicates that we don't have anymore file
501 names characters to read. */
506 /* Get rid of '\n' and '\r' first. */
509 int c
= getc (stdin
);
510 if (c
== '\n' || c
== '\r')
523 int c
= getc (stdin
);
524 /* Exit when we get a delimiter or don't have any characters
526 if (c
== '\n'|| c
== '\r'|| c
== EOF
)
528 s
[pos
++] = (char) c
;
542 /* packing file header */
544 file_header
[0] = 0x50;
545 file_header
[1] = 0x4b;
546 file_header
[2] = 0x03;
547 file_header
[3] = 0x04;
548 /* version number (Unix 1.0)*/
551 /* bit flag (normal deflation)*/
552 file_header
[6] = 0x00;
554 file_header
[7] = 0x00;
555 /* do_compression method (deflation) */
559 /* last mod file time (MS-DOS format) */
562 /* last mod file date (MS-DOS format) */
570 /* compressed size */
575 /* uncompressed size */
580 /* filename length */
583 /* extra field length */
587 /* Initialize the compression DS */
588 PACK_UB4(data_descriptor
, 0, 0x08074b50);
592 void add_entry(struct zipentry
*ze
){
598 ziplist
->next_entry
= ze
;
605 int make_manifest(int jfd
, const char *mf_name
){
607 int nlen
; /* length of file name */
608 int mod_time
; /* file modification time */
611 nlen
= 9; /* trust me on this one */
613 memset((file_header
+ 12), '\0', 16); /*clear mod time, crc, size fields*/
615 current_time
= time(NULL
);
616 if(current_time
== (time_t)-1){
621 mod_time
= unix2dostime(¤t_time
);
623 PACK_UB2(file_header
, LOC_EXTRA
, 0);
624 PACK_UB2(file_header
, LOC_COMP
, 0);
625 PACK_UB2(file_header
, LOC_FNLEN
, nlen
);
626 PACK_UB4(file_header
, LOC_MODTIME
, mod_time
);
629 printf("adding: META-INF/ (in=0) (out=0) (stored 0%%)\n");
631 ze
= (zipentry
*)malloc(sizeof(zipentry
));
637 memset(ze
, 0, sizeof(zipentry
)); /* clear all the fields*/
638 ze
->filename
= (char*)malloc((nlen
+ 1) * sizeof(char) + 1);
639 strcpy(ze
->filename
, "META-INF/");
640 ze
->filename
[nlen
] = '\0';
642 ze
->offset
= lseek(jfd
, 0, SEEK_CUR
);
643 ze
->mod_time
= (ub2
)(mod_time
& 0x0000ffff);
644 ze
->mod_date
= (ub2
)((mod_time
& 0xffff0000) >> 16);
645 ze
->compressed
= FALSE
;
649 write(jfd
, file_header
, 30);
650 write(jfd
, "META-INF/", nlen
);
652 /* if the user didn't specify an external manifest file... */
654 int mf_len
= 37 + strlen(VERSION
);
657 if((mf
= (char *) malloc(mf_len
+ 1))) {
660 sprintf(mf
, "Manifest-Version: 1.0\nCreated-By: %s\n\n", VERSION
);
662 crc
= crc32(0L, Z_NULL
, 0);
664 crc
= crc32(crc
, (const unsigned char *)mf
, mf_len
);
666 nlen
= 20; /* once again, trust me */
668 PACK_UB2(file_header
, LOC_EXTRA
, 0);
669 PACK_UB2(file_header
, LOC_COMP
, 0);
670 PACK_UB2(file_header
, LOC_FNLEN
, nlen
);
671 PACK_UB4(file_header
, LOC_USIZE
, mf_len
);
673 memcpy((file_header
+ LOC_CSIZE
), (file_header
+ LOC_USIZE
), 4);
675 PACK_UB4(file_header
, LOC_CRC
, crc
);
678 printf("adding: META-INF/MANIFEST.MF (in=56) (out=56) (stored 0%%)\n");
680 ze
= (zipentry
*)malloc(sizeof(zipentry
));
686 memset(ze
, 0, sizeof(zipentry
)); /* clear all the fields*/
687 ze
->filename
= (char*)malloc((nlen
+ 1) * sizeof(char) + 1);
688 strcpy(ze
->filename
, "META-INF/MANIFEST.MF");
689 ze
->filename
[nlen
] = '\0';
691 ze
->offset
= lseek(jfd
, 0, SEEK_CUR
);
692 ze
->mod_time
= (ub2
)(mod_time
& 0x0000ffff);
693 ze
->mod_date
= (ub2
)((mod_time
& 0xffff0000) >> 16);
696 ze
->usize
= ze
->csize
;
697 ze
->compressed
= FALSE
;
701 write(jfd
, file_header
, 30);
702 write(jfd
, "META-INF/MANIFEST.MF", nlen
);
703 write(jfd
, mf
, mf_len
);
707 printf("malloc errror\n");
714 stat(mf_name
, &statbuf
);
716 if(!S_ISREG(statbuf
.st_mode
)){
717 fprintf(stderr
, "Invalid manifest file specified.\n");
721 mfd
= open(mf_name
, O_RDONLY
);
724 fprintf(stderr
, "Error opening %s.\n", mf_name
);
728 if(add_file_to_jar(jfd
, mfd
, "META-INF/MANIFEST.MF", &statbuf
)){
729 perror("error writing to jar");
738 int add_to_jar(int fd
, const char *new_dir
, const char *file
){
744 char *old_dir
= NULL
;
746 /* This is a quick compatibility fix -- Simon Weijgers <simon@weijgers.com>
748 * "normal" jar : org/apache/java/io/LogRecord.class
749 * fastjar : ./org/apache/java/io/LogRecord.class
750 * Fastjar's preservation of the ./'s makes the jarfile unusuable for use
751 * with both kaffe-1.0b4 and JDK.
753 while (*file
=='.' && *(file
+1)=='/')
756 /* If new_dir isn't null, we need to change to that directory. However,
757 we also need to return to the old directory when we're done */
759 old_dir
= getcwd(NULL
, 0);
761 if(chdir(new_dir
) == -1){
767 if(!strcmp(file
, jarfile
)){
769 printf("skipping: %s\n", file
);
770 return 0; /* we don't want to add ourselves.. */
773 stat_return
= stat(file
, &statbuf
);
775 if(stat_return
== -1){
777 } else if(S_ISDIR(statbuf
.st_mode
)){
781 unsigned long mod_time
;
790 nlen
= strlen(file
) + 256;
791 fullname
= (char*)malloc(nlen
* sizeof(char));
792 memset(fullname
, 0, (nlen
* sizeof(char)));
794 if(fullname
== NULL
){
795 fprintf(stderr
, "Filename is NULL!\n");
799 strcpy(fullname
, file
);
802 if(fullname
[nlen
- 1] != '/'){
803 fullname
[nlen
] = '/';
804 t_ptr
= (fullname
+ nlen
+ 1);
806 t_ptr
= (fullname
+ nlen
);
809 memset((file_header
+ 12), '\0', 16); /*clear mod time, crc, size fields*/
811 nlen
= (t_ptr
- fullname
);
813 mod_time
= unix2dostime(&statbuf
.st_mtime
);
815 PACK_UB2(file_header
, LOC_EXTRA
, 0);
816 PACK_UB2(file_header
, LOC_COMP
, 0);
817 PACK_UB2(file_header
, LOC_FNLEN
, nlen
);
818 PACK_UB4(file_header
, LOC_MODTIME
, mod_time
);
821 printf("adding: %s (in=%d) (out=%d) (stored 0%%)\n", fullname
, 0, 0);
823 ze
= (zipentry
*)malloc(sizeof(zipentry
));
829 memset(ze
, 0, sizeof(zipentry
)); /* clear all the fields*/
830 ze
->filename
= (char*)malloc((nlen
+ 1) * sizeof(char) + 1);
831 strcpy(ze
->filename
, fullname
);
832 ze
->filename
[nlen
] = '\0';
834 ze
->offset
= lseek(fd
, 0, SEEK_CUR
);
835 ze
->mod_time
= (ub2
)(mod_time
& 0x0000ffff);
836 ze
->mod_date
= (ub2
)((mod_time
& 0xffff0000) >> 16);
837 ze
->compressed
= FALSE
;
841 write(fd
, file_header
, 30);
842 write(fd
, fullname
, nlen
);
844 while(!use_explicit_list_only
&& (de
= readdir(dir
)) != NULL
){
845 if(de
->d_name
[0] == '.')
847 if(!strcmp(de
->d_name
, jarfile
)){ /* we don't want to add ourselves. Believe me */
849 printf("skipping: %s\n", de
->d_name
);
853 strcpy(t_ptr
, de
->d_name
);
855 if(add_to_jar(fd
, NULL
, fullname
)){
856 fprintf(stderr
, "Error adding file to jar!\n");
864 } else if(S_ISREG(statbuf
.st_mode
)){
867 add_fd
= open(file
, O_RDONLY
);
869 fprintf(stderr
, "Error opening %s.\n", file
);
873 if(add_file_to_jar(fd
, add_fd
, file
, &statbuf
)){
874 fprintf(stderr
, "Error adding file to jar!\n");
879 fprintf(stderr
, "Illegal file specified: %s\n", file
);
892 int add_file_to_jar(int jfd
, int ffd
, const char *fname
, struct stat
*statbuf
){
894 unsigned short file_name_length
;
895 unsigned long mod_time
;
902 mod_time
= unix2dostime(&(statbuf
->st_mtime
));
903 file_name_length
= strlen(fname
);
905 if(!seekable
&& !do_compress
){
906 crc
= crc32(0L, Z_NULL
, 0);
908 while((rdamt
= read(ffd
, rd_buff
, RDSZ
)) != 0)
909 crc
= crc32(crc
, rd_buff
, rdamt
);
911 lseek(ffd
, 0, SEEK_SET
);
914 /* data descriptor */
915 if(!seekable
&& do_compress
){
916 PACK_UB2(file_header
, LOC_EXTRA
, 8);
918 PACK_UB2(file_header
, LOC_EXTRA
, 0);
922 PACK_UB2(file_header
, LOC_COMP
, 8);
924 PACK_UB2(file_header
, LOC_COMP
, 0);
927 PACK_UB4(file_header
, LOC_MODTIME
, mod_time
);
928 PACK_UB2(file_header
, LOC_FNLEN
, file_name_length
);
930 if(!seekable
&& !do_compress
){
931 PACK_UB4(file_header
, LOC_CRC
, crc
);
932 PACK_UB4(file_header
, LOC_USIZE
, statbuf
->st_size
);
933 PACK_UB4(file_header
, LOC_CSIZE
, statbuf
->st_size
);
935 memset((file_header
+ LOC_CRC
), '\0', 12); /* clear crc/usize/csize */
937 ze
= (zipentry
*)malloc(sizeof(zipentry
));
943 memset(ze
, 0, sizeof(zipentry
)); /* clear all the fields*/
944 ze
->filename
= (char*)malloc((file_name_length
+ 1) * sizeof(char));
945 strcpy(ze
->filename
, fname
);
947 ze
->mod_time
= (ub2
)(mod_time
& 0x0000ffff);
948 ze
->mod_date
= (ub2
)((mod_time
& 0xffff0000) >> 16);
950 if(!seekable
&& !do_compress
)
953 ze
->csize
= statbuf
->st_size
;
954 ze
->usize
= ze
->csize
;
955 ze
->offset
= lseek(jfd
, 0, SEEK_CUR
);
957 ze
->compressed
= TRUE
;
959 ze
->compressed
= FALSE
;
963 /* Write the local header */
964 write(jfd
, file_header
, 30);
966 /* write the file name to the zip file */
967 write(jfd
, fname
, file_name_length
);
971 printf("adding: %s ", fname
);
976 /* compress the file */
977 compress_file(ffd
, jfd
, ze
);
979 /* Write the contents of the file (uncompressed) to the zip file */
980 /* calculate the CRC as we go along */
981 ze
->crc
= crc32(0L, Z_NULL
, 0);
983 while((rdamt
= read(ffd
, rd_buff
, RDSZ
)) != 0){
984 ze
->crc
= crc32(ze
->crc
, rd_buff
, rdamt
);
985 if(write(jfd
, rd_buff
, rdamt
) != rdamt
){
993 /* write out data descriptor */
994 PACK_UB4(data_descriptor
, 4, ze
->crc
);
995 PACK_UB4(data_descriptor
, 8, ze
->csize
);
996 PACK_UB4(data_descriptor
, 12, ze
->usize
);
998 /* we need to seek back and fill the header */
1000 offset
= (ze
->csize
+ strlen(ze
->filename
) + 16);
1002 if(lseek(jfd
, -offset
, SEEK_CUR
) == (off_t
)-1){
1007 if(write(jfd
, (data_descriptor
+ 4), 12) != 12){
1014 if(lseek(jfd
, offset
, SEEK_CUR
) == (off_t
)-1){
1018 } else if(do_compress
){
1019 /* Sun's jar tool will only allow a data descriptor if the entry is
1020 compressed, but we'll save 16 bytes/entry if we only use it when
1021 we can't seek back on the file */
1023 if(write(jfd
, data_descriptor
, 16) != 16){
1030 printf("(in=%d) (out=%d) (%s %d%%)\n",
1031 (int)ze
->usize
, (int)ze
->csize
,
1032 (do_compress
? "deflated" : "stored"),
1033 (do_compress
? ((int)((1 - ze
->csize
/(float)ze
->usize
) * 100)) : 0));
1038 int create_central_header(int fd
){
1044 int total_in
= 0, total_out
= 22;
1048 iheader
= (int*)header
;
1055 /* version made by */
1058 /* version needed to extract */
1064 /* compression method */
1078 /* compressed size */
1083 /* uncompressed size */
1088 /* filename length */
1091 /* extra field length */
1094 /* file comment length */
1097 /* disk number start */
1100 /* internal file attribs */
1103 /* external file attribs */
1108 /* relative offset of local header */
1114 start_offset
= lseek(fd
, 0, SEEK_CUR
);
1116 for(ze
= ziptail
; ze
!= NULL
; ze
= ze
->next_entry
){
1118 total_in
+= ze
->usize
;
1119 total_out
+= ze
->csize
+ 76 + strlen(ze
->filename
) * 2;
1122 PACK_UB2(header
, CEN_COMP
, 8);
1124 PACK_UB2(header
, CEN_COMP
, 0);
1127 PACK_UB2(header
, CEN_MODTIME
, ze
->mod_time
);
1128 PACK_UB2(header
, CEN_MODDATE
, ze
->mod_date
);
1129 PACK_UB4(header
, CEN_CRC
, ze
->crc
);
1130 PACK_UB4(header
, CEN_CSIZE
, ze
->csize
);
1131 PACK_UB4(header
, CEN_USIZE
, ze
->usize
);
1132 PACK_UB2(header
, CEN_FNLEN
, strlen(ze
->filename
));
1133 PACK_UB4(header
, CEN_OFFSET
, ze
->offset
);
1135 write(fd
, header
, 46);
1137 write(fd
, ze
->filename
, strlen(ze
->filename
));
1140 dir_size
= lseek(fd
, 0, SEEK_CUR
) - start_offset
;
1143 end_header
[0] = 0x50;
1144 end_header
[1] = 0x4b;
1145 end_header
[2] = 0x05;
1146 end_header
[3] = 0x06;
1147 /* number of this disk */
1150 /* number of disk w/ start of central header */
1153 /* total number of entries in central dir on this disk*/
1154 PACK_UB2(end_header
, 8, number_of_entries
);
1155 /* total number of entries in central dir*/
1156 PACK_UB2(end_header
, 10, number_of_entries
);
1157 /* size of central dir. */
1158 PACK_UB4(end_header
, 12, dir_size
);
1159 /* offset of start of central dir */
1160 PACK_UB4(end_header
, 16, start_offset
);
1161 /* zipfile comment length */
1165 write(fd
, end_header
, 22);
1168 printf("Total:\n------\n(in = %d) (out = %d) (%s %d%%)\n",
1171 (do_compress
? "deflated" : "stored"),
1172 (int)((1 - (total_out
/ (float)total_in
)) * 100)
1178 int extract_jar(int fd
, char **files
, int file_num
){
1188 ub1
*filename
= NULL
;
1189 int filename_len
= 0;
1208 dir
= FALSE
; /* by default, the file isn't a dir */
1209 handle
= TRUE
; /* by default we'll extract/create the file */
1211 if((rdamt
= pb_read(&pbf
, scratch
, 4)) != 4){
1216 signature
= UNPACK_UB4(scratch
, 0);
1219 printf("signature is %x\n", signature
);
1221 if(signature
== 0x08074b50){
1223 printf("skipping data descriptor\n");
1225 pb_read(&pbf
, scratch
, 12);
1227 } else if(signature
== 0x02014b50){
1229 printf("Central header reached.. we're all done!\n");
1232 }else if(signature
!= 0x04034b50){
1233 printf("Ick! %#x\n", signature
);
1237 if((rdamt
= pb_read(&pbf
, (file_header
+ 4), 26)) != 26){
1242 csize
= UNPACK_UB4(file_header
, LOC_CSIZE
);
1244 printf("Compressed size is %u\n", csize
);
1247 fnlen
= UNPACK_UB2(file_header
, LOC_FNLEN
);
1249 printf("Filename length is %hu\n", fnlen
);
1252 eflen
= UNPACK_UB2(file_header
, LOC_EFLEN
);
1254 printf("Extra field length is %hu\n", eflen
);
1257 flags
= UNPACK_UB2(file_header
, LOC_EXTRA
);
1259 printf("Flags are %#hx\n", flags
);
1262 method
= UNPACK_UB2(file_header
, LOC_COMP
);
1264 printf("Compression method is %#hx\n", method
);
1267 /* if there isn't a data descriptor */
1268 if(!(flags
& 0x0008)){
1269 crc
= UNPACK_UB4(file_header
, LOC_CRC
);
1271 printf("CRC is %x\n", crc
);
1275 if(filename_len
< fnlen
){
1276 if(filename
!= NULL
)
1279 filename
= malloc(sizeof(ub1
) * (fnlen
+ 1));
1280 filename_len
= fnlen
+ 1;
1283 pb_read(&pbf
, filename
, fnlen
);
1284 filename
[fnlen
] = '\0';
1287 printf("filename is %s\n", filename
);
1293 for(j
= 0; j
< file_num
; j
++)
1294 if(strcmp(files
[j
], (const char *)filename
) == 0){
1303 /* OK, there is some directory information in the file. Nothing to do
1304 but ensure the directory(s) exist, and create them if they don't.
1306 if(strchr((const char *)filename
, '/') != NULL
&& handle
){
1307 /* Loop through all the directories in the path, (everything w/ a '/') */
1308 const ub1
*start
= filename
;
1312 tmp_buff
= malloc(sizeof(char) * strlen((const char *)filename
));
1315 const ub1
*idx
= (const unsigned char *)strchr((const char *)start
, '/');
1319 else if(idx
== start
){
1325 strncpy(tmp_buff
, (const char *)filename
, (idx
- filename
));
1326 tmp_buff
[(idx
- filename
)] = '\0';
1329 printf("checking the existance of %s\n", tmp_buff
);
1332 if(stat(tmp_buff
, &sbuf
) < 0){
1333 if(errno
!= ENOENT
){
1338 } else if(S_ISDIR(sbuf
.st_mode
)){
1340 printf("Directory exists\n");
1344 fprintf(stderr
, "Hmmm.. %s exists but isn't a directory!\n",
1350 printf("Making directory..\n");
1352 if(mkdir(tmp_buff
, 0755) < 0){
1356 if(verbose
&& handle
)
1357 printf("%10s: %s/\n", "created", tmp_buff
);
1361 /* only a directory */
1362 if(strlen((const char *)start
) == 0)
1366 printf("Leftovers are \"%s\" (%d)\n", start
, strlen((const char *)start
));
1369 /* If the entry was just a directory, don't write to file, etc */
1370 if(strlen((const char *)start
) == 0)
1376 if(f_fd
!= -1 && handle
){
1377 f_fd
= creat((const char *)filename
, 00644);
1380 fprintf(stderr
, "Error extracting JAR archive!\n");
1381 perror((const char *)filename
);
1386 if(method
!= 8 && flags
& 0x0008){
1387 fprintf(stderr
, "Error in JAR file! (not compressed but data desc.)\n");
1391 if(method
== 8 || flags
& 0x0008){
1393 lseek(fd
, eflen
, SEEK_CUR
);
1395 consume(&pbf
, eflen
);
1397 inflate_file(&pbf
, f_fd
, &ze
);
1401 printf("writing stored data.. (%d bytes)\n", csize
);
1407 ze
.crc
= crc32(ze
.crc
, NULL
, 0); /* initialize the crc */
1409 while(out_a
< csize
){
1410 rdamt
= (in_a
> RDSZ
? RDSZ
: in_a
);
1411 if(pb_read(&pbf
, rd_buff
, rdamt
) != rdamt
){
1416 ze
.crc
= crc32(ze
.crc
, (Bytef
*)rd_buff
, rdamt
);
1419 write(f_fd
, rd_buff
, rdamt
);
1425 printf("%d bytes written\n", out_a
);
1430 lseek(fd
, eflen
, SEEK_CUR
);
1432 consume(&pbf
, eflen
);
1435 /* if there is a data descriptor left, compare the CRC */
1438 if(pb_read(&pbf
, scratch
, 16) != 16){
1443 signature
= UNPACK_UB4(scratch
, 0);
1445 if(signature
!= 0x08074b50){
1446 fprintf(stderr
, "Error! Missing data descriptor!\n");
1450 crc
= UNPACK_UB4(scratch
, 4);
1455 fprintf(stderr
, "Error! CRCs do not match! Got %x, expected %x\n",
1462 if(verbose
&& dir
== FALSE
&& handle
)
1463 printf("%10s: %s\n",
1464 (method
== 8 ? "inflated" : "extracted"),
1471 int list_jar(int fd
, char **files
, int file_num
){
1484 ub1
*filename
= NULL
;
1487 int filename_len
= 0;
1492 char ascii_date
[30];
1496 printf("Listing jar file, looking for %d files\n", file_num
);
1499 /* This should be the start of the central-header-end section */
1501 if(lseek(fd
, -22, SEEK_END
) == (off_t
)-1){
1506 if(read(fd
, &tmp
, sizeof(ub4
)) != 4){
1511 #ifdef WORDS_BIGENDIAN
1515 if(tmp
!= 0x06054b50){
1516 fprintf(stderr
, "Error in JAR file format. zip-style comment?\n");
1520 if(lseek(fd
, 6, SEEK_CUR
) == (off_t
)-1){
1525 if(read(fd
, &cen_size
, 2) != 2){
1530 #ifdef WORDS_BIGENDIAN
1531 cen_size
= L2BS(cen_size
);
1534 /* printf("%hu entries in central header\n", cen_size); */
1536 if(lseek(fd
, 4, SEEK_CUR
) == (off_t
)-1){
1541 if(read(fd
, &tmp
, 4) != 4){
1546 #ifdef WORDS_BIGENDIAN
1550 /* printf("Central header offset = %d\n", tmp); */
1552 if(lseek(fd
, tmp
, SEEK_SET
) != tmp
){
1557 /* Loop through the entries in the central header */
1558 for(i
= 0; i
< cen_size
; i
++){
1560 if(read(fd
, &cen_header
, 46) != 46){
1565 signature
= UNPACK_UB4(cen_header
, 0);
1566 if(signature
!= 0x02014b50){
1567 fprintf(stderr
, "Error in JAR file! Cannot locate central header!\n");
1571 usize
= UNPACK_UB4(cen_header
, CEN_USIZE
);
1572 fnlen
= UNPACK_UB2(cen_header
, CEN_FNLEN
);
1573 eflen
= UNPACK_UB2(cen_header
, CEN_EFLEN
);
1574 clen
= UNPACK_UB2(cen_header
, CEN_COMLEN
);
1576 /* If we're providing verbose output, we need to make an ASCII
1577 * formatted version of the date. */
1579 mdate
= UNPACK_UB4(cen_header
, CEN_MODTIME
);
1580 tdate
= dos2unixtime(mdate
);
1581 s_tm
= localtime(&tdate
);
1582 strftime(ascii_date
, 30, "%a %b %d %H:%M:%S %Z %Y", s_tm
);
1585 if(filename_len
< fnlen
){
1586 if(filename
!= NULL
)
1589 filename
= malloc(sizeof(ub1
) * (fnlen
+ 1));
1590 filename_len
= fnlen
+ 1;
1593 if(read(fd
, filename
, fnlen
) != fnlen
){
1597 filename
[fnlen
] = '\0';
1599 /* if the user specified a list of files on the command line,
1600 we'll only display those, otherwise we'll display everything */
1602 for(j
= 0; j
< file_num
; j
++)
1603 if(strcmp(files
[j
], (const char *)filename
) == 0){
1605 printf("%6d %s %s\n", usize
, ascii_date
, filename
);
1607 printf("%s\n", filename
);
1612 printf("%6d %s %s\n", usize
, ascii_date
, filename
);
1614 printf("%s\n", filename
);
1617 size
= eflen
+ clen
;
1619 if(lseek(fd
, size
, SEEK_CUR
) == (off_t
)-1){
1626 /* the file isn't seekable.. evil! */
1634 if((rdamt
= pb_read(&pbf
, scratch
, 4)) != 4){
1639 signature
= UNPACK_UB4(scratch
, 0);
1642 printf("signature is %x\n", signature
);
1645 if(signature
== 0x08074b50){
1647 printf("skipping data descriptor\n");
1649 pb_read(&pbf
, scratch
, 12);
1651 } else if(signature
== 0x02014b50){
1653 printf("Central header reached.. we're all done!\n");
1656 }else if(signature
!= 0x04034b50){
1658 printf("Ick! %#x\n", signature
);
1663 if((rdamt
= pb_read(&pbf
, (file_header
+ 4), 26)) != 26){
1668 csize
= UNPACK_UB4(file_header
, LOC_CSIZE
);
1670 printf("Compressed size is %u\n", csize
);
1673 fnlen
= UNPACK_UB2(file_header
, LOC_FNLEN
);
1675 printf("Filename length is %hu\n", fnlen
);
1678 eflen
= UNPACK_UB2(file_header
, LOC_EFLEN
);
1680 printf("Extra field length is %hu\n", eflen
);
1683 method
= UNPACK_UB2(file_header
, LOC_COMP
);
1685 printf("Compression method is %#hx\n", method
);
1688 flags
= UNPACK_UB2(file_header
, LOC_EXTRA
);
1690 printf("Flags are %#hx\n", flags
);
1693 usize
= UNPACK_UB4(file_header
, LOC_USIZE
);
1695 /* If we're providing verbose output, we need to make an ASCII
1696 * formatted version of the date. */
1698 mdate
= UNPACK_UB4(file_header
, LOC_MODTIME
);
1699 tdate
= dos2unixtime(mdate
);
1700 s_tm
= localtime(&tdate
);
1701 strftime(ascii_date
, 30, "%a %b %d %H:%M:%S %Z %Y", s_tm
);
1704 if(filename_len
< fnlen
){
1705 if(filename
!= NULL
)
1708 filename
= malloc(sizeof(ub1
) * (fnlen
+ 1));
1709 filename_len
= fnlen
+ 1;
1712 pb_read(&pbf
, filename
, fnlen
);
1713 filename
[fnlen
] = '\0';
1715 /* the header is at the end. In a JAR file, this means that the data
1716 happens to be compressed. We have no choice but to inflate the
1723 consume(&pbf
, size
);
1727 printf("inflating %s\n", filename
);
1729 inflate_file(&pbf
, -1, &ze
);
1733 printf("We're shit outta luck!\n");
1736 size
= csize
+ (eflen
> 0 ? eflen
: 0);
1740 printf("Skipping %ld bytes\n", (long)size
);
1743 consume(&pbf
, size
);
1745 /* print out the listing */
1747 for(j
= 0; j
< file_num
; j
++)
1748 if(strcmp(files
[j
], (const char *)filename
) == 0){
1750 printf("%6d %s %s\n", usize
, ascii_date
, filename
);
1752 printf("%s\n", filename
);
1757 printf("%6d %s %s\n", usize
, ascii_date
, filename
);
1759 printf("%s\n", filename
);
1766 int consume(pb_file
*pbf
, int amt
){
1767 int tc
= 0; /* total amount consumed */
1772 printf("Consuming %d bytes\n", amt
);
1776 rdamt
= pb_read(pbf
, buff
, ((amt
- tc
) < RDSZ
? (amt
- tc
) : RDSZ
));
1778 printf("got %d bytes\n", rdamt
);
1784 printf("%d bytes consumed\n", tc
);
1790 void usage(const char *filename
){
1792 Usage: %s {ctxuV}[vfm0ME@] [jar-file] [manifest-file] [-C dir] files ...\n\
1794 -c create new archive\n\
1795 -t list table of contents for archive\n\
1796 -x extract named (or all) files from archive\n\
1799 -u update existing archive\n\
1800 -V display version information\n\
1801 -v generate verbose output on standard output\n\
1802 -f specify archive file name\n\
1803 -m include manifest information from specified manifest file\n\
1804 -0 store only; use no ZIP compression\n\
1805 -M Do not create a manifest file for the entries\n\
1806 -C change to the specified directory and include the following file\n\
1807 -E don't include the files found in a directory\n\
1808 -@ Read names from stdin\n\
1811 If any file is a directory then it is processed recursively.\n\
1812 The manifest file name and the archive file name needs to be specified\n\
1813 in the same order the 'm' and 'f' flags are specified.\n\
1815 Example 1: to archive two class files into an archive called classes.jar: \n\
1816 jar cvf classes.jar Foo.class Bar.class \n\
1817 Example 2: use an existing manifest file 'mymanifest' and archive all the\n\
1818 files in the foo/ directory into 'classes.jar': \n\
1819 jar cvfm classes.jar mymanifest -C foo/ .\n\