diff options
author | Alexandre Petit-Bianco <apbianco@cygnus.com> | 2000-12-09 03:08:23 +0000 |
---|---|---|
committer | Alexandre Petit-Bianco <apbianco@gcc.gnu.org> | 2000-12-08 19:08:23 -0800 |
commit | bd8757b313518d151d28fff18f4d7958b5cb7ba3 (patch) | |
tree | 87ea917f50267296afe6acf62ccf01cba566fa09 /fastjar/jartool.c | |
parent | 81522a1f8693a519afc801ca60cb390320791e5f (diff) | |
download | gcc-bd8757b313518d151d28fff18f4d7958b5cb7ba3.tar.gz |
fastjar: Imported.
2000-12-08 Alexandre Petit-Bianco <apbianco@cygnus.com>
* fastjar: Imported.
From-SVN: r38145
Diffstat (limited to 'fastjar/jartool.c')
-rw-r--r-- | fastjar/jartool.c | 1757 |
1 files changed, 1757 insertions, 0 deletions
diff --git a/fastjar/jartool.c b/fastjar/jartool.c new file mode 100644 index 00000000000..402d09ba901 --- /dev/null +++ b/fastjar/jartool.c @@ -0,0 +1,1757 @@ +/* + jartool.c - main functions for fastjar utility + Copyright (C) 1999 Bryan Burns + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +/* $Id: jartool.c,v 1.5 2000/08/24 15:01:27 cory Exp $ + + $Log: jartool.c,v $ + Revision 1.5 2000/08/24 15:01:27 cory + Made certain that fastjar opened the jar file before trying to update it + with the -u option. + + Revision 1.4 2000/08/24 13:39:21 cory + Changed +'s to |'s in jartool.c to insure there was no confusion with sign + when byte swapping. Better safe than sorry. + + Revision 1.3 2000/08/23 19:42:17 cory + Added support for more Unix platforms. The following code has been hacked + to work on AIX, Solaris, True 64, and HP-UX. + Added bigendian check. Probably works on most big and little endian platforms + now. + + Revision 1.2 1999/12/06 07:38:28 toast + fixed recursive archiving bug + + Revision 1.1.1.1 1999/12/06 03:09:34 toast + initial checkin.. + + + + Revision 1.22 1999/10/12 19:45:13 burnsbr + adding patch to fix compat problem + + Revision 1.21 1999/05/10 09:15:49 burnsbr + fixed manifest file version info + + Revision 1.20 1999/05/10 08:53:16 burnsbr + *** empty log message *** + + Revision 1.19 1999/05/10 08:30:39 burnsbr + added extract / listing code + + Revision 1.18 1999/04/28 04:24:29 burnsbr + updated version + + Revision 1.17 1999/04/28 04:21:23 burnsbr + added support for -C dir-changing flag.. Updated total compression display + + Revision 1.16 1999/04/27 10:28:22 burnsbr + updated version string + + Revision 1.15 1999/04/27 10:04:06 burnsbr + configure support + + Revision 1.14 1999/04/27 08:56:14 burnsbr + added -V flag, better error messages + + Revision 1.13 1999/04/26 02:35:21 burnsbr + changed all sorts of stuff.. compression now works 100% + + Revision 1.12 1999/04/23 12:00:45 burnsbr + 90% done with compression code + + Revision 1.11 1999/04/22 04:12:57 burnsbr + finished first round of Manifest file support.. + might need to do more, digest etc.. + + Revision 1.10 1999/04/22 02:35:23 burnsbr + added more manifest support, about 75% done now. Replaced all the + redundant shifts and bit-logic with a macro or two, making the code + easier to read. + + Revision 1.9 1999/04/21 09:55:16 burnsbr + pulled out printfs + + Revision 1.8 1999/04/21 02:58:01 burnsbr + started manifest code + + Revision 1.7 1999/04/20 23:15:28 burnsbr + added patch sent by John Bley <jbb6@acpub.duke.edu> + + Revision 1.6 1999/04/20 08:56:02 burnsbr + added GPL comment + + Revision 1.5 1999/04/20 08:16:09 burnsbr + fixed verbose flag, did some optimization + + Revision 1.4 1999/04/20 05:09:59 burnsbr + added rcsid variable + + Revision 1.3 1999/04/20 05:08:54 burnsbr + fixed Log statement + +*/ + +#include "config.h" + +#include <zlib.h> + +#ifdef STDC_HEADERS +#include <stdlib.h> +#endif + +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include <stdio.h> +#include <sys/stat.h> +#include <sys/types.h> + +#ifdef HAVE_SYS_PARAM_H +#include <sys/param.h> +#else +#define MAXPATHLEN 1024 +#endif + +#ifdef HAVE_DIRENT_H +#include <dirent.h> +#endif + +#ifdef HAVE_FCNTL_H +#include <fcntl.h> +#endif + +#include <string.h> +#include <errno.h> + +#ifdef TM_IN_SYS_TIME +#include <sys/time.h> +#else +#include <time.h> +#endif + +#include "jartool.h" +#include "zipfile.h" +#include "dostime.h" +#include "pushback.h" +#include "compress.h" + +#ifdef WORDS_BIGENDIAN + +#define L2BI(l) ((l & 0xff000000) >> 24) | \ + ((l & 0x00ff0000) >> 8) | \ + ((l & 0x0000ff00) << 8) | \ + ((l & 0x000000ff) << 24); + +#define L2BS(l) ((l & 0xff00) >> 8) | ((l & 0x00ff) << 8); + +#endif + +static char version_string[] = VERSION; +static char rcsid[] = "$Id: jartool.c,v 1.5 2000/08/24 15:01:27 cory Exp $"; + +extern int errno; + +void usage(char*); +void add_entry(struct zipentry *); +void init_headers(); + +int consume(pb_file *, int); +int list_jar(int, char**, int); +int extract_jar(int, char**, int); +int add_file_to_jar(int, int, char*, struct stat*); +int add_to_jar(int, char*, char*); +int create_central_header(int); +int make_manifest(int, char*); +static void init_args(char **, int); +static char *get_next_arg (); + +/* global variables */ +ub1 file_header[30]; +ub1 data_descriptor[16]; +int do_compress; +int seekable; +int verbose; +char jarfile[256]; + +/* If non zero, then don't recurse in directory. Instead, add the + directory entry and relie on an explicit list of files to populate + the archive. This option isn't supported by the original jar tool. */ +int use_explicit_list_only; + +/* If non zero, then read the entry names from stdin. This option + isn't supported by the original jar tool. */ +int read_names_from_stdin; + +zipentry *ziplist; /* linked list of entries */ +zipentry *ziptail; /* tail of the linked list */ + +int number_of_entries; /* number of entries in the linked list */ + +int main(int argc, char **argv){ + + char mfile[256]; + + int action = ACTION_NONE; + int manifest = TRUE; + int manifest_file = FALSE; + int file = FALSE; + int file_first = FALSE; + + int i, j; + int jarfd = -1; + + do_compress = TRUE; + verbose = FALSE; + + ziplist = NULL; + + number_of_entries = 0; + + if(argc < 2) + usage(argv[0]); + + j = strlen(argv[1]); + + for(i = 0; i < j; i++){ + switch(argv[1][i]){ + case 'c': + action = ACTION_CREATE; + break; + case 't': + action = ACTION_LIST; + break; + case 'x': + action = ACTION_EXTRACT; + break; + case 'u': + action = ACTION_UPDATE; + break; + case 'v': + verbose = TRUE; + break; + case 'V': + printf("%s\n", version_string); + exit(0); + case 'f': + file = TRUE; + if(!manifest_file) + file_first = TRUE; + else + file_first = FALSE; + break; + case 'm': + manifest_file = TRUE; + break; + case '0': + do_compress = FALSE; + break; + case 'M': + manifest = FALSE; + break; + case '-': + break; + /* The following options aren't supported by the original jar tool. */ + case 'E': + use_explicit_list_only = TRUE; + break; + case '@': + read_names_from_stdin = TRUE; + break; + default: + fprintf(stderr, "Illegal option: %c\n", argv[1][i]); + usage(argv[0]); + } + } + + if(action == ACTION_NONE){ + fprintf(stderr, "One of options -{ctxu} must be specified.\n"); + usage(argv[0]); + } + + /* Verify unsupported combinations and warn of the use of non + standard features */ + if(verbose && use_explicit_list_only) + fprintf (stderr, "Warning: using non standard '-E' option\n"); + if(verbose && read_names_from_stdin) + fprintf (stderr, "Warning: using non standard '-@' option\n"); + if(read_names_from_stdin + && (action != ACTION_CREATE && action != ACTION_UPDATE)){ + fprintf(stderr, "Option '-@' is supported only with '-c' or '-u'.\n"); + usage(argv[0]); + } + + i = 2; + + /* get the jarfile and manifest file (if any) */ + if(file && file_first){ + if(i >= argc) + usage(argv[0]); + + strncpy(jarfile, argv[i++], 256); + } + if(manifest_file){ + if(i >= argc) + usage(argv[0]); + + strncpy(mfile, argv[i++], 256); + } + + if(file && !file_first){ + if(i >= argc) + usage(argv[0]); + + strncpy(jarfile, argv[i++], 256); + } + + /* create the jarfile */ + if(action == ACTION_CREATE){ + if(file){ + jarfd = creat(jarfile, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + + if(jarfd < 0){ + fprintf(stderr, "Error opening %s for writing!\n", jarfile); + perror(jarfile); + exit(1); + } + + /* We assume that the file is seekable */ + seekable = TRUE; + + } else { + + jarfd = STDOUT_FILENO; /* jarfd is stdout otherwise */ + + /* standard out is not seekable */ + seekable = FALSE; + + /* don't want our output to be part of the jar file.. figured this one + out the hard way.. =P */ + verbose = FALSE; + } + } else if(action == ACTION_LIST || action == ACTION_EXTRACT){ + + if(file){ + jarfd = open(jarfile, O_RDONLY); + + if(jarfd < 0){ + fprintf(stderr, "Error opening %s for reading!\n", jarfile); + perror(jarfile); + exit(1); + } + + seekable = TRUE; + } else { + jarfd = STDIN_FILENO; /* jarfd is standard in */ + + /* we assume that the stream isn't seekable for safety */ + seekable = FALSE; + } + } + + if(action == ACTION_CREATE || action == ACTION_UPDATE){ + char *arg; + init_headers(); + + if((action == ACTION_UPDATE) && file) { + if((jarfd = open(jarfile, O_RDWR)) < 0) { + fprintf(stderr, "Error opening %s for reading!\n", jarfile); + perror(jarfile); + exit(1); + } + } + + if(do_compress) + init_compression(); + + + /* Add the META-INF/ directory and the manifest */ + if(manifest && manifest_file) + make_manifest(jarfd, mfile); + else if(manifest) + make_manifest(jarfd, NULL); + + init_args (argv, i); + /* now we add the files to the archive */ + while ((arg = get_next_arg ())){ + + if(!strcmp(arg, "-C")){ + char *dir_to_change = get_next_arg (); + char *file_to_add = get_next_arg (); + if(!dir_to_change + || !file_to_add + || add_to_jar(jarfd, dir_to_change, file_to_add)){ + printf("Error adding %s to jar archive!\n", arg); + exit(1); + } + } else { + if(add_to_jar(jarfd, NULL, arg)){ + printf("Error adding %s to jar archive!\n", arg); + exit(1); + } + } + } + /* de-initialize the compression DS */ + if(do_compress) + end_compression(); + + create_central_header(jarfd); + + if (close(jarfd) != 0) { + fprintf(stderr, "Error closing jar archive!\n"); + } + } else if(action == ACTION_LIST){ + list_jar(jarfd, &argv[i], (argc - i)); + } else if(action == ACTION_EXTRACT){ + extract_jar(jarfd, &argv[i], (argc - i)); + } + + exit(0); +} + +static int args_current_g; +static char **args_g; + +static void +init_args(args, current) + char **args; + int current; +{ + if(!read_names_from_stdin) + { + args_g = args; + args_current_g = current; + } +} + +static char * +get_next_arg () +{ + static int reached_end = 0; + + if (reached_end) + return NULL; + + if (args_g) + { + if (!args_g [args_current_g]) + { + reached_end = 1; + return NULL; + } + return args_g [args_current_g++]; + } + else + { + /* Read the name from stdin. Delimiters are '\n' and + '\r'. Reading EOF indicates that we don't have anymore file + names characters to read. */ + + char s [MAXPATHLEN]; + int pos = 0; + + /* Get rid of '\n' and '\r' first. */ + while (1) + { + int c = getc (stdin); + if (c == '\n' || c == '\r') + continue; + else + { + if (c == EOF) + return NULL; + ungetc (c, stdin); + break; + } + } + + while (1) + { + int c = getc (stdin); + /* Exit when we get a delimiter or don't have any characters + to read */ + if (c == '\n'|| c == '\r'|| c == EOF) + break; + s [pos++] = (char) c; + } + + if (pos) + { + s [pos] = '\0'; + return strdup (s); + } + else + return NULL; + } +} + +void init_headers(){ + /* packing file header */ + /* magic number */ + file_header[0] = 0x50; + file_header[1] = 0x4b; + file_header[2] = 0x03; + file_header[3] = 0x04; + /* version number (Unix 1.0)*/ + file_header[4] = 10; + file_header[5] = 0; + /* bit flag (normal deflation)*/ + file_header[6] = 0x00; + + file_header[7] = 0x00; + /* do_compression method (deflation) */ + file_header[8] = 0; + file_header[9] = 0; + + /* last mod file time (MS-DOS format) */ + file_header[10] = 0; + file_header[11] = 0; + /* last mod file date (MS-DOS format) */ + file_header[12] = 0; + file_header[13] = 0; + /* CRC 32 */ + file_header[14] = 0; + file_header[15] = 0; + file_header[16] = 0; + file_header[17] = 0; + /* compressed size */ + file_header[18] = 0; + file_header[19] = 0; + file_header[20] = 0; + file_header[21] = 0; + /* uncompressed size */ + file_header[22] = 0; + file_header[23] = 0; + file_header[24] = 0; + file_header[25] = 0; + /* filename length */ + file_header[26] = 0; + file_header[27] = 0; + /* extra field length */ + file_header[28] = 0; + file_header[29] = 0; + + /* Initialize the compression DS */ + PACK_UB4(data_descriptor, 0, 0x08074b50); + +} + +void add_entry(struct zipentry *ze){ + + if(ziplist == NULL){ + ziplist = ze; + ziptail = ziplist; + } else { + ziplist->next_entry = ze; + ziplist = ze; + } + + number_of_entries++; +} + +int make_manifest(int jfd, char *mf_name){ + time_t current_time; + int nlen; /* length of file name */ + int mod_time; /* file modification time */ + struct zipentry *ze; + + nlen = 9; /* trust me on this one */ + + memset((file_header + 12), '\0', 16); /*clear mod time, crc, size fields*/ + + current_time = time(NULL); + if(current_time == (time_t)-1){ + perror("time"); + exit(1); + } + + mod_time = unix2dostime(¤t_time); + + PACK_UB2(file_header, LOC_EXTRA, 0); + PACK_UB2(file_header, LOC_COMP, 0); + PACK_UB2(file_header, LOC_FNLEN, nlen); + PACK_UB4(file_header, LOC_MODTIME, mod_time); + + if(verbose) + printf("adding: META-INF/ (in=0) (out=0) (stored 0%%)\n"); + + ze = (zipentry*)malloc(sizeof(zipentry)); + if(ze == NULL){ + perror("malloc"); + exit(1); + } + + memset(ze, 0, sizeof(zipentry)); /* clear all the fields*/ + ze->filename = (char*)malloc((nlen + 1) * sizeof(char) + 1); + strcpy(ze->filename, "META-INF/"); + ze->filename[nlen] = '\0'; + + ze->offset = lseek(jfd, 0, SEEK_CUR); + ze->mod_time = (ub2)(mod_time & 0x0000ffff); + ze->mod_date = (ub2)((mod_time & 0xffff0000) >> 16); + ze->compressed = FALSE; + + add_entry(ze); + + write(jfd, file_header, 30); + write(jfd, "META-INF/", nlen); + + /* if the user didn't specify an external manifest file... */ + if(mf_name == NULL){ + int mf_len = 37 + strlen(VERSION); + char *mf; + + if(mf = (char *) malloc(mf_len + 1)) { + uLong crc; + + sprintf(mf, "Manifest-Version: 1.0\nCreated-By: %s\n\n", VERSION); + + crc = crc32(0L, Z_NULL, 0); + + crc = crc32(crc, mf, mf_len); + + nlen = 20; /* once again, trust me */ + + PACK_UB2(file_header, LOC_EXTRA, 0); + PACK_UB2(file_header, LOC_COMP, 0); + PACK_UB2(file_header, LOC_FNLEN, nlen); + PACK_UB4(file_header, LOC_USIZE, mf_len); + + memcpy((file_header + LOC_CSIZE), (file_header + LOC_USIZE), 4); + + PACK_UB4(file_header, LOC_CRC, crc); + + if(verbose) + printf("adding: META-INF/MANIFEST.MF (in=56) (out=56) (stored 0%%)\n"); + + ze = (zipentry*)malloc(sizeof(zipentry)); + if(ze == NULL){ + perror("malloc"); + exit(1); + } + + memset(ze, 0, sizeof(zipentry)); /* clear all the fields*/ + ze->filename = (char*)malloc((nlen + 1) * sizeof(char) + 1); + strcpy(ze->filename, "META-INF/MANIFEST.MF"); + ze->filename[nlen] = '\0'; + + ze->offset = lseek(jfd, 0, SEEK_CUR); + ze->mod_time = (ub2)(mod_time & 0x0000ffff); + ze->mod_date = (ub2)((mod_time & 0xffff0000) >> 16); + ze->crc = crc; + ze->csize = mf_len; + ze->usize = ze->csize; + ze->compressed = FALSE; + + add_entry(ze); + + write(jfd, file_header, 30); + write(jfd, "META-INF/MANIFEST.MF", nlen); + write(jfd, mf, mf_len); + free(mf); + } + else { + printf("malloc errror\n"); + exit(-1); + } + } else { + int mfd; + struct stat statbuf; + + stat(mf_name, &statbuf); + + if(!S_ISREG(statbuf.st_mode)){ + fprintf(stderr, "Invalid manifest file specified.\n"); + exit(1); + } + + mfd = open(mf_name, O_RDONLY); + + if(mfd < 0){ + fprintf(stderr, "Error opening %s.\n", mf_name); + exit(1); + } + + if(add_file_to_jar(jfd, mfd, "META-INF/MANIFEST.MF", &statbuf)){ + perror("error writing to jar"); + exit(1); + } + + } + + return 0; +} + +int add_to_jar(int fd, char *new_dir, char *file){ + struct stat statbuf; + DIR *dir; + struct dirent *de; + zipentry *ze; + int stat_return; + char *old_dir = NULL; + + /* This is a quick compatibility fix -- Simon Weijgers <simon@weijgers.com> + * It fixes this: + * "normal" jar : org/apache/java/io/LogRecord.class + * fastjar : ./org/apache/java/io/LogRecord.class + * Fastjar's preservation of the ./'s makes the jarfile unusuable for use + * with both kaffe-1.0b4 and JDK. + */ + while (*file=='.' && *(file+1)=='/') + file+=2; + + /* If new_dir isn't null, we need to change to that directory. However, + we also need to return to the old directory when we're done */ + if(new_dir != NULL){ + old_dir = getcwd(NULL, 0); + + if(chdir(new_dir) == -1){ + perror(new_dir); + return 1; + } + } + + if(!strcmp(file, jarfile)){ + if(verbose) + printf("skipping: %s\n", file); + return 0; /* we don't want to add ourselves.. */ + } + + stat_return = stat(file, &statbuf); + + if(stat_return == -1){ + perror(file); + } else if(S_ISDIR(statbuf.st_mode)){ + char *fullname; + char *t_ptr; + int nlen; + unsigned long mod_time; + + dir = opendir(file); + + if(dir == NULL){ + perror("opendir"); + return 1; + } + + nlen = strlen(file) + 256; + fullname = (char*)malloc(nlen * sizeof(char)); + memset(fullname, 0, (nlen * sizeof(char))); + + if(fullname == NULL){ + fprintf(stderr, "Filename is NULL!\n"); + return 1; + } + + strcpy(fullname, file); + nlen = strlen(file); + + if(fullname[nlen - 1] != '/'){ + fullname[nlen] = '/'; + t_ptr = (fullname + nlen + 1); + } else + t_ptr = (fullname + nlen); + + + memset((file_header + 12), '\0', 16); /*clear mod time, crc, size fields*/ + + nlen = (t_ptr - fullname); + + mod_time = unix2dostime(&statbuf.st_mtime); + + PACK_UB2(file_header, LOC_EXTRA, 0); + PACK_UB2(file_header, LOC_COMP, 0); + PACK_UB2(file_header, LOC_FNLEN, nlen); + PACK_UB4(file_header, LOC_MODTIME, mod_time); + + if(verbose) + printf("adding: %s (in=%d) (out=%d) (stored 0%%)\n", fullname, 0, 0); + + ze = (zipentry*)malloc(sizeof(zipentry)); + if(ze == NULL){ + perror("malloc"); + exit(1); + } + + memset(ze, 0, sizeof(zipentry)); /* clear all the fields*/ + ze->filename = (char*)malloc((nlen + 1) * sizeof(char) + 1); + strcpy(ze->filename, fullname); + ze->filename[nlen] = '\0'; + + ze->offset = lseek(fd, 0, SEEK_CUR); + ze->mod_time = (ub2)(mod_time & 0x0000ffff); + ze->mod_date = (ub2)((mod_time & 0xffff0000) >> 16); + ze->compressed = FALSE; + + add_entry(ze); + + write(fd, file_header, 30); + write(fd, fullname, nlen); + + while(!use_explicit_list_only && (de = readdir(dir)) != NULL){ + if(de->d_name[0] == '.') + continue; + if(!strcmp(de->d_name, jarfile)){ /* we don't want to add ourselves. Believe me */ + if(verbose) + printf("skipping: %s\n", de->d_name); + continue; + } + + strcpy(t_ptr, de->d_name); + + if(add_to_jar(fd, NULL, fullname)){ + fprintf(stderr, "Error adding file to jar!\n"); + return 1; + } + } + + free(fullname); + closedir(dir); + + } else if(S_ISREG(statbuf.st_mode)){ + int add_fd; + + add_fd = open(file, O_RDONLY); + if(add_fd < 0){ + fprintf(stderr, "Error opening %s.\n", file); + return 0; + } + + if(add_file_to_jar(fd, add_fd, file, &statbuf)){ + fprintf(stderr, "Error adding file to jar!\n"); + return 1; + } + + } else { + fprintf(stderr, "Illegal file specified: %s\n", file); + } + + if(old_dir != NULL){ + if(chdir(old_dir)) + perror(old_dir); + + free(old_dir); + } + + return 0; +} + +int add_file_to_jar(int jfd, int ffd, char *fname, struct stat *statbuf){ + + unsigned short file_name_length; + unsigned long mod_time; + ub1 rd_buff[RDSZ]; + uLong crc = 0; + off_t offset = 0; + int rdamt; + struct zipentry *ze; + + mod_time = unix2dostime(&(statbuf->st_mtime)); + file_name_length = strlen(fname); + + if(!seekable && !do_compress){ + crc = crc32(0L, Z_NULL, 0); + + while((rdamt = read(ffd, rd_buff, RDSZ)) != 0) + crc = crc32(crc, rd_buff, rdamt); + + lseek(ffd, 0, SEEK_SET); + } + + /* data descriptor */ + if(!seekable && do_compress){ + PACK_UB2(file_header, LOC_EXTRA, 8); + } else { + PACK_UB2(file_header, LOC_EXTRA, 0); + } + + if(do_compress){ + PACK_UB2(file_header, LOC_COMP, 8); + } else { + PACK_UB2(file_header, LOC_COMP, 0); + } + + PACK_UB4(file_header, LOC_MODTIME, mod_time); + PACK_UB2(file_header, LOC_FNLEN, file_name_length); + + if(!seekable && !do_compress){ + PACK_UB4(file_header, LOC_CRC, crc); + PACK_UB4(file_header, LOC_USIZE, statbuf->st_size); + PACK_UB4(file_header, LOC_CSIZE, statbuf->st_size); + } else + memset((file_header + LOC_CRC), '\0', 12); /* clear crc/usize/csize */ + + ze = (zipentry*)malloc(sizeof(zipentry)); + if(ze == NULL){ + perror("malloc"); + exit(1); + } + + memset(ze, 0, sizeof(zipentry)); /* clear all the fields*/ + ze->filename = (char*)malloc((file_name_length + 1) * sizeof(char)); + strcpy(ze->filename, fname); + + ze->mod_time = (ub2)(mod_time & 0x0000ffff); + ze->mod_date = (ub2)((mod_time & 0xffff0000) >> 16); + + if(!seekable && !do_compress) + ze->crc = crc; + + ze->csize = statbuf->st_size; + ze->usize = ze->csize; + ze->offset = lseek(jfd, 0, SEEK_CUR); + if(do_compress) + ze->compressed = TRUE; + else + ze->compressed = FALSE; + + add_entry(ze); + + /* Write the local header */ + write(jfd, file_header, 30); + + /* write the file name to the zip file */ + write(jfd, fname, file_name_length); + + + if(verbose){ + printf("adding: %s ", fname); + fflush(stdout); + } + + if(do_compress){ + /* compress the file */ + compress_file(ffd, jfd, ze); + } else { + /* Write the contents of the file (uncompressed) to the zip file */ + /* calculate the CRC as we go along */ + ze->crc = crc32(0L, Z_NULL, 0); + + while((rdamt = read(ffd, rd_buff, RDSZ)) != 0){ + ze->crc = crc32(ze->crc, rd_buff, rdamt); + if(write(jfd, rd_buff, rdamt) != rdamt){ + perror("write"); + return 0; + } + } + } + close(ffd); + + /* write out data descriptor */ + PACK_UB4(data_descriptor, 4, ze->crc); + PACK_UB4(data_descriptor, 8, ze->csize); + PACK_UB4(data_descriptor, 12, ze->usize); + + /* we need to seek back and fill the header */ + if(seekable){ + offset = (ze->csize + strlen(ze->filename) + 16); + + if(lseek(jfd, -offset, SEEK_CUR) == (off_t)-1){ + perror("lseek"); + exit(1); + } + + if(write(jfd, (data_descriptor + 4), 12) != 12){ + perror("write"); + return 0; + } + + offset -= 12; + + if(lseek(jfd, offset, SEEK_CUR) == (off_t)-1){ + perror("lseek"); + exit(1); + } + } else if(do_compress){ + /* Sun's jar tool will only allow a data descriptor if the entry is + compressed, but we'll save 16 bytes/entry if we only use it when + we can't seek back on the file */ + + if(write(jfd, data_descriptor, 16) != 16){ + perror("write"); + return 0; + } + } + + if(verbose) + printf("(in=%d) (out=%d) (%s %d%%)\n", + (int)ze->usize, (int)ze->csize, + (do_compress ? "deflated" : "stored"), + (do_compress ? ((int)((1 - ze->csize/(float)ze->usize) * 100)) : 0)); + + return 0; +} + +int create_central_header(int fd){ + ub1 header[46]; + ub1 end_header[22]; + int start_offset; + int dir_size; + int *iheader; + int total_in = 0, total_out = 22; + + zipentry *ze; + + iheader = (int*)header; + + /* magic number */ + header[0] = 'P'; + header[1] = 'K'; + header[2] = 1; + header[3] = 2; + /* version made by */ + header[4] = 10; + header[5] = 0; + /* version needed to extract */ + header[6] = 10; + header[7] = 0; + /* bit flag */ + header[8] = 0; + header[9] = 0; + /* compression method */ + header[10] = 0; + header[11] = 0; + /* file mod time */ + header[12] = 0; + header[13] = 0; + /* file mod date */ + header[14] = 0; + header[15] = 0; + /* crc 32 */ + header[16] = 0; + header[17] = 0; + header[18] = 0; + header[19] = 0; + /* compressed size */ + header[20] = 0; + header[21] = 0; + header[22] = 0; + header[23] = 0; + /* uncompressed size */ + header[24] = 0; + header[25] = 0; + header[26] = 0; + header[27] = 0; + /* filename length */ + header[28] = 0; + header[29] = 0; + /* extra field length */ + header[30] = 0; + header[31] = 0; + /* file comment length */ + header[32] = 0; + header[33] = 0; + /* disk number start */ + header[34] = 0; + header[35] = 0; + /* internal file attribs */ + header[36] = 0; + header[37] = 0; + /* external file attribs */ + header[38] = 0; + header[39] = 0; + header[40] = 0; + header[41] = 0; + /* relative offset of local header */ + header[42] = 0; + header[43] = 0; + header[44] = 0; + header[45] = 0; + + start_offset = lseek(fd, 0, SEEK_CUR); + + for(ze = ziptail; ze != NULL; ze = ze->next_entry){ + + total_in += ze->usize; + total_out += ze->csize + 76 + strlen(ze->filename) * 2; + + if(ze->compressed){ + PACK_UB2(header, CEN_COMP, 8); + } else { + PACK_UB2(header, CEN_COMP, 0); + } + + PACK_UB2(header, CEN_MODTIME, ze->mod_time); + PACK_UB2(header, CEN_MODDATE, ze->mod_date); + PACK_UB4(header, CEN_CRC, ze->crc); + PACK_UB4(header, CEN_CSIZE, ze->csize); + PACK_UB4(header, CEN_USIZE, ze->usize); + PACK_UB2(header, CEN_FNLEN, strlen(ze->filename)); + PACK_UB4(header, CEN_OFFSET, ze->offset); + + write(fd, header, 46); + + write(fd, ze->filename, strlen(ze->filename)); + } + + dir_size = lseek(fd, 0, SEEK_CUR) - start_offset; + + /* magic number */ + end_header[0] = 0x50; + end_header[1] = 0x4b; + end_header[2] = 0x05; + end_header[3] = 0x06; + /* number of this disk */ + end_header[4] = 0; + end_header[5] = 0; + /* number of disk w/ start of central header */ + end_header[6] = 0; + end_header[7] = 0; + /* total number of entries in central dir on this disk*/ + PACK_UB2(end_header, 8, number_of_entries); + /* total number of entries in central dir*/ + PACK_UB2(end_header, 10, number_of_entries); + /* size of central dir. */ + PACK_UB4(end_header, 12, dir_size); + /* offset of start of central dir */ + PACK_UB4(end_header, 16, start_offset); + /* zipfile comment length */ + end_header[20] = 0; + end_header[21] = 0; + + write(fd, end_header, 22); + + if(verbose) + printf("Total:\n------\n(in = %d) (out = %d) (%s %d%%)\n", + total_in, + total_out, + (do_compress ? "deflated" : "stored"), + (int)((1 - (total_out / (float)total_in)) * 100) + ); + + return 0; +} + +int extract_jar(int fd, char **files, int file_num){ + int rdamt; + int out_a, in_a; + ub4 signature; + ub4 csize; + ub4 crc; + ub2 fnlen; + ub2 eflen; + ub2 flags; + ub2 method; + ub1 *filename = NULL; + int filename_len = 0; + ub4 rd_buff[RDSZ]; + pb_file pbf; + ub1 scratch[16]; + zipentry ze; + int f_fd; + int dir; + int handle; + int j; + + init_inflation(); + + pb_init(&pbf, fd); + + for(;;){ + f_fd = 0; + crc = 0; + ze.crc = 0; + + dir = FALSE; /* by default, the file isn't a dir */ + handle = TRUE; /* by default we'll extract/create the file */ + + if((rdamt = pb_read(&pbf, scratch, 4)) != 4){ + perror("read"); + break; + } + + signature = UNPACK_UB4(scratch, 0); + +#ifdef DEBUG + printf("signature is %x\n", signature); +#endif + if(signature == 0x08074b50){ +#ifdef DEBUG + printf("skipping data descriptor\n"); +#endif + pb_read(&pbf, scratch, 12); + continue; + } else if(signature == 0x02014b50){ +#ifdef DEBUG + printf("Central header reached.. we're all done!\n"); +#endif + break; + }else if(signature != 0x04034b50){ + printf("Ick! %#x\n", signature); + break; + } + + if((rdamt = pb_read(&pbf, (file_header + 4), 26)) != 26){ + perror("read"); + break; + } + + csize = UNPACK_UB4(file_header, LOC_CSIZE); +#ifdef DEBUG + printf("Compressed size is %u\n", csize); +#endif + + fnlen = UNPACK_UB2(file_header, LOC_FNLEN); +#ifdef DEBUG + printf("Filename length is %hu\n", fnlen); +#endif + + eflen = UNPACK_UB2(file_header, LOC_EFLEN); +#ifdef DEBUG + printf("Extra field length is %hu\n", eflen); +#endif + + flags = UNPACK_UB2(file_header, LOC_EXTRA); +#ifdef DEBUG + printf("Flags are %#hx\n", flags); +#endif + + method = UNPACK_UB2(file_header, LOC_COMP); +#ifdef DEBUG + printf("Compression method is %#hx\n", method); +#endif + + /* if there isn't a data descriptor */ + if(!(flags & 0x0008)){ + crc = UNPACK_UB4(file_header, LOC_CRC); +#ifdef DEBUG + printf("CRC is %x\n", crc); +#endif + } + + if(filename_len < fnlen){ + if(filename != NULL) + free(filename); + + filename = malloc(sizeof(ub1) * (fnlen + 1)); + filename_len = fnlen + 1; + } + + pb_read(&pbf, filename, fnlen); + filename[fnlen] = '\0'; + +#ifdef DEBUG + printf("filename is %s\n", filename); +#endif + + if(file_num > 0){ + handle = FALSE; + + for(j = 0; j < file_num; j++) + if(strcmp(files[j], filename) == 0){ + handle = TRUE; + break; + } + } + + if(!handle) + f_fd = -1; + + /* OK, there is some directory information in the file. Nothing to do + but ensure the directory(s) exist, and create them if they don't. + What a pain! */ + if(index(filename, '/') != NULL && handle){ + /* Loop through all the directories in the path, (everything w/ a '/') */ + ub1 *start = filename; + char *tmp_buff; + struct stat sbuf; + + tmp_buff = malloc(sizeof(char) * strlen(filename)); + + for(;;){ + ub1 *idx = index(start, '/'); + + if(idx == NULL) + break; + else if(idx == start){ + start++; + continue; + } + start = idx + 1; + + strncpy(tmp_buff, filename, (idx - filename)); + tmp_buff[(idx - filename)] = '\0'; + +#ifdef DEBUG + printf("checking the existance of %s\n", tmp_buff); +#endif + + if(stat(tmp_buff, &sbuf) < 0){ + if(errno != ENOENT){ + perror("stat"); + exit(1); + } + + } else if(S_ISDIR(sbuf.st_mode)){ +#ifdef DEBUG + printf("Directory exists\n"); +#endif + continue; + }else { + fprintf(stderr, "Hmmm.. %s exists but isn't a directory!\n", + tmp_buff); + exit(1); + } + +#ifdef DEBUG + printf("Making directory..\n"); +#endif + if(mkdir(tmp_buff, 0755) < 0){ + perror("mkdir"); + exit(1); + } + if(verbose && handle) + printf("%10s: %s/\n", "created", tmp_buff); + + } + + /* only a directory */ + if(strlen(start) == 0) + dir = TRUE; + +#ifdef DEBUG + printf("Leftovers are \"%s\" (%d)\n", start, strlen(start)); +#endif + + /* If the entry was just a directory, don't write to file, etc */ + if(strlen(start) == 0) + f_fd = -1; + + free(tmp_buff); + } + + if(f_fd != -1 && handle){ + f_fd = creat(filename, 00644); + + if(f_fd < 0){ + fprintf(stderr, "Error extracting JAR archive!\n"); + perror(filename); + exit(1); + } + } + + if(method != 8 && flags & 0x0008){ + fprintf(stderr, "Error in JAR file! (not compressed but data desc.)\n"); + exit(1); + } + + if(method == 8 || flags & 0x0008){ + if(seekable) + lseek(fd, eflen, SEEK_CUR); + else + consume(&pbf, eflen); + + inflate_file(&pbf, f_fd, &ze); + } else { + +#ifdef DEBUG + printf("writing stored data.. (%d bytes)\n", csize); +#endif + + out_a = 0; + in_a = csize; + + ze.crc = crc32(ze.crc, NULL, 0); /* initialize the crc */ + + while(out_a < csize){ + rdamt = (in_a > RDSZ ? RDSZ : in_a); + if(pb_read(&pbf, rd_buff, rdamt) != rdamt){ + perror("read"); + exit(1); + } + + ze.crc = crc32(ze.crc, (Bytef*)rd_buff, rdamt); + + if(f_fd >= 0) + write(f_fd, rd_buff, rdamt); + + out_a += rdamt; + in_a -= rdamt; + +#ifdef DEBUG + printf("%d bytes written\n", out_a); +#endif + } + + if(seekable) + lseek(fd, eflen, SEEK_CUR); + else + consume(&pbf, eflen); + } + + /* if there is a data descriptor left, compare the CRC */ + if(flags & 0x0008){ + + if(pb_read(&pbf, scratch, 16) != 16){ + perror("read"); + exit(1); + } + + signature = UNPACK_UB4(scratch, 0); + + if(signature != 0x08074b50){ + fprintf(stderr, "Error! Missing data descriptor!\n"); + exit(1); + } + + crc = UNPACK_UB4(scratch, 4); + + } + + if(crc != ze.crc){ + fprintf(stderr, "Error! CRCs do not match! Got %x, expected %x\n", + ze.crc, crc); + exit(1); + } + + close(f_fd); + + if(verbose && dir == FALSE && handle) + printf("%10s: %s\n", + (method == 8 ? "inflated" : "extracted"), + filename); + } + + return 0; +} + +int list_jar(int fd, char **files, int file_num){ + int rdamt; + ub4 signature; + ub4 csize; + ub4 usize; + ub4 mdate; + ub4 tmp; + ub2 fnlen; + ub2 eflen; + ub2 clen; + ub2 flags; + ub2 method; + ub2 cen_size; + ub1 *filename = NULL; + ub1 scratch[16]; + ub1 cen_header[46]; + int filename_len = 0; + off_t size; + int i, j; + time_t tdate; + struct tm *s_tm; + char ascii_date[30]; + zipentry ze; + +#ifdef DEBUG + printf("Listing jar file, looking for %d files\n", file_num); +#endif + + /* This should be the start of the central-header-end section */ + if(seekable){ + if(lseek(fd, -22, SEEK_END) == (off_t)-1){ + perror("lseek"); + exit(1); + } + + if(read(fd, &tmp, sizeof(ub4)) != 4){ + perror("read"); + exit(1); + } + +#ifdef WORDS_BIGENDIAN + tmp = L2BI(tmp); +#endif + + if(tmp != 0x06054b50){ + fprintf(stderr, "Error in JAR file format. zip-style comment?\n"); + exit(1); + } + + if(lseek(fd, 6, SEEK_CUR) == (off_t)-1){ + perror("lseek"); + exit(1); + } + + if(read(fd, &cen_size, 2) != 2){ + perror("read"); + exit(1); + } + +#ifdef WORDS_BIGENDIAN + cen_size = L2BS(cen_size); +#endif + + /* printf("%hu entries in central header\n", cen_size); */ + + if(lseek(fd, 4, SEEK_CUR) == (off_t)-1){ + perror("lseek"); + exit(1); + } + + if(read(fd, &tmp, 4) != 4){ + perror("read"); + exit(1); + } + +#ifdef WORDS_BIGENDIAN + tmp = L2BI(tmp); +#endif + + /* printf("Central header offset = %d\n", tmp); */ + + if(lseek(fd, tmp, SEEK_SET) != tmp){ + perror("lseek"); + exit(1); + } + + /* Loop through the entries in the central header */ + for(i = 0; i < cen_size; i++){ + + if(read(fd, &cen_header, 46) != 46){ + perror("read"); + exit(1); + } + + signature = UNPACK_UB4(cen_header, 0); + if(signature != 0x02014b50){ + fprintf(stderr, "Error in JAR file! Cannot locate central header!\n"); + exit(1); + } + + usize = UNPACK_UB4(cen_header, CEN_USIZE); + fnlen = UNPACK_UB2(cen_header, CEN_FNLEN); + eflen = UNPACK_UB2(cen_header, CEN_EFLEN); + clen = UNPACK_UB2(cen_header, CEN_COMLEN); + + /* If we're providing verbose output, we need to make an ASCII + * formatted version of the date. */ + if(verbose){ + mdate = UNPACK_UB4(cen_header, CEN_MODTIME); + tdate = dos2unixtime(mdate); + s_tm = localtime(&tdate); + strftime(ascii_date, 30, "%a %b %d %H:%M:%S %Z %Y", s_tm); + } + + if(filename_len < fnlen){ + if(filename != NULL) + free(filename); + + filename = malloc(sizeof(ub1) * (fnlen + 1)); + filename_len = fnlen + 1; + } + + if(read(fd, filename, fnlen) != fnlen){ + perror("read"); + exit(1); + } + filename[fnlen] = '\0'; + + /* if the user specified a list of files on the command line, + we'll only display those, otherwise we'll display everything */ + if(file_num > 0){ + for(j = 0; j < file_num; j++) + if(strcmp(files[j], filename) == 0){ + if(verbose) + printf("%6d %s %s\n", usize, ascii_date, filename); + else + printf("%s\n", filename); + break; + } + } else { + if(verbose) + printf("%6d %s %s\n", usize, ascii_date, filename); + else + printf("%s\n", filename); + } + + size = eflen + clen; + if(size > 0){ + if(lseek(fd, size, SEEK_CUR) == (off_t)-1){ + perror("lseek"); + exit(1); + } + } + } + } else { + /* the file isn't seekable.. evil! */ + pb_file pbf; + + pb_init(&pbf, fd); + + init_inflation(); + + for(;;){ + if((rdamt = pb_read(&pbf, scratch, 4)) != 4){ + perror("read"); + break; + } + + signature = UNPACK_UB4(scratch, 0); + +#ifdef DEBUG + printf("signature is %x\n", signature); +#endif + + if(signature == 0x08074b50){ +#ifdef DEBUG + printf("skipping data descriptor\n"); +#endif + pb_read(&pbf, scratch, 12); + continue; + } else if(signature == 0x02014b50){ +#ifdef DEBUG + printf("Central header reached.. we're all done!\n"); +#endif + break; + }else if(signature != 0x04034b50){ +#ifdef DEBUG + printf("Ick! %#x\n", signature); +#endif + break; + } + + if((rdamt = pb_read(&pbf, (file_header + 4), 26)) != 26){ + perror("read"); + break; + } + + csize = UNPACK_UB4(file_header, LOC_CSIZE); +#ifdef DEBUG + printf("Compressed size is %u\n", csize); +#endif + + fnlen = UNPACK_UB2(file_header, LOC_FNLEN); +#ifdef DEBUG + printf("Filename length is %hu\n", fnlen); +#endif + + eflen = UNPACK_UB2(file_header, LOC_EFLEN); +#ifdef DEBUG + printf("Extra field length is %hu\n", eflen); +#endif + + method = UNPACK_UB2(file_header, LOC_COMP); +#ifdef DEBUG + printf("Compression method is %#hx\n", method); +#endif + + flags = UNPACK_UB2(file_header, LOC_EXTRA); +#ifdef DEBUG + printf("Flags are %#hx\n", flags); +#endif + + usize = UNPACK_UB4(file_header, LOC_USIZE); + + /* If we're providing verbose output, we need to make an ASCII + * formatted version of the date. */ + if(verbose){ + mdate = UNPACK_UB4(file_header, LOC_MODTIME); + tdate = dos2unixtime(mdate); + s_tm = localtime(&tdate); + strftime(ascii_date, 30, "%a %b %d %H:%M:%S %Z %Y", s_tm); + } + + if(filename_len < fnlen){ + if(filename != NULL) + free(filename); + + filename = malloc(sizeof(ub1) * (fnlen + 1)); + filename_len = fnlen + 1; + } + + pb_read(&pbf, filename, fnlen); + filename[fnlen] = '\0'; + + /* the header is at the end. In a JAR file, this means that the data + happens to be compressed. We have no choice but to inflate the + data */ + if(flags & 0x0008){ + + size = eflen; + + if(size > 0) + consume(&pbf, size); + + if(method == 8){ +#ifdef DEBUG + printf("inflating %s\n", filename); +#endif + inflate_file(&pbf, -1, &ze); + + usize = ze.usize; + } else + printf("We're shit outta luck!\n"); + + } else { + size = csize + (eflen > 0 ? eflen : 0); + + +#ifdef DEBUG + printf("Skipping %d bytes\n", size); +#endif + + consume(&pbf, size); + } + /* print out the listing */ + if(file_num > 0){ + for(j = 0; j < file_num; j++) + if(strcmp(files[j], filename) == 0){ + if(verbose) + printf("%6d %s %s\n", usize, ascii_date, filename); + else + printf("%s\n", filename); + break; + } + } else { + if(verbose) + printf("%6d %s %s\n", usize, ascii_date, filename); + else + printf("%s\n", filename); + } + } + } + return 0; +} + +int consume(pb_file *pbf, int amt){ + int tc = 0; /* total amount consumed */ + ub1 buff[RDSZ]; + int rdamt; + +#ifdef DEBUG + printf("Consuming %d bytes\n", amt); +#endif + + while(tc < amt){ + rdamt = pb_read(pbf, buff, ((amt - tc) < RDSZ ? (amt - tc) : RDSZ)); +#ifdef DEBUG + printf("got %d bytes\n", rdamt); +#endif + tc += rdamt; + } + +#ifdef DEBUG + printf("%d bytes consumed\n", tc); +#endif + + return 0; +} + +void usage(char *filename){ + fprintf(stderr, "Usage: %s {ctxuV}[vfm0ME@] [jar-file] [manifest-file] [-C dir] files ...\nOptions\n -c create new archive\n -t list table of contents for archive\n -x extract named (or all) files from archive\n -u update existing archive\n -V display version information\n -v generate verbose output on standard output\n -f specify archive file name\n -m include manifest information from specified manifest file\n -0 store only; use no ZIP compression\n -M Do not create a manifest file for the entries\n -C change to the specified directory and include the following file\n -E don't include the files found in a directory\n -@ Read names from stdin\nIf any file is a directory then it is processed recursively.\nThe manifest file name and the archive file name needs to be specified\nin the same order the 'm' and 'f' flags are specified.\n\nExample 1: to archive two class files into an archive called classes.jar: \n jar cvf classes.jar Foo.class Bar.class \nExample 2: use an existing manifest file 'mymanifest' and archive all the\n files in the foo/ directory into 'classes.jar': \n jar cvfm classes.jar mymanifest -C foo/ .\n", filename); + + exit(1); +} |