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.4 2000/12/28 21:47:37 robertl Exp $
23 Revision 1.4 2000/12/28 21:47:37 robertl
24 2000-12-28 Robert Lipe <robertl@sco.com>
26 * jartool.c (MAXPATHLEN): Provide if not defined.
28 Revision 1.3 2000/12/14 18:45:35 ghazi
31 * compress.c: Include stdlib.h and compress.h.
33 (report_str_error): Make static.
34 (ez_inflate_str): Delete unused variable. Add parens in if-stmt.
35 (hrd_inflate_str): Likewise.
37 * compress.h (init_compression, end_compression, init_inflation,
38 end_inflation): Prototype void arguments.
40 * dostime.c (rcsid): Delete.
42 * jargrep.c: Include ctype.h, stdlib.h, zlib.h and compress.h.
43 Make functions static. Cast ctype function argument to `unsigned
44 char'. Add parens in if-stmts. Constify.
45 (Usage): Change into a macro.
46 (jargrep): Remove unused parameter.
48 * jartool.c: Constify. Add parens in if-stmts. Align
49 signed/unsigned char pointers in functions calls using casts.
51 (list_jar): Fix printf format specifier.
52 (usage): Chop long string into bits. Reformat.
54 * pushback.c (rcsid): Delete.
56 Revision 1.2 2000/12/13 18:11:57 tromey
57 * jartool.c (extract_jar): Use strchr, not index.
59 Revision 1.1 2000/12/09 03:08:23 apbianco
60 2000-12-08 Alexandre Petit-Bianco <apbianco@cygnus.com>
64 Revision 1.5 2000/08/24 15:01:27 cory
65 Made certain that fastjar opened the jar file before trying to update it
68 Revision 1.4 2000/08/24 13:39:21 cory
69 Changed +'s to |'s in jartool.c to insure there was no confusion with sign
70 when byte swapping. Better safe than sorry.
72 Revision 1.3 2000/08/23 19:42:17 cory
73 Added support for more Unix platforms. The following code has been hacked
74 to work on AIX, Solaris, True 64, and HP-UX.
75 Added bigendian check. Probably works on most big and little endian platforms
78 Revision 1.2 1999/12/06 07:38:28 toast
79 fixed recursive archiving bug
81 Revision 1.1.1.1 1999/12/06 03:09:34 toast
86 Revision 1.22 1999/10/12 19:45:13 burnsbr
87 adding patch to fix compat problem
89 Revision 1.21 1999/05/10 09:15:49 burnsbr
90 fixed manifest file version info
92 Revision 1.20 1999/05/10 08:53:16 burnsbr
93 *** empty log message ***
95 Revision 1.19 1999/05/10 08:30:39 burnsbr
96 added extract / listing code
98 Revision 1.18 1999/04/28 04:24:29 burnsbr
101 Revision 1.17 1999/04/28 04:21:23 burnsbr
102 added support for -C dir-changing flag.. Updated total compression display
104 Revision 1.16 1999/04/27 10:28:22 burnsbr
105 updated version string
107 Revision 1.15 1999/04/27 10:04:06 burnsbr
110 Revision 1.14 1999/04/27 08:56:14 burnsbr
111 added -V flag, better error messages
113 Revision 1.13 1999/04/26 02:35:21 burnsbr
114 changed all sorts of stuff.. compression now works 100%
116 Revision 1.12 1999/04/23 12:00:45 burnsbr
117 90% done with compression code
119 Revision 1.11 1999/04/22 04:12:57 burnsbr
120 finished first round of Manifest file support..
121 might need to do more, digest etc..
123 Revision 1.10 1999/04/22 02:35:23 burnsbr
124 added more manifest support, about 75% done now. Replaced all the
125 redundant shifts and bit-logic with a macro or two, making the code
128 Revision 1.9 1999/04/21 09:55:16 burnsbr
131 Revision 1.8 1999/04/21 02:58:01 burnsbr
132 started manifest code
134 Revision 1.7 1999/04/20 23:15:28 burnsbr
135 added patch sent by John Bley <jbb6@acpub.duke.edu>
137 Revision 1.6 1999/04/20 08:56:02 burnsbr
140 Revision 1.5 1999/04/20 08:16:09 burnsbr
141 fixed verbose flag, did some optimization
143 Revision 1.4 1999/04/20 05:09:59 burnsbr
146 Revision 1.3 1999/04/20 05:08:54 burnsbr
164 #include <sys/stat.h>
165 #include <sys/types.h>
167 #ifdef HAVE_SYS_PARAM_H
168 #include <sys/param.h>
172 #define MAXPATHLEN 1024
186 #ifdef TM_IN_SYS_TIME
187 #include <sys/time.h>
195 #include "pushback.h"
196 #include "compress.h"
198 #ifdef WORDS_BIGENDIAN
200 #define L2BI(l) ((l & 0xff000000) >> 24) | \
201 ((l & 0x00ff0000) >> 8) | \
202 ((l & 0x0000ff00) << 8) | \
203 ((l & 0x000000ff) << 24);
205 #define L2BS(l) ((l & 0xff00) >> 8) | ((l & 0x00ff) << 8);
209 static char version_string[] = VERSION;
213 void usage(const char*);
214 void add_entry(struct zipentry *);
215 void init_headers(void);
217 int consume(pb_file *, int);
218 int list_jar(int, char**, int);
219 int extract_jar(int, char**, int);
220 int add_file_to_jar(int, int, const char*, struct stat*);
221 int add_to_jar(int, const char*, const char*);
222 int create_central_header(int);
223 int make_manifest(int, const char*);
224 static void init_args(char **, int);
225 static char *get_next_arg (void);
226 static char *jt_strdup (char*);
228 /* global variables */
230 ub1 data_descriptor[16];
236 /* If non zero, then don't recurse in directory. Instead, add the
237 directory entry and relie on an explicit list of files to populate
238 the archive. This option isn't supported by the original jar tool. */
239 int use_explicit_list_only;
241 /* If non zero, then read the entry names from stdin. This option
242 isn't supported by the original jar tool. */
243 int read_names_from_stdin;
245 zipentry *ziplist; /* linked list of entries */
246 zipentry *ziptail; /* tail of the linked list */
248 int number_of_entries; /* number of entries in the linked list */
250 int main(int argc, char **argv){
254 int action = ACTION_NONE;
256 int manifest_file = FALSE;
258 int file_first = FALSE;
268 number_of_entries = 0;
275 for(i = 0; i < j; i++){
278 action = ACTION_CREATE;
281 action = ACTION_LIST;
284 action = ACTION_EXTRACT;
287 action = ACTION_UPDATE;
293 printf("%s\n", version_string);
303 manifest_file = TRUE;
313 /* The following options aren't supported by the original jar tool. */
315 use_explicit_list_only = TRUE;
318 read_names_from_stdin = TRUE;
321 fprintf(stderr, "Illegal option: %c\n", argv[1][i]);
326 if(action == ACTION_NONE){
327 fprintf(stderr, "One of options -{ctxu} must be specified.\n");
331 /* Verify unsupported combinations and warn of the use of non
333 if(verbose && use_explicit_list_only)
334 fprintf (stderr, "Warning: using non standard '-E' option\n");
335 if(verbose && read_names_from_stdin)
336 fprintf (stderr, "Warning: using non standard '-@' option\n");
337 if(read_names_from_stdin
338 && (action != ACTION_CREATE && action != ACTION_UPDATE)){
339 fprintf(stderr, "Option '-@' is supported only with '-c' or '-u'.\n");
345 /* get the jarfile and manifest file (if any) */
346 if(file && file_first){
350 strncpy(jarfile, argv[i++], 256);
356 strncpy(mfile, argv[i++], 256);
359 if(file && !file_first){
363 strncpy(jarfile, argv[i++], 256);
366 /* create the jarfile */
367 if(action == ACTION_CREATE){
369 jarfd = creat(jarfile, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
372 fprintf(stderr, "Error opening %s for writing!\n", jarfile);
377 /* We assume that the file is seekable */
382 jarfd = STDOUT_FILENO; /* jarfd is stdout otherwise */
384 /* standard out is not seekable */
387 /* don't want our output to be part of the jar file.. figured this one
388 out the hard way.. =P */
391 } else if(action == ACTION_LIST || action == ACTION_EXTRACT){
394 jarfd = open(jarfile, O_RDONLY);
397 fprintf(stderr, "Error opening %s for reading!\n", jarfile);
404 jarfd = STDIN_FILENO; /* jarfd is standard in */
406 /* we assume that the stream isn't seekable for safety */
411 if(action == ACTION_CREATE || action == ACTION_UPDATE){
415 if((action == ACTION_UPDATE) && file) {
416 if((jarfd = open(jarfile, O_RDWR)) < 0) {
417 fprintf(stderr, "Error opening %s for reading!\n", jarfile);
427 /* Add the META-INF/ directory and the manifest */
428 if(manifest && manifest_file)
429 make_manifest(jarfd, mfile);
431 make_manifest(jarfd, NULL);
434 /* now we add the files to the archive */
435 while ((arg = get_next_arg ())){
437 if(!strcmp(arg, "-C")){
438 const char *dir_to_change = get_next_arg ();
439 const char *file_to_add = get_next_arg ();
442 || add_to_jar(jarfd, dir_to_change, file_to_add)){
443 printf("Error adding %s to jar archive!\n", arg);
447 if(add_to_jar(jarfd, NULL, arg)){
448 printf("Error adding %s to jar archive!\n", arg);
453 /* de-initialize the compression DS */
457 create_central_header(jarfd);
459 if (close(jarfd) != 0) {
460 fprintf(stderr, "Error closing jar archive!\n");
462 } else if(action == ACTION_LIST){
463 list_jar(jarfd, &argv[i], (argc - i));
464 } else if(action == ACTION_EXTRACT){
465 extract_jar(jarfd, &argv[i], (argc - i));
471 static int args_current_g;
472 static char **args_g;
475 init_args(args, current)
479 if(!read_names_from_stdin)
482 args_current_g = current;
489 static int reached_end = 0;
496 if (!args_g [args_current_g])
501 return args_g [args_current_g++];
505 /* Read the name from stdin. Delimiters are '\n' and
506 '\r'. Reading EOF indicates that we don't have anymore file
507 names characters to read. */
512 /* Get rid of '\n' and '\r' first. */
515 int c = getc (stdin);
516 if (c == '\n' || c == '\r')
529 int c = getc (stdin);
530 /* Exit when we get a delimiter or don't have any characters
532 if (c == '\n'|| c == '\r'|| c == EOF)
534 s [pos++] = (char) c;
540 return jt_strdup (s);
548 /* packing file header */
550 file_header[0] = 0x50;
551 file_header[1] = 0x4b;
552 file_header[2] = 0x03;
553 file_header[3] = 0x04;
554 /* version number (Unix 1.0)*/
557 /* bit flag (normal deflation)*/
558 file_header[6] = 0x00;
560 file_header[7] = 0x00;
561 /* do_compression method (deflation) */
565 /* last mod file time (MS-DOS format) */
568 /* last mod file date (MS-DOS format) */
576 /* compressed size */
581 /* uncompressed size */
586 /* filename length */
589 /* extra field length */
593 /* Initialize the compression DS */
594 PACK_UB4(data_descriptor, 0, 0x08074b50);
598 void add_entry(struct zipentry *ze){
604 ziplist->next_entry = ze;
611 int make_manifest(int jfd, const char *mf_name){
613 int nlen; /* length of file name */
614 int mod_time; /* file modification time */
617 nlen = 9; /* trust me on this one */
619 memset((file_header + 12), '\0', 16); /*clear mod time, crc, size fields*/
621 current_time = time(NULL);
622 if(current_time == (time_t)-1){
627 mod_time = unix2dostime(¤t_time);
629 PACK_UB2(file_header, LOC_EXTRA, 0);
630 PACK_UB2(file_header, LOC_COMP, 0);
631 PACK_UB2(file_header, LOC_FNLEN, nlen);
632 PACK_UB4(file_header, LOC_MODTIME, mod_time);
635 printf("adding: META-INF/ (in=0) (out=0) (stored 0%%)\n");
637 ze = (zipentry*)malloc(sizeof(zipentry));
643 memset(ze, 0, sizeof(zipentry)); /* clear all the fields*/
644 ze->filename = (char*)malloc((nlen + 1) * sizeof(char) + 1);
645 strcpy(ze->filename, "META-INF/");
646 ze->filename[nlen] = '\0';
648 ze->offset = lseek(jfd, 0, SEEK_CUR);
649 ze->mod_time = (ub2)(mod_time & 0x0000ffff);
650 ze->mod_date = (ub2)((mod_time & 0xffff0000) >> 16);
651 ze->compressed = FALSE;
655 write(jfd, file_header, 30);
656 write(jfd, "META-INF/", nlen);
658 /* if the user didn't specify an external manifest file... */
660 int mf_len = 37 + strlen(VERSION);
663 if((mf = (char *) malloc(mf_len + 1))) {
666 sprintf(mf, "Manifest-Version: 1.0\nCreated-By: %s\n\n", VERSION);
668 crc = crc32(0L, Z_NULL, 0);
670 crc = crc32(crc, (const unsigned char *)mf, mf_len);
672 nlen = 20; /* once again, trust me */
674 PACK_UB2(file_header, LOC_EXTRA, 0);
675 PACK_UB2(file_header, LOC_COMP, 0);
676 PACK_UB2(file_header, LOC_FNLEN, nlen);
677 PACK_UB4(file_header, LOC_USIZE, mf_len);
679 memcpy((file_header + LOC_CSIZE), (file_header + LOC_USIZE), 4);
681 PACK_UB4(file_header, LOC_CRC, crc);
684 printf("adding: META-INF/MANIFEST.MF (in=56) (out=56) (stored 0%%)\n");
686 ze = (zipentry*)malloc(sizeof(zipentry));
692 memset(ze, 0, sizeof(zipentry)); /* clear all the fields*/
693 ze->filename = (char*)malloc((nlen + 1) * sizeof(char) + 1);
694 strcpy(ze->filename, "META-INF/MANIFEST.MF");
695 ze->filename[nlen] = '\0';
697 ze->offset = lseek(jfd, 0, SEEK_CUR);
698 ze->mod_time = (ub2)(mod_time & 0x0000ffff);
699 ze->mod_date = (ub2)((mod_time & 0xffff0000) >> 16);
702 ze->usize = ze->csize;
703 ze->compressed = FALSE;
707 write(jfd, file_header, 30);
708 write(jfd, "META-INF/MANIFEST.MF", nlen);
709 write(jfd, mf, mf_len);
713 printf("malloc errror\n");
720 stat(mf_name, &statbuf);
722 if(!S_ISREG(statbuf.st_mode)){
723 fprintf(stderr, "Invalid manifest file specified.\n");
727 mfd = open(mf_name, O_RDONLY);
730 fprintf(stderr, "Error opening %s.\n", mf_name);
734 if(add_file_to_jar(jfd, mfd, "META-INF/MANIFEST.MF", &statbuf)){
735 perror("error writing to jar");
744 int add_to_jar(int fd, const char *new_dir, const char *file){
750 char *old_dir = NULL;
752 /* This is a quick compatibility fix -- Simon Weijgers <simon@weijgers.com>
754 * "normal" jar : org/apache/java/io/LogRecord.class
755 * fastjar : ./org/apache/java/io/LogRecord.class
756 * Fastjar's preservation of the ./'s makes the jarfile unusuable for use
757 * with both kaffe-1.0b4 and JDK.
759 while (*file=='.' && *(file+1)=='/')
762 /* If new_dir isn't null, we need to change to that directory. However,
763 we also need to return to the old directory when we're done */
765 old_dir = getcwd(NULL, 0);
767 if(chdir(new_dir) == -1){
773 if(!strcmp(file, jarfile)){
775 printf("skipping: %s\n", file);
776 return 0; /* we don't want to add ourselves.. */
779 stat_return = stat(file, &statbuf);
781 if(stat_return == -1){
783 } else if(S_ISDIR(statbuf.st_mode)){
787 unsigned long mod_time;
796 nlen = strlen(file) + 256;
797 fullname = (char*)malloc(nlen * sizeof(char));
798 memset(fullname, 0, (nlen * sizeof(char)));
800 if(fullname == NULL){
801 fprintf(stderr, "Filename is NULL!\n");
805 strcpy(fullname, file);
808 if(fullname[nlen - 1] != '/'){
809 fullname[nlen] = '/';
810 t_ptr = (fullname + nlen + 1);
812 t_ptr = (fullname + nlen);
815 memset((file_header + 12), '\0', 16); /*clear mod time, crc, size fields*/
817 nlen = (t_ptr - fullname);
819 mod_time = unix2dostime(&statbuf.st_mtime);
821 PACK_UB2(file_header, LOC_EXTRA, 0);
822 PACK_UB2(file_header, LOC_COMP, 0);
823 PACK_UB2(file_header, LOC_FNLEN, nlen);
824 PACK_UB4(file_header, LOC_MODTIME, mod_time);
827 printf("adding: %s (in=%d) (out=%d) (stored 0%%)\n", fullname, 0, 0);
829 ze = (zipentry*)malloc(sizeof(zipentry));
835 memset(ze, 0, sizeof(zipentry)); /* clear all the fields*/
836 ze->filename = (char*)malloc((nlen + 1) * sizeof(char) + 1);
837 strcpy(ze->filename, fullname);
838 ze->filename[nlen] = '\0';
840 ze->offset = lseek(fd, 0, SEEK_CUR);
841 ze->mod_time = (ub2)(mod_time & 0x0000ffff);
842 ze->mod_date = (ub2)((mod_time & 0xffff0000) >> 16);
843 ze->compressed = FALSE;
847 write(fd, file_header, 30);
848 write(fd, fullname, nlen);
850 while(!use_explicit_list_only && (de = readdir(dir)) != NULL){
851 if(de->d_name[0] == '.')
853 if(!strcmp(de->d_name, jarfile)){ /* we don't want to add ourselves. Believe me */
855 printf("skipping: %s\n", de->d_name);
859 strcpy(t_ptr, de->d_name);
861 if(add_to_jar(fd, NULL, fullname)){
862 fprintf(stderr, "Error adding file to jar!\n");
870 } else if(S_ISREG(statbuf.st_mode)){
873 add_fd = open(file, O_RDONLY);
875 fprintf(stderr, "Error opening %s.\n", file);
879 if(add_file_to_jar(fd, add_fd, file, &statbuf)){
880 fprintf(stderr, "Error adding file to jar!\n");
885 fprintf(stderr, "Illegal file specified: %s\n", file);
898 int add_file_to_jar(int jfd, int ffd, const char *fname, struct stat *statbuf){
900 unsigned short file_name_length;
901 unsigned long mod_time;
908 mod_time = unix2dostime(&(statbuf->st_mtime));
909 file_name_length = strlen(fname);
911 if(!seekable && !do_compress){
912 crc = crc32(0L, Z_NULL, 0);
914 while((rdamt = read(ffd, rd_buff, RDSZ)) != 0)
915 crc = crc32(crc, rd_buff, rdamt);
917 lseek(ffd, 0, SEEK_SET);
920 /* data descriptor */
921 if(!seekable && do_compress){
922 PACK_UB2(file_header, LOC_EXTRA, 8);
924 PACK_UB2(file_header, LOC_EXTRA, 0);
928 PACK_UB2(file_header, LOC_COMP, 8);
930 PACK_UB2(file_header, LOC_COMP, 0);
933 PACK_UB4(file_header, LOC_MODTIME, mod_time);
934 PACK_UB2(file_header, LOC_FNLEN, file_name_length);
936 if(!seekable && !do_compress){
937 PACK_UB4(file_header, LOC_CRC, crc);
938 PACK_UB4(file_header, LOC_USIZE, statbuf->st_size);
939 PACK_UB4(file_header, LOC_CSIZE, statbuf->st_size);
941 memset((file_header + LOC_CRC), '\0', 12); /* clear crc/usize/csize */
943 ze = (zipentry*)malloc(sizeof(zipentry));
949 memset(ze, 0, sizeof(zipentry)); /* clear all the fields*/
950 ze->filename = (char*)malloc((file_name_length + 1) * sizeof(char));
951 strcpy(ze->filename, fname);
953 ze->mod_time = (ub2)(mod_time & 0x0000ffff);
954 ze->mod_date = (ub2)((mod_time & 0xffff0000) >> 16);
956 if(!seekable && !do_compress)
959 ze->csize = statbuf->st_size;
960 ze->usize = ze->csize;
961 ze->offset = lseek(jfd, 0, SEEK_CUR);
963 ze->compressed = TRUE;
965 ze->compressed = FALSE;
969 /* Write the local header */
970 write(jfd, file_header, 30);
972 /* write the file name to the zip file */
973 write(jfd, fname, file_name_length);
977 printf("adding: %s ", fname);
982 /* compress the file */
983 compress_file(ffd, jfd, ze);
985 /* Write the contents of the file (uncompressed) to the zip file */
986 /* calculate the CRC as we go along */
987 ze->crc = crc32(0L, Z_NULL, 0);
989 while((rdamt = read(ffd, rd_buff, RDSZ)) != 0){
990 ze->crc = crc32(ze->crc, rd_buff, rdamt);
991 if(write(jfd, rd_buff, rdamt) != rdamt){
999 /* write out data descriptor */
1000 PACK_UB4(data_descriptor, 4, ze->crc);
1001 PACK_UB4(data_descriptor, 8, ze->csize);
1002 PACK_UB4(data_descriptor, 12, ze->usize);
1004 /* we need to seek back and fill the header */
1006 offset = (ze->csize + strlen(ze->filename) + 16);
1008 if(lseek(jfd, -offset, SEEK_CUR) == (off_t)-1){
1013 if(write(jfd, (data_descriptor + 4), 12) != 12){
1020 if(lseek(jfd, offset, SEEK_CUR) == (off_t)-1){
1024 } else if(do_compress){
1025 /* Sun's jar tool will only allow a data descriptor if the entry is
1026 compressed, but we'll save 16 bytes/entry if we only use it when
1027 we can't seek back on the file */
1029 if(write(jfd, data_descriptor, 16) != 16){
1036 printf("(in=%d) (out=%d) (%s %d%%)\n",
1037 (int)ze->usize, (int)ze->csize,
1038 (do_compress ? "deflated" : "stored"),
1039 (do_compress ? ((int)((1 - ze->csize/(float)ze->usize) * 100)) : 0));
1044 int create_central_header(int fd){
1050 int total_in = 0, total_out = 22;
1054 iheader = (int*)header;
1061 /* version made by */
1064 /* version needed to extract */
1070 /* compression method */
1084 /* compressed size */
1089 /* uncompressed size */
1094 /* filename length */
1097 /* extra field length */
1100 /* file comment length */
1103 /* disk number start */
1106 /* internal file attribs */
1109 /* external file attribs */
1114 /* relative offset of local header */
1120 start_offset = lseek(fd, 0, SEEK_CUR);
1122 for(ze = ziptail; ze != NULL; ze = ze->next_entry){
1124 total_in += ze->usize;
1125 total_out += ze->csize + 76 + strlen(ze->filename) * 2;
1128 PACK_UB2(header, CEN_COMP, 8);
1130 PACK_UB2(header, CEN_COMP, 0);
1133 PACK_UB2(header, CEN_MODTIME, ze->mod_time);
1134 PACK_UB2(header, CEN_MODDATE, ze->mod_date);
1135 PACK_UB4(header, CEN_CRC, ze->crc);
1136 PACK_UB4(header, CEN_CSIZE, ze->csize);
1137 PACK_UB4(header, CEN_USIZE, ze->usize);
1138 PACK_UB2(header, CEN_FNLEN, strlen(ze->filename));
1139 PACK_UB4(header, CEN_OFFSET, ze->offset);
1141 write(fd, header, 46);
1143 write(fd, ze->filename, strlen(ze->filename));
1146 dir_size = lseek(fd, 0, SEEK_CUR) - start_offset;
1149 end_header[0] = 0x50;
1150 end_header[1] = 0x4b;
1151 end_header[2] = 0x05;
1152 end_header[3] = 0x06;
1153 /* number of this disk */
1156 /* number of disk w/ start of central header */
1159 /* total number of entries in central dir on this disk*/
1160 PACK_UB2(end_header, 8, number_of_entries);
1161 /* total number of entries in central dir*/
1162 PACK_UB2(end_header, 10, number_of_entries);
1163 /* size of central dir. */
1164 PACK_UB4(end_header, 12, dir_size);
1165 /* offset of start of central dir */
1166 PACK_UB4(end_header, 16, start_offset);
1167 /* zipfile comment length */
1171 write(fd, end_header, 22);
1174 printf("Total:\n------\n(in = %d) (out = %d) (%s %d%%)\n",
1177 (do_compress ? "deflated" : "stored"),
1178 (int)((1 - (total_out / (float)total_in)) * 100)
1184 int extract_jar(int fd, char **files, int file_num){
1194 ub1 *filename = NULL;
1195 int filename_len = 0;
1214 dir = FALSE; /* by default, the file isn't a dir */
1215 handle = TRUE; /* by default we'll extract/create the file */
1217 if((rdamt = pb_read(&pbf, scratch, 4)) != 4){
1222 signature = UNPACK_UB4(scratch, 0);
1225 printf("signature is %x\n", signature);
1227 if(signature == 0x08074b50){
1229 printf("skipping data descriptor\n");
1231 pb_read(&pbf, scratch, 12);
1233 } else if(signature == 0x02014b50){
1235 printf("Central header reached.. we're all done!\n");
1238 }else if(signature != 0x04034b50){
1239 printf("Ick! %#x\n", signature);
1243 if((rdamt = pb_read(&pbf, (file_header + 4), 26)) != 26){
1248 csize = UNPACK_UB4(file_header, LOC_CSIZE);
1250 printf("Compressed size is %u\n", csize);
1253 fnlen = UNPACK_UB2(file_header, LOC_FNLEN);
1255 printf("Filename length is %hu\n", fnlen);
1258 eflen = UNPACK_UB2(file_header, LOC_EFLEN);
1260 printf("Extra field length is %hu\n", eflen);
1263 flags = UNPACK_UB2(file_header, LOC_EXTRA);
1265 printf("Flags are %#hx\n", flags);
1268 method = UNPACK_UB2(file_header, LOC_COMP);
1270 printf("Compression method is %#hx\n", method);
1273 /* if there isn't a data descriptor */
1274 if(!(flags & 0x0008)){
1275 crc = UNPACK_UB4(file_header, LOC_CRC);
1277 printf("CRC is %x\n", crc);
1281 if(filename_len < fnlen){
1282 if(filename != NULL)
1285 filename = malloc(sizeof(ub1) * (fnlen + 1));
1286 filename_len = fnlen + 1;
1289 pb_read(&pbf, filename, fnlen);
1290 filename[fnlen] = '\0';
1293 printf("filename is %s\n", filename);
1299 for(j = 0; j < file_num; j++)
1300 if(strcmp(files[j], (const char *)filename) == 0){
1309 /* OK, there is some directory information in the file. Nothing to do
1310 but ensure the directory(s) exist, and create them if they don't.
1312 if(strchr((const char *)filename, '/') != NULL && handle){
1313 /* Loop through all the directories in the path, (everything w/ a '/') */
1314 const ub1 *start = filename;
1318 tmp_buff = malloc(sizeof(char) * strlen((const char *)filename));
1321 const ub1 *idx = (const unsigned char *)strchr((const char *)start, '/');
1325 else if(idx == start){
1331 strncpy(tmp_buff, (const char *)filename, (idx - filename));
1332 tmp_buff[(idx - filename)] = '\0';
1335 printf("checking the existance of %s\n", tmp_buff);
1338 if(stat(tmp_buff, &sbuf) < 0){
1339 if(errno != ENOENT){
1344 } else if(S_ISDIR(sbuf.st_mode)){
1346 printf("Directory exists\n");
1350 fprintf(stderr, "Hmmm.. %s exists but isn't a directory!\n",
1356 printf("Making directory..\n");
1358 if(mkdir(tmp_buff, 0755) < 0){
1362 if(verbose && handle)
1363 printf("%10s: %s/\n", "created", tmp_buff);
1367 /* only a directory */
1368 if(strlen((const char *)start) == 0)
1372 printf("Leftovers are \"%s\" (%d)\n", start, strlen((const char *)start));
1375 /* If the entry was just a directory, don't write to file, etc */
1376 if(strlen((const char *)start) == 0)
1382 if(f_fd != -1 && handle){
1383 f_fd = creat((const char *)filename, 00644);
1386 fprintf(stderr, "Error extracting JAR archive!\n");
1387 perror((const char *)filename);
1392 if(method != 8 && flags & 0x0008){
1393 fprintf(stderr, "Error in JAR file! (not compressed but data desc.)\n");
1397 if(method == 8 || flags & 0x0008){
1399 lseek(fd, eflen, SEEK_CUR);
1401 consume(&pbf, eflen);
1403 inflate_file(&pbf, f_fd, &ze);
1407 printf("writing stored data.. (%d bytes)\n", csize);
1413 ze.crc = crc32(ze.crc, NULL, 0); /* initialize the crc */
1415 while(out_a < csize){
1416 rdamt = (in_a > RDSZ ? RDSZ : in_a);
1417 if(pb_read(&pbf, rd_buff, rdamt) != rdamt){
1422 ze.crc = crc32(ze.crc, (Bytef*)rd_buff, rdamt);
1425 write(f_fd, rd_buff, rdamt);
1431 printf("%d bytes written\n", out_a);
1436 lseek(fd, eflen, SEEK_CUR);
1438 consume(&pbf, eflen);
1441 /* if there is a data descriptor left, compare the CRC */
1444 if(pb_read(&pbf, scratch, 16) != 16){
1449 signature = UNPACK_UB4(scratch, 0);
1451 if(signature != 0x08074b50){
1452 fprintf(stderr, "Error! Missing data descriptor!\n");
1456 crc = UNPACK_UB4(scratch, 4);
1461 fprintf(stderr, "Error! CRCs do not match! Got %x, expected %x\n",
1468 if(verbose && dir == FALSE && handle)
1469 printf("%10s: %s\n",
1470 (method == 8 ? "inflated" : "extracted"),
1477 int list_jar(int fd, char **files, int file_num){
1490 ub1 *filename = NULL;
1493 int filename_len = 0;
1498 char ascii_date[30];
1502 printf("Listing jar file, looking for %d files\n", file_num);
1505 /* This should be the start of the central-header-end section */
1507 if(lseek(fd, -22, SEEK_END) == (off_t)-1){
1512 if(read(fd, &tmp, sizeof(ub4)) != 4){
1517 #ifdef WORDS_BIGENDIAN
1521 if(tmp != 0x06054b50){
1522 fprintf(stderr, "Error in JAR file format. zip-style comment?\n");
1526 if(lseek(fd, 6, SEEK_CUR) == (off_t)-1){
1531 if(read(fd, &cen_size, 2) != 2){
1536 #ifdef WORDS_BIGENDIAN
1537 cen_size = L2BS(cen_size);
1540 /* printf("%hu entries in central header\n", cen_size); */
1542 if(lseek(fd, 4, SEEK_CUR) == (off_t)-1){
1547 if(read(fd, &tmp, 4) != 4){
1552 #ifdef WORDS_BIGENDIAN
1556 /* printf("Central header offset = %d\n", tmp); */
1558 if(lseek(fd, tmp, SEEK_SET) != tmp){
1563 /* Loop through the entries in the central header */
1564 for(i = 0; i < cen_size; i++){
1566 if(read(fd, &cen_header, 46) != 46){
1571 signature = UNPACK_UB4(cen_header, 0);
1572 if(signature != 0x02014b50){
1573 fprintf(stderr, "Error in JAR file! Cannot locate central header!\n");
1577 usize = UNPACK_UB4(cen_header, CEN_USIZE);
1578 fnlen = UNPACK_UB2(cen_header, CEN_FNLEN);
1579 eflen = UNPACK_UB2(cen_header, CEN_EFLEN);
1580 clen = UNPACK_UB2(cen_header, CEN_COMLEN);
1582 /* If we're providing verbose output, we need to make an ASCII
1583 * formatted version of the date. */
1585 mdate = UNPACK_UB4(cen_header, CEN_MODTIME);
1586 tdate = dos2unixtime(mdate);
1587 s_tm = localtime(&tdate);
1588 strftime(ascii_date, 30, "%a %b %d %H:%M:%S %Z %Y", s_tm);
1591 if(filename_len < fnlen){
1592 if(filename != NULL)
1595 filename = malloc(sizeof(ub1) * (fnlen + 1));
1596 filename_len = fnlen + 1;
1599 if(read(fd, filename, fnlen) != fnlen){
1603 filename[fnlen] = '\0';
1605 /* if the user specified a list of files on the command line,
1606 we'll only display those, otherwise we'll display everything */
1608 for(j = 0; j < file_num; j++)
1609 if(strcmp(files[j], (const char *)filename) == 0){
1611 printf("%6d %s %s\n", usize, ascii_date, filename);
1613 printf("%s\n", filename);
1618 printf("%6d %s %s\n", usize, ascii_date, filename);
1620 printf("%s\n", filename);
1623 size = eflen + clen;
1625 if(lseek(fd, size, SEEK_CUR) == (off_t)-1){
1632 /* the file isn't seekable.. evil! */
1640 if((rdamt = pb_read(&pbf, scratch, 4)) != 4){
1645 signature = UNPACK_UB4(scratch, 0);
1648 printf("signature is %x\n", signature);
1651 if(signature == 0x08074b50){
1653 printf("skipping data descriptor\n");
1655 pb_read(&pbf, scratch, 12);
1657 } else if(signature == 0x02014b50){
1659 printf("Central header reached.. we're all done!\n");
1662 }else if(signature != 0x04034b50){
1664 printf("Ick! %#x\n", signature);
1669 if((rdamt = pb_read(&pbf, (file_header + 4), 26)) != 26){
1674 csize = UNPACK_UB4(file_header, LOC_CSIZE);
1676 printf("Compressed size is %u\n", csize);
1679 fnlen = UNPACK_UB2(file_header, LOC_FNLEN);
1681 printf("Filename length is %hu\n", fnlen);
1684 eflen = UNPACK_UB2(file_header, LOC_EFLEN);
1686 printf("Extra field length is %hu\n", eflen);
1689 method = UNPACK_UB2(file_header, LOC_COMP);
1691 printf("Compression method is %#hx\n", method);
1694 flags = UNPACK_UB2(file_header, LOC_EXTRA);
1696 printf("Flags are %#hx\n", flags);
1699 usize = UNPACK_UB4(file_header, LOC_USIZE);
1701 /* If we're providing verbose output, we need to make an ASCII
1702 * formatted version of the date. */
1704 mdate = UNPACK_UB4(file_header, LOC_MODTIME);
1705 tdate = dos2unixtime(mdate);
1706 s_tm = localtime(&tdate);
1707 strftime(ascii_date, 30, "%a %b %d %H:%M:%S %Z %Y", s_tm);
1710 if(filename_len < fnlen){
1711 if(filename != NULL)
1714 filename = malloc(sizeof(ub1) * (fnlen + 1));
1715 filename_len = fnlen + 1;
1718 pb_read(&pbf, filename, fnlen);
1719 filename[fnlen] = '\0';
1721 /* the header is at the end. In a JAR file, this means that the data
1722 happens to be compressed. We have no choice but to inflate the
1729 consume(&pbf, size);
1733 printf("inflating %s\n", filename);
1735 inflate_file(&pbf, -1, &ze);
1739 printf("We're shit outta luck!\n");
1742 size = csize + (eflen > 0 ? eflen : 0);
1746 printf("Skipping %ld bytes\n", (long)size);
1749 consume(&pbf, size);
1751 /* print out the listing */
1753 for(j = 0; j < file_num; j++)
1754 if(strcmp(files[j], (const char *)filename) == 0){
1756 printf("%6d %s %s\n", usize, ascii_date, filename);
1758 printf("%s\n", filename);
1763 printf("%6d %s %s\n", usize, ascii_date, filename);
1765 printf("%s\n", filename);
1772 int consume(pb_file *pbf, int amt){
1773 int tc = 0; /* total amount consumed */
1778 printf("Consuming %d bytes\n", amt);
1782 rdamt = pb_read(pbf, buff, ((amt - tc) < RDSZ ? (amt - tc) : RDSZ));
1784 printf("got %d bytes\n", rdamt);
1790 printf("%d bytes consumed\n", tc);
1796 void usage(const char *filename){
1798 Usage: %s {ctxuV}[vfm0ME@] [jar-file] [manifest-file] [-C dir] files ...\n\
1800 -c create new archive\n\
1801 -t list table of contents for archive\n\
1802 -x extract named (or all) files from archive\n\
1805 -u update existing archive\n\
1806 -V display version information\n\
1807 -v generate verbose output on standard output\n\
1808 -f specify archive file name\n\
1809 -m include manifest information from specified manifest file\n\
1810 -0 store only; use no ZIP compression\n\
1811 -M Do not create a manifest file for the entries\n\
1812 -C change to the specified directory and include the following file\n\
1813 -E don't include the files found in a directory\n\
1814 -@ Read names from stdin\n\
1817 If any file is a directory then it is processed recursively.\n\
1818 The manifest file name and the archive file name needs to be specified\n\
1819 in the same order the 'm' and 'f' flags are specified.\n\
1821 Example 1: to archive two class files into an archive called classes.jar: \n\
1822 jar cvf classes.jar Foo.class Bar.class \n\
1823 Example 2: use an existing manifest file 'mymanifest' and archive all the\n\
1824 files in the foo/ directory into 'classes.jar': \n\
1825 jar cvfm classes.jar mymanifest -C foo/ .\n\
1835 char *result = (char*)malloc(strlen(s) + 1);
1836 if (result == (char*)0)