mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-01-31 03:27:39 +00:00
5660ec4741
This release is an atomic upgrade to GCC 14.1.0 with C23 and C++23
4824 lines
137 KiB
C
4824 lines
137 KiB
C
/*
|
|
fileio.c - Zip 3
|
|
|
|
Copyright (c) 1990-2008 Info-ZIP. All rights reserved.
|
|
|
|
See the accompanying file LICENSE, version 2007-Mar-4 or later
|
|
(the contents of which are also included in zip.h) for terms of use.
|
|
If, for some reason, all these files are missing, the Info-ZIP license
|
|
also may be found at: ftp://ftp.info-zip.org/pub/infozip/license.html
|
|
*/
|
|
/*
|
|
* fileio.c by Mark Adler
|
|
*/
|
|
#define __FILEIO_C
|
|
|
|
#include "third_party/zip/zip.h"
|
|
#include "libc/ctype.h"
|
|
#include "libc/wctype.h"
|
|
#include "third_party/zip/crc32.h"
|
|
|
|
#ifdef MACOS
|
|
// MISSING #include "helpers.h"
|
|
#endif
|
|
|
|
#ifdef VMS
|
|
// MISSING #include "vms/vms.h"
|
|
#endif /* def VMS */
|
|
|
|
#include "libc/calls/struct/timespec.h"
|
|
#include "libc/calls/struct/timeval.h"
|
|
#include "libc/calls/weirdtypes.h"
|
|
#include "libc/sysv/consts/clock.h"
|
|
#include "libc/sysv/consts/sched.h"
|
|
#include "libc/sysv/consts/timer.h"
|
|
#include "libc/time.h"
|
|
|
|
#ifdef NO_MKTIME
|
|
time_t mktime OF((struct tm *));
|
|
#endif
|
|
|
|
#ifdef OSF
|
|
#define EXDEV 18 /* avoid a bug in the DEC OSF/1 header files. */
|
|
#else
|
|
#include "libc/errno.h"
|
|
#endif
|
|
|
|
#ifdef NO_ERRNO
|
|
extern int errno;
|
|
#endif
|
|
|
|
/* -----------------------
|
|
For long option support
|
|
----------------------- */
|
|
#include "libc/str/str.h"
|
|
|
|
|
|
#if defined(VMS) || defined(TOPS20)
|
|
# define PAD 5
|
|
#else
|
|
# define PAD 0
|
|
#endif
|
|
|
|
#ifdef NO_RENAME
|
|
int rename OF((ZCONST char *, ZCONST char *));
|
|
#endif
|
|
|
|
|
|
/* Local functions */
|
|
local int optionerr OF((char *, ZCONST char *, int, int));
|
|
local unsigned long get_shortopt OF((char **, int, int *, int *, char **, int *, int));
|
|
local unsigned long get_longopt OF((char **, int, int *, int *, char **, int *, int));
|
|
|
|
#ifdef UNICODE_SUPPORT
|
|
local int utf8_char_bytes OF((ZCONST char *utf8));
|
|
local long ucs4_char_from_utf8 OF((ZCONST char **utf8 ));
|
|
local int utf8_from_ucs4_char OF((char *utf8buf, ulg ch));
|
|
local int utf8_to_ucs4_string OF((ZCONST char *utf8, ulg *usc4buf,
|
|
int buflen));
|
|
local int ucs4_string_to_utf8 OF((ZCONST ulg *ucs4, char *utf8buf,
|
|
int buflen));
|
|
#if 0
|
|
local int utf8_chars OF((ZCONST char *utf8));
|
|
#endif
|
|
#endif /* UNICODE_SUPPORT */
|
|
|
|
#ifndef UTIL /* the companion #endif is a bit of ways down ... */
|
|
|
|
local int fqcmp OF((ZCONST zvoid *, ZCONST zvoid *));
|
|
local int fqcmpz OF((ZCONST zvoid *, ZCONST zvoid *));
|
|
|
|
|
|
/* Local module level variables. */
|
|
char *label = NULL; /* global, but only used in `system'.c */
|
|
local z_stat zipstatb; /* now use z_stat globally - 7/24/04 EG */
|
|
#if defined(UNICODE_SUPPORT) && defined(WIN32)
|
|
local zw_stat zipstatbw;
|
|
#endif
|
|
#if (!defined(MACOS) && !defined(WINDLL))
|
|
local int zipstate = -1;
|
|
#else
|
|
int zipstate;
|
|
#endif
|
|
/* -1 unknown, 0 old zip file exists, 1 new zip file */
|
|
|
|
#if 0
|
|
char *getnam(n, fp)
|
|
char *n; /* where to put name (must have >=FNMAX+1 bytes) */
|
|
#endif
|
|
|
|
/* converted to return string pointer from malloc to avoid
|
|
size limitation - 11/8/04 EG */
|
|
#define GETNAM_MAX 9000 /* hopefully big enough for now */
|
|
char *getnam(fp)
|
|
FILE *fp;
|
|
/* Read a \n or \r delimited name from stdin into n, and return
|
|
n. If EOF, then return NULL. Also, if problem return NULL. */
|
|
{
|
|
char name[GETNAM_MAX + 1];
|
|
int c; /* last character read */
|
|
char *p; /* pointer into name area */
|
|
|
|
|
|
p = name;
|
|
while ((c = getc(fp)) == '\n' || c == '\r')
|
|
;
|
|
if (c == EOF)
|
|
return NULL;
|
|
do {
|
|
if (p - name >= GETNAM_MAX)
|
|
return NULL;
|
|
*p++ = (char) c;
|
|
c = getc(fp);
|
|
} while (c != EOF && (c != '\n' && c != '\r'));
|
|
#ifdef WIN32
|
|
/*
|
|
* WIN32 strips off trailing spaces and periods in filenames
|
|
* XXX what about a filename that only consists of spaces ?
|
|
* Answer: on WIN32, a filename must contain at least one non-space char
|
|
*/
|
|
while (p > name) {
|
|
if ((c = p[-1]) != ' ' && c != '.')
|
|
break;
|
|
--p;
|
|
}
|
|
#endif
|
|
*p = 0;
|
|
/* malloc a copy */
|
|
if ((p = malloc(strlen(name) + 1)) == NULL) {
|
|
return NULL;
|
|
}
|
|
strcpy(p, name);
|
|
return p;
|
|
}
|
|
|
|
struct flist far *fexpel(f)
|
|
struct flist far *f; /* entry to delete */
|
|
/* Delete the entry *f in the doubly-linked found list. Return pointer to
|
|
next entry to allow stepping through list. */
|
|
{
|
|
struct flist far *t; /* temporary variable */
|
|
|
|
t = f->nxt;
|
|
*(f->lst) = t; /* point last to next, */
|
|
if (t != NULL)
|
|
t->lst = f->lst; /* and next to last */
|
|
if (f->name != NULL) /* free memory used */
|
|
free((zvoid *)(f->name));
|
|
if (f->zname != NULL)
|
|
free((zvoid *)(f->zname));
|
|
if (f->iname != NULL)
|
|
free((zvoid *)(f->iname));
|
|
#ifdef UNICODE_SUPPORT
|
|
if (f->uname)
|
|
free((zvoid *)f->uname);
|
|
# ifdef WIN32
|
|
if (f->namew)
|
|
free((zvoid *)f->namew);
|
|
if (f->inamew)
|
|
free((zvoid *)f->inamew);
|
|
if (f->znamew)
|
|
free((zvoid *)f->znamew);
|
|
# endif
|
|
#endif
|
|
farfree((zvoid far *)f);
|
|
fcount--; /* decrement count */
|
|
return t; /* return pointer to next */
|
|
}
|
|
|
|
local int fqcmp(a, b)
|
|
ZCONST zvoid *a, *b; /* pointers to pointers to found entries */
|
|
/* Used by qsort() to compare entries in the found list by name. */
|
|
{
|
|
return strcmp((*(struct flist far **)a)->name,
|
|
(*(struct flist far **)b)->name);
|
|
}
|
|
|
|
local int fqcmpz(a, b)
|
|
ZCONST zvoid *a, *b; /* pointers to pointers to found entries */
|
|
/* Used by qsort() to compare entries in the found list by iname. */
|
|
{
|
|
return strcmp((*(struct flist far **)a)->iname,
|
|
(*(struct flist far **)b)->iname);
|
|
}
|
|
|
|
char *last(p, c)
|
|
char *p; /* sequence of path components */
|
|
int c; /* path components separator character */
|
|
/* Return a pointer to the start of the last path component. For a directory
|
|
* name terminated by the character in c, the return value is an empty string.
|
|
*/
|
|
{
|
|
char *t; /* temporary variable */
|
|
|
|
if ((t = strrchr(p, c)) != NULL)
|
|
return t + 1;
|
|
else
|
|
#ifndef AOS_VS
|
|
return p;
|
|
#else
|
|
/* We want to allow finding of end of path in either AOS/VS-style pathnames
|
|
* or Unix-style pathnames. This presents a few little problems ...
|
|
*/
|
|
{
|
|
if (*p == '=' || *p == '^') /* like ./ and ../ respectively */
|
|
return p + 1;
|
|
else
|
|
return p;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#if defined(UNICODE_SUPPORT) && defined(WIN32)
|
|
wchar_t *lastw(pw, c)
|
|
wchar_t *pw; /* sequence of path components */
|
|
wchar_t c; /* path components separator character */
|
|
/* Return a pointer to the start of the last path component. For a directory
|
|
* name terminated by the character in c, the return value is an empty string.
|
|
*/
|
|
{
|
|
wchar_t *tw; /* temporary variable */
|
|
|
|
if ((tw = wcsrchr(pw, c)) != NULL)
|
|
return tw + 1;
|
|
else
|
|
# ifndef AOS_VS
|
|
return pw;
|
|
# else
|
|
/* We want to allow finding of end of path in either AOS/VS-style pathnames
|
|
* or Unix-style pathnames. This presents a few little problems ...
|
|
*/
|
|
{
|
|
if (*pw == (wchar_t)'=' || *pw == (wchar_t)'^') /* like ./ and ../ respectively */
|
|
return pw + 1;
|
|
else
|
|
return pw;
|
|
}
|
|
# endif
|
|
}
|
|
#endif
|
|
|
|
|
|
char *msname(n)
|
|
char *n;
|
|
/* Reduce all path components to MSDOS upper case 8.3 style names. */
|
|
{
|
|
int c; /* current character */
|
|
int f; /* characters in current component */
|
|
char *p; /* source pointer */
|
|
char *q; /* destination pointer */
|
|
|
|
p = q = n;
|
|
f = 0;
|
|
while ((c = (unsigned char)*POSTINCSTR(p)) != 0)
|
|
if (c == ' ' || c == ':' || c == '"' || c == '*' || c == '+' ||
|
|
c == ',' || c == ';' || c == '<' || c == '=' || c == '>' ||
|
|
c == '?' || c == '[' || c == ']' || c == '|')
|
|
continue; /* char is discarded */
|
|
else if (c == '/')
|
|
{
|
|
*POSTINCSTR(q) = (char)c;
|
|
f = 0; /* new component */
|
|
}
|
|
#ifdef __human68k__
|
|
else if (ismbblead(c) && *p)
|
|
{
|
|
if (f == 7 || f == 11)
|
|
f++;
|
|
else if (*p && f < 12 && f != 8)
|
|
{
|
|
*q++ = c;
|
|
*q++ = *p++;
|
|
f += 2;
|
|
}
|
|
}
|
|
#endif /* __human68k__ */
|
|
else if (c == '.')
|
|
{
|
|
if (f == 0)
|
|
continue; /* leading dots are discarded */
|
|
else if (f < 9)
|
|
{
|
|
*POSTINCSTR(q) = (char)c;
|
|
f = 9; /* now in file type */
|
|
}
|
|
else
|
|
f = 12; /* now just excess characters */
|
|
}
|
|
else
|
|
if (f < 12 && f != 8)
|
|
{
|
|
f += CLEN(p); /* do until end of name or type */
|
|
*POSTINCSTR(q) = (char)(to_up(c));
|
|
}
|
|
*q = 0;
|
|
return n;
|
|
}
|
|
|
|
#ifdef UNICODE_SUPPORT
|
|
wchar_t *msnamew(nw)
|
|
wchar_t *nw;
|
|
/* Reduce all path components to MSDOS upper case 8.3 style names. */
|
|
{
|
|
wchar_t c; /* current character */
|
|
int f; /* characters in current component */
|
|
wchar_t *pw; /* source pointer */
|
|
wchar_t *qw; /* destination pointer */
|
|
|
|
pw = qw = nw;
|
|
f = 0;
|
|
while ((c = (unsigned char)*pw++) != 0)
|
|
if (c == ' ' || c == ':' || c == '"' || c == '*' || c == '+' ||
|
|
c == ',' || c == ';' || c == '<' || c == '=' || c == '>' ||
|
|
c == '?' || c == '[' || c == ']' || c == '|')
|
|
continue; /* char is discarded */
|
|
else if (c == '/')
|
|
{
|
|
*qw++ = c;
|
|
f = 0; /* new component */
|
|
}
|
|
#ifdef __human68k__
|
|
else if (ismbblead(c) && *pw)
|
|
{
|
|
if (f == 7 || f == 11)
|
|
f++;
|
|
else if (*pw && f < 12 && f != 8)
|
|
{
|
|
*qw++ = c;
|
|
*qw++ = *pw++;
|
|
f += 2;
|
|
}
|
|
}
|
|
#endif /* __human68k__ */
|
|
else if (c == '.')
|
|
{
|
|
if (f == 0)
|
|
continue; /* leading dots are discarded */
|
|
else if (f < 9)
|
|
{
|
|
*qw++ = c;
|
|
f = 9; /* now in file type */
|
|
}
|
|
else
|
|
f = 12; /* now just excess characters */
|
|
}
|
|
else
|
|
if (f < 12 && f != 8)
|
|
{
|
|
f++; /* do until end of name or type */
|
|
*qw++ = towupper(c);
|
|
}
|
|
*qw = 0;
|
|
return nw;
|
|
}
|
|
#endif
|
|
|
|
|
|
int proc_archive_name(n, caseflag)
|
|
char *n; /* name to process */
|
|
int caseflag; /* true to force case-sensitive match */
|
|
/* Process a name or sh expression in existing archive to operate
|
|
on (or exclude). Return an error code in the ZE_ class. */
|
|
{
|
|
int m; /* matched flag */
|
|
char *p; /* path for recursion */
|
|
struct zlist far *z; /* steps through zfiles list */
|
|
|
|
if (strcmp(n, "-") == 0) { /* if compressing stdin */
|
|
zipwarn("Cannot select stdin when selecting archive entries", "");
|
|
return ZE_MISS;
|
|
}
|
|
else
|
|
{
|
|
/* Search for shell expression in zip file */
|
|
p = ex2in(n, 0, (int *)NULL); /* shouldn't affect matching chars */
|
|
m = 1;
|
|
for (z = zfiles; z != NULL; z = z->nxt) {
|
|
if (MATCH(p, z->iname, caseflag))
|
|
{
|
|
z->mark = pcount ? filter(z->zname, caseflag) : 1;
|
|
if (verbose)
|
|
fprintf(mesg, "zip diagnostic: %scluding %s\n",
|
|
z->mark ? "in" : "ex", z->oname);
|
|
m = 0;
|
|
}
|
|
}
|
|
#ifdef UNICODE_SUPPORT
|
|
/* also check escaped Unicode names */
|
|
for (z = zfiles; z != NULL; z = z->nxt) {
|
|
if (z->zuname) {
|
|
#ifdef WIN32
|
|
/* It seems something is lost in going from a listed
|
|
name from zip -su in a console window to using that
|
|
name in a command line. This kluge may fix it
|
|
and just takes zuname, converts to oem (i.e. ouname),
|
|
then converts it back which ends up not the same as
|
|
started with.
|
|
*/
|
|
char *zuname = z->wuname;
|
|
#else
|
|
char *zuname = z->zuname;
|
|
#endif
|
|
if (MATCH(p, zuname, caseflag))
|
|
{
|
|
z->mark = pcount ? filter(zuname, caseflag) : 1;
|
|
if (verbose) {
|
|
fprintf(mesg, "zip diagnostic: %scluding %s\n",
|
|
z->mark ? "in" : "ex", z->oname);
|
|
fprintf(mesg, " Escaped Unicode: %s\n",
|
|
z->ouname);
|
|
}
|
|
m = 0;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
free((zvoid *)p);
|
|
return m ? ZE_MISS : ZE_OK;
|
|
}
|
|
}
|
|
|
|
|
|
int check_dup()
|
|
/* Sort the found list and remove duplicates.
|
|
Return an error code in the ZE_ class. */
|
|
{
|
|
struct flist far *f; /* steps through found linked list */
|
|
extent j, k; /* indices for s */
|
|
struct flist far **s; /* sorted table */
|
|
struct flist far **nodup; /* sorted table without duplicates */
|
|
|
|
/* sort found list, remove duplicates */
|
|
if (fcount)
|
|
{
|
|
extent fl_size = fcount * sizeof(struct flist far *);
|
|
if ((fl_size / sizeof(struct flist far *)) != fcount ||
|
|
(s = (struct flist far **)malloc(fl_size)) == NULL)
|
|
return ZE_MEM;
|
|
for (j = 0, f = found; f != NULL; f = f->nxt)
|
|
s[j++] = f;
|
|
/* Check names as given (f->name) */
|
|
qsort((char *)s, fcount, sizeof(struct flist far *), fqcmp);
|
|
for (k = j = fcount - 1; j > 0; j--)
|
|
if (strcmp(s[j - 1]->name, s[j]->name) == 0)
|
|
/* remove duplicate entry from list */
|
|
fexpel(s[j]); /* fexpel() changes fcount */
|
|
else
|
|
/* copy valid entry into destination position */
|
|
s[k--] = s[j];
|
|
s[k] = s[0]; /* First entry is always valid */
|
|
nodup = &s[k]; /* Valid entries are at end of array s */
|
|
|
|
/* sort only valid items and check for unique internal names (f->iname) */
|
|
qsort((char *)nodup, fcount, sizeof(struct flist far *), fqcmpz);
|
|
for (j = 1; j < fcount; j++)
|
|
if (strcmp(nodup[j - 1]->iname, nodup[j]->iname) == 0)
|
|
{
|
|
char tempbuf[FNMAX+4081];
|
|
|
|
sprintf(errbuf, " first full name: %s\n", nodup[j - 1]->name);
|
|
sprintf(tempbuf, " second full name: %s\n", nodup[j]->name);
|
|
strcat(errbuf, " ");
|
|
strcat(errbuf, tempbuf);
|
|
#ifdef EBCDIC
|
|
strtoebc(nodup[j]->iname, nodup[j]->iname);
|
|
#endif
|
|
sprintf(tempbuf, "name in zip file repeated: %s", nodup[j]->iname);
|
|
strcat(errbuf, " ");
|
|
strcat(errbuf, tempbuf);
|
|
if (pathput == 0) {
|
|
strcat(errbuf, "\n this may be a result of using -j");
|
|
}
|
|
#ifdef EBCDIC
|
|
strtoasc(nodup[j]->iname, nodup[j]->iname);
|
|
#endif
|
|
zipwarn(errbuf, "");
|
|
return ZE_PARMS;
|
|
}
|
|
free((zvoid *)s);
|
|
}
|
|
return ZE_OK;
|
|
}
|
|
|
|
int filter(name, casesensitive)
|
|
char *name;
|
|
int casesensitive;
|
|
/* Scan the -R, -i and -x lists for matches to the given name.
|
|
Return TRUE if the name must be included, FALSE otherwise.
|
|
Give precedence to -x over -i and -R.
|
|
Note that if both R and i patterns are given then must
|
|
have a match for both.
|
|
This routine relies on the following global variables:
|
|
patterns array of match pattern structures
|
|
pcount total number of patterns
|
|
icount number of -i patterns
|
|
Rcount number of -R patterns
|
|
These data are set up by the command line parsing code.
|
|
*/
|
|
{
|
|
unsigned int n;
|
|
int slashes;
|
|
char *p, *q;
|
|
/* without -i patterns, every name matches the "-i select rules" */
|
|
int imatch = (icount == 0);
|
|
/* without -R patterns, every name matches the "-R select rules" */
|
|
int Rmatch = (Rcount == 0);
|
|
|
|
if (pcount == 0) return TRUE;
|
|
|
|
for (n = 0; n < pcount; n++) {
|
|
if (!patterns[n].zname[0]) /* it can happen... */
|
|
continue;
|
|
p = name;
|
|
switch (patterns[n].select) {
|
|
case 'R':
|
|
if (Rmatch)
|
|
/* one -R match is sufficient, skip this pattern */
|
|
continue;
|
|
/* With -R patterns, if the pattern has N path components (that is,
|
|
N-1 slashes), then we test only the last N components of name.
|
|
*/
|
|
slashes = 0;
|
|
for (q = patterns[n].zname; (q = MBSCHR(q, '/')) != NULL; MB_NEXTCHAR(q))
|
|
slashes++;
|
|
/* The name may have M path components (M-1 slashes) */
|
|
for (q = p; (q = MBSCHR(q, '/')) != NULL; MB_NEXTCHAR(q))
|
|
slashes--;
|
|
/* Now, "slashes" contains the difference "N-M" between the number
|
|
of path components in the pattern (N) and in the name (M).
|
|
*/
|
|
if (slashes < 0)
|
|
/* We found "M > N"
|
|
--> skip the first (M-N) path components of the name.
|
|
*/
|
|
for (q = p; (q = MBSCHR(q, '/')) != NULL; MB_NEXTCHAR(q))
|
|
if (++slashes == 0) {
|
|
p = q + 1; /* q points at '/', mblen("/") is 1 */
|
|
break;
|
|
}
|
|
break;
|
|
case 'i':
|
|
if (imatch)
|
|
/* one -i match is sufficient, skip this pattern */
|
|
continue;
|
|
break;
|
|
}
|
|
if (MATCH(patterns[n].zname, p, casesensitive)) {
|
|
switch (patterns[n].select) {
|
|
case 'x':
|
|
/* The -x match takes precedence over everything else */
|
|
return FALSE;
|
|
case 'R':
|
|
Rmatch = TRUE;
|
|
break;
|
|
default:
|
|
/* this must be a type -i match */
|
|
imatch = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return imatch && Rmatch;
|
|
}
|
|
|
|
|
|
#ifdef UNICODE_SUPPORT
|
|
# ifdef WIN32
|
|
|
|
int newnamew(namew, isdir, casesensitive)
|
|
wchar_t *namew; /* name to add (or exclude) */
|
|
int isdir; /* true for a directory */
|
|
int casesensitive; /* true for case-sensitive matching */
|
|
/* Add (or exclude) the name of an existing disk file. Return an error
|
|
code in the ZE_ class. */
|
|
{
|
|
wchar_t *inamew = NULL; /* internal name */
|
|
wchar_t *znamew = NULL; /* external version of iname */
|
|
wchar_t *undosmw = NULL; /* zname version with "-j" and "-k" options disabled */
|
|
char *oname = NULL; /* iname converted for display */
|
|
char *name = NULL;
|
|
char *iname = NULL;
|
|
char *zname = NULL;
|
|
char *zuname = NULL;
|
|
char *undosm = NULL;
|
|
struct flist far *f; /* where in found, or new found entry */
|
|
struct zlist far *z; /* where in zfiles (if found) */
|
|
int dosflag;
|
|
|
|
/* Scanning files ...
|
|
*
|
|
* After 5 seconds output Scanning files...
|
|
* then a dot every 2 seconds
|
|
*/
|
|
if (noisy) {
|
|
/* If find files then output message after delay */
|
|
if (scan_count == 0) {
|
|
time_t current = time(NULL);
|
|
scan_start = current;
|
|
}
|
|
scan_count++;
|
|
if (scan_count % 100 == 0) {
|
|
time_t current = time(NULL);
|
|
|
|
if (current - scan_start > scan_delay) {
|
|
if (scan_last == 0) {
|
|
zipmessage_nl("Scanning files ", 0);
|
|
scan_last = current;
|
|
}
|
|
if (current - scan_last > scan_dot_time) {
|
|
scan_last = current;
|
|
fprintf(mesg, ".");
|
|
fflush(mesg);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Search for name in zip file. If there, mark it, else add to
|
|
list of new names to do (or remove from that list). */
|
|
if ((inamew = ex2inw(namew, isdir, &dosflag)) == NULL)
|
|
return ZE_MEM;
|
|
|
|
/* Discard directory names with zip -rj */
|
|
if (*inamew == (wchar_t)'\0') {
|
|
|
|
/* If extensions needs to be swapped, we will have empty directory names
|
|
instead of the original directory. For example, zipping 'c.', 'c.main'
|
|
should zip only 'main.c' while 'c.' will be converted to '\0' by ex2in. */
|
|
|
|
if (pathput && !recurse) error("empty name without -j or -r");
|
|
free((zvoid *)inamew);
|
|
return ZE_OK;
|
|
}
|
|
|
|
if (dosflag || !pathput) {
|
|
int save_dosify = dosify, save_pathput = pathput;
|
|
dosify = 0;
|
|
pathput = 1;
|
|
/* zname is temporarly mis-used as "undosmode" iname pointer */
|
|
if ((znamew = ex2inw(namew, isdir, NULL)) != NULL) {
|
|
undosmw = in2exw(znamew);
|
|
free(znamew);
|
|
}
|
|
dosify = save_dosify;
|
|
pathput = save_pathput;
|
|
}
|
|
if ((znamew = in2exw(inamew)) == NULL)
|
|
return ZE_MEM;
|
|
|
|
/* Convert names from wchar_t to char */
|
|
|
|
name = wchar_to_local_string(namew);
|
|
iname = wchar_to_local_string(inamew);
|
|
zname = wchar_to_local_string(znamew);
|
|
|
|
oname = local_to_display_string(zname);
|
|
|
|
zuname = wchar_to_local_string(znamew);
|
|
|
|
if (undosmw == NULL)
|
|
undosmw = znamew;
|
|
undosm = wchar_to_local_string(undosmw);
|
|
|
|
if ((z = zsearch(zuname)) != NULL) {
|
|
if (pcount && !filter(undosm, casesensitive)) {
|
|
/* Do not clear z->mark if "exclude", because, when "dosify || !pathput"
|
|
* is in effect, two files with different filter options may hit the
|
|
* same z entry.
|
|
*/
|
|
if (verbose)
|
|
fprintf(mesg, "excluding %s\n", oname);
|
|
} else {
|
|
z->mark = 1;
|
|
if ((z->name = malloc(strlen(name) + 1 + PAD)) == NULL) {
|
|
if (undosmw != znamew)
|
|
free(undosmw);
|
|
if (undosm) free(undosm);
|
|
if (inamew) free(inamew);
|
|
if (znamew) free(znamew);
|
|
if (name) free(name);
|
|
if (iname) free(iname);
|
|
if (zname) free(zname);
|
|
if (oname) free(oname);
|
|
if (zuname) free(zuname);
|
|
return ZE_MEM;
|
|
}
|
|
strcpy(z->name, name);
|
|
z->oname = oname;
|
|
oname = NULL;
|
|
z->dosflag = dosflag;
|
|
|
|
#ifdef FORCE_NEWNAME
|
|
free((zvoid *)(z->iname));
|
|
z->iname = iname;
|
|
iname = NULL;
|
|
#else
|
|
/* Better keep the old name. Useful when updating on MSDOS a zip file
|
|
* made on Unix.
|
|
*/
|
|
#endif /* ? FORCE_NEWNAME */
|
|
}
|
|
|
|
if ((z->namew = (wchar_t *)malloc((wcslen(namew) + 1) * sizeof(wchar_t))) == NULL) {
|
|
if (undosmw != znamew)
|
|
free(undosmw);
|
|
if (undosm) free(undosm);
|
|
if (inamew) free(inamew);
|
|
if (znamew) free(znamew);
|
|
if (name) free(name);
|
|
if (iname) free(iname);
|
|
if (zname) free(zname);
|
|
if (oname) free(oname);
|
|
if (zuname) free(zuname);
|
|
return ZE_MEM;
|
|
}
|
|
wcscpy(z->namew, namew);
|
|
z->inamew = inamew;
|
|
inamew = NULL;
|
|
z->znamew = znamew;
|
|
znamew = NULL;
|
|
z->uname = wchar_to_utf8_string(z->inamew);
|
|
if (name == label) {
|
|
label = z->name;
|
|
}
|
|
} else if (pcount == 0 || filter(undosm, casesensitive)) {
|
|
|
|
/* Check that we are not adding the zip file to itself. This
|
|
* catches cases like "zip -m foo ../dir/foo.zip".
|
|
*/
|
|
/* Version of stat() for CMS/MVS isn't complete enough to see if */
|
|
/* files match. Just let ZIP.C compare the filenames. That's good */
|
|
/* enough for CMS anyway since there aren't paths to worry about. */
|
|
zw_stat statbw; /* need for wide stat */
|
|
wchar_t *zipfilew = local_to_wchar_string(zipfile);
|
|
|
|
if (zipstate == -1)
|
|
zipstate = strcmp(zipfile, "-") != 0 &&
|
|
zwstat(zipfilew, &zipstatbw) == 0;
|
|
free(zipfilew);
|
|
|
|
if (zipstate == 1 && (statbw = zipstatbw, zwstat(namew, &statbw) == 0
|
|
&& zipstatbw.st_mode == statbw.st_mode
|
|
&& zipstatbw.st_ino == statbw.st_ino
|
|
&& zipstatbw.st_dev == statbw.st_dev
|
|
&& zipstatbw.st_uid == statbw.st_uid
|
|
&& zipstatbw.st_gid == statbw.st_gid
|
|
&& zipstatbw.st_size == statbw.st_size
|
|
&& zipstatbw.st_mtime == statbw.st_mtime
|
|
&& zipstatbw.st_ctime == statbw.st_ctime)) {
|
|
/* Don't compare a_time since we are reading the file */
|
|
if (verbose)
|
|
fprintf(mesg, "file matches zip file -- skipping\n");
|
|
if (undosmw != znamew)
|
|
free(undosmw);
|
|
if (undosm) free(undosm);
|
|
if (inamew) free(inamew);
|
|
if (znamew) free(znamew);
|
|
if (name) free(name);
|
|
if (iname) free(iname);
|
|
if (zname) free(zname);
|
|
if (oname) free(oname);
|
|
if (zuname) free(zuname);
|
|
return ZE_OK;
|
|
}
|
|
|
|
/* allocate space and add to list */
|
|
if ((f = (struct flist far *)farmalloc(sizeof(struct flist))) == NULL ||
|
|
fcount + 1 < fcount ||
|
|
(f->name = malloc(strlen(name) + 1 + PAD)) == NULL)
|
|
{
|
|
if (f != NULL)
|
|
farfree((zvoid far *)f);
|
|
if (undosmw != znamew)
|
|
free(undosmw);
|
|
if (undosm) free(undosm);
|
|
if (inamew) free(inamew);
|
|
if (znamew) free(znamew);
|
|
if (name) free(name);
|
|
if (iname) free(iname);
|
|
if (zname) free(zname);
|
|
if (oname) free(oname);
|
|
if (zuname) free(zuname);
|
|
return ZE_MEM;
|
|
}
|
|
if (undosmw != znamew)
|
|
free((zvoid *)undosmw);
|
|
strcpy(f->name, name);
|
|
f->iname = iname;
|
|
iname = NULL;
|
|
f->zname = zname;
|
|
zname = NULL;
|
|
/* Unicode */
|
|
if ((f->namew = (wchar_t *)malloc((wcslen(namew) + 1) * sizeof(wchar_t))) == NULL) {
|
|
if (f != NULL)
|
|
farfree((zvoid far *)f);
|
|
if (undosmw != znamew)
|
|
free(undosmw);
|
|
if (undosm) free(undosm);
|
|
if (inamew) free(inamew);
|
|
if (znamew) free(znamew);
|
|
if (name) free(name);
|
|
if (iname) free(iname);
|
|
if (zname) free(zname);
|
|
if (oname) free(oname);
|
|
if (zuname) free(zuname);
|
|
return ZE_MEM;
|
|
}
|
|
wcscpy(f->namew, namew);
|
|
f->znamew = znamew;
|
|
znamew = NULL;
|
|
f->uname = wchar_to_utf8_string(inamew);
|
|
f->inamew = inamew;
|
|
inamew = NULL;
|
|
f->oname = oname;
|
|
oname = NULL;
|
|
f->dosflag = dosflag;
|
|
*fnxt = f;
|
|
f->lst = fnxt;
|
|
f->nxt = NULL;
|
|
fnxt = &f->nxt;
|
|
fcount++;
|
|
if (name == label) {
|
|
label = f->name;
|
|
}
|
|
}
|
|
if (undosm) free(undosm);
|
|
if (inamew) free(inamew);
|
|
if (znamew) free(znamew);
|
|
if (name) free(name);
|
|
if (iname) free(iname);
|
|
if (zname) free(zname);
|
|
if (oname) free(oname);
|
|
if (zuname) free(zuname);
|
|
return ZE_OK;
|
|
}
|
|
|
|
# endif
|
|
#endif
|
|
|
|
int newname(name, isdir, casesensitive)
|
|
char *name; /* name to add (or exclude) */
|
|
int isdir; /* true for a directory */
|
|
int casesensitive; /* true for case-sensitive matching */
|
|
/* Add (or exclude) the name of an existing disk file. Return an error
|
|
code in the ZE_ class. */
|
|
{
|
|
char *iname, *zname; /* internal name, external version of iname */
|
|
char *undosm; /* zname version with "-j" and "-k" options disabled */
|
|
char *oname; /* iname converted for display */
|
|
struct flist far *f; /* where in found, or new found entry */
|
|
struct zlist far *z; /* where in zfiles (if found) */
|
|
int dosflag;
|
|
|
|
/* Scanning files ...
|
|
*
|
|
* After 5 seconds output Scanning files...
|
|
* then a dot every 2 seconds
|
|
*/
|
|
if (noisy) {
|
|
/* If find files then output message after delay */
|
|
if (scan_count == 0) {
|
|
time_t current = time(NULL);
|
|
scan_start = current;
|
|
}
|
|
scan_count++;
|
|
if (scan_count % 100 == 0) {
|
|
time_t current = time(NULL);
|
|
|
|
if (current - scan_start > scan_delay) {
|
|
if (scan_last == 0) {
|
|
zipmessage_nl("Scanning files ", 0);
|
|
scan_last = current;
|
|
}
|
|
if (current - scan_last > scan_dot_time) {
|
|
scan_last = current;
|
|
fprintf(mesg, ".");
|
|
fflush(mesg);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Search for name in zip file. If there, mark it, else add to
|
|
list of new names to do (or remove from that list). */
|
|
if ((iname = ex2in(name, isdir, &dosflag)) == NULL)
|
|
return ZE_MEM;
|
|
|
|
/* Discard directory names with zip -rj */
|
|
if (*iname == '\0') {
|
|
#ifndef AMIGA
|
|
/* A null string is a legitimate external directory name in AmigaDOS; also,
|
|
* a command like "zip -r zipfile FOO:" produces an empty internal name.
|
|
*/
|
|
# ifndef RISCOS
|
|
/* If extensions needs to be swapped, we will have empty directory names
|
|
instead of the original directory. For example, zipping 'c.', 'c.main'
|
|
should zip only 'main.c' while 'c.' will be converted to '\0' by ex2in. */
|
|
|
|
if (pathput && !recurse) error("empty name without -j or -r");
|
|
|
|
# endif /* !RISCOS */
|
|
#endif /* !AMIGA */
|
|
free((zvoid *)iname);
|
|
return ZE_OK;
|
|
}
|
|
undosm = NULL;
|
|
if (dosflag || !pathput) {
|
|
int save_dosify = dosify, save_pathput = pathput;
|
|
dosify = 0;
|
|
pathput = 1;
|
|
/* zname is temporarly mis-used as "undosmode" iname pointer */
|
|
if ((zname = ex2in(name, isdir, NULL)) != NULL) {
|
|
undosm = in2ex(zname);
|
|
free(zname);
|
|
}
|
|
dosify = save_dosify;
|
|
pathput = save_pathput;
|
|
}
|
|
if ((zname = in2ex(iname)) == NULL)
|
|
return ZE_MEM;
|
|
#ifdef UNICODE_SUPPORT
|
|
/* Convert name to display or OEM name */
|
|
oname = local_to_display_string(iname);
|
|
#else
|
|
if ((oname = malloc(strlen(zname) + 1)) == NULL)
|
|
return ZE_MEM;
|
|
strcpy(oname, zname);
|
|
#endif
|
|
if (undosm == NULL)
|
|
undosm = zname;
|
|
if ((z = zsearch(zname)) != NULL) {
|
|
if (pcount && !filter(undosm, casesensitive)) {
|
|
/* Do not clear z->mark if "exclude", because, when "dosify || !pathput"
|
|
* is in effect, two files with different filter options may hit the
|
|
* same z entry.
|
|
*/
|
|
if (verbose)
|
|
fprintf(mesg, "excluding %s\n", oname);
|
|
free((zvoid *)iname);
|
|
free((zvoid *)zname);
|
|
} else {
|
|
z->mark = 1;
|
|
if ((z->name = malloc(strlen(name) + 1 + PAD)) == NULL) {
|
|
if (undosm != zname)
|
|
free((zvoid *)undosm);
|
|
free((zvoid *)iname);
|
|
free((zvoid *)zname);
|
|
return ZE_MEM;
|
|
}
|
|
strcpy(z->name, name);
|
|
z->oname = oname;
|
|
z->dosflag = dosflag;
|
|
|
|
#ifdef FORCE_NEWNAME
|
|
free((zvoid *)(z->iname));
|
|
z->iname = iname;
|
|
#else
|
|
/* Better keep the old name. Useful when updating on MSDOS a zip file
|
|
* made on Unix.
|
|
*/
|
|
free((zvoid *)iname);
|
|
free((zvoid *)zname);
|
|
#endif /* ? FORCE_NEWNAME */
|
|
}
|
|
#if defined(UNICODE_SUPPORT) && defined(WIN32)
|
|
z->namew = NULL;
|
|
z->inamew = NULL;
|
|
z->znamew = NULL;
|
|
#endif
|
|
if (name == label) {
|
|
label = z->name;
|
|
}
|
|
} else if (pcount == 0 || filter(undosm, casesensitive)) {
|
|
|
|
/* Check that we are not adding the zip file to itself. This
|
|
* catches cases like "zip -m foo ../dir/foo.zip".
|
|
*/
|
|
#ifndef CMS_MVS
|
|
/* Version of stat() for CMS/MVS isn't complete enough to see if */
|
|
/* files match. Just let ZIP.C compare the filenames. That's good */
|
|
/* enough for CMS anyway since there aren't paths to worry about. */
|
|
z_stat statb; /* now use structure z_stat and function zstat globally 7/24/04 EG */
|
|
|
|
if (zipstate == -1)
|
|
zipstate = strcmp(zipfile, "-") != 0 &&
|
|
zstat(zipfile, &zipstatb) == 0;
|
|
|
|
if (zipstate == 1 && (statb = zipstatb, zstat(name, &statb) == 0
|
|
&& zipstatb.st_mode == statb.st_mode
|
|
#ifdef VMS
|
|
&& memcmp(zipstatb.st_ino, statb.st_ino, sizeof(statb.st_ino)) == 0
|
|
&& strcmp(zipstatb.st_dev, statb.st_dev) == 0
|
|
&& zipstatb.st_uid == statb.st_uid
|
|
#else /* !VMS */
|
|
&& zipstatb.st_ino == statb.st_ino
|
|
&& zipstatb.st_dev == statb.st_dev
|
|
&& zipstatb.st_uid == statb.st_uid
|
|
&& zipstatb.st_gid == statb.st_gid
|
|
#endif /* ?VMS */
|
|
&& zipstatb.st_size == statb.st_size
|
|
&& zipstatb.st_mtime == statb.st_mtime
|
|
&& zipstatb.st_ctime == statb.st_ctime)) {
|
|
/* Don't compare a_time since we are reading the file */
|
|
if (verbose)
|
|
fprintf(mesg, "file matches zip file -- skipping\n");
|
|
if (undosm != zname)
|
|
free((zvoid *)zname);
|
|
if (undosm != iname)
|
|
free((zvoid *)undosm);
|
|
free((zvoid *)iname);
|
|
free(oname);
|
|
return ZE_OK;
|
|
}
|
|
#endif /* CMS_MVS */
|
|
|
|
/* allocate space and add to list */
|
|
if ((f = (struct flist far *)farmalloc(sizeof(struct flist))) == NULL ||
|
|
fcount + 1 < fcount ||
|
|
(f->name = malloc(strlen(name) + 1 + PAD)) == NULL)
|
|
{
|
|
if (f != NULL)
|
|
farfree((zvoid far *)f);
|
|
if (undosm != zname)
|
|
free((zvoid *)undosm);
|
|
free((zvoid *)iname);
|
|
free((zvoid *)zname);
|
|
free(oname);
|
|
return ZE_MEM;
|
|
}
|
|
strcpy(f->name, name);
|
|
f->iname = iname;
|
|
f->zname = zname;
|
|
#ifdef UNICODE_SUPPORT
|
|
/* Unicode */
|
|
f->uname = local_to_utf8_string(iname);
|
|
#ifdef WIN32
|
|
f->namew = NULL;
|
|
f->inamew = NULL;
|
|
f->znamew = NULL;
|
|
if (strcmp(f->name, "-") == 0) {
|
|
f->namew = local_to_wchar_string(f->name);
|
|
}
|
|
#endif
|
|
|
|
#endif
|
|
f->oname = oname;
|
|
f->dosflag = dosflag;
|
|
|
|
*fnxt = f;
|
|
f->lst = fnxt;
|
|
f->nxt = NULL;
|
|
fnxt = &f->nxt;
|
|
fcount++;
|
|
if (name == label) {
|
|
label = f->name;
|
|
}
|
|
}
|
|
if (undosm != zname)
|
|
free((zvoid *)undosm);
|
|
return ZE_OK;
|
|
}
|
|
|
|
ulg dostime(y, n, d, h, m, s)
|
|
int y; /* year */
|
|
int n; /* month */
|
|
int d; /* day */
|
|
int h; /* hour */
|
|
int m; /* minute */
|
|
int s; /* second */
|
|
/* Convert the date y/n/d and time h:m:s to a four byte DOS date and
|
|
time (date in high two bytes, time in low two bytes allowing magnitude
|
|
comparison). */
|
|
{
|
|
return y < 1980 ? DOSTIME_MINIMUM /* dostime(1980, 1, 1, 0, 0, 0) */ :
|
|
(((ulg)y - 1980) << 25) | ((ulg)n << 21) | ((ulg)d << 16) |
|
|
((ulg)h << 11) | ((ulg)m << 5) | ((ulg)s >> 1);
|
|
}
|
|
|
|
|
|
ulg unix2dostime(t)
|
|
time_t *t; /* unix time to convert */
|
|
/* Return the Unix time t in DOS format, rounded up to the next two
|
|
second boundary. */
|
|
{
|
|
time_t t_even;
|
|
struct tm *s; /* result of localtime() */
|
|
|
|
t_even = (time_t)(((unsigned long)(*t) + 1) & (~1));
|
|
/* Round up to even seconds. */
|
|
s = localtime(&t_even); /* Use local time since MSDOS does. */
|
|
if (s == (struct tm *)NULL) {
|
|
/* time conversion error; use current time as emergency value
|
|
(assuming that localtime() does at least accept this value!) */
|
|
t_even = (time_t)(((unsigned long)time(NULL) + 1) & (~1));
|
|
s = localtime(&t_even);
|
|
}
|
|
return dostime(s->tm_year + 1900, s->tm_mon + 1, s->tm_mday,
|
|
s->tm_hour, s->tm_min, s->tm_sec);
|
|
}
|
|
|
|
int issymlnk(a)
|
|
ulg a; /* Attributes returned by filetime() */
|
|
/* Return true if the attributes are those of a symbolic link */
|
|
{
|
|
#ifndef QDOS
|
|
#ifdef S_IFLNK
|
|
#ifdef __human68k__
|
|
int *_dos_importlnenv(void);
|
|
|
|
if (_dos_importlnenv() == NULL)
|
|
return 0;
|
|
#endif
|
|
return ((a >> 16) & S_IFMT) == S_IFLNK;
|
|
#else /* !S_IFLNK */
|
|
return (int)a & 0; /* avoid warning on unused parameter */
|
|
#endif /* ?S_IFLNK */
|
|
#else
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
#endif /* !UTIL */
|
|
|
|
|
|
#if (!defined(UTIL) && !defined(ZP_NEED_GEN_D2U_TIME))
|
|
/* There is no need for dos2unixtime() in the ZipUtils' code. */
|
|
# define ZP_NEED_GEN_D2U_TIME
|
|
#endif
|
|
#if ((defined(OS2) || defined(VMS)) && defined(ZP_NEED_GEN_D2U_TIME))
|
|
/* OS/2 and VMS use a special solution to handle time-stams of files. */
|
|
# undef ZP_NEED_GEN_D2U_TIME
|
|
#endif
|
|
#if (defined(W32_STATROOT_FIX) && !defined(ZP_NEED_GEN_D2U_TIME))
|
|
/* The Win32 stat()-bandaid to fix stat'ing root directories needs
|
|
* dos2unixtime() to calculate the time-stamps. */
|
|
# define ZP_NEED_GEN_D2U_TIME
|
|
#endif
|
|
|
|
#ifdef ZP_NEED_GEN_D2U_TIME
|
|
|
|
time_t dos2unixtime(dostime)
|
|
ulg dostime; /* DOS time to convert */
|
|
/* Return the Unix time_t value (GMT/UTC time) for the DOS format (local)
|
|
* time dostime, where dostime is a four byte value (date in most significant
|
|
* word, time in least significant word), see dostime() function.
|
|
*/
|
|
{
|
|
struct tm *t; /* argument for mktime() */
|
|
ZCONST time_t clock = time(NULL);
|
|
|
|
t = localtime(&clock);
|
|
t->tm_isdst = -1; /* let mktime() determine if DST is in effect */
|
|
/* Convert DOS time to UNIX time_t format */
|
|
t->tm_sec = (((int)dostime) << 1) & 0x3e;
|
|
t->tm_min = (((int)dostime) >> 5) & 0x3f;
|
|
t->tm_hour = (((int)dostime) >> 11) & 0x1f;
|
|
t->tm_mday = (int)(dostime >> 16) & 0x1f;
|
|
t->tm_mon = ((int)(dostime >> 21) & 0x0f) - 1;
|
|
t->tm_year = ((int)(dostime >> 25) & 0x7f) + 80;
|
|
|
|
return mktime(t);
|
|
}
|
|
|
|
#undef ZP_NEED_GEN_D2U_TIME
|
|
#endif /* ZP_NEED_GEN_D2U_TIME */
|
|
|
|
|
|
#ifndef MACOS
|
|
int destroy(f)
|
|
char *f; /* file to delete */
|
|
/* Delete the file *f, returning non-zero on failure. */
|
|
{
|
|
return unlink(f);
|
|
}
|
|
|
|
|
|
int replace(d, s)
|
|
char *d, *s; /* destination and source file names */
|
|
/* Replace file *d by file *s, removing the old *s. Return an error code
|
|
in the ZE_ class. This function need not preserve the file attributes,
|
|
this will be done by setfileattr() later.
|
|
*/
|
|
{
|
|
z_stat t; /* results of stat() */
|
|
#if defined(CMS_MVS)
|
|
/* cmsmvs.h defines FOPW_TEMP as memory(hiperspace). Since memory is
|
|
* lost at end of run, always do copy instead of rename.
|
|
*/
|
|
int copy = 1;
|
|
#else
|
|
int copy = 0;
|
|
#endif
|
|
int d_exists;
|
|
|
|
#if defined(VMS) || defined(CMS_MVS)
|
|
/* stat() is broken on VMS remote files (accessed through Decnet).
|
|
* This patch allows creation of remote zip files, but is not sufficient
|
|
* to update them or compress remote files */
|
|
unlink(d);
|
|
#else /* !(VMS || CMS_MVS) */
|
|
d_exists = (LSTAT(d, &t) == 0);
|
|
if (d_exists)
|
|
{
|
|
/*
|
|
* respect existing soft and hard links!
|
|
*/
|
|
if (t.st_nlink > 1
|
|
# ifdef S_IFLNK
|
|
|| (t.st_mode & S_IFMT) == S_IFLNK
|
|
# endif
|
|
)
|
|
copy = 1;
|
|
else if (unlink(d))
|
|
return ZE_CREAT; /* Can't erase zip file--give up */
|
|
}
|
|
#endif /* ?(VMS || CMS_MVS) */
|
|
#ifndef CMS_MVS
|
|
if (!copy) {
|
|
if (rename(s, d)) { /* Just move s on top of d */
|
|
copy = 1; /* failed ? */
|
|
#if !defined(VMS) && !defined(ATARI) && !defined(AZTEC_C)
|
|
#if !defined(CMS_MVS) && !defined(RISCOS) && !defined(QDOS)
|
|
/* For VMS, ATARI, AMIGA Aztec, VM_CMS, MVS, RISCOS,
|
|
always assume that failure is EXDEV */
|
|
if (errno != EXDEV
|
|
# ifdef THEOS
|
|
&& errno != EEXIST
|
|
# else
|
|
# ifdef ENOTSAM
|
|
&& errno != ENOTSAM /* Used at least on Turbo C */
|
|
# endif
|
|
# endif
|
|
) return ZE_CREAT;
|
|
#endif /* !CMS_MVS && !RISCOS */
|
|
#endif /* !VMS && !ATARI && !AZTEC_C */
|
|
}
|
|
}
|
|
#endif /* !CMS_MVS */
|
|
|
|
if (copy) {
|
|
FILE *f, *g; /* source and destination files */
|
|
int r; /* temporary variable */
|
|
|
|
#ifdef RISCOS
|
|
if (SWI_OS_FSControl_26(s,d,0xA1)!=NULL) {
|
|
#endif
|
|
|
|
/* Use zfopen for almost all opens where fopen is used. For
|
|
most OS that support large files we use the 64-bit file
|
|
environment and zfopen maps to fopen, but this allows
|
|
tweeking ports that don't do that. 7/24/04 */
|
|
if ((f = zfopen(s, FOPR)) == NULL) {
|
|
fprintf(mesg," replace: can't open %s\n", s);
|
|
return ZE_TEMP;
|
|
}
|
|
if ((g = zfopen(d, FOPW)) == NULL)
|
|
{
|
|
fclose(f);
|
|
return ZE_CREAT;
|
|
}
|
|
|
|
r = fcopy(f, g, (ulg)-1L);
|
|
fclose(f);
|
|
if (fclose(g) || r != ZE_OK)
|
|
{
|
|
unlink(d);
|
|
return r ? (r == ZE_TEMP ? ZE_WRITE : r) : ZE_WRITE;
|
|
}
|
|
unlink(s);
|
|
#ifdef RISCOS
|
|
}
|
|
#endif
|
|
}
|
|
return ZE_OK;
|
|
}
|
|
#endif /* !MACOS */
|
|
|
|
|
|
int getfileattr(f)
|
|
char *f; /* file path */
|
|
/* Return the file attributes for file f or 0 if failure */
|
|
{
|
|
#ifdef __human68k__
|
|
struct _filbuf buf;
|
|
|
|
return _dos_files(&buf, f, 0xff) < 0 ? 0x20 : buf.atr;
|
|
#else
|
|
z_stat s;
|
|
|
|
return SSTAT(f, &s) == 0 ? (int) s.st_mode : 0;
|
|
#endif
|
|
}
|
|
|
|
|
|
int setfileattr(f, a)
|
|
char *f; /* file path */
|
|
int a; /* attributes returned by getfileattr() */
|
|
/* Give the file f the attributes a, return non-zero on failure */
|
|
{
|
|
#if defined(TOPS20) || defined (CMS_MVS)
|
|
return 0;
|
|
#else
|
|
#ifdef __human68k__
|
|
return _dos_chmod(f, a) < 0 ? -1 : 0;
|
|
#else
|
|
return chmod(f, a);
|
|
#endif
|
|
#endif
|
|
}
|
|
|
|
|
|
/* tempname */
|
|
|
|
#ifndef VMS /* VMS-specific function is in VMS.C. */
|
|
|
|
char *tempname(zip)
|
|
char *zip; /* path name of zip file to generate temp name for */
|
|
|
|
/* Return a temporary file name in its own malloc'ed space, using tempath. */
|
|
{
|
|
char *t = zip; /* malloc'ed space for name (use zip to avoid warning) */
|
|
|
|
# ifdef CMS_MVS
|
|
if ((t = malloc(strlen(tempath) + L_tmpnam + 2)) == NULL)
|
|
return NULL;
|
|
|
|
# ifdef VM_CMS
|
|
tmpnam(t);
|
|
/* Remove filemode and replace with tempath, if any. */
|
|
/* Otherwise A-disk is used by default */
|
|
*(strrchr(t, ' ')+1) = '\0';
|
|
if (tempath!=NULL)
|
|
strcat(t, tempath);
|
|
return t;
|
|
# else /* !VM_CMS */
|
|
/* For MVS */
|
|
tmpnam(t);
|
|
if (tempath != NULL)
|
|
{
|
|
int l1 = strlen(t);
|
|
char *dot;
|
|
if (*t == '\'' && *(t+l1-1) == '\'' && (dot = strchr(t, '.')))
|
|
{
|
|
/* MVS and not OE. tmpnam() returns quoted string of 5 qualifiers.
|
|
* First is HLQ, rest are timestamps. User can only replace HLQ.
|
|
*/
|
|
int l2 = strlen(tempath);
|
|
if (strchr(tempath, '.') || l2 < 1 || l2 > 8)
|
|
ziperr(ZE_PARMS, "On MVS and not OE, tempath (-b) can only be HLQ");
|
|
memmove(t+1+l2, dot, l1+1-(dot-t)); /* shift dot ready for new hlq */
|
|
memcpy(t+1, tempath, l2); /* insert new hlq */
|
|
}
|
|
else
|
|
{
|
|
/* MVS and probably OE. tmpnam() returns filename based on TMPDIR,
|
|
* no point in even attempting to change it. User should modify TMPDIR
|
|
* instead.
|
|
*/
|
|
zipwarn("MVS, assumed to be OE, change TMPDIR instead of option -b: ",
|
|
tempath);
|
|
}
|
|
}
|
|
return t;
|
|
# endif /* !VM_CMS */
|
|
|
|
# else /* !CMS_MVS */
|
|
|
|
# ifdef TANDEM
|
|
char cur_subvol [FILENAME_MAX];
|
|
char temp_subvol [FILENAME_MAX];
|
|
char *zptr;
|
|
char *ptr;
|
|
char *cptr = &cur_subvol[0];
|
|
char *tptr = &temp_subvol[0];
|
|
short err;
|
|
FILE *tempf;
|
|
int attempts;
|
|
|
|
t = (char *)malloc(NAMELEN); /* malloc here as you cannot free */
|
|
/* tmpnam allocated storage later */
|
|
|
|
zptr = strrchr(zip, TANDEM_DELIMITER);
|
|
|
|
if (zptr != NULL) {
|
|
/* ZIP file specifies a Subvol so make temp file there so it can just
|
|
be renamed at end */
|
|
|
|
*tptr = *cptr = '\0';
|
|
strcat(cptr, getenv("DEFAULTS"));
|
|
|
|
strncat(tptr, zip, _min(FILENAME_MAX, (zptr - zip)) ); /* temp subvol */
|
|
strncat(t, zip, _min(NAMELEN, ((zptr - zip) + 1)) ); /* temp stem */
|
|
|
|
err = chvol(tptr);
|
|
ptr = t + strlen(t); /* point to end of stem */
|
|
}
|
|
else
|
|
ptr = t;
|
|
|
|
/* If two zips are running in same subvol then we can get contention problems
|
|
with the temporary filename. As a work around we attempt to create
|
|
the file here, and if it already exists we get a new temporary name */
|
|
|
|
attempts = 0;
|
|
do {
|
|
attempts++;
|
|
tmpnam(ptr); /* Add filename */
|
|
tempf = zfopen(ptr, FOPW_TMP); /* Attempt to create file */
|
|
} while (tempf == NULL && attempts < 100);
|
|
|
|
if (attempts >= 100) {
|
|
ziperr(ZE_TEMP, "Could not get unique temp file name");
|
|
}
|
|
|
|
fclose(tempf);
|
|
|
|
if (zptr != NULL) {
|
|
err = chvol(cptr); /* Put ourself back to where we came in */
|
|
}
|
|
|
|
return t;
|
|
|
|
# else /* !CMS_MVS && !TANDEM */
|
|
/*
|
|
* Do something with TMPDIR, TMP, TEMP ????
|
|
*/
|
|
if (tempath != NULL)
|
|
{
|
|
if ((t = malloc(strlen(tempath) + 12)) == NULL)
|
|
return NULL;
|
|
strcpy(t, tempath);
|
|
|
|
# if (!defined(VMS) && !defined(TOPS20))
|
|
# ifdef MSDOS
|
|
{
|
|
char c = (char)lastchar(t);
|
|
if (c != '/' && c != ':' && c != '\\')
|
|
strcat(t, "/");
|
|
}
|
|
# else
|
|
|
|
# ifdef AMIGA
|
|
{
|
|
char c = (char)lastchar(t);
|
|
if (c != '/' && c != ':')
|
|
strcat(t, "/");
|
|
}
|
|
# else /* !AMIGA */
|
|
# ifdef RISCOS
|
|
if (lastchar(t) != '.')
|
|
strcat(t, ".");
|
|
# else /* !RISCOS */
|
|
|
|
# ifdef QDOS
|
|
if (lastchar(t) != '_')
|
|
strcat(t, "_");
|
|
# else
|
|
if (lastchar(t) != '/')
|
|
strcat(t, "/");
|
|
# endif /* ?QDOS */
|
|
# endif /* ?RISCOS */
|
|
# endif /* ?AMIGA */
|
|
# endif /* ?MSDOS */
|
|
# endif /* !VMS && !TOPS20 */
|
|
}
|
|
else
|
|
{
|
|
if ((t = malloc(12)) == NULL)
|
|
return NULL;
|
|
*t = 0;
|
|
}
|
|
# ifdef NO_MKTEMP
|
|
{
|
|
char *p = t + strlen(t);
|
|
sprintf(p, "%08lx", (ulg)time(NULL));
|
|
return t;
|
|
}
|
|
# else
|
|
strcat(t, "ziXXXXXX"); /* must use lowercase for Linux dos file system */
|
|
# if defined(UNIX) && !defined(NO_MKSTEMP)
|
|
/* tempname should not be called */
|
|
return t;
|
|
# else
|
|
return mktemp(t);
|
|
# endif
|
|
# endif /* NO_MKTEMP */
|
|
# endif /* TANDEM */
|
|
# endif /* CMS_MVS */
|
|
}
|
|
#endif /* !VMS */
|
|
|
|
int fcopy(f, g, n)
|
|
FILE *f, *g; /* source and destination files */
|
|
/* now use uzoff_t for all file sizes 5/14/05 CS */
|
|
uzoff_t n; /* number of bytes to copy or -1 for all */
|
|
/* Copy n bytes from file *f to file *g, or until EOF if (zoff_t)n == -1.
|
|
Return an error code in the ZE_ class. */
|
|
{
|
|
char *b; /* malloc'ed buffer for copying */
|
|
extent k; /* result of fread() */
|
|
uzoff_t m; /* bytes copied so far */
|
|
|
|
if ((b = malloc(CBSZ)) == NULL)
|
|
return ZE_MEM;
|
|
m = 0;
|
|
while (n == (uzoff_t)(-1L) || m < n)
|
|
{
|
|
if ((k = fread(b, 1, n == (uzoff_t)(-1) ?
|
|
CBSZ : (n - m < CBSZ ? (extent)(n - m) : CBSZ), f)) == 0)
|
|
{
|
|
if (ferror(f))
|
|
{
|
|
free((zvoid *)b);
|
|
return ZE_READ;
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
if (fwrite(b, 1, k, g) != k)
|
|
{
|
|
free((zvoid *)b);
|
|
fprintf(mesg," fcopy: write error\n");
|
|
return ZE_TEMP;
|
|
}
|
|
m += k;
|
|
}
|
|
free((zvoid *)b);
|
|
return ZE_OK;
|
|
}
|
|
|
|
|
|
/* from zipfile.c */
|
|
|
|
#ifdef THEOS
|
|
/* Macros cause stack overflow in compiler */
|
|
ush SH(uch* p) { return ((ush)(uch)((p)[0]) | ((ush)(uch)((p)[1]) << 8)); }
|
|
ulg LG(uch* p) { return ((ulg)(SH(p)) | ((ulg)(SH((p)+2)) << 16)); }
|
|
#else /* !THEOS */
|
|
/* Macros for converting integers in little-endian to machine format */
|
|
# define SH(a) ((ush)(((ush)(uch)(a)[0]) | (((ush)(uch)(a)[1]) << 8)))
|
|
# define LG(a) ((ulg)SH(a) | ((ulg)SH((a)+2) << 16))
|
|
# ifdef ZIP64_SUPPORT /* zip64 support 08/31/2003 R.Nausedat */
|
|
# define LLG(a) ((zoff_t)LG(a) | ((zoff_t)LG((a)+4) << 32))
|
|
# endif
|
|
#endif /* ?THEOS */
|
|
|
|
|
|
/* always copies from global in_file to global output file y */
|
|
int bfcopy(n)
|
|
/* now use uzoff_t for all file sizes 5/14/05 CS */
|
|
uzoff_t n; /* number of bytes to copy or -1 for all */
|
|
/* Copy n bytes from in_file to out_file, or until EOF if (zoff_t)n == -1.
|
|
|
|
Normally we have the compressed size from either the central directory
|
|
entry or the local header.
|
|
|
|
If n != -1 and EOF, close current split and open next and continue
|
|
copying.
|
|
|
|
If n == -2, copy until find the extended header (data descriptor). Only
|
|
used for -FF when no size available.
|
|
|
|
If fix == 1 calculate CRC of input entry and verify matches.
|
|
|
|
If fix == 2 and this entry using data descriptor keep a sliding
|
|
window in the buffer for looking for signature.
|
|
|
|
Return an error code in the ZE_ class. */
|
|
{
|
|
char *b; /* malloc'ed buffer for copying */
|
|
extent k; /* result of fread() */
|
|
uzoff_t m; /* bytes copied so far */
|
|
extent brd; /* bytes to read */
|
|
zoff_t data_start = 0;
|
|
zoff_t des_start = 0;
|
|
char *split_path;
|
|
extent kk;
|
|
int i;
|
|
char sbuf[4]; /* buffer for sliding signature window for fix = 2 */
|
|
int des = 0; /* this entry has data descriptor to find */
|
|
|
|
if ((b = malloc(CBSZ)) == NULL)
|
|
return ZE_MEM;
|
|
|
|
if (copy_only && !display_globaldots) {
|
|
/* initialize dot count */
|
|
dot_count = -1;
|
|
}
|
|
|
|
if (fix == 2 && n == (uzoff_t) -2) {
|
|
data_start = zftello(in_file);
|
|
for (kk = 0; kk < 4; kk++)
|
|
sbuf[kk] = 0;
|
|
des = 1;
|
|
}
|
|
|
|
des_good = 0;
|
|
|
|
m = 0;
|
|
while (des || n == (uzoff_t)(-1L) || m < n)
|
|
{
|
|
if (des || n == (uzoff_t)(-1))
|
|
brd = CBSZ;
|
|
else
|
|
brd = (n - m < CBSZ ? (extent)(n - m) : CBSZ);
|
|
|
|
des_start = zftello(in_file);
|
|
|
|
if ((k = fread(b, 1, brd, in_file)) == 0)
|
|
{
|
|
if (fix == 2 && k < brd) {
|
|
free((zvoid *)b);
|
|
return ZE_READ;
|
|
}
|
|
else if (ferror(in_file))
|
|
{
|
|
free((zvoid *)b);
|
|
return ZE_READ;
|
|
}
|
|
else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
/* end at extended local header (data descriptor) signature */
|
|
if (des) {
|
|
des_crc = 0;
|
|
des_csize = 0;
|
|
des_usize = 0;
|
|
|
|
/* If first 4 bytes in buffer are data descriptor signature then
|
|
try to read the data descriptor.
|
|
If not, scan for signature and break if found, let bfwrite flush
|
|
the data and then next read should put the data descriptor at
|
|
the beginning of the buffer.
|
|
*/
|
|
|
|
if (
|
|
(b[0] != 0x50 /*'P' except EBCDIC*/ ||
|
|
b[1] != 0x4b /*'K' except EBCDIC*/ ||
|
|
b[2] != '\07' ||
|
|
b[3] != '\010')) {
|
|
/* buffer is not start of data descriptor */
|
|
|
|
for (kk = 0; kk < k; kk++) {
|
|
/* add byte to end of sbuf */
|
|
for (i = 0; i < 3; i++)
|
|
sbuf[i] = sbuf[i + 1];
|
|
sbuf[3] = b[kk];
|
|
|
|
/* see if this is signature */
|
|
if (
|
|
(sbuf[0] == 0x50 /*'P' except EBCDIC*/ &&
|
|
sbuf[1] == 0x4b /*'K' except EBCDIC*/ &&
|
|
sbuf[2] == '\07' &&
|
|
sbuf[3] == '\010')) {
|
|
kk -= 3;
|
|
if (zfseeko(in_file, bytes_this_split + kk, SEEK_SET) != 0) {
|
|
/* seek error */
|
|
ZIPERR(ZE_READ, "seek failed reading descriptor");
|
|
}
|
|
des_start = zftello(in_file);
|
|
k = kk;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
|
|
/* signature at start of buffer */
|
|
{
|
|
des_good = 0;
|
|
|
|
#ifdef ZIP64_SUPPORT
|
|
if (zip64_entry) {
|
|
|
|
/* read Zip64 data descriptor */
|
|
if (k < 24) {
|
|
/* not enough bytes, so can't be data descriptor
|
|
as data descriptors can't be split across splits
|
|
*/
|
|
}
|
|
else
|
|
{
|
|
/* read the Zip64 descriptor */
|
|
|
|
des_crc = LG(b + 4);
|
|
des_csize = LLG(b + 8);
|
|
des_usize = LLG(b + 16);
|
|
|
|
/* if this is the right data descriptor then the sizes should match */
|
|
if ((uzoff_t)des_start - (uzoff_t)data_start != des_csize) {
|
|
/* apparently this signature does not go with this data so skip */
|
|
|
|
/* write out signature as data */
|
|
k = 4;
|
|
if (zfseeko(in_file, des_start + k, SEEK_SET) != 0) {
|
|
/* seek error */
|
|
ZIPERR(ZE_READ, "seek failed reading descriptor");
|
|
}
|
|
if (bfwrite(b, 1, k, BFWRITE_DATA) != k)
|
|
{
|
|
free((zvoid *)b);
|
|
fprintf(mesg," fcopy: write error\n");
|
|
return ZE_TEMP;
|
|
}
|
|
m += k;
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
/* apparently this is the correct data descriptor */
|
|
|
|
/* we should check the CRC but would need to inflate
|
|
the data */
|
|
|
|
/* skip descriptor as will write out later */
|
|
des_good = 1;
|
|
k = 24;
|
|
data_start = zftello(in_file);
|
|
if (zfseeko(in_file, des_start + k, SEEK_SET) != 0) {
|
|
/* seek error */
|
|
ZIPERR(ZE_READ, "seek failed reading descriptor");
|
|
}
|
|
data_start = zftello(in_file);
|
|
}
|
|
}
|
|
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
/* read standard data descriptor */
|
|
|
|
if (k < 16) {
|
|
/* not enough bytes, so can't be data descriptor
|
|
as data descriptors can't be split across splits
|
|
*/
|
|
}
|
|
else
|
|
{
|
|
/* read the descriptor */
|
|
|
|
des_crc = LG(b + 4);
|
|
des_csize = LG(b + 8);
|
|
des_usize = LG(b + 12);
|
|
|
|
/* if this is the right data descriptor then the sizes should match */
|
|
if ((uzoff_t)des_start - (uzoff_t)data_start != des_csize) {
|
|
/* apparently this signature does not go with this data so skip */
|
|
|
|
/* write out signature as data */
|
|
k = 4;
|
|
if (zfseeko(in_file, des_start + k, SEEK_SET) != 0) {
|
|
/* seek error */
|
|
ZIPERR(ZE_READ, "seek failed reading descriptor");
|
|
}
|
|
if (bfwrite(b, 1, k, BFWRITE_DATA) != k)
|
|
{
|
|
free((zvoid *)b);
|
|
fprintf(mesg," fcopy: write error\n");
|
|
return ZE_TEMP;
|
|
}
|
|
m += k;
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
/* apparently this is the correct data descriptor */
|
|
|
|
/* we should check the CRC but this does not work for
|
|
encrypted data */
|
|
|
|
/* skip descriptor as will write out later */
|
|
des_good = 1;
|
|
data_start = zftello(in_file);
|
|
k = 16;
|
|
if (zfseeko(in_file, des_start + k, SEEK_SET) != 0) {
|
|
/* seek error */
|
|
ZIPERR(ZE_READ, "seek failed reading descriptor");
|
|
}
|
|
data_start = zftello(in_file);
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
if (des_good) {
|
|
/* skip descriptor as will write out later */
|
|
} else {
|
|
/* write out apparently wrong descriptor as data */
|
|
if (bfwrite(b, 1, k, BFWRITE_DATA) != k)
|
|
{
|
|
free((zvoid *)b);
|
|
fprintf(mesg," fcopy: write error\n");
|
|
return ZE_TEMP;
|
|
}
|
|
m += k;
|
|
}
|
|
|
|
if (copy_only && !display_globaldots) {
|
|
if (dot_size > 0) {
|
|
/* initial space */
|
|
if (noisy && dot_count == -1) {
|
|
#ifndef WINDLL
|
|
putc(' ', mesg);
|
|
fflush(mesg);
|
|
#else
|
|
fprintf(stdout,"%c",' ');
|
|
#endif
|
|
dot_count++;
|
|
}
|
|
dot_count += k;
|
|
if (dot_size <= dot_count) dot_count = 0;
|
|
}
|
|
if ((verbose || noisy) && dot_size && !dot_count) {
|
|
#ifndef WINDLL
|
|
putc('.', mesg);
|
|
fflush(mesg);
|
|
#else
|
|
fprintf(stdout,"%c",'.');
|
|
#endif
|
|
mesg_line_started = 1;
|
|
}
|
|
}
|
|
|
|
if (des_good)
|
|
break;
|
|
|
|
if (des)
|
|
continue;
|
|
|
|
if ((des || n != (uzoff_t)(-1L)) && m < n && feof(in_file)) {
|
|
/* open next split */
|
|
current_in_disk++;
|
|
|
|
if (current_in_disk >= total_disks) {
|
|
/* done */
|
|
break;
|
|
|
|
} else if (current_in_disk == total_disks - 1) {
|
|
/* last disk is archive.zip */
|
|
if ((split_path = malloc(strlen(in_path) + 1)) == NULL) {
|
|
zipwarn("reading archive: ", in_path);
|
|
return ZE_MEM;
|
|
}
|
|
strcpy(split_path, in_path);
|
|
} else {
|
|
/* other disks are archive.z01, archive.z02, ... */
|
|
split_path = get_in_split_path(in_path, current_in_disk);
|
|
}
|
|
|
|
fclose(in_file);
|
|
|
|
/* open the split */
|
|
while ((in_file = zfopen(split_path, FOPR)) == NULL) {
|
|
int r = 0;
|
|
|
|
/* could not open split */
|
|
|
|
if (fix == 1 && skip_this_disk) {
|
|
free(split_path);
|
|
free((zvoid *)b);
|
|
return ZE_FORM;
|
|
}
|
|
|
|
/* Ask for directory with split. Updates in_path */
|
|
r = ask_for_split_read_path(current_in_disk);
|
|
if (r == ZE_ABORT) {
|
|
zipwarn("could not find split: ", split_path);
|
|
free(split_path);
|
|
free((zvoid *)b);
|
|
return ZE_ABORT;
|
|
}
|
|
if (r == ZE_EOF) {
|
|
zipmessage_nl("", 1);
|
|
zipwarn("user ended reading - closing archive", "");
|
|
free(split_path);
|
|
free((zvoid *)b);
|
|
return ZE_EOF;
|
|
}
|
|
if (fix == 2 && skip_this_disk) {
|
|
/* user asked to skip this disk */
|
|
zipwarn("skipping split file: ", split_path);
|
|
current_in_disk++;
|
|
}
|
|
|
|
if (current_in_disk == total_disks - 1) {
|
|
/* last disk is archive.zip */
|
|
if ((split_path = malloc(strlen(in_path) + 1)) == NULL) {
|
|
zipwarn("reading archive: ", in_path);
|
|
return ZE_MEM;
|
|
}
|
|
strcpy(split_path, in_path);
|
|
} else {
|
|
/* other disks are archive.z01, archive.z02, ... */
|
|
split_path = get_in_split_path(zipfile, current_in_disk);
|
|
}
|
|
}
|
|
if (fix == 2 && skip_this_disk) {
|
|
/* user asked to skip this disk */
|
|
free(split_path);
|
|
free((zvoid *)b);
|
|
return ZE_FORM;
|
|
}
|
|
free(split_path);
|
|
}
|
|
}
|
|
free((zvoid *)b);
|
|
return ZE_OK;
|
|
}
|
|
|
|
/*------------------------------------------------------------------
|
|
* Split archives
|
|
*/
|
|
|
|
|
|
/* ask_for_split_read_path
|
|
*
|
|
* If the next split file is not in the current directory, ask
|
|
* the user where it is.
|
|
*
|
|
* in_path is the base path for reading splits and is usually
|
|
* the same as zipfile. The path in in_path must be the archive
|
|
* file ending in .zip as this is assumed by get_in_split_path().
|
|
*
|
|
* Updates in_path if changed. Returns ZE_OK if OK or ZE_ABORT if
|
|
* user cancels reading archive.
|
|
*
|
|
* If fix = 1 then allow skipping disk (user may not have it).
|
|
*/
|
|
|
|
#define SPLIT_MAXPATH (FNMAX + 4010)
|
|
|
|
int ask_for_split_read_path(current_disk)
|
|
ulg current_disk;
|
|
{
|
|
FILE *f;
|
|
int is_readable = 0;
|
|
int i;
|
|
char *split_dir = NULL;
|
|
char *archive_name = NULL;
|
|
char *split_name = NULL;
|
|
char *split_path = NULL;
|
|
char buf[SPLIT_MAXPATH + 100];
|
|
|
|
/* get split path */
|
|
split_path = get_in_split_path(in_path, current_disk);
|
|
|
|
/* get the directory */
|
|
if ((split_dir = malloc(strlen(in_path) + 40)) == NULL) {
|
|
ZIPERR(ZE_MEM, "split path");
|
|
}
|
|
strcpy(split_dir, in_path);
|
|
|
|
/* remove any name at end */
|
|
for (i = strlen(split_dir) - 1; i >= 0; i--) {
|
|
if (split_dir[i] == '/' || split_dir[i] == '\\'
|
|
|| split_dir[i] == ':') {
|
|
split_dir[i + 1] = '\0';
|
|
break;
|
|
}
|
|
}
|
|
if (i < 0)
|
|
split_dir[0] = '\0';
|
|
|
|
/* get the name of the archive */
|
|
if ((archive_name = malloc(strlen(in_path) + 1)) == NULL) {
|
|
ZIPERR(ZE_MEM, "split path");
|
|
}
|
|
if (strlen(in_path) == strlen(split_dir)) {
|
|
archive_name[0] = '\0';
|
|
} else {
|
|
strcpy(archive_name, in_path + strlen(split_dir));
|
|
}
|
|
|
|
/* get the name of the split */
|
|
if ((split_name = malloc(strlen(split_path) + 1)) == NULL) {
|
|
ZIPERR(ZE_MEM, "split path");
|
|
}
|
|
if (strlen(in_path) == strlen(split_dir)) {
|
|
split_name[0] = '\0';
|
|
} else {
|
|
strcpy(split_name, split_path + strlen(split_dir));
|
|
}
|
|
if (i < 0) {
|
|
strcpy(split_dir, "(current directory)");
|
|
}
|
|
|
|
fprintf(mesg, "\n\nCould not find:\n");
|
|
fprintf(mesg, " %s\n", split_path);
|
|
/*
|
|
fprintf(mesg, "Please enter the path directory (. for cur dir) where\n");
|
|
fprintf(mesg, " %s\n", split_name);
|
|
fprintf(mesg, "is located\n");
|
|
*/
|
|
for (;;) {
|
|
if (is_readable) {
|
|
fprintf(mesg, "\nHit c (change path to where this split file is)");
|
|
fprintf(mesg, "\n q (abort archive - quit)");
|
|
fprintf(mesg, "\n or ENTER (continue with this split): ");
|
|
} else {
|
|
if (fix == 1) {
|
|
fprintf(mesg, "\nHit c (change path to where this split file is)");
|
|
fprintf(mesg, "\n s (skip this split)");
|
|
fprintf(mesg, "\n q (abort archive - quit)");
|
|
fprintf(mesg, "\n or ENTER (try reading this split again): ");
|
|
} else if (fix == 2) {
|
|
fprintf(mesg, "\nHit c (change path to where this split file is)");
|
|
fprintf(mesg, "\n s (skip this split)");
|
|
fprintf(mesg, "\n q (abort archive - quit)");
|
|
fprintf(mesg, "\n e (end this archive - no more splits)");
|
|
fprintf(mesg, "\n z (look for .zip split - the last split)");
|
|
fprintf(mesg, "\n or ENTER (try reading this split again): ");
|
|
} else {
|
|
fprintf(mesg, "\nHit c (change path to where this split file is)");
|
|
fprintf(mesg, "\n q (abort archive - quit)");
|
|
fprintf(mesg, "\n or ENTER (try reading this split again): ");
|
|
}
|
|
}
|
|
fflush(mesg);
|
|
fgets(buf, SPLIT_MAXPATH, stdin);
|
|
/* remove any newline */
|
|
for (i = 0; buf[i]; i++) {
|
|
if (buf[i] == '\n') {
|
|
buf[i] = '\0';
|
|
break;
|
|
}
|
|
}
|
|
if (toupper(buf[0]) == 'Q') {
|
|
return ZE_ABORT;
|
|
} else if ((fix == 1 || fix == 2) && toupper(buf[0]) == 'S') {
|
|
/*
|
|
fprintf(mesg, "\nSkip this split/disk? (files in this split will not be recovered) [n/y] ");
|
|
fflush(mesg);
|
|
fgets(buf, SPLIT_MAXPATH, stdin);
|
|
if (buf[0] == 'y' || buf[0] == 'Y') {
|
|
*/
|
|
skip_this_disk = current_in_disk + 1;
|
|
return ZE_FORM;
|
|
} else if (toupper(buf[0]) == 'C') {
|
|
fprintf(mesg, "\nEnter path where this split is (ENTER = same dir, . = current dir)");
|
|
fprintf(mesg, "\n: ");
|
|
fflush(mesg);
|
|
fgets(buf, SPLIT_MAXPATH, stdin);
|
|
is_readable = 0;
|
|
/* remove any newline */
|
|
for (i = 0; buf[i]; i++) {
|
|
if (buf[i] == '\n') {
|
|
buf[i] = '\0';
|
|
break;
|
|
}
|
|
}
|
|
if (buf[0] == '\0') {
|
|
/* Hit ENTER so try old path again - could be removable media was changed */
|
|
strcpy(buf, split_path);
|
|
}
|
|
} else if (fix == 2 && toupper(buf[0]) == 'E') {
|
|
/* no more splits to read */
|
|
return ZE_EOF;
|
|
} else if (fix == 2 && toupper(buf[0]) == 'Z') {
|
|
total_disks = current_disk + 1;
|
|
free(split_path);
|
|
split_path = get_in_split_path(in_path, current_disk);
|
|
buf[0] = '\0';
|
|
strncat(buf, split_path, SPLIT_MAXPATH);
|
|
}
|
|
if (strlen(buf) > 0) {
|
|
/* changing path */
|
|
|
|
/* check if user wants current directory */
|
|
if (buf[0] == '.' && buf[1] == '\0') {
|
|
buf[0] = '\0';
|
|
}
|
|
/* remove any name at end */
|
|
for (i = strlen(buf); i >= 0; i--) {
|
|
if (buf[i] == '/' || buf[i] == '\\'
|
|
|| buf[i] == ':') {
|
|
buf[i + 1] = '\0';
|
|
break;
|
|
}
|
|
}
|
|
/* update base_path to newdir/split_name - in_path is the .zip file path */
|
|
free(in_path);
|
|
if (i < 0) {
|
|
/* just name so current directory */
|
|
strcpy(buf, "(current directory)");
|
|
if (archive_name == NULL) {
|
|
i = 0;
|
|
} else {
|
|
i = strlen(archive_name);
|
|
}
|
|
if ((in_path = malloc(strlen(archive_name) + 40)) == NULL) {
|
|
ZIPERR(ZE_MEM, "split path");
|
|
}
|
|
strcpy(in_path, archive_name);
|
|
} else {
|
|
/* not the current directory */
|
|
/* remove any name at end */
|
|
for (i = strlen(buf); i >= 0; i--) {
|
|
if (buf[i] == '/') {
|
|
buf[i + 1] = '\0';
|
|
break;
|
|
}
|
|
}
|
|
if (i < 0) {
|
|
buf[0] = '\0';
|
|
}
|
|
if ((in_path = malloc(strlen(buf) + strlen(archive_name) + 40)) == NULL) {
|
|
ZIPERR(ZE_MEM, "split path");
|
|
}
|
|
strcpy(in_path, buf);
|
|
strcat(in_path, archive_name);
|
|
}
|
|
|
|
free(split_path);
|
|
|
|
/* get split path */
|
|
split_path = get_in_split_path(in_path, current_disk);
|
|
|
|
free(split_dir);
|
|
if ((split_dir = malloc(strlen(in_path) + 40)) == NULL) {
|
|
ZIPERR(ZE_MEM, "split path");
|
|
}
|
|
strcpy(split_dir, in_path);
|
|
/* remove any name at end */
|
|
for (i = strlen(split_dir); i >= 0; i--) {
|
|
if (split_dir[i] == '/') {
|
|
split_dir[i + 1] = '\0';
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* try to open it */
|
|
if ((f = fopen(split_path, "r")) == NULL) {
|
|
fprintf(mesg, "\nCould not find or open\n");
|
|
fprintf(mesg, " %s\n", split_path);
|
|
/*
|
|
fprintf(mesg, "Please enter the path (. for cur dir) where\n");
|
|
fprintf(mesg, " %s\n", split_name);
|
|
fprintf(mesg, "is located\n");
|
|
*/
|
|
continue;
|
|
}
|
|
fclose(f);
|
|
is_readable = 1;
|
|
fprintf(mesg, "Found: %s\n", split_path);
|
|
} else {
|
|
/* try to open it */
|
|
if ((f = fopen(split_path, "r")) == NULL) {
|
|
fprintf(mesg, "\nCould not find or open\n");
|
|
fprintf(mesg, " %s\n", split_path);
|
|
/*
|
|
fprintf(mesg, "Please enter the path (. for cur dir) where\n");
|
|
fprintf(mesg, " %s\n", split_name);
|
|
fprintf(mesg, "is located\n");
|
|
*/
|
|
continue;
|
|
}
|
|
fclose(f);
|
|
is_readable = 1;
|
|
fprintf(mesg, "\nFound: %s\n", split_path);
|
|
break;
|
|
}
|
|
}
|
|
free(archive_name);
|
|
free(split_dir);
|
|
free(split_name);
|
|
|
|
return ZE_OK;
|
|
}
|
|
|
|
|
|
/* ask_for_split_write_path
|
|
*
|
|
* Verify the directory for the next split. Called
|
|
* when -sp is used to pause between writing splits.
|
|
*
|
|
* Updates out_path and return 1 if OK or 0 if cancel
|
|
*/
|
|
int ask_for_split_write_path(current_disk)
|
|
ulg current_disk;
|
|
{
|
|
unsigned int num = (unsigned int)current_disk + 1;
|
|
int i;
|
|
char *split_dir = NULL;
|
|
char *split_name = NULL;
|
|
char buf[FNMAX + 40];
|
|
|
|
/* get the directory */
|
|
if ((split_dir = malloc(strlen(out_path) + 40)) == NULL) {
|
|
ZIPERR(ZE_MEM, "split path");
|
|
}
|
|
strcpy(split_dir, out_path);
|
|
|
|
/* remove any name at end */
|
|
for (i = strlen(split_dir); i >= 0; i--) {
|
|
if (split_dir[i] == '/' || split_dir[i] == '\\'
|
|
|| split_dir[i] == ':') {
|
|
split_dir[i + 1] = '\0';
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* get the name of the split */
|
|
if ((split_name = malloc(strlen(out_path) + 1)) == NULL) {
|
|
ZIPERR(ZE_MEM, "split path");
|
|
}
|
|
if (strlen(out_path) == strlen(split_dir)) {
|
|
split_name[0] = '\0';
|
|
} else {
|
|
strcpy(split_name, out_path + strlen(split_dir));
|
|
}
|
|
if (i < 0) {
|
|
strcpy(split_dir, "(current directory)");
|
|
}
|
|
if (mesg_line_started)
|
|
fprintf(mesg, "\n");
|
|
fprintf(mesg, "\nOpening disk %d\n", num);
|
|
fprintf(mesg, "Hit ENTER to write to default path of\n");
|
|
fprintf(mesg, " %s\n", split_dir);
|
|
fprintf(mesg, "or enter a new directory path (. for cur dir) and hit ENTER\n");
|
|
for (;;) {
|
|
fprintf(mesg, "\nPath (or hit ENTER to continue): ");
|
|
fflush(mesg);
|
|
fgets(buf, FNMAX, stdin);
|
|
/* remove any newline */
|
|
for (i = 0; buf[i]; i++) {
|
|
if (buf[i] == '\n') {
|
|
buf[i] = '\0';
|
|
break;
|
|
}
|
|
}
|
|
if (strlen(buf) > 0) {
|
|
/* changing path */
|
|
|
|
/* current directory */
|
|
if (buf[0] == '.' && buf[1] == '\0') {
|
|
buf[0] = '\0';
|
|
}
|
|
/* remove any name at end */
|
|
for (i = strlen(buf); i >= 0; i--) {
|
|
if (buf[i] == '/' || buf[i] == '\\'
|
|
|| buf[i] == ':') {
|
|
buf[i + 1] = '\0';
|
|
break;
|
|
}
|
|
}
|
|
/* update out_path to newdir/split_name */
|
|
free(out_path);
|
|
if (i < 0) {
|
|
/* just name so current directory */
|
|
strcpy(buf, "(current directory)");
|
|
if (split_name == NULL) {
|
|
i = 0;
|
|
} else {
|
|
i = strlen(split_name);
|
|
}
|
|
if ((out_path = malloc(strlen(split_name) + 40)) == NULL) {
|
|
ZIPERR(ZE_MEM, "split path");
|
|
}
|
|
strcpy(out_path, split_name);
|
|
} else {
|
|
/* not the current directory */
|
|
/* remove any name at end */
|
|
for (i = strlen(buf); i >= 0; i--) {
|
|
if (buf[i] == '/') {
|
|
buf[i + 1] = '\0';
|
|
break;
|
|
}
|
|
}
|
|
if (i < 0) {
|
|
buf[0] = '\0';
|
|
}
|
|
if ((out_path = malloc(strlen(buf) + strlen(split_name) + 40)) == NULL) {
|
|
ZIPERR(ZE_MEM, "split path");
|
|
}
|
|
strcpy(out_path, buf);
|
|
strcat(out_path, split_name);
|
|
}
|
|
fprintf(mesg, "Writing to:\n %s\n", buf);
|
|
free(split_name);
|
|
free(split_dir);
|
|
if ((split_dir = malloc(strlen(out_path) + 40)) == NULL) {
|
|
ZIPERR(ZE_MEM, "split path");
|
|
}
|
|
strcpy(split_dir, out_path);
|
|
/* remove any name at end */
|
|
for (i = strlen(split_dir); i >= 0; i--) {
|
|
if (split_dir[i] == '/') {
|
|
split_dir[i + 1] = '\0';
|
|
break;
|
|
}
|
|
}
|
|
if ((split_name = malloc(strlen(out_path) + 1)) == NULL) {
|
|
ZIPERR(ZE_MEM, "split path");
|
|
}
|
|
strcpy(split_name, out_path + strlen(split_dir));
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
free(split_dir);
|
|
free(split_name);
|
|
|
|
/* for now no way out except Ctrl C */
|
|
return 1;
|
|
}
|
|
|
|
|
|
/* split_name
|
|
*
|
|
* get name of split being read
|
|
*/
|
|
char *get_in_split_path(base_path, disk_number)
|
|
char *base_path;
|
|
ulg disk_number;
|
|
{
|
|
char *split_path = NULL;
|
|
int base_len = 0;
|
|
int path_len = 0;
|
|
ulg num = disk_number + 1;
|
|
char ext[6];
|
|
#ifdef VMS
|
|
int vers_len; /* File version length. */
|
|
char *vers_ptr; /* File version string. */
|
|
#endif /* def VMS */
|
|
|
|
/*
|
|
* A split has extension z01, z02, ..., z99, z100, z101, ... z999
|
|
* We currently support up to .z99999
|
|
* WinZip will also read .100, .101, ... but AppNote 6.2.2 uses above
|
|
* so use that. Means on DOS can only have 100 splits.
|
|
*/
|
|
|
|
if (num == total_disks) {
|
|
/* last disk is base path */
|
|
if ((split_path = malloc(strlen(base_path) + 1)) == NULL) {
|
|
ZIPERR(ZE_MEM, "base path");
|
|
}
|
|
strcpy(split_path, base_path);
|
|
|
|
return split_path;
|
|
} else {
|
|
if (num > 99999) {
|
|
ZIPERR(ZE_BIG, "More than 99999 splits needed");
|
|
}
|
|
sprintf(ext, "z%02lu", num);
|
|
}
|
|
|
|
/* create path for this split - zip.c checked for .zip extension */
|
|
base_len = strlen(base_path) - 3;
|
|
path_len = base_len + strlen(ext);
|
|
|
|
#ifdef VMS
|
|
/* On VMS, locate the file version, and adjust base_len accordingly.
|
|
Note that path_len is correct, as-is.
|
|
*/
|
|
vers_ptr = vms_file_version( base_path);
|
|
vers_len = strlen( vers_ptr);
|
|
base_len -= vers_len;
|
|
#endif /* def VMS */
|
|
|
|
if ((split_path = malloc(path_len + 1)) == NULL) {
|
|
ZIPERR(ZE_MEM, "split path");
|
|
}
|
|
/* copy base_path except for end zip */
|
|
strcpy(split_path, base_path);
|
|
split_path[base_len] = '\0';
|
|
/* add extension */
|
|
strcat(split_path, ext);
|
|
|
|
#ifdef VMS
|
|
/* On VMS, append (preserve) the file version. */
|
|
strcat(split_path, vers_ptr);
|
|
#endif /* def VMS */
|
|
|
|
return split_path;
|
|
}
|
|
|
|
|
|
/* split_name
|
|
*
|
|
* get name of split being written
|
|
*/
|
|
char *get_out_split_path(base_path, disk_number)
|
|
char *base_path;
|
|
ulg disk_number;
|
|
{
|
|
char *split_path = NULL;
|
|
int base_len = 0;
|
|
int path_len = 0;
|
|
ulg num = disk_number + 1;
|
|
char ext[6];
|
|
#ifdef VMS
|
|
int vers_len; /* File version length. */
|
|
char *vers_ptr; /* File version string. */
|
|
#endif /* def VMS */
|
|
|
|
/*
|
|
* A split has extension z01, z02, ..., z99, z100, z101, ... z999
|
|
* We currently support up to .z99999
|
|
* WinZip will also read .100, .101, ... but AppNote 6.2.2 uses above
|
|
* so use that. Means on DOS can only have 100 splits.
|
|
*/
|
|
|
|
if (num > 99999) {
|
|
ZIPERR(ZE_BIG, "More than 99999 splits needed");
|
|
}
|
|
sprintf(ext, "z%02lu", num);
|
|
|
|
/* create path for this split - zip.c checked for .zip extension */
|
|
base_len = strlen(base_path) - 3;
|
|
path_len = base_len + strlen(ext);
|
|
|
|
#ifdef VMS
|
|
/* On VMS, locate the file version, and adjust base_len accordingly.
|
|
Note that path_len is correct, as-is.
|
|
*/
|
|
vers_ptr = vms_file_version( base_path);
|
|
vers_len = strlen( vers_ptr);
|
|
base_len -= vers_len;
|
|
#endif /* def VMS */
|
|
|
|
if ((split_path = malloc(path_len + 1)) == NULL) {
|
|
ZIPERR(ZE_MEM, "split path");
|
|
}
|
|
/* copy base_path except for end zip */
|
|
strcpy(split_path, base_path);
|
|
split_path[base_len] = '\0';
|
|
/* add extension */
|
|
strcat(split_path, ext);
|
|
|
|
#ifdef VMS
|
|
/* On VMS, append (preserve) the file version. */
|
|
strcat(split_path, vers_ptr);
|
|
#endif /* def VMS */
|
|
|
|
return split_path;
|
|
}
|
|
|
|
/* close_split
|
|
*
|
|
* close a split - assume that the paths needed for the splits are
|
|
* available.
|
|
*/
|
|
int close_split(disk_number, tempfile, temp_name)
|
|
ulg disk_number;
|
|
FILE *tempfile;
|
|
char *temp_name;
|
|
{
|
|
char *split_path = NULL;
|
|
|
|
split_path = get_out_split_path(out_path, disk_number);
|
|
|
|
if (noisy_splits) {
|
|
zipmessage("\tClosing split ", split_path);
|
|
}
|
|
|
|
fclose(tempfile);
|
|
|
|
rename_split(temp_name, split_path);
|
|
set_filetype(split_path);
|
|
|
|
return ZE_OK;
|
|
}
|
|
|
|
/* bfwrite
|
|
Does the fwrite but also counts bytes and does splits */
|
|
size_t bfwrite(buffer, size, count, mode)
|
|
ZCONST void *buffer;
|
|
size_t size;
|
|
size_t count;
|
|
int mode;
|
|
{
|
|
size_t bytes_written = 0;
|
|
size_t r;
|
|
size_t b = size * count;
|
|
uzoff_t bytes_left_in_split = 0;
|
|
size_t bytes_to_write = b;
|
|
|
|
|
|
/* -------------------------------- */
|
|
/* local header */
|
|
if (mode == BFWRITE_LOCALHEADER) {
|
|
/* writing local header - reset entry data count */
|
|
bytes_this_entry = 0;
|
|
/* save start of local header so we can rewrite later */
|
|
current_local_file = y;
|
|
current_local_disk = current_disk;
|
|
current_local_offset = bytes_this_split;
|
|
}
|
|
|
|
if (split_size == 0)
|
|
bytes_left_in_split = bytes_to_write;
|
|
else
|
|
bytes_left_in_split = split_size - bytes_this_split;
|
|
|
|
if (bytes_to_write > bytes_left_in_split) {
|
|
if (mode == BFWRITE_HEADER ||
|
|
mode == BFWRITE_LOCALHEADER ||
|
|
mode == BFWRITE_CENTRALHEADER) {
|
|
/* if can't write entire header save for next split */
|
|
bytes_to_write = 0;
|
|
} else {
|
|
/* normal data so fill the split */
|
|
bytes_to_write = (size_t)bytes_left_in_split;
|
|
}
|
|
}
|
|
|
|
/* -------------------------------- */
|
|
/* central header */
|
|
if (mode == BFWRITE_CENTRALHEADER) {
|
|
/* set start disk for CD */
|
|
if (cd_start_disk == (ulg)-1) {
|
|
cd_start_disk = current_disk;
|
|
cd_start_offset = bytes_this_split;
|
|
}
|
|
cd_entries_this_disk++;
|
|
total_cd_entries++;
|
|
}
|
|
|
|
/* -------------------------------- */
|
|
if (bytes_to_write > 0) {
|
|
/* write out the bytes for this split */
|
|
r = fwrite(buffer, size, bytes_to_write, y);
|
|
bytes_written += r;
|
|
bytes_to_write = b - r;
|
|
bytes_this_split += r;
|
|
if (mode == BFWRITE_DATA)
|
|
/* if data descriptor do not include in count */
|
|
bytes_this_entry += r;
|
|
} else {
|
|
bytes_to_write = b;
|
|
}
|
|
|
|
if (bytes_to_write > 0) {
|
|
if (split_method) {
|
|
/* still bytes to write so close split and open next split */
|
|
bytes_prev_splits += bytes_this_split;
|
|
|
|
if (split_method == 1 && ferror(y)) {
|
|
/* if writing all splits to same place and have problem then bad */
|
|
ZIPERR(ZE_WRITE, "Could not write split");
|
|
}
|
|
|
|
if (split_method == 2 && ferror(y)) {
|
|
/* A split must be at least 64K except last .zip split */
|
|
if (bytes_this_split < 64 * (uzoff_t)0x400) {
|
|
ZIPERR(ZE_WRITE, "Not enough space to write split");
|
|
}
|
|
}
|
|
|
|
/* close this split */
|
|
if (split_method == 1 && current_local_disk == current_disk) {
|
|
/* keep split open so can update it */
|
|
current_local_tempname = tempzip;
|
|
} else {
|
|
/* close split */
|
|
close_split(current_disk, y, tempzip);
|
|
y = NULL;
|
|
free(tempzip);
|
|
tempzip = NULL;
|
|
}
|
|
cd_entries_this_disk = 0;
|
|
bytes_this_split = 0;
|
|
|
|
/* increment disk - disks are numbered 0, 1, 2, ... and
|
|
splits are 01, 02, ... */
|
|
current_disk++;
|
|
|
|
if (split_method == 2 && split_bell) {
|
|
/* bell when pause to ask for next split */
|
|
putc('\007', mesg);
|
|
fflush(mesg);
|
|
}
|
|
|
|
for (;;) {
|
|
/* if method 2 pause and allow changing path */
|
|
if (split_method == 2) {
|
|
if (ask_for_split_write_path(current_disk) == 0) {
|
|
ZIPERR(ZE_ABORT, "could not write split");
|
|
}
|
|
}
|
|
|
|
/* open next split */
|
|
#if defined(UNIX) && !defined(NO_MKSTEMP)
|
|
{
|
|
int yd;
|
|
int i;
|
|
|
|
/* use mkstemp to avoid race condition and compiler warning */
|
|
|
|
if (tempath != NULL)
|
|
{
|
|
/* if -b used to set temp file dir use that for split temp */
|
|
if ((tempzip = malloc(strlen(tempath) + 12)) == NULL) {
|
|
ZIPERR(ZE_MEM, "allocating temp filename");
|
|
}
|
|
strcpy(tempzip, tempath);
|
|
if (lastchar(tempzip) != '/')
|
|
strcat(tempzip, "/");
|
|
}
|
|
else
|
|
{
|
|
/* create path by stripping name and appending template */
|
|
if ((tempzip = malloc(strlen(zipfile) + 12)) == NULL) {
|
|
ZIPERR(ZE_MEM, "allocating temp filename");
|
|
}
|
|
strcpy(tempzip, zipfile);
|
|
for(i = strlen(tempzip); i > 0; i--) {
|
|
if (tempzip[i - 1] == '/')
|
|
break;
|
|
}
|
|
tempzip[i] = '\0';
|
|
}
|
|
strcat(tempzip, "ziXXXXXX");
|
|
|
|
if ((yd = mkstemp(tempzip)) == EOF) {
|
|
ZIPERR(ZE_TEMP, tempzip);
|
|
}
|
|
if ((y = fdopen(yd, FOPW_TMP)) == NULL) {
|
|
ZIPERR(ZE_TEMP, tempzip);
|
|
}
|
|
}
|
|
#else
|
|
if ((tempzip = tempname(zipfile)) == NULL) {
|
|
ZIPERR(ZE_MEM, "allocating temp filename");
|
|
}
|
|
if ((y = zfopen(tempzip, FOPW_TMP)) == NULL) {
|
|
ZIPERR(ZE_TEMP, tempzip);
|
|
}
|
|
#endif
|
|
|
|
r = fwrite((char *)buffer + bytes_written, 1, bytes_to_write, y);
|
|
bytes_written += r;
|
|
bytes_this_split += r;
|
|
if (!(mode == BFWRITE_HEADER ||
|
|
mode == BFWRITE_LOCALHEADER ||
|
|
mode == BFWRITE_CENTRALHEADER)) {
|
|
bytes_this_entry += r;
|
|
}
|
|
if (bytes_to_write > r) {
|
|
/* buffer bigger than split */
|
|
if (split_method == 2) {
|
|
/* let user choose another disk */
|
|
zipwarn("Not enough room on disk", "");
|
|
continue;
|
|
} else {
|
|
ZIPERR(ZE_WRITE, "Not enough room on disk");
|
|
}
|
|
}
|
|
if (mode == BFWRITE_LOCALHEADER ||
|
|
mode == BFWRITE_HEADER ||
|
|
mode == BFWRITE_CENTRALHEADER) {
|
|
if (split_method == 1 && current_local_file &&
|
|
current_local_disk != current_disk) {
|
|
/* We're opening a new split because the next header
|
|
did not fit on the last split. We need to now close
|
|
the last split and update the pointers for
|
|
the current split. */
|
|
close_split(current_local_disk, current_local_file,
|
|
current_local_tempname);
|
|
free(current_local_tempname);
|
|
}
|
|
current_local_tempname = tempzip;
|
|
current_local_file = y;
|
|
current_local_offset = 0;
|
|
current_local_disk = current_disk;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* likely have more than fits but no splits */
|
|
|
|
/* probably already have error "no space left on device" */
|
|
/* could let flush_outbuf() handle error but bfwrite() is called for
|
|
headers also */
|
|
if (ferror(y))
|
|
ziperr(ZE_WRITE, "write error on zip file");
|
|
}
|
|
}
|
|
|
|
|
|
/* display dots for archive instead of for each file */
|
|
if (display_globaldots) {
|
|
if (dot_size > 0) {
|
|
/* initial space */
|
|
if (dot_count == -1) {
|
|
#ifndef WINDLL
|
|
putc(' ', mesg);
|
|
fflush(mesg);
|
|
#else
|
|
fprintf(stdout,"%c",' ');
|
|
#endif
|
|
/* assume a header will be written first, so avoid 0 */
|
|
dot_count = 1;
|
|
}
|
|
/* skip incrementing dot count for small buffers like for headers */
|
|
if (size * count > 1000) {
|
|
dot_count++;
|
|
if (dot_size <= dot_count * (zoff_t)size * (zoff_t)count) dot_count = 0;
|
|
}
|
|
}
|
|
if (dot_size && !dot_count) {
|
|
dot_count++;
|
|
#ifndef WINDLL
|
|
putc('.', mesg);
|
|
fflush(mesg);
|
|
#else
|
|
fprintf(stdout,"%c",'.');
|
|
#endif
|
|
mesg_line_started = 1;
|
|
}
|
|
}
|
|
|
|
|
|
return bytes_written;
|
|
}
|
|
|
|
|
|
#ifdef UNICODE_SUPPORT
|
|
|
|
/*---------------------------------------------
|
|
* Unicode conversion functions
|
|
*
|
|
* Provided by Paul Kienitz
|
|
*
|
|
* Some modifications to work with Zip
|
|
*
|
|
*---------------------------------------------
|
|
*/
|
|
|
|
/*
|
|
NOTES APPLICABLE TO ALL STRING FUNCTIONS:
|
|
|
|
All of the x_to_y functions take parameters for an output buffer and
|
|
its available length, and return an int. The value returned is the
|
|
length of the string that the input produces, which may be larger than
|
|
the provided buffer length. If the returned value is less than the
|
|
buffer length, then the contents of the buffer will be null-terminated;
|
|
otherwise, it will not be terminated and may be invalid, possibly
|
|
stopping in the middle of a multibyte sequence.
|
|
|
|
In all cases you may pass NULL as the buffer and/or 0 as the length, if
|
|
you just want to learn how much space the string is going to require.
|
|
|
|
The functions will return -1 if the input is invalid UTF-8 or cannot be
|
|
encoded as UTF-8.
|
|
*/
|
|
|
|
/* utility functions for managing UTF-8 and UCS-4 strings */
|
|
|
|
|
|
/* utf8_char_bytes
|
|
*
|
|
* Returns the number of bytes used by the first character in a UTF-8
|
|
* string, or -1 if the UTF-8 is invalid or null.
|
|
*/
|
|
local int utf8_char_bytes(utf8)
|
|
ZCONST char *utf8;
|
|
{
|
|
int t, r;
|
|
unsigned lead;
|
|
|
|
if (!utf8)
|
|
return -1; /* no input */
|
|
lead = (unsigned char) *utf8;
|
|
if (lead < 0x80)
|
|
r = 1; /* an ascii-7 character */
|
|
else if (lead < 0xC0)
|
|
return -1; /* error: trailing byte without lead byte */
|
|
else if (lead < 0xE0)
|
|
r = 2; /* an 11 bit character */
|
|
else if (lead < 0xF0)
|
|
r = 3; /* a 16 bit character */
|
|
else if (lead < 0xF8)
|
|
r = 4; /* a 21 bit character (the most currently used) */
|
|
else if (lead < 0xFC)
|
|
r = 5; /* a 26 bit character (shouldn't happen) */
|
|
else if (lead < 0xFE)
|
|
r = 6; /* a 31 bit character (shouldn't happen) */
|
|
else
|
|
return -1; /* error: invalid lead byte */
|
|
for (t = 1; t < r; t++)
|
|
if ((unsigned char) utf8[t] < 0x80 || (unsigned char) utf8[t] >= 0xC0)
|
|
return -1; /* error: not enough valid trailing bytes */
|
|
return r;
|
|
}
|
|
|
|
|
|
/* ucs4_char_from_utf8
|
|
*
|
|
* Given a reference to a pointer into a UTF-8 string, returns the next
|
|
* UCS-4 character and advances the pointer to the next character sequence.
|
|
* Returns ~0 and does not advance the pointer when input is ill-formed.
|
|
*
|
|
* Since the Unicode standard says 32-bit values won't be used (just
|
|
* up to the current 21-bit mappings) changed this to signed to allow -1 to
|
|
* be returned.
|
|
*/
|
|
long ucs4_char_from_utf8(utf8)
|
|
ZCONST char **utf8;
|
|
{
|
|
ulg ret;
|
|
int t, bytes;
|
|
|
|
if (!utf8)
|
|
return -1; /* no input */
|
|
bytes = utf8_char_bytes(*utf8);
|
|
if (bytes <= 0)
|
|
return -1; /* invalid input */
|
|
if (bytes == 1)
|
|
ret = **utf8; /* ascii-7 */
|
|
else
|
|
ret = **utf8 & (0x7F >> bytes); /* lead byte of a multibyte sequence */
|
|
(*utf8)++;
|
|
for (t = 1; t < bytes; t++) /* consume trailing bytes */
|
|
ret = (ret << 6) | (*((*utf8)++) & 0x3F);
|
|
return (long) ret;
|
|
}
|
|
|
|
|
|
/* utf8_from_ucs4_char - Convert UCS char to UTF-8
|
|
*
|
|
* Returns the number of bytes put into utf8buf to represent ch, from 1 to 6,
|
|
* or -1 if ch is too large to represent. utf8buf must have room for 6 bytes.
|
|
*/
|
|
local int utf8_from_ucs4_char(utf8buf, ch)
|
|
char *utf8buf;
|
|
ulg ch;
|
|
{
|
|
int trailing = 0;
|
|
int leadmask = 0x80;
|
|
int leadbits = 0x3F;
|
|
ulg tch = ch;
|
|
int ret;
|
|
|
|
if (ch > 0x7FFFFFFF)
|
|
return -1; /* UTF-8 can represent 31 bits */
|
|
if (ch < 0x7F)
|
|
{
|
|
*utf8buf++ = (char) ch; /* ascii-7 */
|
|
return 1;
|
|
}
|
|
do {
|
|
trailing++;
|
|
leadmask = (leadmask >> 1) | 0x80;
|
|
leadbits >>= 1;
|
|
tch >>= 6;
|
|
} while (tch & ~leadbits);
|
|
ret = trailing + 1;
|
|
/* produce lead byte */
|
|
*utf8buf++ = (char) (leadmask | (ch >> (6 * trailing)));
|
|
/* produce trailing bytes */
|
|
while (--trailing >= 0)
|
|
*utf8buf++ = (char) (0x80 | ((ch >> (6 * trailing)) & 0x3F));
|
|
return ret;
|
|
}
|
|
|
|
|
|
/*===================================================================*/
|
|
|
|
/* utf8_to_ucs4_string - convert UTF-8 string to UCS string
|
|
*
|
|
* Return UCS count. Now returns int so can return -1.
|
|
*/
|
|
local int utf8_to_ucs4_string(utf8, ucs4buf, buflen)
|
|
ZCONST char *utf8;
|
|
ulg *ucs4buf;
|
|
int buflen;
|
|
{
|
|
int count = 0;
|
|
|
|
for (;;)
|
|
{
|
|
long ch = ucs4_char_from_utf8(&utf8);
|
|
if (ch == -1)
|
|
return -1;
|
|
else
|
|
{
|
|
if (ucs4buf && count < buflen)
|
|
ucs4buf[count] = ch;
|
|
if (ch == 0)
|
|
return count;
|
|
count++;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/* ucs4_string_to_utf8
|
|
*
|
|
*
|
|
*/
|
|
local int ucs4_string_to_utf8(ucs4, utf8buf, buflen)
|
|
ZCONST ulg *ucs4;
|
|
char *utf8buf;
|
|
int buflen;
|
|
{
|
|
char mb[6];
|
|
int count = 0;
|
|
|
|
if (!ucs4)
|
|
return -1;
|
|
for (;;)
|
|
{
|
|
int mbl = utf8_from_ucs4_char(mb, *ucs4++);
|
|
int c;
|
|
if (mbl <= 0)
|
|
return -1;
|
|
/* We could optimize this a bit by passing utf8buf + count */
|
|
/* directly to utf8_from_ucs4_char when buflen >= count + 6... */
|
|
c = buflen - count;
|
|
if (mbl < c)
|
|
c = mbl;
|
|
if (utf8buf && count < buflen)
|
|
strlcpy(utf8buf + count, mb, c);
|
|
if (mbl == 1 && !mb[0])
|
|
return count; /* terminating nul */
|
|
count += mbl;
|
|
}
|
|
}
|
|
|
|
|
|
#if 0 /* currently unused */
|
|
/* utf8_chars
|
|
*
|
|
* Wrapper: counts the actual unicode characters in a UTF-8 string.
|
|
*/
|
|
local int utf8_chars(utf8)
|
|
ZCONST char *utf8;
|
|
{
|
|
return utf8_to_ucs4_string(utf8, NULL, 0);
|
|
}
|
|
#endif
|
|
|
|
|
|
/* --------------------------------------------------- */
|
|
/* Unicode Support
|
|
*
|
|
* These functions common for all Unicode ports.
|
|
*
|
|
* These functions should allocate and return strings that can be
|
|
* freed with free().
|
|
*
|
|
* 8/27/05 EG
|
|
*
|
|
* Use zwchar for wide char which is unsigned long
|
|
* in zip.h and 32 bits. This avoids problems with
|
|
* different sizes of wchar_t.
|
|
*/
|
|
|
|
#ifdef WIN32
|
|
|
|
zwchar *wchar_to_wide_string(wchar_string)
|
|
wchar_t *wchar_string;
|
|
{
|
|
int i;
|
|
int wchar_len;
|
|
zwchar *wide_string;
|
|
|
|
wchar_len = wcslen(wchar_string);
|
|
|
|
if ((wide_string = malloc((wchar_len + 1) * sizeof(zwchar))) == NULL) {
|
|
ZIPERR(ZE_MEM, "wchar to wide conversion");
|
|
}
|
|
for (i = 0; i <= wchar_len; i++) {
|
|
wide_string[i] = wchar_string[i];
|
|
}
|
|
|
|
return wide_string;
|
|
}
|
|
|
|
/* is_ascii_stringw
|
|
* Checks if a wide string is all ascii
|
|
*/
|
|
int is_ascii_stringw(wstring)
|
|
wchar_t *wstring;
|
|
{
|
|
wchar_t *pw;
|
|
wchar_t cw;
|
|
|
|
if (wstring == NULL)
|
|
return 0;
|
|
|
|
for (pw = wstring; (cw = *pw) != '\0'; pw++) {
|
|
if (cw > 0x7F) {
|
|
return 0;
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
#endif
|
|
|
|
/* is_ascii_string
|
|
* Checks if a string is all ascii
|
|
*/
|
|
int is_ascii_string(mbstring)
|
|
char *mbstring;
|
|
{
|
|
char *p;
|
|
uch c;
|
|
|
|
if (mbstring == NULL)
|
|
return 0;
|
|
|
|
for (p = mbstring; (c = (uch)*p) != '\0'; p++) {
|
|
if (c > 0x7F) {
|
|
return 0;
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/* local to UTF-8 */
|
|
char *local_to_utf8_string(local_string)
|
|
char *local_string;
|
|
{
|
|
zwchar *wide_string = local_to_wide_string(local_string);
|
|
char *utf8_string = wide_to_utf8_string(wide_string);
|
|
|
|
free(wide_string);
|
|
return utf8_string;
|
|
}
|
|
|
|
/* wide_char_to_escape_string
|
|
provides a string that represents a wide char not in local char set
|
|
|
|
An initial try at an algorithm. Suggestions welcome.
|
|
|
|
If not an ASCII char, probably need 2 bytes at least. So if
|
|
a 2-byte wide encode it as 4 hex digits with a leading #U.
|
|
Since the Unicode standard has been frozen, it looks like 3 bytes
|
|
should be enough for any large Unicode character. In these cases
|
|
prefix the string with #L.
|
|
So
|
|
#U1234
|
|
is a 2-byte wide character with bytes 0x12 and 0x34 while
|
|
#L123456
|
|
is a 3-byte wide with bytes 0x12, 0x34, and 0x56.
|
|
On Windows, wide that need two wide characters as a surrogate pair
|
|
to represent them need to be converted to a single number.
|
|
*/
|
|
|
|
/* set this to the max bytes an escape can be */
|
|
#define MAX_ESCAPE_BYTES 8
|
|
|
|
char *wide_char_to_escape_string(wide_char)
|
|
zwchar wide_char;
|
|
{
|
|
int i;
|
|
zwchar w = wide_char;
|
|
uch b[9];
|
|
char e[7];
|
|
int len;
|
|
char *r;
|
|
|
|
/* fill byte array with zeros */
|
|
for (len = 0; len < sizeof(zwchar); len++) {
|
|
b[len] = 0;
|
|
}
|
|
/* get bytes in right to left order */
|
|
for (len = 0; w; len++) {
|
|
b[len] = (char)(w % 0x100);
|
|
w /= 0x100;
|
|
}
|
|
|
|
if ((r = malloc(MAX_ESCAPE_BYTES + 8)) == NULL) {
|
|
ZIPERR(ZE_MEM, "wide_char_to_escape_string");
|
|
}
|
|
strcpy(r, "#");
|
|
/* either 2 bytes or 4 bytes */
|
|
if (len < 3) {
|
|
len = 2;
|
|
strcat(r, "U");
|
|
} else {
|
|
len = 3;
|
|
strcat(r, "L");
|
|
}
|
|
for (i = len - 1; i >= 0; i--) {
|
|
sprintf(e, "%02x", b[i]);
|
|
strcat(r, e);
|
|
}
|
|
return r;
|
|
}
|
|
|
|
#if 0
|
|
/* returns the wide character represented by the escape string */
|
|
zwchar escape_string_to_wide(escape_string)
|
|
char *escape_string;
|
|
{
|
|
int i;
|
|
zwchar w;
|
|
char c;
|
|
char u;
|
|
int len;
|
|
char *e = escape_string;
|
|
|
|
if (e == NULL) {
|
|
return 0;
|
|
}
|
|
if (e[0] != '#') {
|
|
/* no leading # */
|
|
return 0;
|
|
}
|
|
len = strlen(e);
|
|
/* either #U1234 or #L123456 format */
|
|
if (len != 6 && len != 8) {
|
|
return 0;
|
|
}
|
|
w = 0;
|
|
if (e[1] == 'L') {
|
|
if (len != 8) {
|
|
return 0;
|
|
}
|
|
/* 3 bytes */
|
|
for (i = 2; i < 8; i++) {
|
|
c = e[i];
|
|
u = toupper(c);
|
|
if (u >= 'A' && u <= 'F') {
|
|
w = w * 0x10 + (zwchar)(u + 10 - 'A');
|
|
} else if (c >= '0' && c <= '9') {
|
|
w = w * 0x10 + (zwchar)(c - '0');
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
} else if (e[1] == 'U') {
|
|
/* 2 bytes */
|
|
for (i = 2; i < 6; i++) {
|
|
c = e[i];
|
|
u = toupper(c);
|
|
if (u >= 'A' && u <= 'F') {
|
|
w = w * 0x10 + (zwchar)(u + 10 - 'A');
|
|
} else if (c >= '0' && c <= '9') {
|
|
w = w * 0x10 + (zwchar)(c - '0');
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
return w;
|
|
}
|
|
#endif
|
|
|
|
|
|
char *local_to_escape_string(local_string)
|
|
char *local_string;
|
|
{
|
|
zwchar *wide_string = local_to_wide_string(local_string);
|
|
char *escape_string = wide_to_escape_string(wide_string);
|
|
|
|
free(wide_string);
|
|
return escape_string;
|
|
}
|
|
|
|
#ifdef WIN32
|
|
char *wchar_to_local_string(wstring)
|
|
wchar_t *wstring;
|
|
{
|
|
zwchar *wide_string = wchar_to_wide_string(wstring);
|
|
char *local_string = wide_to_local_string(wide_string);
|
|
|
|
free(wide_string);
|
|
|
|
return local_string;
|
|
}
|
|
#endif
|
|
|
|
|
|
#ifndef WIN32 /* The Win32 port uses a system-specific variant. */
|
|
/* convert wide character string to multi-byte character string */
|
|
char *wide_to_local_string(wide_string)
|
|
zwchar *wide_string;
|
|
{
|
|
int i;
|
|
wchar_t wc;
|
|
int b;
|
|
int wsize = 0;
|
|
int max_bytes = MB_CUR_MAX;
|
|
char buf[9];
|
|
char *buffer = NULL;
|
|
char *local_string = NULL;
|
|
|
|
for (wsize = 0; wide_string[wsize]; wsize++) ;
|
|
|
|
if (MAX_ESCAPE_BYTES > max_bytes)
|
|
max_bytes = MAX_ESCAPE_BYTES;
|
|
|
|
if ((buffer = (char *)malloc(wsize * max_bytes + 1)) == NULL) {
|
|
ZIPERR(ZE_MEM, "wide_to_local_string");
|
|
}
|
|
|
|
/* convert it */
|
|
buffer[0] = '\0';
|
|
/* set initial state if state-dependent encoding */
|
|
wc = (wchar_t)'a';
|
|
b = wctomb(NULL, wc);
|
|
for (i = 0; i < wsize; i++) {
|
|
if (sizeof(wchar_t) < 4 && wide_string[i] > 0xFFFF) {
|
|
/* wchar_t probably 2 bytes */
|
|
/* could do surrogates if state_dependent and wctomb can do */
|
|
wc = zwchar_to_wchar_t_default_char;
|
|
} else {
|
|
wc = (wchar_t)wide_string[i];
|
|
}
|
|
b = wctomb(buf, wc);
|
|
if (unicode_escape_all) {
|
|
if (b == 1 && (uch)buf[0] <= 0x7f) {
|
|
/* ASCII */
|
|
strncat(buffer, buf, b);
|
|
} else {
|
|
/* use escape for wide character */
|
|
char *e = wide_char_to_escape_string(wide_string[i]);
|
|
strcat(buffer, e);
|
|
free(e);
|
|
}
|
|
} else if (b > 0) {
|
|
/* multi-byte char */
|
|
strncat(buffer, buf, b);
|
|
} else {
|
|
/* no MB for this wide */
|
|
if (use_wide_to_mb_default) {
|
|
/* default character */
|
|
strcat(buffer, wide_to_mb_default_string);
|
|
} else {
|
|
/* use escape for wide character */
|
|
char *e = wide_char_to_escape_string(wide_string[i]);
|
|
strcat(buffer, e);
|
|
free(e);
|
|
}
|
|
}
|
|
}
|
|
if ((local_string = (char *)malloc(strlen(buffer) + 1)) == NULL) {
|
|
free(buffer);
|
|
ZIPERR(ZE_MEM, "wide_to_local_string");
|
|
}
|
|
strcpy(local_string, buffer);
|
|
free(buffer);
|
|
|
|
return local_string;
|
|
}
|
|
#endif /* !WIN32 */
|
|
|
|
|
|
/* convert wide character string to escaped string */
|
|
char *wide_to_escape_string(wide_string)
|
|
zwchar *wide_string;
|
|
{
|
|
int i;
|
|
int wsize = 0;
|
|
char buf[9];
|
|
char *buffer = NULL;
|
|
char *escape_string = NULL;
|
|
|
|
for (wsize = 0; wide_string[wsize]; wsize++) ;
|
|
|
|
if ((buffer = (char *)malloc(wsize * MAX_ESCAPE_BYTES + 1)) == NULL) {
|
|
ZIPERR(ZE_MEM, "wide_to_escape_string");
|
|
}
|
|
|
|
/* convert it */
|
|
buffer[0] = '\0';
|
|
for (i = 0; i < wsize; i++) {
|
|
if (wide_string[i] <= 0x7f && isprint((char)wide_string[i])) {
|
|
/* ASCII */
|
|
buf[0] = (char)wide_string[i];
|
|
buf[1] = '\0';
|
|
strcat(buffer, buf);
|
|
} else {
|
|
/* use escape for wide character */
|
|
char *e = wide_char_to_escape_string(wide_string[i]);
|
|
strcat(buffer, e);
|
|
free(e);
|
|
}
|
|
}
|
|
if ((escape_string = (char *)malloc(strlen(buffer) + 1)) == NULL) {
|
|
ZIPERR(ZE_MEM, "wide_to_escape_string");
|
|
}
|
|
strcpy(escape_string, buffer);
|
|
free(buffer);
|
|
|
|
return escape_string;
|
|
}
|
|
|
|
|
|
/* convert local string to display character set string */
|
|
char *local_to_display_string(local_string)
|
|
char *local_string;
|
|
{
|
|
char *temp_string;
|
|
char *display_string;
|
|
|
|
/* For Windows, OEM string should never be bigger than ANSI string, says
|
|
CharToOem description.
|
|
On UNIX, non-printable characters (0x00 - 0xFF) will be replaced by
|
|
"^x", so more space may be needed. Note that "^" itself is a valid
|
|
name character, so this leaves an ambiguity, but UnZip displays
|
|
names this way, too. (0x00 is not possible, I hope.)
|
|
For all other ports, just make a copy of local_string.
|
|
*/
|
|
|
|
#ifdef UNIX
|
|
char *cp_dst; /* Character pointers used in the */
|
|
char *cp_src; /* copying/changing procedure. */
|
|
#endif
|
|
|
|
if ((temp_string = (char *)malloc(2 * strlen(local_string) + 1)) == NULL) {
|
|
ZIPERR(ZE_MEM, "local_to_display_string");
|
|
}
|
|
|
|
#ifdef WIN32
|
|
/* convert to OEM display character set */
|
|
local_to_oem_string(temp_string, local_string);
|
|
#else
|
|
# ifdef UNIX
|
|
/* Copy source string, expanding non-printable characters to "^x". */
|
|
cp_dst = temp_string;
|
|
cp_src = local_string;
|
|
while (*cp_src != '\0') {
|
|
if ((unsigned char)*cp_src < ' ') {
|
|
*cp_dst++ = '^';
|
|
*cp_dst++ = '@'+ *cp_src++;
|
|
}
|
|
else {
|
|
*cp_dst++ = *cp_src++;
|
|
}
|
|
}
|
|
*cp_dst = '\0';
|
|
# else /* not UNIX */
|
|
strcpy(temp_string, local_string);
|
|
# endif /* UNIX */
|
|
#endif
|
|
|
|
#ifdef EBCDIC
|
|
{
|
|
char *ebc;
|
|
|
|
if ((ebc = malloc(strlen(display_string) + 1)) == NULL) {
|
|
ZIPERR(ZE_MEM, "local_to_display_string");
|
|
}
|
|
strtoebc(ebc, display_string);
|
|
free(display_string);
|
|
display_string = ebc;
|
|
}
|
|
#endif
|
|
|
|
if ((display_string = (char *)malloc(strlen(temp_string) + 1)) == NULL) {
|
|
ZIPERR(ZE_MEM, "local_to_display_string");
|
|
}
|
|
strcpy(display_string, temp_string);
|
|
free(temp_string);
|
|
|
|
return display_string;
|
|
}
|
|
|
|
/* UTF-8 to local */
|
|
char *utf8_to_local_string(utf8_string)
|
|
char *utf8_string;
|
|
{
|
|
zwchar *wide_string = utf8_to_wide_string(utf8_string);
|
|
char *loc = wide_to_local_string(wide_string);
|
|
if (wide_string)
|
|
free(wide_string);
|
|
return loc;
|
|
}
|
|
|
|
/* UTF-8 to local */
|
|
char *utf8_to_escape_string(utf8_string)
|
|
char *utf8_string;
|
|
{
|
|
zwchar *wide_string = utf8_to_wide_string(utf8_string);
|
|
char *escape_string = wide_to_escape_string(wide_string);
|
|
free(wide_string);
|
|
return escape_string;
|
|
}
|
|
|
|
#ifndef WIN32 /* The Win32 port uses a system-specific variant. */
|
|
/* convert multi-byte character string to wide character string */
|
|
zwchar *local_to_wide_string(local_string)
|
|
char *local_string;
|
|
{
|
|
int wsize;
|
|
wchar_t *wc_string;
|
|
zwchar *wide_string;
|
|
|
|
/* for now try to convert as string - fails if a bad char in string */
|
|
wsize = mbstowcs(NULL, local_string, MB_CUR_MAX );
|
|
if (wsize == (size_t)-1) {
|
|
/* could not convert */
|
|
return NULL;
|
|
}
|
|
|
|
/* convert it */
|
|
if ((wc_string = (wchar_t *)malloc((wsize + 1) * sizeof(wchar_t))) == NULL) {
|
|
ZIPERR(ZE_MEM, "local_to_wide_string");
|
|
}
|
|
wsize = mbstowcs(wc_string, local_string, strlen(local_string) + 1);
|
|
wc_string[wsize] = (wchar_t) 0;
|
|
|
|
/* in case wchar_t is not zwchar */
|
|
if ((wide_string = (zwchar *)malloc((wsize + 1) * sizeof(zwchar))) == NULL) {
|
|
ZIPERR(ZE_MEM, "local_to_wide_string");
|
|
}
|
|
for (wsize = 0; (wide_string[wsize] = (zwchar)wc_string[wsize]); wsize++) ;
|
|
wide_string[wsize] = (zwchar)0;
|
|
free(wc_string);
|
|
|
|
return wide_string;
|
|
}
|
|
#endif /* !WIN32 */
|
|
|
|
|
|
#if 0
|
|
/* All wchar functions are only used by Windows and are
|
|
now in win32zip.c so that the Windows functions can
|
|
be used and multiple character wide characters can
|
|
be handled easily. */
|
|
# ifndef WIN32
|
|
char *wchar_to_utf8_string(wstring)
|
|
wchar_t *wstring;
|
|
{
|
|
zwchar *wide_string = wchar_to_wide_string(wstring);
|
|
char *local_string = wide_to_utf8_string(wide_string);
|
|
|
|
free(wide_string);
|
|
|
|
return local_string;
|
|
}
|
|
# endif
|
|
#endif
|
|
|
|
|
|
/* convert wide string to UTF-8 */
|
|
char *wide_to_utf8_string(wide_string)
|
|
zwchar *wide_string;
|
|
{
|
|
int mbcount;
|
|
char *utf8_string;
|
|
|
|
/* get size of utf8 string */
|
|
mbcount = ucs4_string_to_utf8(wide_string, NULL, 0);
|
|
if (mbcount == -1)
|
|
return NULL;
|
|
if ((utf8_string = (char *) malloc(mbcount + 1)) == NULL) {
|
|
ZIPERR(ZE_MEM, "wide_to_utf8_string");
|
|
}
|
|
mbcount = ucs4_string_to_utf8(wide_string, utf8_string, mbcount + 1);
|
|
if (mbcount == -1)
|
|
return NULL;
|
|
|
|
return utf8_string;
|
|
}
|
|
|
|
/* convert UTF-8 string to wide string */
|
|
zwchar *utf8_to_wide_string(utf8_string)
|
|
char *utf8_string;
|
|
{
|
|
int wcount;
|
|
zwchar *wide_string;
|
|
|
|
wcount = utf8_to_ucs4_string(utf8_string, NULL, 0);
|
|
if (wcount == -1)
|
|
return NULL;
|
|
if ((wide_string = (zwchar *) malloc((wcount + 2) * sizeof(zwchar))) == NULL) {
|
|
ZIPERR(ZE_MEM, "utf8_to_wide_string");
|
|
}
|
|
wcount = utf8_to_ucs4_string(utf8_string, wide_string, wcount + 1);
|
|
|
|
return wide_string;
|
|
}
|
|
|
|
|
|
#endif /* UNICODE_SUPPORT */
|
|
|
|
|
|
/*---------------------------------------------------------------
|
|
* Long option support
|
|
* 8/23/2003
|
|
*
|
|
* Defines function get_option() to get and process the command
|
|
* line options and arguments from argv[]. The caller calls
|
|
* get_option() in a loop to get either one option and possible
|
|
* value or a non-option argument each loop.
|
|
*
|
|
* This version does not include argument file support and can
|
|
* work directly on argv. The argument file code complicates things and
|
|
* it seemed best to leave it out for now. If argument file support (reading
|
|
* in command line arguments stored in a file and inserting into
|
|
* command line where @filename is found) is added later the arguments
|
|
* can change and a freeable copy of argv will be needed and can be
|
|
* created using copy_args in the left out code.
|
|
*
|
|
* Supports short and long options as defined in the array options[]
|
|
* in zip.c, multiple short options in an argument (like -jlv), long
|
|
* option abbreviation (like --te for --temp-file if --te unique),
|
|
* short and long option values (like -b filename or --temp-file filename
|
|
* or --temp-file=filename), optional and required values, option negation
|
|
* by trailing - (like -S- to not include hidden and system files in MSDOS),
|
|
* value lists (like -x a b c), argument permuting (returning all options
|
|
* and values before any non-option arguments), and argument files (where any
|
|
* non-option non-value argument in form @path gets substituted with the
|
|
* white space separated arguments in the text file at path). In this
|
|
* version argument file support has been removed to simplify development but
|
|
* may be added later.
|
|
*
|
|
* E. Gordon
|
|
*/
|
|
|
|
|
|
/* message output - char casts are needed to handle constants */
|
|
#define oWARN(message) zipwarn((char *) message, "")
|
|
#define oERR(err,message) ZIPERR(err, (char *) message)
|
|
|
|
|
|
/* Although the below provides some support for multibyte characters
|
|
the proper thing to do may be to use wide characters and support
|
|
Unicode. May get to it soon. EG
|
|
*/
|
|
|
|
/* For now stay with muti-byte characters. May support wide characters
|
|
in Zip 3.1.
|
|
*/
|
|
|
|
/* multibyte character set support
|
|
Multibyte characters use typically two or more sequential bytes
|
|
to represent additional characters than can fit in a single byte
|
|
character set. The code used here is based on the ANSI mblen function. */
|
|
#ifdef MULTIBYTE_GETOPTNS
|
|
int mb_clen(ptr)
|
|
ZCONST char *ptr;
|
|
{
|
|
/* return the number of bytes that the char pointed to is. Return 1 if
|
|
null character or error like not start of valid multibyte character. */
|
|
int cl;
|
|
|
|
cl = mblen(ptr, MB_CUR_MAX);
|
|
return (cl > 0) ? cl : 1;
|
|
}
|
|
#endif
|
|
|
|
|
|
/* moved to zip.h */
|
|
#if 0
|
|
#ifdef UNICODE_SUPPORT
|
|
# define MB_CLEN(ptr) (1)
|
|
# define MB_NEXTCHAR(ptr) ((ptr)++)
|
|
# ifdef MULTIBYTE_GETOPTNS
|
|
# undef MULTIBYTE_GETOPTNS
|
|
# endif
|
|
#else
|
|
# ifdef _MBCS
|
|
# ifndef MULTIBYTE_GETOPTNS
|
|
# define MULTIBYTE_GETOPTNS
|
|
# endif
|
|
# endif
|
|
/* multibyte character set support
|
|
Multibyte characters use typically two or more sequential bytes
|
|
to represent additional characters than can fit in a single byte
|
|
character set. The code used here is based on the ANSI mblen function. */
|
|
# ifdef MULTIBYTE_GETOPTNS
|
|
local int mb_clen OF((ZCONST char *)); /* declare proto first */
|
|
local int mb_clen(ptr)
|
|
ZCONST char *ptr;
|
|
{
|
|
/* return the number of bytes that the char pointed to is. Return 1 if
|
|
null character or error like not start of valid multibyte character. */
|
|
int cl;
|
|
|
|
cl = mblen(ptr, MB_CUR_MAX);
|
|
return (cl > 0) ? cl : 1;
|
|
}
|
|
# define MB_CLEN(ptr) mb_clen(ptr)
|
|
# define MB_NEXTCHAR(ptr) ((ptr) += MB_CLEN(ptr))
|
|
# else
|
|
# define MB_CLEN(ptr) (1)
|
|
# define MB_NEXTCHAR(ptr) ((ptr)++)
|
|
# endif
|
|
#endif
|
|
#endif
|
|
|
|
|
|
/* constants */
|
|
|
|
/* function get_args_from_arg_file() can return this in depth parameter */
|
|
#define ARG_FILE_ERR -1
|
|
|
|
/* internal settings for optchar */
|
|
#define SKIP_VALUE_ARG -1
|
|
#define THIS_ARG_DONE -2
|
|
#define START_VALUE_LIST -3
|
|
#define IN_VALUE_LIST -4
|
|
#define NON_OPTION_ARG -5
|
|
#define STOP_VALUE_LIST -6
|
|
/* 7/25/04 EG */
|
|
#define READ_REST_ARGS_VERBATIM -7
|
|
|
|
|
|
/* global veriables */
|
|
|
|
int enable_permute = 1; /* yes - return options first */
|
|
/* 7/25/04 EG */
|
|
int doubledash_ends_options = 1; /* when -- what follows are not options */
|
|
|
|
/* buffer for error messages (this sizing is a guess but must hold 2 paths) */
|
|
#define OPTIONERR_BUF_SIZE (FNMAX * 2 + 4000)
|
|
local char Far optionerrbuf[OPTIONERR_BUF_SIZE + 1];
|
|
|
|
/* error messages */
|
|
static ZCONST char Far op_not_neg_err[] = "option %s not negatable";
|
|
static ZCONST char Far op_req_val_err[] = "option %s requires a value";
|
|
static ZCONST char Far op_no_allow_val_err[] = "option %s does not allow a value";
|
|
static ZCONST char Far sh_op_not_sup_err[] = "short option '%c' not supported";
|
|
static ZCONST char Far oco_req_val_err[] = "option %s requires one character value";
|
|
static ZCONST char Far oco_no_mbc_err[] = "option %s does not support multibyte values";
|
|
static ZCONST char Far num_req_val_err[] = "option %s requires number value";
|
|
static ZCONST char Far long_op_ambig_err[] = "long option '%s' ambiguous";
|
|
static ZCONST char Far long_op_not_sup_err[] = "long option '%s' not supported";
|
|
|
|
static ZCONST char Far no_arg_files_err[] = "argument files not enabled\n";
|
|
|
|
|
|
/* below removed as only used for processing argument files */
|
|
|
|
/* get_nextarg */
|
|
/* get_args_from_string */
|
|
/* insert_args */
|
|
/* get_args_from_arg_file */
|
|
|
|
|
|
/* copy error, option name, and option description if any to buf */
|
|
local int optionerr(buf, err, optind, islong)
|
|
char *buf;
|
|
ZCONST char *err;
|
|
int optind;
|
|
int islong;
|
|
{
|
|
char optname[50];
|
|
|
|
if (options[optind].name && options[optind].name[0] != '\0') {
|
|
if (islong)
|
|
sprintf(optname, "'%s' (%s)", options[optind].longopt, options[optind].name);
|
|
else
|
|
sprintf(optname, "'%s' (%s)", options[optind].shortopt, options[optind].name);
|
|
} else {
|
|
if (islong)
|
|
sprintf(optname, "'%s'", options[optind].longopt);
|
|
else
|
|
sprintf(optname, "'%s'", options[optind].shortopt);
|
|
}
|
|
sprintf(buf, err, optname);
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* copy_args
|
|
*
|
|
* Copy arguments in args, allocating storage with malloc.
|
|
* Copies until a NULL argument is found or until max_args args
|
|
* including args[0] are copied. Set max_args to 0 to copy
|
|
* until NULL. Always terminates returned args[] with NULL arg.
|
|
*
|
|
* Any argument in the returned args can be freed with free(). Any
|
|
* freed argument should be replaced with either another string
|
|
* allocated with malloc or by NULL if last argument so that free_args
|
|
* will properly work.
|
|
*/
|
|
char **copy_args(args, max_args)
|
|
char **args;
|
|
int max_args;
|
|
{
|
|
int j;
|
|
char **new_args;
|
|
|
|
if (args == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
/* count args */
|
|
for (j = 0; args[j] && (max_args == 0 || j < max_args); j++) ;
|
|
|
|
if ((new_args = (char **) malloc((j + 1) * sizeof(char *))) == NULL) {
|
|
oERR(ZE_MEM, "ca");
|
|
}
|
|
|
|
for (j = 0; args[j] && (max_args == 0 || j < max_args); j++) {
|
|
if (args[j] == NULL) {
|
|
/* null argument is end of args */
|
|
new_args[j] = NULL;
|
|
break;
|
|
}
|
|
if ((new_args[j] = malloc(strlen(args[j]) + 1)) == NULL) {
|
|
free_args(new_args);
|
|
oERR(ZE_MEM, "ca");
|
|
}
|
|
strcpy(new_args[j], args[j]);
|
|
}
|
|
new_args[j] = NULL;
|
|
|
|
return new_args;
|
|
}
|
|
|
|
|
|
/* free args - free args created with one of these functions */
|
|
int free_args(args)
|
|
char **args;
|
|
{
|
|
int i;
|
|
|
|
if (args == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
for (i = 0; args[i]; i++) {
|
|
free(args[i]);
|
|
}
|
|
free(args);
|
|
return i;
|
|
}
|
|
|
|
|
|
/* insert_arg
|
|
*
|
|
* Insert the argument arg into the array args before argument at_arg.
|
|
* Return the new count of arguments (argc).
|
|
*
|
|
* If free_args is true, this function frees the old args array
|
|
* (but not the component strings). DO NOT set free_args on original
|
|
* argv but only on args allocated with malloc.
|
|
*/
|
|
|
|
int insert_arg(pargs, arg, at_arg, free_args)
|
|
char ***pargs;
|
|
ZCONST char *arg;
|
|
int at_arg;
|
|
int free_args;
|
|
{
|
|
char *newarg = NULL;
|
|
char **args;
|
|
char **newargs = NULL;
|
|
int argnum;
|
|
int newargnum;
|
|
int argcnt;
|
|
int newargcnt;
|
|
|
|
if (pargs == NULL) {
|
|
return 0;
|
|
}
|
|
args = *pargs;
|
|
|
|
/* count args */
|
|
if (args == NULL) {
|
|
argcnt = 0;
|
|
} else {
|
|
for (argcnt = 0; args[argcnt]; argcnt++) ;
|
|
}
|
|
if (arg == NULL) {
|
|
/* done */
|
|
return argcnt;
|
|
}
|
|
newargcnt = argcnt + 1;
|
|
|
|
/* get storage for new args */
|
|
if ((newargs = (char **) malloc((newargcnt + 1) * sizeof(char *))) == NULL) {
|
|
oERR(ZE_MEM, "ia");
|
|
}
|
|
|
|
/* copy argument pointers from args to position at_arg, copy arg, then rest args */
|
|
argnum = 0;
|
|
newargnum = 0;
|
|
if (args) {
|
|
for (; args[argnum] && argnum < at_arg; argnum++) {
|
|
newargs[newargnum++] = args[argnum];
|
|
}
|
|
}
|
|
/* copy new arg */
|
|
if ((newarg = (char *) malloc(strlen(arg) + 1)) == NULL) {
|
|
oERR(ZE_MEM, "ia");
|
|
}
|
|
strcpy(newarg, arg);
|
|
|
|
newargs[newargnum++] = newarg;
|
|
if (args) {
|
|
for ( ; args[argnum]; argnum++) {
|
|
newargs[newargnum++] = args[argnum];
|
|
}
|
|
}
|
|
newargs[newargnum] = NULL;
|
|
|
|
/* free old args array but not component strings - this assumes that
|
|
args was allocated with malloc as copy_args does. DO NOT DO THIS
|
|
on the original argv.
|
|
*/
|
|
if (free_args)
|
|
free(args);
|
|
|
|
*pargs = newargs;
|
|
|
|
return newargnum;
|
|
}
|
|
|
|
/* ------------------------------------- */
|
|
|
|
|
|
|
|
|
|
/* get_shortopt
|
|
*
|
|
* Get next short option from arg. The state is stored in argnum, optchar, and
|
|
* option_num so no static storage is used. Returns the option_ID.
|
|
*
|
|
* parameters:
|
|
* args - argv array of arguments
|
|
* argnum - index of current arg in args
|
|
* optchar - pointer to index of next char to process. Can be 0 or
|
|
* const defined at top of this file like THIS_ARG_DONE
|
|
* negated - on return pointer to int set to 1 if option negated or 0 otherwise
|
|
* value - on return pointer to string set to value of option if any or NULL
|
|
* if none. If value is returned then the caller should free()
|
|
* it when not needed anymore.
|
|
* option_num - pointer to index in options[] of returned option or
|
|
* o_NO_OPTION_MATCH if none. Do not change as used by
|
|
* value lists.
|
|
* depth - recursion depth (0 at top level, 1 or more in arg files)
|
|
*/
|
|
local unsigned long get_shortopt(args, argnum, optchar, negated, value,
|
|
option_num, depth)
|
|
char **args;
|
|
int argnum;
|
|
int *optchar;
|
|
int *negated;
|
|
char **value;
|
|
int *option_num;
|
|
int depth;
|
|
{
|
|
char *shortopt;
|
|
int clen;
|
|
char *nextchar;
|
|
char *s;
|
|
char *start;
|
|
int op;
|
|
char *arg;
|
|
int match = -1;
|
|
|
|
|
|
/* get arg */
|
|
arg = args[argnum];
|
|
/* current char in arg */
|
|
nextchar = arg + (*optchar);
|
|
clen = MB_CLEN(nextchar);
|
|
/* next char in arg */
|
|
(*optchar) += clen;
|
|
/* get first char of short option */
|
|
shortopt = arg + (*optchar);
|
|
/* no value */
|
|
*value = NULL;
|
|
|
|
if (*shortopt == '\0') {
|
|
/* no more options in arg */
|
|
*optchar = 0;
|
|
*option_num = o_NO_OPTION_MATCH;
|
|
return 0;
|
|
}
|
|
|
|
/* look for match in options */
|
|
clen = MB_CLEN(shortopt);
|
|
for (op = 0; options[op].option_ID; op++) {
|
|
s = options[op].shortopt;
|
|
if (s && s[0] == shortopt[0]) {
|
|
if (s[1] == '\0' && clen == 1) {
|
|
/* single char match */
|
|
match = op;
|
|
} else {
|
|
/* 2 wide short opt. Could support more chars but should use long opts instead */
|
|
if (s[1] == shortopt[1]) {
|
|
/* match 2 char short opt or 2 byte char */
|
|
match = op;
|
|
if (clen == 1) (*optchar)++;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (match > -1) {
|
|
/* match */
|
|
clen = MB_CLEN(shortopt);
|
|
nextchar = arg + (*optchar) + clen;
|
|
/* check for trailing dash negating option */
|
|
if (*nextchar == '-') {
|
|
/* negated */
|
|
if (options[match].negatable == o_NOT_NEGATABLE) {
|
|
if (options[match].value_type == o_NO_VALUE) {
|
|
optionerr(optionerrbuf, op_not_neg_err, match, 0);
|
|
if (depth > 0) {
|
|
/* unwind */
|
|
oWARN(optionerrbuf);
|
|
return o_ARG_FILE_ERR;
|
|
} else {
|
|
oERR(ZE_PARMS, optionerrbuf);
|
|
}
|
|
}
|
|
} else {
|
|
*negated = 1;
|
|
/* set up to skip negating dash */
|
|
(*optchar) += clen;
|
|
clen = 1;
|
|
}
|
|
}
|
|
|
|
/* value */
|
|
clen = MB_CLEN(arg + (*optchar));
|
|
/* optional value, one char value, and number value must follow option */
|
|
if (options[match].value_type == o_ONE_CHAR_VALUE) {
|
|
/* one char value */
|
|
if (arg[(*optchar) + clen]) {
|
|
/* has value */
|
|
if (MB_CLEN(arg + (*optchar) + clen) > 1) {
|
|
/* multibyte value not allowed for now */
|
|
optionerr(optionerrbuf, oco_no_mbc_err, match, 0);
|
|
if (depth > 0) {
|
|
/* unwind */
|
|
oWARN(optionerrbuf);
|
|
return o_ARG_FILE_ERR;
|
|
} else {
|
|
oERR(ZE_PARMS, optionerrbuf);
|
|
}
|
|
}
|
|
if ((*value = (char *) malloc(2)) == NULL) {
|
|
oERR(ZE_MEM, "gso");
|
|
}
|
|
(*value)[0] = *(arg + (*optchar) + clen);
|
|
(*value)[1] = '\0';
|
|
*optchar += clen;
|
|
clen = 1;
|
|
} else {
|
|
/* one char values require a value */
|
|
optionerr(optionerrbuf, oco_req_val_err, match, 0);
|
|
if (depth > 0) {
|
|
oWARN(optionerrbuf);
|
|
return o_ARG_FILE_ERR;
|
|
} else {
|
|
oERR(ZE_PARMS, optionerrbuf);
|
|
}
|
|
}
|
|
} else if (options[match].value_type == o_NUMBER_VALUE) {
|
|
/* read chars until end of number */
|
|
start = arg + (*optchar) + clen;
|
|
if (*start == '+' || *start == '-') {
|
|
start++;
|
|
}
|
|
s = start;
|
|
for (; isdigit(*s); MB_NEXTCHAR(s)) ;
|
|
if (s == start) {
|
|
/* no digits */
|
|
optionerr(optionerrbuf, num_req_val_err, match, 0);
|
|
if (depth > 0) {
|
|
oWARN(optionerrbuf);
|
|
return o_ARG_FILE_ERR;
|
|
} else {
|
|
oERR(ZE_PARMS, optionerrbuf);
|
|
}
|
|
}
|
|
start = arg + (*optchar) + clen;
|
|
if ((*value = (char *) malloc((int)(s - start) + 1)) == NULL) {
|
|
oERR(ZE_MEM, "gso");
|
|
}
|
|
*optchar += (int)(s - start);
|
|
strncpy(*value, start, (int)(s - start));
|
|
(*value)[(int)(s - start)] = '\0';
|
|
clen = MB_CLEN(s);
|
|
} else if (options[match].value_type == o_OPTIONAL_VALUE) {
|
|
/* optional value */
|
|
/* This seemed inconsistent so now if no value attached to argument look
|
|
to the next argument if that argument is not an option for option
|
|
value - 11/12/04 EG */
|
|
if (arg[(*optchar) + clen]) {
|
|
/* has value */
|
|
/* add support for optional = - 2/6/05 EG */
|
|
if (arg[(*optchar) + clen] == '=') {
|
|
/* skip = */
|
|
clen++;
|
|
}
|
|
if (arg[(*optchar) + clen]) {
|
|
if ((*value = (char *)malloc(strlen(arg + (*optchar) + clen) + 1))
|
|
== NULL) {
|
|
oERR(ZE_MEM, "gso");
|
|
}
|
|
strcpy(*value, arg + (*optchar) + clen);
|
|
}
|
|
*optchar = THIS_ARG_DONE;
|
|
} else if (args[argnum + 1] && args[argnum + 1][0] != '-') {
|
|
/* use next arg for value */
|
|
if ((*value = (char *)malloc(strlen(args[argnum + 1]) + 1)) == NULL) {
|
|
oERR(ZE_MEM, "gso");
|
|
}
|
|
/* using next arg as value */
|
|
strcpy(*value, args[argnum + 1]);
|
|
*optchar = SKIP_VALUE_ARG;
|
|
}
|
|
} else if (options[match].value_type == o_REQUIRED_VALUE ||
|
|
options[match].value_type == o_VALUE_LIST) {
|
|
/* see if follows option */
|
|
if (arg[(*optchar) + clen]) {
|
|
/* has value following option as -ovalue */
|
|
/* add support for optional = - 6/5/05 EG */
|
|
if (arg[(*optchar) + clen] == '=') {
|
|
/* skip = */
|
|
clen++;
|
|
}
|
|
if ((*value = (char *)malloc(strlen(arg + (*optchar) + clen) + 1))
|
|
== NULL) {
|
|
oERR(ZE_MEM, "gso");
|
|
}
|
|
strcpy(*value, arg + (*optchar) + clen);
|
|
*optchar = THIS_ARG_DONE;
|
|
} else {
|
|
/* use next arg for value */
|
|
if (args[argnum + 1]) {
|
|
if ((*value = (char *)malloc(strlen(args[argnum + 1]) + 1)) == NULL) {
|
|
oERR(ZE_MEM, "gso");
|
|
}
|
|
strcpy(*value, args[argnum + 1]);
|
|
if (options[match].value_type == o_VALUE_LIST) {
|
|
*optchar = START_VALUE_LIST;
|
|
} else {
|
|
*optchar = SKIP_VALUE_ARG;
|
|
}
|
|
} else {
|
|
/* no value found */
|
|
optionerr(optionerrbuf, op_req_val_err, match, 0);
|
|
if (depth > 0) {
|
|
oWARN(optionerrbuf);
|
|
return o_ARG_FILE_ERR;
|
|
} else {
|
|
oERR(ZE_PARMS, optionerrbuf);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
*option_num = match;
|
|
return options[match].option_ID;
|
|
}
|
|
sprintf(optionerrbuf, sh_op_not_sup_err, *shortopt);
|
|
if (depth > 0) {
|
|
/* unwind */
|
|
oWARN(optionerrbuf);
|
|
return o_ARG_FILE_ERR;
|
|
} else {
|
|
oERR(ZE_PARMS, optionerrbuf);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* get_longopt
|
|
*
|
|
* Get the long option in args array at argnum.
|
|
* Parameters same as for get_shortopt.
|
|
*/
|
|
|
|
local unsigned long get_longopt(args, argnum, optchar, negated, value,
|
|
option_num, depth)
|
|
char **args;
|
|
int argnum;
|
|
int *optchar;
|
|
int *negated;
|
|
char **value;
|
|
int *option_num;
|
|
int depth;
|
|
{
|
|
char *longopt;
|
|
char *lastchr;
|
|
char *valuestart;
|
|
int op;
|
|
char *arg;
|
|
int match = -1;
|
|
*value = NULL;
|
|
|
|
if (args == NULL) {
|
|
*option_num = o_NO_OPTION_MATCH;
|
|
return 0;
|
|
}
|
|
if (args[argnum] == NULL) {
|
|
*option_num = o_NO_OPTION_MATCH;
|
|
return 0;
|
|
}
|
|
/* copy arg so can chop end if value */
|
|
if ((arg = (char *)malloc(strlen(args[argnum]) + 1)) == NULL) {
|
|
oERR(ZE_MEM, "glo");
|
|
}
|
|
strcpy(arg, args[argnum]);
|
|
|
|
/* get option */
|
|
longopt = arg + 2;
|
|
/* no value */
|
|
*value = NULL;
|
|
|
|
/* find = */
|
|
for (lastchr = longopt, valuestart = longopt;
|
|
*valuestart && *valuestart != '=';
|
|
lastchr = valuestart, MB_NEXTCHAR(valuestart)) ;
|
|
if (*valuestart) {
|
|
/* found =value */
|
|
*valuestart = '\0';
|
|
valuestart++;
|
|
} else {
|
|
valuestart = NULL;
|
|
}
|
|
|
|
if (*lastchr == '-') {
|
|
/* option negated */
|
|
*negated = 1;
|
|
*lastchr = '\0';
|
|
} else {
|
|
*negated = 0;
|
|
}
|
|
|
|
/* look for long option match */
|
|
for (op = 0; options[op].option_ID; op++) {
|
|
if (options[op].longopt && strcmp(options[op].longopt, longopt) == 0) {
|
|
/* exact match */
|
|
match = op;
|
|
break;
|
|
}
|
|
if (options[op].longopt && strncmp(options[op].longopt, longopt, strlen(longopt)) == 0) {
|
|
if (match > -1) {
|
|
sprintf(optionerrbuf, long_op_ambig_err, longopt);
|
|
free(arg);
|
|
if (depth > 0) {
|
|
/* unwind */
|
|
oWARN(optionerrbuf);
|
|
return o_ARG_FILE_ERR;
|
|
} else {
|
|
oERR(ZE_PARMS, optionerrbuf);
|
|
}
|
|
}
|
|
match = op;
|
|
}
|
|
}
|
|
|
|
if (match == -1) {
|
|
sprintf(optionerrbuf, long_op_not_sup_err, longopt);
|
|
free(arg);
|
|
if (depth > 0) {
|
|
oWARN(optionerrbuf);
|
|
return o_ARG_FILE_ERR;
|
|
} else {
|
|
oERR(ZE_PARMS, optionerrbuf);
|
|
}
|
|
}
|
|
|
|
/* one long option an arg */
|
|
*optchar = THIS_ARG_DONE;
|
|
|
|
/* if negated then see if allowed */
|
|
if (*negated && options[match].negatable == o_NOT_NEGATABLE) {
|
|
optionerr(optionerrbuf, op_not_neg_err, match, 1);
|
|
free(arg);
|
|
if (depth > 0) {
|
|
/* unwind */
|
|
oWARN(optionerrbuf);
|
|
return o_ARG_FILE_ERR;
|
|
} else {
|
|
oERR(ZE_PARMS, optionerrbuf);
|
|
}
|
|
}
|
|
/* get value */
|
|
if (options[match].value_type == o_OPTIONAL_VALUE) {
|
|
/* optional value in form option=value */
|
|
if (valuestart) {
|
|
/* option=value */
|
|
if ((*value = (char *)malloc(strlen(valuestart) + 1)) == NULL) {
|
|
free(arg);
|
|
oERR(ZE_MEM, "glo");
|
|
}
|
|
strcpy(*value, valuestart);
|
|
}
|
|
} else if (options[match].value_type == o_REQUIRED_VALUE ||
|
|
options[match].value_type == o_NUMBER_VALUE ||
|
|
options[match].value_type == o_ONE_CHAR_VALUE ||
|
|
options[match].value_type == o_VALUE_LIST) {
|
|
/* handle long option one char and number value as required value */
|
|
if (valuestart) {
|
|
/* option=value */
|
|
if ((*value = (char *)malloc(strlen(valuestart) + 1)) == NULL) {
|
|
free(arg);
|
|
oERR(ZE_MEM, "glo");
|
|
}
|
|
strcpy(*value, valuestart);
|
|
} else {
|
|
/* use next arg */
|
|
if (args[argnum + 1]) {
|
|
if ((*value = (char *)malloc(strlen(args[argnum + 1]) + 1)) == NULL) {
|
|
free(arg);
|
|
oERR(ZE_MEM, "glo");
|
|
}
|
|
/* using next arg as value */
|
|
strcpy(*value, args[argnum + 1]);
|
|
if (options[match].value_type == o_VALUE_LIST) {
|
|
*optchar = START_VALUE_LIST;
|
|
} else {
|
|
*optchar = SKIP_VALUE_ARG;
|
|
}
|
|
} else {
|
|
/* no value found */
|
|
optionerr(optionerrbuf, op_req_val_err, match, 1);
|
|
free(arg);
|
|
if (depth > 0) {
|
|
/* unwind */
|
|
oWARN(optionerrbuf);
|
|
return o_ARG_FILE_ERR;
|
|
} else {
|
|
oERR(ZE_PARMS, optionerrbuf);
|
|
}
|
|
}
|
|
}
|
|
} else if (options[match].value_type == o_NO_VALUE) {
|
|
/* this option does not accept a value */
|
|
if (valuestart) {
|
|
/* --option=value */
|
|
optionerr(optionerrbuf, op_no_allow_val_err, match, 1);
|
|
free(arg);
|
|
if (depth > 0) {
|
|
oWARN(optionerrbuf);
|
|
return o_ARG_FILE_ERR;
|
|
} else {
|
|
oERR(ZE_PARMS, optionerrbuf);
|
|
}
|
|
}
|
|
}
|
|
free(arg);
|
|
|
|
*option_num = match;
|
|
return options[match].option_ID;
|
|
}
|
|
|
|
|
|
|
|
/* get_option
|
|
*
|
|
* Main interface for user. Use this function to get options, values and
|
|
* non-option arguments from a command line provided in argv form.
|
|
*
|
|
* To use get_option() first define valid options by setting
|
|
* the global variable options[] to an array of option_struct. Also
|
|
* either change defaults below or make variables global and set elsewhere.
|
|
* Zip uses below defaults.
|
|
*
|
|
* Call get_option() to get an option (like -b or --temp-file) and any
|
|
* value for that option (like filename for -b) or a non-option argument
|
|
* (like archive name) each call. If *value* is not NULL after calling
|
|
* get_option() it is a returned value and the caller should either store
|
|
* the char pointer or free() it before calling get_option() again to avoid
|
|
* leaking memory. If a non-option non-value argument is returned get_option()
|
|
* returns o_NON_OPTION_ARG and value is set to the entire argument.
|
|
* When there are no more arguments get_option() returns 0.
|
|
*
|
|
* The parameters argnum (after set to 0 on initial call),
|
|
* optchar, first_nonopt_arg, option_num, and depth (after initial
|
|
* call) are set and maintained by get_option() and should not be
|
|
* changed. The parameters argc, negated, and value are outputs and
|
|
* can be used by the calling program. get_option() returns either the
|
|
* option_ID for the current option, a special value defined in
|
|
* zip.h, or 0 when no more arguments.
|
|
*
|
|
* The value returned by get_option() is the ID value in the options
|
|
* table. This value can be duplicated in the table if different
|
|
* options are really the same option. The index into the options[]
|
|
* table is given by option_num, though the ID should be used as
|
|
* option numbers change when the table is changed. The ID must
|
|
* not be 0 for any option as this ends the table. If get_option()
|
|
* finds an option not in the table it calls oERR to post an
|
|
* error and exit. Errors also result if the option requires a
|
|
* value that is missing, a value is present but the option does
|
|
* not take one, and an option is negated but is not
|
|
* negatable. Non-option arguments return o_NON_OPTION_ARG
|
|
* with the entire argument in value.
|
|
*
|
|
* For Zip, permuting is on and all options and their values are
|
|
* returned before any non-option arguments like archive name.
|
|
*
|
|
* The arguments "-" alone and "--" alone return as non-option arguments.
|
|
* Note that "-" should not be used as part of a short option
|
|
* entry in the table but can be used in the middle of long
|
|
* options such as in the long option "a-long-option". Now "--" alone
|
|
* stops option processing, returning any arguments following "--" as
|
|
* non-option arguments instead of options.
|
|
*
|
|
* Argument file support is removed from this version. It may be added later.
|
|
*
|
|
* After each call:
|
|
* argc is set to the current size of args[] but should not change
|
|
* with argument file support removed,
|
|
* argnum is the index of the current arg,
|
|
* value is either the value of the returned option or non-option
|
|
* argument or NULL if option with no value,
|
|
* negated is set if the option was negated by a trailing dash (-)
|
|
* option_num is set to either the index in options[] for the option or
|
|
* o_NO_OPTION_MATCH if no match.
|
|
* Negation is checked before the value is read if the option is negatable so
|
|
* that the - is not included in the value. If the option is not negatable
|
|
* but takes a value then the - will start the value. If permuting then
|
|
* argnum and first_nonopt_arg are unreliable and should not be used.
|
|
*
|
|
* Command line is read from left to right. As get_option() finds non-option
|
|
* arguments (arguments not starting with - and that are not values to options)
|
|
* it moves later options and values in front of the non-option arguments.
|
|
* This permuting is turned off by setting enable_permute to 0. Then
|
|
* get_option() will return options and non-option arguments in the order
|
|
* found. Currently permuting is only done after an argument is completely
|
|
* processed so that any value can be moved with options they go with. All
|
|
* state information is stored in the parameters argnum, optchar,
|
|
* first_nonopt_arg and option_num. You should not change these after the
|
|
* first call to get_option(). If you need to back up to a previous arg then
|
|
* set argnum to that arg (remembering that args may have been permuted) and
|
|
* set optchar = 0 and first_nonopt_arg to the first non-option argument if
|
|
* permuting. After all arguments are returned the next call to get_option()
|
|
* returns 0. The caller can then call free_args(args) if appropriate.
|
|
*
|
|
* get_option() accepts arguments in the following forms:
|
|
* short options
|
|
* of 1 and 2 characters, e.g. a, b, cc, d, and ba, after a single
|
|
* leading -, as in -abccdba. In this example if 'b' is followed by 'a'
|
|
* it matches short option 'ba' else it is interpreted as short option
|
|
* b followed by another option. The character - is not legal as a
|
|
* short option or as part of a 2 character short option.
|
|
*
|
|
* If a short option has a value it immediately follows the option or
|
|
* if that option is the end of the arg then the next arg is used as
|
|
* the value. So if short option e has a value, it can be given as
|
|
* -evalue
|
|
* or
|
|
* -e value
|
|
* and now
|
|
* -e=value
|
|
* but now that = is optional a leading = is stripped for the first.
|
|
* This change allows optional short option values to be defaulted as
|
|
* -e=
|
|
* Either optional or required values can be specified. Optional values
|
|
* now use both forms as ignoring the later got confusing. Any
|
|
* non-value short options can preceed a valued short option as in
|
|
* -abevalue
|
|
* Some value types (one_char and number) allow options after the value
|
|
* so if oc is an option that takes a character and n takes a number
|
|
* then
|
|
* -abocVccn42evalue
|
|
* returns value V for oc and value 42 for n. All values are strings
|
|
* so programs may have to convert the "42" to a number. See long
|
|
* options below for how value lists are handled.
|
|
*
|
|
* Any short option can be negated by following it with -. Any - is
|
|
* handled and skipped over before any value is read unless the option
|
|
* is not negatable but takes a value and then - starts the value.
|
|
*
|
|
* If the value for an optional value is just =, then treated as no
|
|
* value.
|
|
*
|
|
* long options
|
|
* of arbitrary length are assumed if an arg starts with -- but is not
|
|
* exactly --. Long options are given one per arg and can be abbreviated
|
|
* if the abbreviation uniquely matches one of the long options.
|
|
* Exact matches always match before partial matches. If ambiguous an
|
|
* error is generated.
|
|
*
|
|
* Values are specified either in the form
|
|
* --longoption=value
|
|
* or can be the following arg if the value is required as in
|
|
* --longoption value
|
|
* Optional values to long options must be in the first form.
|
|
*
|
|
* Value lists are specified by o_VALUE_LIST and consist of an option
|
|
* that takes a value followed by one or more value arguments.
|
|
* The two forms are
|
|
* --option=value
|
|
* or
|
|
* -ovalue
|
|
* for a single value or
|
|
* --option value1 value2 value3 ... --option2
|
|
* or
|
|
* -o value1 value2 value3 ...
|
|
* for a list of values. The list ends at the next option, the
|
|
* end of the command line, or at a single "@" argument.
|
|
* Each value is treated as if it was preceeded by the option, so
|
|
* --option1 val1 val2
|
|
* with option1 value_type set to o_VALUE_LIST is the same as
|
|
* --option1=val1 --option1=val2
|
|
*
|
|
* Long options can be negated by following the option with - as in
|
|
* --longoption-
|
|
* Long options with values can also be negated if this makes sense for
|
|
* the caller as:
|
|
* --longoption-=value
|
|
* If = is not followed by anything it is treated as no value.
|
|
*
|
|
* @path
|
|
* When an argument in the form @path is encountered, the file at path
|
|
* is opened and white space separated arguments read from the file
|
|
* and inserted into the command line at that point as if the contents
|
|
* of the file were directly typed at that location. The file can
|
|
* have options, files to zip, or anything appropriate at that location
|
|
* in the command line. Since Zip has permuting enabled, options and
|
|
* files will propagate to the appropriate locations in the command
|
|
* line.
|
|
*
|
|
* Argument files support has been removed from this version. It may
|
|
* be added back later.
|
|
*
|
|
* non-option argument
|
|
* is any argument not given above. If enable_permute is 1 then
|
|
* these are returned after all options, otherwise all options and
|
|
* args are returned in order. Returns option ID o_NON_OPTION_ARG
|
|
* and sets value to the argument.
|
|
*
|
|
*
|
|
* Arguments to get_option:
|
|
* char ***pargs - pointer to arg array in the argv form
|
|
* int *argc - returns the current argc for args incl. args[0]
|
|
* int *argnum - the index of the current argument (caller
|
|
* should set = 0 on first call and not change
|
|
* after that)
|
|
* int *optchar - index of next short opt in arg or special
|
|
* int *first_nonopt_arg - used by get_option to permute args
|
|
* int *negated - option was negated (had trailing -)
|
|
* char *value - value of option if any (free when done with it) or NULL
|
|
* int *option_num - the index in options of the last option returned
|
|
* (can be o_NO_OPTION_MATCH)
|
|
* int recursion_depth - current depth of recursion
|
|
* (always set to 0 by caller)
|
|
* (always 0 with argument files support removed)
|
|
*
|
|
* Caller should only read the returned option ID and the value, negated,
|
|
* and option_num (if required) parameters after each call.
|
|
*
|
|
* Ed Gordon
|
|
* 24 August 2003 (last updated 2 April 2008 EG)
|
|
*
|
|
*/
|
|
|
|
unsigned long get_option(pargs, argc, argnum, optchar, value,
|
|
negated, first_nonopt_arg, option_num, recursion_depth)
|
|
char ***pargs;
|
|
int *argc;
|
|
int *argnum;
|
|
int *optchar;
|
|
char **value;
|
|
int *negated;
|
|
int *first_nonopt_arg;
|
|
int *option_num;
|
|
int recursion_depth;
|
|
{
|
|
char **args;
|
|
unsigned long option_ID;
|
|
|
|
int argcnt;
|
|
int first_nonoption_arg;
|
|
char *arg = NULL;
|
|
int h;
|
|
int optc;
|
|
int argn;
|
|
int j;
|
|
int v;
|
|
int read_rest_args_verbatim = 0; /* 7/25/04 - ignore options and arg files for rest args */
|
|
|
|
/* value is outdated. The caller should free value before
|
|
calling get_option again. */
|
|
*value = NULL;
|
|
|
|
/* if args is NULL then done */
|
|
if (pargs == NULL) {
|
|
*argc = 0;
|
|
return 0;
|
|
}
|
|
args = *pargs;
|
|
if (args == NULL) {
|
|
*argc = 0;
|
|
return 0;
|
|
}
|
|
|
|
/* count args */
|
|
for (argcnt = 0; args[argcnt]; argcnt++) ;
|
|
|
|
/* if no provided args then nothing to do */
|
|
if (argcnt < 1 || (recursion_depth == 0 && argcnt < 2)) {
|
|
*argc = argcnt;
|
|
/* return 0 to note that no args are left */
|
|
return 0;
|
|
}
|
|
|
|
*negated = 0;
|
|
first_nonoption_arg = *first_nonopt_arg;
|
|
argn = *argnum;
|
|
optc = *optchar;
|
|
|
|
if (optc == READ_REST_ARGS_VERBATIM) {
|
|
read_rest_args_verbatim = 1;
|
|
}
|
|
|
|
if (argn == -1 || (recursion_depth == 0 && argn == 0)) {
|
|
/* first call */
|
|
/* if depth = 0 then args[0] is argv[0] so skip */
|
|
*option_num = o_NO_OPTION_MATCH;
|
|
optc = THIS_ARG_DONE;
|
|
first_nonoption_arg = -1;
|
|
}
|
|
|
|
/* if option_num is set then restore last option_ID in case continuing value list */
|
|
option_ID = 0;
|
|
if (*option_num != o_NO_OPTION_MATCH) {
|
|
option_ID = options[*option_num].option_ID;
|
|
}
|
|
|
|
/* get next option if any */
|
|
for (;;) {
|
|
if (read_rest_args_verbatim) {
|
|
/* rest of args after "--" are non-option args if doubledash_ends_options set */
|
|
argn++;
|
|
if (argn > argcnt || args[argn] == NULL) {
|
|
/* done */
|
|
option_ID = 0;
|
|
break;
|
|
}
|
|
arg = args[argn];
|
|
if ((*value = (char *)malloc(strlen(arg) + 1)) == NULL) {
|
|
oERR(ZE_MEM, "go");
|
|
}
|
|
strcpy(*value, arg);
|
|
*option_num = o_NO_OPTION_MATCH;
|
|
option_ID = o_NON_OPTION_ARG;
|
|
break;
|
|
|
|
/* permute non-option args after option args so options are returned first */
|
|
} else if (enable_permute) {
|
|
if (optc == SKIP_VALUE_ARG || optc == THIS_ARG_DONE ||
|
|
optc == START_VALUE_LIST || optc == IN_VALUE_LIST ||
|
|
optc == STOP_VALUE_LIST) {
|
|
/* moved to new arg */
|
|
if (first_nonoption_arg > -1 && args[first_nonoption_arg]) {
|
|
/* do the permuting - move non-options after this option */
|
|
/* if option and value separate args or starting list skip option */
|
|
if (optc == SKIP_VALUE_ARG || optc == START_VALUE_LIST) {
|
|
v = 1;
|
|
} else {
|
|
v = 0;
|
|
}
|
|
for (h = first_nonoption_arg; h < argn; h++) {
|
|
arg = args[first_nonoption_arg];
|
|
for (j = first_nonoption_arg; j < argn + v; j++) {
|
|
args[j] = args[j + 1];
|
|
}
|
|
args[j] = arg;
|
|
}
|
|
first_nonoption_arg += 1 + v;
|
|
}
|
|
}
|
|
} else if (optc == NON_OPTION_ARG) {
|
|
/* if not permuting then already returned arg */
|
|
optc = THIS_ARG_DONE;
|
|
}
|
|
|
|
/* value lists */
|
|
if (optc == STOP_VALUE_LIST) {
|
|
optc = THIS_ARG_DONE;
|
|
}
|
|
|
|
if (optc == START_VALUE_LIST || optc == IN_VALUE_LIST) {
|
|
if (optc == START_VALUE_LIST) {
|
|
/* already returned first value */
|
|
argn++;
|
|
optc = IN_VALUE_LIST;
|
|
}
|
|
argn++;
|
|
arg = args[argn];
|
|
/* if end of args and still in list and there are non-option args then
|
|
terminate list */
|
|
if (arg == NULL && (optc == START_VALUE_LIST || optc == IN_VALUE_LIST)
|
|
&& first_nonoption_arg > -1) {
|
|
/* terminate value list with @ */
|
|
/* this is only needed for argument files */
|
|
/* but is also good for show command line so command lines with lists
|
|
can always be read back in */
|
|
argcnt = insert_arg(&args, "@", first_nonoption_arg, 1);
|
|
argn++;
|
|
if (first_nonoption_arg > -1) {
|
|
first_nonoption_arg++;
|
|
}
|
|
}
|
|
|
|
arg = args[argn];
|
|
if (arg && arg[0] == '@' && arg[1] == '\0') {
|
|
/* inserted arguments terminator */
|
|
optc = STOP_VALUE_LIST;
|
|
continue;
|
|
} else if (arg && arg[0] != '-') { /* not option */
|
|
/* - and -- are not allowed in value lists unless escaped */
|
|
/* another value in value list */
|
|
if ((*value = (char *)malloc(strlen(args[argn]) + 1)) == NULL) {
|
|
oERR(ZE_MEM, "go");
|
|
}
|
|
strcpy(*value, args[argn]);
|
|
break;
|
|
|
|
} else {
|
|
argn--;
|
|
optc = THIS_ARG_DONE;
|
|
}
|
|
}
|
|
|
|
/* move to next arg */
|
|
if (optc == SKIP_VALUE_ARG) {
|
|
argn += 2;
|
|
optc = 0;
|
|
} else if (optc == THIS_ARG_DONE) {
|
|
argn++;
|
|
optc = 0;
|
|
}
|
|
if (argn > argcnt) {
|
|
break;
|
|
}
|
|
if (args[argn] == NULL) {
|
|
/* done unless permuting and non-option args */
|
|
if (first_nonoption_arg > -1 && args[first_nonoption_arg]) {
|
|
/* return non-option arguments at end */
|
|
if (optc == NON_OPTION_ARG) {
|
|
first_nonoption_arg++;
|
|
}
|
|
/* after first pass args are permuted but skipped over non-option args */
|
|
/* swap so argn points to first non-option arg */
|
|
j = argn;
|
|
argn = first_nonoption_arg;
|
|
first_nonoption_arg = j;
|
|
}
|
|
if (argn > argcnt || args[argn] == NULL) {
|
|
/* done */
|
|
option_ID = 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* after swap first_nonoption_arg points to end which is NULL */
|
|
if (first_nonoption_arg > -1 && (args[first_nonoption_arg] == NULL)) {
|
|
/* only non-option args left */
|
|
if (optc == NON_OPTION_ARG) {
|
|
argn++;
|
|
}
|
|
if (argn > argcnt || args[argn] == NULL) {
|
|
/* done */
|
|
option_ID = 0;
|
|
break;
|
|
}
|
|
if ((*value = (char *)malloc(strlen(args[argn]) + 1)) == NULL) {
|
|
oERR(ZE_MEM, "go");
|
|
}
|
|
strcpy(*value, args[argn]);
|
|
optc = NON_OPTION_ARG;
|
|
option_ID = o_NON_OPTION_ARG;
|
|
break;
|
|
}
|
|
|
|
arg = args[argn];
|
|
|
|
/* is it an option */
|
|
if (arg[0] == '-') {
|
|
/* option */
|
|
if (arg[1] == '\0') {
|
|
/* arg = - */
|
|
/* treat like non-option arg */
|
|
*option_num = o_NO_OPTION_MATCH;
|
|
if (enable_permute) {
|
|
/* permute args to move all non-option args to end */
|
|
if (first_nonoption_arg < 0) {
|
|
first_nonoption_arg = argn;
|
|
}
|
|
argn++;
|
|
} else {
|
|
/* not permute args so return non-option args when found */
|
|
if ((*value = (char *)malloc(strlen(arg) + 1)) == NULL) {
|
|
oERR(ZE_MEM, "go");
|
|
}
|
|
strcpy(*value, arg);
|
|
optc = NON_OPTION_ARG;
|
|
option_ID = o_NON_OPTION_ARG;
|
|
break;
|
|
}
|
|
|
|
} else if (arg[1] == '-') {
|
|
/* long option */
|
|
if (arg[2] == '\0') {
|
|
/* arg = -- */
|
|
if (doubledash_ends_options) {
|
|
/* Now -- stops permuting and forces the rest of
|
|
the command line to be read verbatim - 7/25/04 EG */
|
|
|
|
/* never permute args after -- and return as non-option args */
|
|
if (first_nonoption_arg < 1) {
|
|
/* -- is first non-option argument - 8/7/04 EG */
|
|
argn--;
|
|
} else {
|
|
/* go back to start of non-option args - 8/7/04 EG */
|
|
argn = first_nonoption_arg - 1;
|
|
}
|
|
|
|
/* disable permuting and treat remaining arguments as not
|
|
options */
|
|
read_rest_args_verbatim = 1;
|
|
optc = READ_REST_ARGS_VERBATIM;
|
|
|
|
} else {
|
|
/* treat like non-option arg */
|
|
*option_num = o_NO_OPTION_MATCH;
|
|
if (enable_permute) {
|
|
/* permute args to move all non-option args to end */
|
|
if (first_nonoption_arg < 0) {
|
|
first_nonoption_arg = argn;
|
|
}
|
|
argn++;
|
|
} else {
|
|
/* not permute args so return non-option args when found */
|
|
if ((*value = (char *)malloc(strlen(arg) + 1)) == NULL) {
|
|
oERR(ZE_MEM, "go");
|
|
}
|
|
strcpy(*value, arg);
|
|
optc = NON_OPTION_ARG;
|
|
option_ID = o_NON_OPTION_ARG;
|
|
break;
|
|
}
|
|
}
|
|
|
|
} else {
|
|
option_ID = get_longopt(args, argn, &optc, negated, value, option_num, recursion_depth);
|
|
if (option_ID == o_ARG_FILE_ERR) {
|
|
/* unwind as only get this if recursion_depth > 0 */
|
|
return option_ID;
|
|
}
|
|
break;
|
|
}
|
|
|
|
} else {
|
|
/* short option */
|
|
option_ID = get_shortopt(args, argn, &optc, negated, value, option_num, recursion_depth);
|
|
|
|
if (option_ID == o_ARG_FILE_ERR) {
|
|
/* unwind as only get this if recursion_depth > 0 */
|
|
return option_ID;
|
|
}
|
|
|
|
if (optc == 0) {
|
|
/* if optc = 0 then ran out of short opts this arg */
|
|
optc = THIS_ARG_DONE;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
#if 0
|
|
/* argument file code left out
|
|
so for now let filenames start with @
|
|
*/
|
|
|
|
} else if (allow_arg_files && arg[0] == '@') {
|
|
/* arg file */
|
|
oERR(ZE_PARMS, no_arg_files_err);
|
|
#endif
|
|
|
|
} else {
|
|
/* non-option */
|
|
if (enable_permute) {
|
|
/* permute args to move all non-option args to end */
|
|
if (first_nonoption_arg < 0) {
|
|
first_nonoption_arg = argn;
|
|
}
|
|
argn++;
|
|
} else {
|
|
/* no permute args so return non-option args when found */
|
|
if ((*value = (char *)malloc(strlen(arg) + 1)) == NULL) {
|
|
oERR(ZE_MEM, "go");
|
|
}
|
|
strcpy(*value, arg);
|
|
*option_num = o_NO_OPTION_MATCH;
|
|
optc = NON_OPTION_ARG;
|
|
option_ID = o_NON_OPTION_ARG;
|
|
break;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
*pargs = args;
|
|
*argc = argcnt;
|
|
*first_nonopt_arg = first_nonoption_arg;
|
|
*argnum = argn;
|
|
*optchar = optc;
|
|
|
|
return option_ID;
|
|
}
|