1/* 2 zipnote.c - Zip 3 3 4 Copyright (c) 1990-2008 Info-ZIP. All rights reserved. 5 6 See the accompanying file LICENSE, version 2007-Mar-4 or later 7 (the contents of which are also included in zip.h) for terms of use. 8 If, for some reason, all these files are missing, the Info-ZIP license 9 also may be found at: ftp://ftp.info-zip.org/pub/infozip/license.html 10*/ 11/* 12 * zipnote.c by Mark Adler. 13 */ 14#define __ZIPNOTE_C 15 16#ifndef UTIL 17#define UTIL 18#endif 19#include "zip.h" 20#define DEFCPYRT /* main module: enable copyright string defines! */ 21#include "revision.h" 22#include <signal.h> 23 24/* Calculate size of static line buffer used in write (-w) mode. */ 25#define WRBUFSIZ 2047 26/* The line buffer size should be at least as large as FNMAX. */ 27#if FNMAX > WRBUFSIZ 28# undef WRBUFSIZ 29# define WRBUFSIZ FNMAX 30#endif 31 32/* Character to mark zip entry names in the comment file */ 33#define MARK '@' 34#define MARKE " (comment above this line)" 35#define MARKZ " (zip file comment below this line)" 36 37/* Temporary zip file pointer */ 38local FILE *tempzf; 39 40 41/* Local functions */ 42local void handler OF((int)); 43local void license OF((void)); 44local void help OF((void)); 45local void version_info OF((void)); 46local void putclean OF((char *, extent)); 47/* getline name conflicts with GNU getline() function */ 48local char *zgetline OF((char *, extent)); 49local int catalloc OF((char * far *, char *)); 50int main OF((int, char **)); 51 52/* keep compiler happy until implement long options - 11/4/2003 EG */ 53struct option_struct far options[] = { 54 /* short longopt value_type negatable ID name */ 55 {"h", "help", o_NO_VALUE, o_NOT_NEGATABLE, 'h', "help"}, 56 /* the end of the list */ 57 {NULL, NULL, o_NO_VALUE, o_NOT_NEGATABLE, 0, NULL} /* end has option_ID = 0 */ 58 }; 59 60#ifdef MACOS 61#define ziperr(c, h) zipnoteerr(c, h) 62#define zipwarn(a, b) zipnotewarn(a, b) 63 64void zipnoteerr(int c, ZCONST char *h); 65void zipnotewarn(ZCONST char *a, ZCONST char *b); 66#endif 67 68#ifdef QDOS 69#define exit(p1) QDOSexit() 70#endif 71 72int set_filetype(out_path) 73 char *out_path; 74{ 75#ifdef __BEOS__ 76 /* Set the filetype of the zipfile to "application/zip" */ 77 setfiletype( out_path, "application/zip" ); 78#endif 79 80#ifdef __ATHEOS__ 81 /* Set the filetype of the zipfile to "application/x-zip" */ 82 setfiletype(out_path, "application/x-zip"); 83#endif 84 85#ifdef MACOS 86 /* Set the Creator/Type of the zipfile to 'IZip' and 'ZIP ' */ 87 setfiletype(out_path, 'IZip', 'ZIP '); 88#endif 89 90#ifdef RISCOS 91 /* Set the filetype of the zipfile to &DDC */ 92 setfiletype(out_path, 0xDDC); 93#endif 94 return ZE_OK; 95} 96 97/* rename a split 98 * A split has a tempfile name until it is closed, then 99 * here rename it as out_path the final name for the split. 100 */ 101int rename_split(temp_name, out_path) 102 char *temp_name; 103 char *out_path; 104{ 105 int r; 106 /* Replace old zip file with new zip file, leaving only the new one */ 107 if ((r = replace(out_path, temp_name)) != ZE_OK) 108 { 109 zipwarn("new zip file left as: ", temp_name); 110 free((zvoid *)tempzip); 111 tempzip = NULL; 112 ZIPERR(r, "was replacing split file"); 113 } 114 if (zip_attributes) { 115 setfileattr(out_path, zip_attributes); 116 } 117 return ZE_OK; 118} 119 120void zipmessage_nl(a, nl) 121ZCONST char *a; /* message string to output */ 122int nl; /* 1 = add nl to end */ 123/* If nl false, print a message to mesg without new line. 124 If nl true, print and add new line. If logfile is 125 open then also write message to log file. */ 126{ 127 if (noisy) { 128 fprintf(mesg, "%s", a); 129 if (nl) { 130 fprintf(mesg, "\n"); 131 mesg_line_started = 0; 132 } else { 133 mesg_line_started = 1; 134 } 135 fflush(mesg); 136 } 137} 138 139void zipmessage(a, b) 140ZCONST char *a, *b; /* message strings juxtaposed in output */ 141/* Print a message to mesg and flush. Also write to log file if 142 open. Write new line first if current line has output already. */ 143{ 144 if (noisy) { 145 if (mesg_line_started) 146 fprintf(mesg, "\n"); 147 fprintf(mesg, "%s%s\n", a, b); 148 mesg_line_started = 0; 149 fflush(mesg); 150 } 151} 152 153void ziperr(c, h) 154int c; /* error code from the ZE_ class */ 155ZCONST char *h; /* message about how it happened */ 156/* Issue a message for the error, clean up files and memory, and exit. */ 157{ 158 if (PERR(c)) 159 perror("zipnote error"); 160 fprintf(mesg, "zipnote error: %s (%s)\n", ZIPERRORS(c), h); 161 if (tempzf != NULL) 162 fclose(tempzf); 163 if (tempzip != NULL) 164 { 165 destroy(tempzip); 166 free((zvoid *)tempzip); 167 } 168 if (zipfile != NULL) 169 free((zvoid *)zipfile); 170 EXIT(c); 171} 172 173 174local void handler(s) 175int s; /* signal number (ignored) */ 176/* Upon getting a user interrupt, abort cleanly using ziperr(). */ 177{ 178#ifndef MSDOS 179 putc('\n', mesg); 180#endif /* !MSDOS */ 181 ziperr(ZE_ABORT, "aborting"); 182 s++; /* keep some compilers happy */ 183} 184 185 186void zipwarn(a, b) 187ZCONST char *a, *b; /* message strings juxtaposed in output */ 188/* Print a warning message to mesg (usually stderr) and return. */ 189{ 190 fprintf(mesg, "zipnote warning: %s%s\n", a, b); 191} 192 193 194local void license() 195/* Print license information to stdout. */ 196{ 197 extent i; /* counter for copyright array */ 198 199 for (i = 0; i < sizeof(swlicense)/sizeof(char *); i++) 200 puts(swlicense[i]); 201} 202 203 204local void help() 205/* Print help (along with license info) to stdout. */ 206{ 207 extent i; /* counter for help array */ 208 209 /* help array */ 210 static ZCONST char *text[] = { 211"", 212"ZipNote %s (%s)", 213#ifdef VM_CMS 214"Usage: zipnote [-w] [-q] [-b fm] zipfile", 215#else 216"Usage: zipnote [-w] [-q] [-b path] zipfile", 217#endif 218" the default action is to write the comments in zipfile to stdout", 219" -w write the zipfile comments from stdin", 220#ifdef VM_CMS 221" -b use \"fm\" as the filemode for the temporary zip file", 222#else 223" -b use \"path\" for the temporary zip file", 224#endif 225" -q quieter operation, suppress some informational messages", 226" -h show this help -v show version info -L show software license", 227"", 228"Example:", 229#ifdef VMS 230" define/user sys$output foo.tmp", 231" zipnote foo.zip", 232" edit foo.tmp", 233" ... then you edit the comments, save, and exit ...", 234" define/user sys$input foo.tmp", 235" zipnote -w foo.zip", 236#else 237#ifdef RISCOS 238" zipnote foo/zip > foo/tmp", 239" <!Edit> foo/tmp", 240" ... then you edit the comments, save, and exit ...", 241" zipnote -w foo/zip < foo/tmp", 242#else 243#ifdef VM_CMS 244" zipnote foo.zip > foo.tmp", 245" xedit foo tmp", 246" ... then you edit the comments, save, and exit ...", 247" zipnote -w foo.zip < foo.tmp", 248#else 249" zipnote foo.zip > foo.tmp", 250" ed foo.tmp", 251" ... then you edit the comments, save, and exit ...", 252" zipnote -w foo.zip < foo.tmp", 253#endif /* VM_CMS */ 254#endif /* RISCOS */ 255#endif /* VMS */ 256"", 257" \"@ name\" can be followed by an \"@=newname\" line to change the name" 258 }; 259 260 for (i = 0; i < sizeof(copyright)/sizeof(char *); i++) { 261 printf(copyright[i], "zipnote"); 262 putchar('\n'); 263 } 264 for (i = 0; i < sizeof(text)/sizeof(char *); i++) 265 { 266 printf(text[i], VERSION, REVDATE); 267 putchar('\n'); 268 } 269} 270 271/* 272 * XXX put this in version.c 273 */ 274 275local void version_info() 276/* Print verbose info about program version and compile time options 277 to stdout. */ 278{ 279 extent i; /* counter in text arrays */ 280 281 /* Options info array */ 282 static ZCONST char *comp_opts[] = { 283#ifdef DEBUG 284 "DEBUG", 285#endif 286 NULL 287 }; 288 289 for (i = 0; i < sizeof(copyright)/sizeof(char *); i++) 290 { 291 printf(copyright[i], "zipnote"); 292 putchar('\n'); 293 } 294 295 for (i = 0; i < sizeof(versinfolines)/sizeof(char *); i++) 296 { 297 printf(versinfolines[i], "ZipNote", VERSION, REVDATE); 298 putchar('\n'); 299 } 300 301 version_local(); 302 303 puts("ZipNote special compilation options:"); 304 for (i = 0; (int)i < (int)(sizeof(comp_opts)/sizeof(char *) - 1); i++) 305 { 306 printf("\t%s\n",comp_opts[i]); 307 } 308 if (i == 0) 309 puts("\t[none]"); 310} 311 312 313local void putclean(s, n) 314char *s; /* string to write to stdout */ 315extent n; /* length of string */ 316/* Write the string s to stdout, filtering out control characters that are 317 not tab or newline (mainly to remove carriage returns), and prefix MARK's 318 and backslashes with a backslash. Also, terminate with a newline if 319 needed. */ 320{ 321 int c; /* next character in string */ 322 int e; /* last character written */ 323 324 e = '\n'; /* if empty, write nothing */ 325 while (n--) 326 { 327 c = *(uch *)s++; 328 if (c == MARK || c == '\\') 329 putchar('\\'); 330 if (c >= ' ' || c == '\t' || c == '\n') 331 { e=c; putchar(e); } 332 } 333 if (e != '\n') 334 putchar('\n'); 335} 336 337 338local char *zgetline(buf, size) 339char *buf; 340extent size; 341/* Read a line of text from stdin into string buffer 'buf' of size 'size'. 342 In case of buffer overflow or EOF, a NULL pointer is returned. */ 343{ 344 char *line; 345 unsigned len; 346 347 line = fgets(buf, size, stdin); 348 if (line != NULL && (len = strlen(line)) > 0) { 349 if (len == size-1 && line[len-1] != '\n') { 350 /* buffer is full and record delimiter not seen -> overflow */ 351 line = NULL; 352 } else { 353 /* delete trailing record delimiter */ 354 if (line[len-1] == '\n') line[len-1] = '\0'; 355 } 356 } 357 return line; 358} 359 360 361local int catalloc(a, s) 362char * far *a; /* pointer to a pointer to a malloc'ed string */ 363char *s; /* string to concatenate on a */ 364/* Concatentate the string s to the malloc'ed string pointed to by a. 365 Preprocess s by removing backslash escape characters. */ 366{ 367 char *p; /* temporary pointer */ 368 char *q; /* temporary pointer */ 369 370 for (p = q = s; *q; *p++ = *q++) 371 if (*q == '\\' && *(q+1)) 372 q++; 373 *p = 0; 374 if ((p = malloc(strlen(*a) + strlen(s) + 3)) == NULL) 375 return ZE_MEM; 376 strcat(strcat(strcpy(p, *a), **a ? "\r\n" : ""), s); 377 free((zvoid *)*a); 378 *a = p; 379 return ZE_OK; 380} 381 382 383#ifndef USE_ZIPNOTEMAIN 384int main(argc, argv) 385#else 386int zipnotemain(argc, argv) 387#endif 388int argc; /* number of tokens in command line */ 389char **argv; /* command line tokens */ 390/* Write the comments in the zipfile to stdout, or read them from stdin. */ 391{ 392 char abf[WRBUFSIZ+1]; /* input line buffer */ 393 char *a; /* pointer to line buffer or NULL */ 394 zoff_t c; /* start of central directory */ 395 int k; /* next argument type */ 396 char *q; /* steps through option arguments */ 397 int r; /* arg counter, temporary variable */ 398 zoff_t s; /* length of central directory */ 399 int t; /* attributes of zip file */ 400 int w; /* true if updating zip file from stdin */ 401 FILE *x; /* input file for testing if can write it */ 402 struct zlist far *z; /* steps through zfiles linked list */ 403 404#ifdef THEOS 405 setlocale(LC_CTYPE, "I"); 406#endif 407 408#ifdef UNICODE_SUPPORT 409# ifdef UNIX 410 /* For Unix, set the locale to UTF-8. Any UTF-8 locale is 411 OK and they should all be the same. This allows seeing, 412 writing, and displaying (if the fonts are loaded) all 413 characters in UTF-8. */ 414 { 415 char *loc; 416 417 /* 418 loc = setlocale(LC_CTYPE, NULL); 419 printf(" Initial language locale = '%s'\n", loc); 420 */ 421 422 loc = setlocale(LC_CTYPE, "en_US.UTF-8"); 423 424 /* 425 printf("langinfo %s\n", nl_langinfo(CODESET)); 426 */ 427 428 if (loc != NULL) { 429 /* using UTF-8 character set so can set UTF-8 GPBF bit 11 */ 430 using_utf8 = 1; 431 /* 432 printf(" Locale set to %s\n", loc); 433 */ 434 } else { 435 /* 436 printf(" Could not set Unicode UTF-8 locale\n"); 437 */ 438 } 439 } 440# endif 441#endif 442 443 /* If no args, show help */ 444 if (argc == 1) 445 { 446 help(); 447 EXIT(ZE_OK); 448 } 449 450 /* Direct info messages to stderr; stdout is used for data output. */ 451 mesg = stderr; 452 453 init_upper(); /* build case map table */ 454 455 /* Go through args */ 456 zipfile = tempzip = NULL; 457 tempzf = NULL; 458 signal(SIGINT, handler); 459#ifdef SIGTERM /* AMIGA has no SIGTERM */ 460 signal(SIGTERM, handler); 461#endif 462#ifdef SIGABRT 463 signal(SIGABRT, handler); 464#endif 465#ifdef SIGBREAK 466 signal(SIGBREAK, handler); 467#endif 468#ifdef SIGBUS 469 signal(SIGBUS, handler); 470#endif 471#ifdef SIGILL 472 signal(SIGILL, handler); 473#endif 474#ifdef SIGSEGV 475 signal(SIGSEGV, handler); 476#endif 477 k = w = 0; 478 for (r = 1; r < argc; r++) 479 if (*argv[r] == '-') { 480 if (argv[r][1]) 481 for (q = argv[r]+1; *q; q++) 482 switch (*q) 483 { 484 case 'b': /* Specify path for temporary file */ 485 if (k) 486 ziperr(ZE_PARMS, "use -b before zip file name"); 487 else 488 k = 1; /* Next non-option is path */ 489 break; 490 case 'h': /* Show help */ 491 help(); EXIT(ZE_OK); 492 case 'l': case 'L': /* Show copyright and disclaimer */ 493 license(); EXIT(ZE_OK); 494 case 'q': /* Quiet operation, suppress info messages */ 495 noisy = 0; break; 496 case 'v': /* Show version info */ 497 version_info(); EXIT(ZE_OK); 498 case 'w': 499 w = 1; break; 500 default: 501 ziperr(ZE_PARMS, "unknown option"); 502 } 503 else 504 ziperr(ZE_PARMS, "zip file cannot be stdin"); 505 } else 506 if (k == 0) 507 { 508 if (zipfile == NULL) 509 { 510 if ((zipfile = ziptyp(argv[r])) == NULL) 511 ziperr(ZE_MEM, "was processing arguments"); 512 } 513 else 514 ziperr(ZE_PARMS, "can only specify one zip file"); 515 } 516 else 517 { 518 tempath = argv[r]; 519 k = 0; 520 } 521 if (zipfile == NULL) 522 ziperr(ZE_PARMS, "need to specify zip file"); 523 524 if ((in_path = malloc(strlen(zipfile) + 1)) == NULL) { 525 ziperr(ZE_MEM, "input"); 526 } 527 strcpy(in_path, zipfile); 528 529 /* Read zip file */ 530 if ((r = readzipfile()) != ZE_OK) 531 ziperr(r, zipfile); 532 if (zfiles == NULL) 533 ziperr(ZE_NAME, zipfile); 534 535 /* Put comments to stdout, if not -w */ 536 if (!w) 537 { 538 for (z = zfiles; z != NULL; z = z->nxt) 539 { 540 printf("%c %s\n", MARK, z->zname); 541 putclean(z->comment, z->com); 542 printf("%c%s\n", MARK, MARKE); 543 } 544 printf("%c%s\n", MARK, MARKZ); 545 putclean(zcomment, zcomlen); 546 EXIT(ZE_OK); 547 } 548 549 /* If updating comments, make sure zip file is writeable */ 550 if ((x = fopen(zipfile, "a")) == NULL) 551 ziperr(ZE_CREAT, zipfile); 552 fclose(x); 553 t = getfileattr(zipfile); 554 555 /* Process stdin, replacing comments */ 556 z = zfiles; 557 while ((a = zgetline(abf, WRBUFSIZ+1)) != NULL && 558 (a[0] != MARK || strcmp(a + 1, MARKZ))) 559 { /* while input and not file comment */ 560 if (a[0] != MARK || a[1] != ' ') /* better be "@ name" */ 561 ziperr(ZE_NOTE, "unexpected input"); 562 while (z != NULL && strcmp(a + 2, z->zname)) 563 z = z->nxt; /* allow missing entries in order */ 564 if (z == NULL) 565 ziperr(ZE_NOTE, "unknown entry name"); 566 if ((a = zgetline(abf, WRBUFSIZ+1)) != NULL && a[0] == MARK && a[1] == '=') 567 { 568 if (z->name != z->iname) 569 free((zvoid *)z->iname); 570 if ((z->iname = malloc(strlen(a+1))) == NULL) 571 ziperr(ZE_MEM, "was changing name"); 572#ifdef EBCDIC 573 strtoasc(z->iname, a+2); 574#else 575 strcpy(z->iname, a+2); 576#endif 577 578/* 579 * Don't update z->nam here, we need the old value a little later..... 580 * The update is handled in zipcopy(). 581 */ 582 a = zgetline(abf, WRBUFSIZ+1); 583 } 584 if (z->com) /* change zip entry comment */ 585 free((zvoid *)z->comment); 586 z->comment = malloc(1); *(z->comment) = 0; 587 while (a != NULL && *a != MARK) 588 { 589 if ((r = catalloc(&(z->comment), a)) != ZE_OK) 590 ziperr(r, "was building new zipentry comments"); 591 a = zgetline(abf, WRBUFSIZ+1); 592 } 593 z->com = strlen(z->comment); 594 z = z->nxt; /* point to next entry */ 595 } 596 if (a != NULL) /* change zip file comment */ 597 { 598 zcomment = malloc(1); *zcomment = 0; 599 while ((a = zgetline(abf, WRBUFSIZ+1)) != NULL) 600 if ((r = catalloc(&zcomment, a)) != ZE_OK) 601 ziperr(r, "was building new zipfile comment"); 602 zcomlen = strlen(zcomment); 603 } 604 605 /* Open output zip file for writing */ 606#if defined(UNIX) && !defined(NO_MKSTEMP) 607 { 608 int yd; 609 int i; 610 611 /* use mkstemp to avoid race condition and compiler warning */ 612 613 if (tempath != NULL) 614 { 615 /* if -b used to set temp file dir use that for split temp */ 616 if ((tempzip = malloc(strlen(tempath) + 12)) == NULL) { 617 ZIPERR(ZE_MEM, "allocating temp filename"); 618 } 619 strcpy(tempzip, tempath); 620 if (lastchar(tempzip) != '/') 621 strcat(tempzip, "/"); 622 } 623 else 624 { 625 /* create path by stripping name and appending template */ 626 if ((tempzip = malloc(strlen(zipfile) + 12)) == NULL) { 627 ZIPERR(ZE_MEM, "allocating temp filename"); 628 } 629 strcpy(tempzip, zipfile); 630 for(i = strlen(tempzip); i > 0; i--) { 631 if (tempzip[i - 1] == '/') 632 break; 633 } 634 tempzip[i] = '\0'; 635 } 636 strcat(tempzip, "ziXXXXXX"); 637 638 if ((yd = mkstemp(tempzip)) == EOF) { 639 ZIPERR(ZE_TEMP, tempzip); 640 } 641 if ((tempzf = y = fdopen(yd, FOPW)) == NULL) { 642 ZIPERR(ZE_TEMP, tempzip); 643 } 644 } 645#else 646 if ((tempzf = y = fopen(tempzip = tempname(zipfile), FOPW)) == NULL) 647 ziperr(ZE_TEMP, tempzip); 648#endif 649 650 /* Open input zip file again, copy preamble if any */ 651 if ((in_file = fopen(zipfile, FOPR)) == NULL) 652 ziperr(ZE_NAME, zipfile); 653 654 if (zipbeg && (r = bfcopy(zipbeg)) != ZE_OK) 655 ziperr(r, r == ZE_TEMP ? tempzip : zipfile); 656 tempzn = zipbeg; 657 658 /* Go through local entries, copying them over as is */ 659 fix = 3; /* needed for zipcopy if name changed */ 660 for (z = zfiles; z != NULL; z = z->nxt) { 661 if ((r = zipcopy(z)) != ZE_OK) 662 ziperr(r, "was copying an entry"); 663 } 664 fclose(x); 665 666 /* Write central directory and end of central directory with new comments */ 667 if ((c = zftello(y)) == (zoff_t)-1) /* get start of central */ 668 ziperr(ZE_TEMP, tempzip); 669 for (z = zfiles; z != NULL; z = z->nxt) 670 if ((r = putcentral(z)) != ZE_OK) 671 ziperr(r, tempzip); 672 if ((s = zftello(y)) == (zoff_t)-1) /* get end of central */ 673 ziperr(ZE_TEMP, tempzip); 674 s -= c; /* compute length of central */ 675 if ((r = putend((zoff_t)zcount, s, c, zcomlen, zcomment)) != ZE_OK) 676 ziperr(r, tempzip); 677 tempzf = NULL; 678 if (fclose(y)) 679 ziperr(ZE_TEMP, tempzip); 680 if ((r = replace(zipfile, tempzip)) != ZE_OK) 681 { 682 zipwarn("new zip file left as: ", tempzip); 683 free((zvoid *)tempzip); 684 tempzip = NULL; 685 ziperr(r, "was replacing the original zip file"); 686 } 687 free((zvoid *)tempzip); 688 tempzip = NULL; 689 setfileattr(zipfile, t); 690#ifdef RISCOS 691 /* Set the filetype of the zipfile to &DDC */ 692 setfiletype(zipfile,0xDDC); 693#endif 694 free((zvoid *)zipfile); 695 zipfile = NULL; 696 697 /* Done! */ 698 RETURN(0); 699} 700