2 jartool.c - main functions for fastjar utility
3 Copyright (C) 1999, 2000, 2001 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.7 2001/08/27 23:09:37 tromey Exp $
23 Revision 1.7 2001/08/27 23:09:37 tromey
24 * jartool.c (jarfile): Remove length limitation.
25 (main): Use jt_strdup when initializing jarfile.
27 Revision 1.6 2001/07/04 18:33:53 tromey
28 Modified from patch by Julian Hall <jules@acris.co.uk>:
29 * jartool.c (errno): Conditionally declare.
30 (O_BINARY): Conditionally define.
31 (main): Use open, not creat. Use O_BINARY everywhere.
32 (make_manifest): Use O_BINARY.
33 (add_to_jar): Likewise.
35 Revision 1.5 2001/05/03 21:40:47 danglin
36 * jartool.c (jt_strdup): New function.
37 (get_next_arg): Use jt_strdup instead of strdup.
39 Revision 1.4 2000/12/28 21:47:37 robertl
40 2000-12-28 Robert Lipe <robertl@sco.com>
42 * jartool.c (MAXPATHLEN): Provide if not defined.
44 Revision 1.3 2000/12/14 18:45:35 ghazi
47 * compress.c: Include stdlib.h and compress.h.
49 (report_str_error): Make static.
50 (ez_inflate_str): Delete unused variable. Add parens in if-stmt.
51 (hrd_inflate_str): Likewise.
53 * compress.h (init_compression, end_compression, init_inflation,
54 end_inflation): Prototype void arguments.
56 * dostime.c (rcsid): Delete.
58 * jargrep.c: Include ctype.h, stdlib.h, zlib.h and compress.h.
59 Make functions static. Cast ctype function argument to `unsigned
60 char'. Add parens in if-stmts. Constify.
61 (Usage): Change into a macro.
62 (jargrep): Remove unused parameter.
64 * jartool.c: Constify. Add parens in if-stmts. Align
65 signed/unsigned char pointers in functions calls using casts.
67 (list_jar): Fix printf format specifier.
68 (usage): Chop long string into bits. Reformat.
70 * pushback.c (rcsid): Delete.
72 Revision 1.2 2000/12/13 18:11:57 tromey
73 * jartool.c (extract_jar): Use strchr, not index.
75 Revision 1.1 2000/12/09 03:08:23 apbianco
76 2000-12-08 Alexandre Petit-Bianco <apbianco@cygnus.com>
80 Revision 1.5 2000/08/24 15:01:27 cory
81 Made certain that fastjar opened the jar file before trying to update it
84 Revision 1.4 2000/08/24 13:39:21 cory
85 Changed +'s to |'s in jartool.c to insure there was no confusion with sign
86 when byte swapping. Better safe than sorry.
88 Revision 1.3 2000/08/23 19:42:17 cory
89 Added support for more Unix platforms. The following code has been hacked
90 to work on AIX, Solaris, True 64, and HP-UX.
91 Added bigendian check. Probably works on most big and little endian platforms
94 Revision 1.2 1999/12/06 07:38:28 toast
95 fixed recursive archiving bug
97 Revision 1.1.1.1 1999/12/06 03:09:34 toast
102 Revision 1.22 1999/10/12 19:45:13 burnsbr
103 adding patch to fix compat problem
105 Revision 1.21 1999/05/10 09:15:49 burnsbr
106 fixed manifest file version info
108 Revision 1.20 1999/05/10 08:53:16 burnsbr
109 *** empty log message ***
111 Revision 1.19 1999/05/10 08:30:39 burnsbr
112 added extract / listing code
114 Revision 1.18 1999/04/28 04:24:29 burnsbr
117 Revision 1.17 1999/04/28 04:21:23 burnsbr
118 added support for -C dir-changing flag.. Updated total compression display
120 Revision 1.16 1999/04/27 10:28:22 burnsbr
121 updated version string
123 Revision 1.15 1999/04/27 10:04:06 burnsbr
126 Revision 1.14 1999/04/27 08:56:14 burnsbr
127 added -V flag, better error messages
129 Revision 1.13 1999/04/26 02:35:21 burnsbr
130 changed all sorts of stuff.. compression now works 100%
132 Revision 1.12 1999/04/23 12:00:45 burnsbr
133 90% done with compression code
135 Revision 1.11 1999/04/22 04:12:57 burnsbr
136 finished first round of Manifest file support..
137 might need to do more, digest etc..
139 Revision 1.10 1999/04/22 02:35:23 burnsbr
140 added more manifest support, about 75% done now. Replaced all the
141 redundant shifts and bit-logic with a macro or two, making the code
144 Revision 1.9 1999/04/21 09:55:16 burnsbr
147 Revision 1.8 1999/04/21 02:58:01 burnsbr
148 started manifest code
150 Revision 1.7 1999/04/20 23:15:28 burnsbr
151 added patch sent by John Bley <jbb6@acpub.duke.edu>
153 Revision 1.6 1999/04/20 08:56:02 burnsbr
156 Revision 1.5 1999/04/20 08:16:09 burnsbr
157 fixed verbose flag, did some optimization
159 Revision 1.4 1999/04/20 05:09:59 burnsbr
162 Revision 1.3 1999/04/20 05:08:54 burnsbr
180 #include <sys/stat.h>
181 #include <sys/types.h>
183 #ifdef HAVE_SYS_PARAM_H
184 #include <sys/param.h>
188 #define MAXPATHLEN 1024
202 #ifdef TM_IN_SYS_TIME
203 #include <sys/time.h>
211 #include "pushback.h"
212 #include "compress.h"
214 #ifdef WORDS_BIGENDIAN
216 #define L2BI(l) ((l & 0xff000000) >> 24) | \
217 ((l & 0x00ff0000) >> 8) | \
218 ((l & 0x0000ff00) << 8) | \
219 ((l & 0x000000ff) << 24);
221 #define L2BS(l) ((l & 0xff00) >> 8) | ((l & 0x00ff) << 8);
225 static char version_string
[] = VERSION
;
235 void usage(const char*);
236 void add_entry(struct zipentry
*);
237 void init_headers(void);
239 int consume(pb_file
*, int);
240 int list_jar(int, char**, int);
241 int extract_jar(int, char**, int);
242 int add_file_to_jar(int, int, const char*, struct stat
*);
243 int add_to_jar(int, const char*, const char*);
244 int create_central_header(int);
245 int make_manifest(int, const char*);
246 static void init_args(char **, int);
247 static char *get_next_arg (void);
248 static char *jt_strdup (char*);
250 /* global variables */
252 ub1 data_descriptor
[16];
258 /* If non zero, then don't recurse in directory. Instead, add the
259 directory entry and relie on an explicit list of files to populate
260 the archive. This option isn't supported by the original jar tool. */
261 int use_explicit_list_only
;
263 /* If non zero, then read the entry names from stdin. This option
264 isn't supported by the original jar tool. */
265 int read_names_from_stdin
;
267 zipentry
*ziplist
; /* linked list of entries */
268 zipentry
*ziptail
; /* tail of the linked list */
270 int number_of_entries
; /* number of entries in the linked list */
272 int main(int argc
, char **argv
){
276 int action
= ACTION_NONE
;
278 int manifest_file
= FALSE
;
280 int file_first
= FALSE
;
290 number_of_entries
= 0;
297 for(i
= 0; i
< j
; i
++){
300 action
= ACTION_CREATE
;
303 action
= ACTION_LIST
;
306 action
= ACTION_EXTRACT
;
309 action
= ACTION_UPDATE
;
315 printf("%s\n", version_string
);
325 manifest_file
= TRUE
;
335 /* The following options aren't supported by the original jar tool. */
337 use_explicit_list_only
= TRUE
;
340 read_names_from_stdin
= TRUE
;
343 fprintf(stderr
, "Illegal option: %c\n", argv
[1][i
]);
348 if(action
== ACTION_NONE
){
349 fprintf(stderr
, "One of options -{ctxu} must be specified.\n");
353 /* Verify unsupported combinations and warn of the use of non
355 if(verbose
&& use_explicit_list_only
)
356 fprintf (stderr
, "Warning: using non standard '-E' option\n");
357 if(verbose
&& read_names_from_stdin
)
358 fprintf (stderr
, "Warning: using non standard '-@' option\n");
359 if(read_names_from_stdin
360 && (action
!= ACTION_CREATE
&& action
!= ACTION_UPDATE
)){
361 fprintf(stderr
, "Option '-@' is supported only with '-c' or '-u'.\n");
367 /* get the jarfile and manifest file (if any) */
368 if(file
&& file_first
){
372 jarfile
= jt_strdup (argv
[i
++]);
378 strncpy(mfile
, argv
[i
++], 256);
381 if(file
&& !file_first
){
385 jarfile
= jt_strdup (argv
[i
++]);
388 /* create the jarfile */
389 if(action
== ACTION_CREATE
){
391 jarfd
= open(jarfile
, O_CREAT
| O_BINARY
| O_WRONLY
| O_TRUNC
,
392 S_IRUSR
| S_IWUSR
| S_IRGRP
| S_IROTH
);
395 fprintf(stderr
, "Error opening %s for writing!\n", jarfile
);
400 /* We assume that the file is seekable */
405 jarfd
= STDOUT_FILENO
; /* jarfd is stdout otherwise */
407 /* standard out is not seekable */
410 /* don't want our output to be part of the jar file.. figured this one
411 out the hard way.. =P */
414 } else if(action
== ACTION_LIST
|| action
== ACTION_EXTRACT
){
417 jarfd
= open(jarfile
, O_RDONLY
| O_BINARY
);
420 fprintf(stderr
, "Error opening %s for reading!\n", jarfile
);
427 jarfd
= STDIN_FILENO
; /* jarfd is standard in */
429 /* we assume that the stream isn't seekable for safety */
434 if(action
== ACTION_CREATE
|| action
== ACTION_UPDATE
){
438 if((action
== ACTION_UPDATE
) && file
) {
439 if((jarfd
= open(jarfile
, O_RDWR
| O_BINARY
)) < 0) {
440 fprintf(stderr
, "Error opening %s for reading!\n", jarfile
);
450 /* Add the META-INF/ directory and the manifest */
451 if(manifest
&& manifest_file
)
452 make_manifest(jarfd
, mfile
);
454 make_manifest(jarfd
, NULL
);
457 /* now we add the files to the archive */
458 while ((arg
= get_next_arg ())){
460 if(!strcmp(arg
, "-C")){
461 const char *dir_to_change
= get_next_arg ();
462 const char *file_to_add
= get_next_arg ();
465 || add_to_jar(jarfd
, dir_to_change
, file_to_add
)){
466 printf("Error adding %s to jar archive!\n", arg
);
470 if(add_to_jar(jarfd
, NULL
, arg
)){
471 printf("Error adding %s to jar archive!\n", arg
);
476 /* de-initialize the compression DS */
480 create_central_header(jarfd
);
482 if (close(jarfd
) != 0) {
483 fprintf(stderr
, "Error closing jar archive!\n");
485 } else if(action
== ACTION_LIST
){
486 list_jar(jarfd
, &argv
[i
], (argc
- i
));
487 } else if(action
== ACTION_EXTRACT
){
488 extract_jar(jarfd
, &argv
[i
], (argc
- i
));
494 static int args_current_g
;
495 static char **args_g
;
498 init_args(args
, current
)
502 if(!read_names_from_stdin
)
505 args_current_g
= current
;
512 static int reached_end
= 0;
519 if (!args_g
[args_current_g
])
524 return args_g
[args_current_g
++];
528 /* Read the name from stdin. Delimiters are '\n' and
529 '\r'. Reading EOF indicates that we don't have anymore file
530 names characters to read. */
535 /* Get rid of '\n' and '\r' first. */
538 int c
= getc (stdin
);
539 if (c
== '\n' || c
== '\r')
552 int c
= getc (stdin
);
553 /* Exit when we get a delimiter or don't have any characters
555 if (c
== '\n'|| c
== '\r'|| c
== EOF
)
557 s
[pos
++] = (char) c
;
563 return jt_strdup (s
);
571 /* packing file header */
573 file_header
[0] = 0x50;
574 file_header
[1] = 0x4b;
575 file_header
[2] = 0x03;
576 file_header
[3] = 0x04;
577 /* version number (Unix 1.0)*/
580 /* bit flag (normal deflation)*/
581 file_header
[6] = 0x00;
583 file_header
[7] = 0x00;
584 /* do_compression method (deflation) */
588 /* last mod file time (MS-DOS format) */
591 /* last mod file date (MS-DOS format) */
599 /* compressed size */
604 /* uncompressed size */
609 /* filename length */
612 /* extra field length */
616 /* Initialize the compression DS */
617 PACK_UB4(data_descriptor
, 0, 0x08074b50);
621 void add_entry(struct zipentry
*ze
){
627 ziplist
->next_entry
= ze
;
634 int make_manifest(int jfd
, const char *mf_name
){
636 int nlen
; /* length of file name */
637 int mod_time
; /* file modification time */
640 nlen
= 9; /* trust me on this one */
642 memset((file_header
+ 12), '\0', 16); /*clear mod time, crc, size fields*/
644 current_time
= time(NULL
);
645 if(current_time
== (time_t)-1){
650 mod_time
= unix2dostime(¤t_time
);
652 PACK_UB2(file_header
, LOC_EXTRA
, 0);
653 PACK_UB2(file_header
, LOC_COMP
, 0);
654 PACK_UB2(file_header
, LOC_FNLEN
, nlen
);
655 PACK_UB4(file_header
, LOC_MODTIME
, mod_time
);
658 printf("adding: META-INF/ (in=0) (out=0) (stored 0%%)\n");
660 ze
= (zipentry
*)malloc(sizeof(zipentry
));
666 memset(ze
, 0, sizeof(zipentry
)); /* clear all the fields*/
667 ze
->filename
= (char*)malloc((nlen
+ 1) * sizeof(char) + 1);
668 strcpy(ze
->filename
, "META-INF/");
669 ze
->filename
[nlen
] = '\0';
671 ze
->offset
= lseek(jfd
, 0, SEEK_CUR
);
672 ze
->mod_time
= (ub2
)(mod_time
& 0x0000ffff);
673 ze
->mod_date
= (ub2
)((mod_time
& 0xffff0000) >> 16);
674 ze
->compressed
= FALSE
;
678 write(jfd
, file_header
, 30);
679 write(jfd
, "META-INF/", nlen
);
681 /* if the user didn't specify an external manifest file... */
683 int mf_len
= 37 + strlen(VERSION
);
686 if((mf
= (char *) malloc(mf_len
+ 1))) {
689 sprintf(mf
, "Manifest-Version: 1.0\nCreated-By: %s\n\n", VERSION
);
691 crc
= crc32(0L, Z_NULL
, 0);
693 crc
= crc32(crc
, (const unsigned char *)mf
, mf_len
);
695 nlen
= 20; /* once again, trust me */
697 PACK_UB2(file_header
, LOC_EXTRA
, 0);
698 PACK_UB2(file_header
, LOC_COMP
, 0);
699 PACK_UB2(file_header
, LOC_FNLEN
, nlen
);
700 PACK_UB4(file_header
, LOC_USIZE
, mf_len
);
702 memcpy((file_header
+ LOC_CSIZE
), (file_header
+ LOC_USIZE
), 4);
704 PACK_UB4(file_header
, LOC_CRC
, crc
);
707 printf("adding: META-INF/MANIFEST.MF (in=56) (out=56) (stored 0%%)\n");
709 ze
= (zipentry
*)malloc(sizeof(zipentry
));
715 memset(ze
, 0, sizeof(zipentry
)); /* clear all the fields*/
716 ze
->filename
= (char*)malloc((nlen
+ 1) * sizeof(char) + 1);
717 strcpy(ze
->filename
, "META-INF/MANIFEST.MF");
718 ze
->filename
[nlen
] = '\0';
720 ze
->offset
= lseek(jfd
, 0, SEEK_CUR
);
721 ze
->mod_time
= (ub2
)(mod_time
& 0x0000ffff);
722 ze
->mod_date
= (ub2
)((mod_time
& 0xffff0000) >> 16);
725 ze
->usize
= ze
->csize
;
726 ze
->compressed
= FALSE
;
730 write(jfd
, file_header
, 30);
731 write(jfd
, "META-INF/MANIFEST.MF", nlen
);
732 write(jfd
, mf
, mf_len
);
736 printf("malloc errror\n");
743 stat(mf_name
, &statbuf
);
745 if(!S_ISREG(statbuf
.st_mode
)){
746 fprintf(stderr
, "Invalid manifest file specified.\n");
750 mfd
= open(mf_name
, O_RDONLY
| O_BINARY
);
753 fprintf(stderr
, "Error opening %s.\n", mf_name
);
757 if(add_file_to_jar(jfd
, mfd
, "META-INF/MANIFEST.MF", &statbuf
)){
758 perror("error writing to jar");
767 int add_to_jar(int fd
, const char *new_dir
, const char *file
){
773 char *old_dir
= NULL
;
775 /* This is a quick compatibility fix -- Simon Weijgers <simon@weijgers.com>
777 * "normal" jar : org/apache/java/io/LogRecord.class
778 * fastjar : ./org/apache/java/io/LogRecord.class
779 * Fastjar's preservation of the ./'s makes the jarfile unusuable for use
780 * with both kaffe-1.0b4 and JDK.
782 while (*file
=='.' && *(file
+1)=='/')
785 /* If new_dir isn't null, we need to change to that directory. However,
786 we also need to return to the old directory when we're done */
788 old_dir
= getcwd(NULL
, 0);
790 if(chdir(new_dir
) == -1){
796 if(!strcmp(file
, jarfile
)){
798 printf("skipping: %s\n", file
);
799 return 0; /* we don't want to add ourselves.. */
802 stat_return
= stat(file
, &statbuf
);
804 if(stat_return
== -1){
807 } else if(S_ISDIR(statbuf
.st_mode
)){
811 unsigned long mod_time
;
820 nlen
= strlen(file
) + 256;
821 fullname
= (char*)malloc(nlen
* sizeof(char));
822 memset(fullname
, 0, (nlen
* sizeof(char)));
824 if(fullname
== NULL
){
825 fprintf(stderr
, "Filename is NULL!\n");
829 strcpy(fullname
, file
);
832 if(fullname
[nlen
- 1] != '/'){
833 fullname
[nlen
] = '/';
834 t_ptr
= (fullname
+ nlen
+ 1);
836 t_ptr
= (fullname
+ nlen
);
839 memset((file_header
+ 12), '\0', 16); /*clear mod time, crc, size fields*/
841 nlen
= (t_ptr
- fullname
);
843 mod_time
= unix2dostime(&statbuf
.st_mtime
);
845 PACK_UB2(file_header
, LOC_EXTRA
, 0);
846 PACK_UB2(file_header
, LOC_COMP
, 0);
847 PACK_UB2(file_header
, LOC_FNLEN
, nlen
);
848 PACK_UB4(file_header
, LOC_MODTIME
, mod_time
);
851 printf("adding: %s (in=%d) (out=%d) (stored 0%%)\n", fullname
, 0, 0);
853 ze
= (zipentry
*)malloc(sizeof(zipentry
));
859 memset(ze
, 0, sizeof(zipentry
)); /* clear all the fields*/
860 ze
->filename
= (char*)malloc((nlen
+ 1) * sizeof(char) + 1);
861 strcpy(ze
->filename
, fullname
);
862 ze
->filename
[nlen
] = '\0';
864 ze
->offset
= lseek(fd
, 0, SEEK_CUR
);
865 ze
->mod_time
= (ub2
)(mod_time
& 0x0000ffff);
866 ze
->mod_date
= (ub2
)((mod_time
& 0xffff0000) >> 16);
867 ze
->compressed
= FALSE
;
871 write(fd
, file_header
, 30);
872 write(fd
, fullname
, nlen
);
874 while(!use_explicit_list_only
&& (de
= readdir(dir
)) != NULL
){
875 if(de
->d_name
[0] == '.')
877 if(!strcmp(de
->d_name
, jarfile
)){ /* we don't want to add ourselves. Believe me */
879 printf("skipping: %s\n", de
->d_name
);
883 strcpy(t_ptr
, de
->d_name
);
885 if(add_to_jar(fd
, NULL
, fullname
)){
886 fprintf(stderr
, "Error adding file to jar!\n");
894 } else if(S_ISREG(statbuf
.st_mode
)){
897 add_fd
= open(file
, O_RDONLY
| O_BINARY
);
899 fprintf(stderr
, "Error opening %s.\n", file
);
903 if(add_file_to_jar(fd
, add_fd
, file
, &statbuf
)){
904 fprintf(stderr
, "Error adding file to jar!\n");
909 fprintf(stderr
, "Illegal file specified: %s\n", file
);
922 int add_file_to_jar(int jfd
, int ffd
, const char *fname
, struct stat
*statbuf
){
924 unsigned short file_name_length
;
925 unsigned long mod_time
;
932 mod_time
= unix2dostime(&(statbuf
->st_mtime
));
933 file_name_length
= strlen(fname
);
935 if(!seekable
&& !do_compress
){
936 crc
= crc32(0L, Z_NULL
, 0);
938 while((rdamt
= read(ffd
, rd_buff
, RDSZ
)) != 0)
939 crc
= crc32(crc
, rd_buff
, rdamt
);
941 lseek(ffd
, 0, SEEK_SET
);
944 /* data descriptor */
945 if(!seekable
&& do_compress
){
946 PACK_UB2(file_header
, LOC_EXTRA
, 8);
948 PACK_UB2(file_header
, LOC_EXTRA
, 0);
952 PACK_UB2(file_header
, LOC_COMP
, 8);
954 PACK_UB2(file_header
, LOC_COMP
, 0);
957 PACK_UB4(file_header
, LOC_MODTIME
, mod_time
);
958 PACK_UB2(file_header
, LOC_FNLEN
, file_name_length
);
960 if(!seekable
&& !do_compress
){
961 PACK_UB4(file_header
, LOC_CRC
, crc
);
962 PACK_UB4(file_header
, LOC_USIZE
, statbuf
->st_size
);
963 PACK_UB4(file_header
, LOC_CSIZE
, statbuf
->st_size
);
965 memset((file_header
+ LOC_CRC
), '\0', 12); /* clear crc/usize/csize */
967 ze
= (zipentry
*)malloc(sizeof(zipentry
));
973 memset(ze
, 0, sizeof(zipentry
)); /* clear all the fields*/
974 ze
->filename
= (char*)malloc((file_name_length
+ 1) * sizeof(char));
975 strcpy(ze
->filename
, fname
);
977 ze
->mod_time
= (ub2
)(mod_time
& 0x0000ffff);
978 ze
->mod_date
= (ub2
)((mod_time
& 0xffff0000) >> 16);
980 if(!seekable
&& !do_compress
)
983 ze
->csize
= statbuf
->st_size
;
984 ze
->usize
= ze
->csize
;
985 ze
->offset
= lseek(jfd
, 0, SEEK_CUR
);
987 ze
->compressed
= TRUE
;
989 ze
->compressed
= FALSE
;
993 /* Write the local header */
994 write(jfd
, file_header
, 30);
996 /* write the file name to the zip file */
997 write(jfd
, fname
, file_name_length
);
1001 printf("adding: %s ", fname
);
1006 /* compress the file */
1007 compress_file(ffd
, jfd
, ze
);
1009 /* Write the contents of the file (uncompressed) to the zip file */
1010 /* calculate the CRC as we go along */
1011 ze
->crc
= crc32(0L, Z_NULL
, 0);
1013 while((rdamt
= read(ffd
, rd_buff
, RDSZ
)) != 0){
1014 ze
->crc
= crc32(ze
->crc
, rd_buff
, rdamt
);
1015 if(write(jfd
, rd_buff
, rdamt
) != rdamt
){
1023 /* write out data descriptor */
1024 PACK_UB4(data_descriptor
, 4, ze
->crc
);
1025 PACK_UB4(data_descriptor
, 8, ze
->csize
);
1026 PACK_UB4(data_descriptor
, 12, ze
->usize
);
1028 /* we need to seek back and fill the header */
1030 offset
= (ze
->csize
+ strlen(ze
->filename
) + 16);
1032 if(lseek(jfd
, -offset
, SEEK_CUR
) == (off_t
)-1){
1037 if(write(jfd
, (data_descriptor
+ 4), 12) != 12){
1044 if(lseek(jfd
, offset
, SEEK_CUR
) == (off_t
)-1){
1048 } else if(do_compress
){
1049 /* Sun's jar tool will only allow a data descriptor if the entry is
1050 compressed, but we'll save 16 bytes/entry if we only use it when
1051 we can't seek back on the file */
1053 if(write(jfd
, data_descriptor
, 16) != 16){
1060 printf("(in=%d) (out=%d) (%s %d%%)\n",
1061 (int)ze
->usize
, (int)ze
->csize
,
1062 (do_compress
? "deflated" : "stored"),
1063 (do_compress
? ((int)((1 - ze
->csize
/(float)ze
->usize
) * 100)) : 0));
1068 int create_central_header(int fd
){
1074 int total_in
= 0, total_out
= 22;
1078 iheader
= (int*)header
;
1085 /* version made by */
1088 /* version needed to extract */
1094 /* compression method */
1108 /* compressed size */
1113 /* uncompressed size */
1118 /* filename length */
1121 /* extra field length */
1124 /* file comment length */
1127 /* disk number start */
1130 /* internal file attribs */
1133 /* external file attribs */
1138 /* relative offset of local header */
1144 start_offset
= lseek(fd
, 0, SEEK_CUR
);
1146 for(ze
= ziptail
; ze
!= NULL
; ze
= ze
->next_entry
){
1148 total_in
+= ze
->usize
;
1149 total_out
+= ze
->csize
+ 76 + strlen(ze
->filename
) * 2;
1152 PACK_UB2(header
, CEN_COMP
, 8);
1154 PACK_UB2(header
, CEN_COMP
, 0);
1157 PACK_UB2(header
, CEN_MODTIME
, ze
->mod_time
);
1158 PACK_UB2(header
, CEN_MODDATE
, ze
->mod_date
);
1159 PACK_UB4(header
, CEN_CRC
, ze
->crc
);
1160 PACK_UB4(header
, CEN_CSIZE
, ze
->csize
);
1161 PACK_UB4(header
, CEN_USIZE
, ze
->usize
);
1162 PACK_UB2(header
, CEN_FNLEN
, strlen(ze
->filename
));
1163 PACK_UB4(header
, CEN_OFFSET
, ze
->offset
);
1165 write(fd
, header
, 46);
1167 write(fd
, ze
->filename
, strlen(ze
->filename
));
1170 dir_size
= lseek(fd
, 0, SEEK_CUR
) - start_offset
;
1173 end_header
[0] = 0x50;
1174 end_header
[1] = 0x4b;
1175 end_header
[2] = 0x05;
1176 end_header
[3] = 0x06;
1177 /* number of this disk */
1180 /* number of disk w/ start of central header */
1183 /* total number of entries in central dir on this disk*/
1184 PACK_UB2(end_header
, 8, number_of_entries
);
1185 /* total number of entries in central dir*/
1186 PACK_UB2(end_header
, 10, number_of_entries
);
1187 /* size of central dir. */
1188 PACK_UB4(end_header
, 12, dir_size
);
1189 /* offset of start of central dir */
1190 PACK_UB4(end_header
, 16, start_offset
);
1191 /* zipfile comment length */
1195 write(fd
, end_header
, 22);
1198 printf("Total:\n------\n(in = %d) (out = %d) (%s %d%%)\n",
1201 (do_compress
? "deflated" : "stored"),
1202 (int)((1 - (total_out
/ (float)total_in
)) * 100)
1208 int extract_jar(int fd
, char **files
, int file_num
){
1218 ub1
*filename
= NULL
;
1219 int filename_len
= 0;
1238 dir
= FALSE
; /* by default, the file isn't a dir */
1239 handle
= TRUE
; /* by default we'll extract/create the file */
1241 if((rdamt
= pb_read(&pbf
, scratch
, 4)) != 4){
1246 signature
= UNPACK_UB4(scratch
, 0);
1249 printf("signature is %x\n", signature
);
1251 if(signature
== 0x08074b50){
1253 printf("skipping data descriptor\n");
1255 pb_read(&pbf
, scratch
, 12);
1257 } else if(signature
== 0x02014b50){
1259 printf("Central header reached.. we're all done!\n");
1262 }else if(signature
!= 0x04034b50){
1263 printf("Ick! %#x\n", signature
);
1267 if((rdamt
= pb_read(&pbf
, (file_header
+ 4), 26)) != 26){
1272 csize
= UNPACK_UB4(file_header
, LOC_CSIZE
);
1274 printf("Compressed size is %u\n", csize
);
1277 fnlen
= UNPACK_UB2(file_header
, LOC_FNLEN
);
1279 printf("Filename length is %hu\n", fnlen
);
1282 eflen
= UNPACK_UB2(file_header
, LOC_EFLEN
);
1284 printf("Extra field length is %hu\n", eflen
);
1287 flags
= UNPACK_UB2(file_header
, LOC_EXTRA
);
1289 printf("Flags are %#hx\n", flags
);
1292 method
= UNPACK_UB2(file_header
, LOC_COMP
);
1294 printf("Compression method is %#hx\n", method
);
1297 /* if there isn't a data descriptor */
1298 if(!(flags
& 0x0008)){
1299 crc
= UNPACK_UB4(file_header
, LOC_CRC
);
1301 printf("CRC is %x\n", crc
);
1305 if(filename_len
< fnlen
){
1306 if(filename
!= NULL
)
1309 filename
= malloc(sizeof(ub1
) * (fnlen
+ 1));
1310 filename_len
= fnlen
+ 1;
1313 pb_read(&pbf
, filename
, fnlen
);
1314 filename
[fnlen
] = '\0';
1317 printf("filename is %s\n", filename
);
1323 for(j
= 0; j
< file_num
; j
++)
1324 if(strcmp(files
[j
], (const char *)filename
) == 0){
1333 /* OK, there is some directory information in the file. Nothing to do
1334 but ensure the directory(s) exist, and create them if they don't.
1336 if(strchr((const char *)filename
, '/') != NULL
&& handle
){
1337 /* Loop through all the directories in the path, (everything w/ a '/') */
1338 const ub1
*start
= filename
;
1342 tmp_buff
= malloc(sizeof(char) * strlen((const char *)filename
));
1345 const ub1
*idx
= (const unsigned char *)strchr((const char *)start
, '/');
1349 else if(idx
== start
){
1355 strncpy(tmp_buff
, (const char *)filename
, (idx
- filename
));
1356 tmp_buff
[(idx
- filename
)] = '\0';
1359 printf("checking the existance of %s\n", tmp_buff
);
1362 if(stat(tmp_buff
, &sbuf
) < 0){
1363 if(errno
!= ENOENT
){
1368 } else if(S_ISDIR(sbuf
.st_mode
)){
1370 printf("Directory exists\n");
1374 fprintf(stderr
, "Hmmm.. %s exists but isn't a directory!\n",
1380 printf("Making directory..\n");
1382 if(mkdir(tmp_buff
, 0755) < 0){
1386 if(verbose
&& handle
)
1387 printf("%10s: %s/\n", "created", tmp_buff
);
1391 /* only a directory */
1392 if(strlen((const char *)start
) == 0)
1396 printf("Leftovers are \"%s\" (%d)\n", start
, strlen((const char *)start
));
1399 /* If the entry was just a directory, don't write to file, etc */
1400 if(strlen((const char *)start
) == 0)
1406 if(f_fd
!= -1 && handle
){
1407 f_fd
= creat((const char *)filename
, 00644);
1410 fprintf(stderr
, "Error extracting JAR archive!\n");
1411 perror((const char *)filename
);
1416 if(method
!= 8 && flags
& 0x0008){
1417 fprintf(stderr
, "Error in JAR file! (not compressed but data desc.)\n");
1421 if(method
== 8 || flags
& 0x0008){
1423 lseek(fd
, eflen
, SEEK_CUR
);
1425 consume(&pbf
, eflen
);
1427 inflate_file(&pbf
, f_fd
, &ze
);
1431 printf("writing stored data.. (%d bytes)\n", csize
);
1437 ze
.crc
= crc32(ze
.crc
, NULL
, 0); /* initialize the crc */
1439 while(out_a
< csize
){
1440 rdamt
= (in_a
> RDSZ
? RDSZ
: in_a
);
1441 if(pb_read(&pbf
, rd_buff
, rdamt
) != rdamt
){
1446 ze
.crc
= crc32(ze
.crc
, (Bytef
*)rd_buff
, rdamt
);
1449 write(f_fd
, rd_buff
, rdamt
);
1455 printf("%d bytes written\n", out_a
);
1460 lseek(fd
, eflen
, SEEK_CUR
);
1462 consume(&pbf
, eflen
);
1465 /* if there is a data descriptor left, compare the CRC */
1468 if(pb_read(&pbf
, scratch
, 16) != 16){
1473 signature
= UNPACK_UB4(scratch
, 0);
1475 if(signature
!= 0x08074b50){
1476 fprintf(stderr
, "Error! Missing data descriptor!\n");
1480 crc
= UNPACK_UB4(scratch
, 4);
1485 fprintf(stderr
, "Error! CRCs do not match! Got %x, expected %x\n",
1492 if(verbose
&& dir
== FALSE
&& handle
)
1493 printf("%10s: %s\n",
1494 (method
== 8 ? "inflated" : "extracted"),
1501 int list_jar(int fd
, char **files
, int file_num
){
1514 ub1
*filename
= NULL
;
1517 int filename_len
= 0;
1522 char ascii_date
[30];
1526 printf("Listing jar file, looking for %d files\n", file_num
);
1529 /* This should be the start of the central-header-end section */
1531 if(lseek(fd
, -22, SEEK_END
) == (off_t
)-1){
1536 if(read(fd
, &tmp
, sizeof(ub4
)) != 4){
1541 #ifdef WORDS_BIGENDIAN
1545 if(tmp
!= 0x06054b50){
1546 fprintf(stderr
, "Error in JAR file format. zip-style comment?\n");
1550 if(lseek(fd
, 6, SEEK_CUR
) == (off_t
)-1){
1555 if(read(fd
, &cen_size
, 2) != 2){
1560 #ifdef WORDS_BIGENDIAN
1561 cen_size
= L2BS(cen_size
);
1564 /* printf("%hu entries in central header\n", cen_size); */
1566 if(lseek(fd
, 4, SEEK_CUR
) == (off_t
)-1){
1571 if(read(fd
, &tmp
, 4) != 4){
1576 #ifdef WORDS_BIGENDIAN
1580 /* printf("Central header offset = %d\n", tmp); */
1582 if(lseek(fd
, tmp
, SEEK_SET
) != tmp
){
1587 /* Loop through the entries in the central header */
1588 for(i
= 0; i
< cen_size
; i
++){
1590 if(read(fd
, &cen_header
, 46) != 46){
1595 signature
= UNPACK_UB4(cen_header
, 0);
1596 if(signature
!= 0x02014b50){
1597 fprintf(stderr
, "Error in JAR file! Cannot locate central header!\n");
1601 usize
= UNPACK_UB4(cen_header
, CEN_USIZE
);
1602 fnlen
= UNPACK_UB2(cen_header
, CEN_FNLEN
);
1603 eflen
= UNPACK_UB2(cen_header
, CEN_EFLEN
);
1604 clen
= UNPACK_UB2(cen_header
, CEN_COMLEN
);
1606 /* If we're providing verbose output, we need to make an ASCII
1607 * formatted version of the date. */
1609 mdate
= UNPACK_UB4(cen_header
, CEN_MODTIME
);
1610 tdate
= dos2unixtime(mdate
);
1611 s_tm
= localtime(&tdate
);
1612 strftime(ascii_date
, 30, "%a %b %d %H:%M:%S %Z %Y", s_tm
);
1615 if(filename_len
< fnlen
){
1616 if(filename
!= NULL
)
1619 filename
= malloc(sizeof(ub1
) * (fnlen
+ 1));
1620 filename_len
= fnlen
+ 1;
1623 if(read(fd
, filename
, fnlen
) != fnlen
){
1627 filename
[fnlen
] = '\0';
1629 /* if the user specified a list of files on the command line,
1630 we'll only display those, otherwise we'll display everything */
1632 for(j
= 0; j
< file_num
; j
++)
1633 if(strcmp(files
[j
], (const char *)filename
) == 0){
1635 printf("%6d %s %s\n", usize
, ascii_date
, filename
);
1637 printf("%s\n", filename
);
1642 printf("%6d %s %s\n", usize
, ascii_date
, filename
);
1644 printf("%s\n", filename
);
1647 size
= eflen
+ clen
;
1649 if(lseek(fd
, size
, SEEK_CUR
) == (off_t
)-1){
1656 /* the file isn't seekable.. evil! */
1664 if((rdamt
= pb_read(&pbf
, scratch
, 4)) != 4){
1669 signature
= UNPACK_UB4(scratch
, 0);
1672 printf("signature is %x\n", signature
);
1675 if(signature
== 0x08074b50){
1677 printf("skipping data descriptor\n");
1679 pb_read(&pbf
, scratch
, 12);
1681 } else if(signature
== 0x02014b50){
1683 printf("Central header reached.. we're all done!\n");
1686 }else if(signature
!= 0x04034b50){
1688 printf("Ick! %#x\n", signature
);
1693 if((rdamt
= pb_read(&pbf
, (file_header
+ 4), 26)) != 26){
1698 csize
= UNPACK_UB4(file_header
, LOC_CSIZE
);
1700 printf("Compressed size is %u\n", csize
);
1703 fnlen
= UNPACK_UB2(file_header
, LOC_FNLEN
);
1705 printf("Filename length is %hu\n", fnlen
);
1708 eflen
= UNPACK_UB2(file_header
, LOC_EFLEN
);
1710 printf("Extra field length is %hu\n", eflen
);
1713 method
= UNPACK_UB2(file_header
, LOC_COMP
);
1715 printf("Compression method is %#hx\n", method
);
1718 flags
= UNPACK_UB2(file_header
, LOC_EXTRA
);
1720 printf("Flags are %#hx\n", flags
);
1723 usize
= UNPACK_UB4(file_header
, LOC_USIZE
);
1725 /* If we're providing verbose output, we need to make an ASCII
1726 * formatted version of the date. */
1728 mdate
= UNPACK_UB4(file_header
, LOC_MODTIME
);
1729 tdate
= dos2unixtime(mdate
);
1730 s_tm
= localtime(&tdate
);
1731 strftime(ascii_date
, 30, "%a %b %d %H:%M:%S %Z %Y", s_tm
);
1734 if(filename_len
< fnlen
){
1735 if(filename
!= NULL
)
1738 filename
= malloc(sizeof(ub1
) * (fnlen
+ 1));
1739 filename_len
= fnlen
+ 1;
1742 pb_read(&pbf
, filename
, fnlen
);
1743 filename
[fnlen
] = '\0';
1745 /* the header is at the end. In a JAR file, this means that the data
1746 happens to be compressed. We have no choice but to inflate the
1753 consume(&pbf
, size
);
1757 printf("inflating %s\n", filename
);
1759 inflate_file(&pbf
, -1, &ze
);
1763 printf("We're shit outta luck!\n");
1766 size
= csize
+ (eflen
> 0 ? eflen
: 0);
1770 printf("Skipping %ld bytes\n", (long)size
);
1773 consume(&pbf
, size
);
1775 /* print out the listing */
1777 for(j
= 0; j
< file_num
; j
++)
1778 if(strcmp(files
[j
], (const char *)filename
) == 0){
1780 printf("%6d %s %s\n", usize
, ascii_date
, filename
);
1782 printf("%s\n", filename
);
1787 printf("%6d %s %s\n", usize
, ascii_date
, filename
);
1789 printf("%s\n", filename
);
1796 int consume(pb_file
*pbf
, int amt
){
1797 int tc
= 0; /* total amount consumed */
1802 printf("Consuming %d bytes\n", amt
);
1806 rdamt
= pb_read(pbf
, buff
, ((amt
- tc
) < RDSZ
? (amt
- tc
) : RDSZ
));
1808 printf("got %d bytes\n", rdamt
);
1814 printf("%d bytes consumed\n", tc
);
1820 void usage(const char *filename
){
1822 Usage: %s {ctxuV}[vfm0ME@] [jar-file] [manifest-file] [-C dir] files ...\n\
1824 -c create new archive\n\
1825 -t list table of contents for archive\n\
1826 -x extract named (or all) files from archive\n\
1829 -u update existing archive\n\
1830 -V display version information\n\
1831 -v generate verbose output on standard output\n\
1832 -f specify archive file name\n\
1833 -m include manifest information from specified manifest file\n\
1834 -0 store only; use no ZIP compression\n\
1835 -M Do not create a manifest file for the entries\n\
1836 -C change to the specified directory and include the following file\n\
1837 -E don't include the files found in a directory\n\
1838 -@ Read names from stdin\n\
1841 If any file is a directory then it is processed recursively.\n\
1842 The manifest file name and the archive file name needs to be specified\n\
1843 in the same order the 'm' and 'f' flags are specified.\n\
1845 Example 1: to archive two class files into an archive called classes.jar: \n\
1846 jar cvf classes.jar Foo.class Bar.class \n\
1847 Example 2: use an existing manifest file 'mymanifest' and archive all the\n\
1848 files in the foo/ directory into 'classes.jar': \n\
1849 jar cvfm classes.jar mymanifest -C foo/ .\n\
1859 char *result
= (char*)malloc(strlen(s
) + 1);
1860 if (result
== (char*)0)