1/*
2  Copyright (c) 1990-2005 Info-ZIP.  All rights reserved.
3
4  See the accompanying file LICENSE, version 2000-Apr-09 or later
5  (the contents of which are also included in unzip.h) for terms of use.
6  If, for some reason, all these files are missing, the Info-ZIP license
7  also may be found at:  ftp://ftp.info-zip.org/pub/infozip/license.html
8*/
9/*---------------------------------------------------------------------------
10
11  theos.c
12
13  Theos-specific routines for use with Info-ZIP's UnZip 5.41 and later.
14
15  Contains:  do_wild()           <-- generic enough to put in fileio.c?
16             mapattr()
17             mapname()
18             checkdir()
19             close_outfile()
20             defer_dir_attribs()
21             set_direc_attribs()
22             stamp_file()
23             version()
24
25  ---------------------------------------------------------------------------*/
26
27
28#define UNZIP_INTERNAL
29#include "unzip.h"
30#include <direct.h>
31#include <sc.h>
32#include <fdb.h>
33#include <nuc.h>
34#include <peek.h>
35
36/* standard function doesn't work with a trailing / */
37#define opendir(a) _opendir(a)
38extern DIR* _opendir(const char* dirpath);
39
40#ifdef SET_DIR_ATTRIB
41typedef struct uxdirattr {      /* struct for holding unix style directory */
42    struct uxdirattr *next;     /*  info until can be sorted and set at end */
43    char *fn;                   /* filename of directory */
44    union {
45        iztimes t3;             /* mtime, atime, ctime */
46        ztimbuf t2;             /* modtime, actime */
47    } u;
48    unsigned perms;             /* same as min_info.file_attr */
49    char fnbuf[1];              /* buffer stub for directory name */
50} uxdirattr;
51#define UxAtt(d)  ((uxdirattr *)d)    /* typecast shortcut */
52#endif /* SET_DIR_ATTRIB */
53
54#ifdef ACORN_FTYPE_NFS
55/* Acorn bits for NFS filetyping */
56typedef struct {
57  uch ID[2];
58  uch size[2];
59  uch ID_2[4];
60  uch loadaddr[4];
61  uch execaddr[4];
62  uch attr[4];
63} RO_extra_block;
64
65#endif /* ACORN_FTYPE_NFS */
66
67static int created_dir;        /* used in mapname(), checkdir() */
68static int renamed_fullpath;   /* ditto */
69
70#define _FDB_SHARED_EXECUTE_PROTECT _FDB_MODIFIED
71
72uch _um2tm_(ush mask);
73
74int chmodv2_3(const char *fname, short mask)
75{
76    return _filechange(fname,'p',(size_t) _um2tm_(mask)|0x80);
77}
78
79int chlen(char *fname, unsigned short reclen, unsigned short keylen)
80{
81    size_t a = reclen + ((size_t) keylen << 16);
82    return _filechange(fname,'ma',(size_t) &a);
83}
84
85#define chgrow(a,b) ((int) _filechange(a,'g',(size_t)(b)))
86#define chorg(a,b)  ((int) _filechange(a,'m',(size_t)(b)))
87
88#ifndef SFX
89
90/*************************/
91/* Function dateformat() */
92/*************************/
93
94int dateformat()
95{
96/*---------------------------------------------------------------------------
97    For those operating systems that support it, this function returns a
98    value that tells how national convention says that numeric dates are
99    displayed.  Return values are DF_YMD, DF_DMY and DF_MDY (the meanings
100    should be fairly obvious).
101  ---------------------------------------------------------------------------*/
102
103    switch (peeknuc(&NUC->dateopt) & (aform|eform|iform)) {
104    case aform: return DF_MDY;
105    case eform: return DF_DMY;
106    }
107    return DF_YMD;
108}
109
110
111/* usual THEOS match function for filenames */
112
113/* match from Phase One Systems */
114
115/* Returns non-zero if string matches the literal mask */
116int match(string, pattern, ignore_case __WDL)
117    ZCONST char *string;
118    ZCONST char *pattern;
119    int ignore_case;            /* unused in this variant of match()! */
120    __WDLDEF
121{
122    int matched, k;
123
124    if (!(*pattern))
125        return 1;
126    for(;;) {
127        if ( (!(*string)) && (!(*pattern)) )
128            return(1);
129        else if ( !(*pattern) )
130            return(0);
131        else if (*pattern == '*') {
132            if (!*(pattern+1))
133                return(1);
134            k=0;
135            do {
136                matched = match(string+k, pattern+1, ignore_case __WDL);
137                k++;
138            } while ( (!matched) && *(string+k));
139            return(matched);
140        } else if (*pattern == '@') {
141            if (!((*string >= 'a' && *string <= 'z')
142                ||(*string >= 'A' && *string <= 'Z')))
143                return(0);
144        } else if (*pattern == '#') {
145            if (*string < '0' || *string > '9')
146                return(0);
147        } else if (*pattern != '?') {
148            if (toupper(*string) != toupper(*pattern))
149                return(0);
150
151        }
152        string++; pattern++;
153    }
154} /* end function match() */
155
156
157
158/**********************/
159/* Function do_wild() */   /* for porting:  dir separator; match(ignore_case) */
160/**********************/
161
162char *do_wild(__G__ wildspec)
163    __GDEF
164    ZCONST char *wildspec;  /* only used first time on a given dir */
165{
166    static DIR *wild_dir = (DIR *)NULL;
167    static ZCONST char *wildname;
168    static char *dirname, matchname[FILNAMSIZ];
169    static int notfirstcall=FALSE, have_dirname, dirnamelen;
170    struct dirent *file;
171
172    /* Even when we're just returning wildspec, we *always* do so in
173     * matchname[]--calling routine is allowed to append four characters
174     * to the returned string, and wildspec may be a pointer to argv[].
175     */
176    if (!notfirstcall) {    /* first call:  must initialize everything */
177        notfirstcall = TRUE;
178
179        if (!iswild(wildspec)) {
180            strcpy(matchname, wildspec);
181            have_dirname = FALSE;
182            wild_dir = NULL;
183            return matchname;
184        }
185
186        /* break the wildspec into a directory part and a wildcard filename */
187        if ((wildname = (ZCONST char *)strrchr(wildspec, '/')) ==
188            (ZCONST char *)NULL)
189        {
190            dirname = ".";
191            dirnamelen = 1;
192            have_dirname = FALSE;
193            wildname = wildspec;
194        } else {
195            ++wildname;     /* point at character after '/' */
196            dirnamelen = wildname - wildspec;
197            if ((dirname = (char *)malloc(dirnamelen+1)) == (char *)NULL) {
198                Info(slide, 0x201, ((char *)slide,
199                  "warning:  cannot allocate wildcard buffers\n"));
200                strncpy(matchname, wildspec, FILNAMSIZ);
201                matchname[FILNAMSIZ-1] = '\0';
202                return matchname;   /* but maybe filespec was not a wildcard */
203            }
204            strncpy(dirname, wildspec, dirnamelen);
205            dirname[dirnamelen] = '\0';   /* terminate for strcpy below */
206            have_dirname = TRUE;
207        }
208
209        if ((wild_dir = opendir(dirname)) != (DIR *)NULL) {
210            while ((file = readdir(wild_dir)) != (struct dirent *)NULL) {
211                Trace((stderr, "do_wild:  readdir returns %s\n",
212                  FnFilter1(file->d_name)));
213                if (match(file->d_name, wildname, 1 WISEP)) { /*1=ignore case*/
214                    Trace((stderr, "do_wild:  match() succeeds\n"));
215                    if (have_dirname) {
216                        strcpy(matchname, dirname);
217                        strcpy(matchname+dirnamelen, file->d_name);
218                    } else
219                        strcpy(matchname, file->d_name);
220                    return matchname;
221                }
222            }
223            /* if we get to here directory is exhausted, so close it */
224            closedir(wild_dir);
225            wild_dir = (DIR *)NULL;
226        }
227
228        /* return the raw wildspec in case that works (e.g., directory not
229         * searchable, but filespec was not wild and file is readable) */
230        strncpy(matchname, wildspec, FILNAMSIZ);
231        matchname[FILNAMSIZ-1] = '\0';
232        return matchname;
233    }
234
235    /* last time through, might have failed opendir but returned raw wildspec */
236    if (wild_dir == (DIR *)NULL) {
237        notfirstcall = FALSE; /* nothing left to try--reset for new wildspec */
238        if (have_dirname)
239            free(dirname);
240        return (char *)NULL;
241    }
242
243    /* If we've gotten this far, we've read and matched at least one entry
244     * successfully (in a previous call), so dirname has been copied into
245     * matchname already.
246     */
247    while ((file = readdir(wild_dir)) != (struct dirent *)NULL) {
248        Trace((stderr, "do_wild:  readdir returns %s\n",
249          FnFilter1(file->d_name)));
250        if (file->d_name[0] == '.' && wildname[0] != '.')
251            continue;   /* Unix:  '*' and '?' do not match leading dot */
252        if (match(file->d_name, wildname, 1 WISEP)) {   /* 1 == ignore case */
253            Trace((stderr, "do_wild:  match() succeeds\n"));
254            if (have_dirname) {
255                /* strcpy(matchname, dirname); */
256                strcpy(matchname+dirnamelen, file->d_name);
257            } else
258                strcpy(matchname, file->d_name);
259            return matchname;
260        }
261    }
262
263    closedir(wild_dir);     /* have read at least one entry; nothing left */
264    wild_dir = (DIR *)NULL;
265    notfirstcall = FALSE;   /* reset for new wildspec */
266    if (have_dirname)
267        free(dirname);
268    return (char *)NULL;
269
270} /* end function do_wild() */
271
272#endif /* !SFX */
273
274
275
276
277
278/**********************/
279/* Function mapattr() */
280/**********************/
281
282int mapattr(__G)
283    __GDEF
284{
285    ulg tmp = G.crec.external_file_attributes;
286
287    G.pInfo->file_attr = 0;
288    /* initialized to 0 for check in "default" branch below... */
289
290    switch (G.pInfo->hostnum) {
291        case AMIGA_:
292            tmp = (unsigned)(tmp>>17 & 7);   /* Amiga RWE bits */
293            G.pInfo->file_attr = (unsigned)(tmp<<6 | tmp<<3 | tmp);
294            break;
295        case UNIX_:
296        case VMS_:
297        case ACORN_:
298        case ATARI_:
299        case ATHEOS_:
300        case BEOS_:
301        case QDOS_:
302        case TANDEM_:
303        case THEOS_:
304            G.pInfo->file_attr = (unsigned)(tmp >> 16);
305            if (G.pInfo->file_attr != 0 || !G.extra_field) {
306                return 0;
307            } else {
308                /* Some (non-Info-ZIP) implementations of Zip for Unix and
309                 * VMS (and probably others ??) leave 0 in the upper 16-bit
310                 * part of the external_file_attributes field. Instead, they
311                 * store file permission attributes in some extra field.
312                 * As a work-around, we search for the presence of one of
313                 * these extra fields and fall back to the MSDOS compatible
314                 * part of external_file_attributes if one of the known
315                 * e.f. types has been detected.
316                 * Later, we might implement extraction of the permission
317                 * bits from the VMS extra field. But for now, the work-around
318                 * should be sufficient to provide "readable" extracted files.
319                 * (For ASI Unix e.f., an experimental remap from the e.f.
320                 * mode value IS already provided!)
321                 */
322                ush ebID;
323                unsigned ebLen;
324                uch *ef = G.extra_field;
325                unsigned ef_len = G.crec.extra_field_length;
326                int r = FALSE;
327
328                while (!r && ef_len >= EB_HEADSIZE) {
329                    ebID = makeword(ef);
330                    ebLen = (unsigned)makeword(ef+EB_LEN);
331                    if (ebLen > (ef_len - EB_HEADSIZE))
332                        /* discoverd some e.f. inconsistency! */
333                        break;
334                    switch (ebID) {
335                      case EF_ASIUNIX:
336                        if (ebLen >= (EB_ASI_MODE+2)) {
337                            G.pInfo->file_attr =
338                              (unsigned)makeword(ef+(EB_HEADSIZE+EB_ASI_MODE));
339                            /* force stop of loop: */
340                            ef_len = (ebLen + EB_HEADSIZE);
341                            break;
342                        }
343                        /* else: fall through! */
344                      case EF_PKVMS:
345                        /* "found nondecypherable e.f. with perm. attr" */
346                        r = TRUE;
347                      default:
348                        break;
349                    }
350                    ef_len -= (ebLen + EB_HEADSIZE);
351                    ef += (ebLen + EB_HEADSIZE);
352                }
353                if (!r)
354                    return 0;
355            }
356            /* fall through! */
357        /* all remaining cases:  expand MSDOS read-only bit into write perms */
358        case FS_FAT_:
359            /* PKWARE's PKZip for Unix marks entries as FS_FAT_, but stores the
360             * Unix attributes in the upper 16 bits of the external attributes
361             * field, just like Info-ZIP's Zip for Unix.  We try to use that
362             * value, after a check for consistency with the MSDOS attribute
363             * bits (see below).
364             */
365            G.pInfo->file_attr = (unsigned)(tmp >> 16);
366            /* fall through! */
367        case FS_HPFS_:
368        case FS_NTFS_:
369        case MAC_:
370        case TOPS20_:
371        default:
372            /* Ensure that DOS subdir bit is set when the entry's name ends
373             * in a '/'.  Some third-party Zip programs fail to set the subdir
374             * bit for directory entries.
375             */
376            if ((tmp & 0x10) == 0) {
377                extent fnlen = strlen(G.filename);
378                if (fnlen > 0 && G.filename[fnlen-1] == '/')
379                    tmp |= 0x10;
380            }
381            /* read-only bit --> write perms; subdir bit --> dir exec bit */
382            tmp = !(tmp & 1) << 1  |  (tmp & 0x10) >> 4;
383            if ((G.pInfo->file_attr & 0700) == (unsigned)(0400 | tmp<<6))
384                /* keep previous G.pInfo->file_attr setting, when its "owner"
385                 * part appears to be consistent with DOS attribute flags!
386                 */
387                return 0;
388            G.pInfo->file_attr = (unsigned)(0444 | tmp<<6 | tmp<<3 | tmp);
389            break;
390    } /* end switch (host-OS-created-by) */
391
392    /* for originating systems with no concept of "group," "other," "system": */
393    G.pInfo->file_attr &= ~tmp;
394
395    return 0;
396
397} /* end function mapattr() */
398
399
400
401/* portabilibity functions to ensure access to port 2.0 and new ports */
402
403int isv2_3()
404{
405    return G.extra_field[0] == 'T' && G.extra_field[1] == 'h';
406}
407
408int isv2_3lib()
409{
410    return isv2_3() && S_ISLIB((G.pInfo->file_attr >> 8) & 0xFF);
411}
412
413int isv2_3dir()
414{
415    return isv2_3() && S_ISDIR((G.pInfo->file_attr >> 8) & 0xFF);
416}
417
418#ifdef OLD_THEOS_EXTRA
419#define S_IFMT_     0xf800      /* type of file */
420#define S_IFLIB_    0x8000      /* library */
421#define S_IFDIR_    0x4000      /* directory */
422
423#define S_ISLIB_(a) (((a) & 0xff) == _FDB_STAT_LIBRARY)
424#define S_ISDIR_(a) (((a) & 0xff) == _FDB_STAT_DIRECTORY)
425
426struct extra_block
427{
428    ush     signature;
429    ush     size;
430    ush     flags;
431    ulg     filesize;
432    ush     reclen;
433    ush     keylen;
434    uch     filegrow;
435    uch     reserved[3];
436};
437
438struct extra_block *v2_0extra()
439{
440    return (struct extra_block *) G.extra_field;
441}
442
443int isv2_0()
444{
445    return ((struct extra_block *) G.extra_field)->signature == 0x4854;
446}
447
448int isv2_0lib()
449{
450  return isv2_0() && S_ISLIB_(G.pInfo->file_attr);
451}
452
453int isv2_0dir()
454{
455  return isv2_0() && S_ISDIR_(G.pInfo->file_attr);
456}
457
458#define islib() (isv2_0lib() || isv2_3lib())
459#define isdir() (isv2_0dir() || isv2_3dir())
460#define chmodv2_0(a,b)  ((int) _sc_168(a,'p',(size_t)(b)|0x80))
461#undef chmod
462#define chmod(a,b) (isv2_0() && chmodv2_0(a,b))||(isv2_3() && chmodv2_3(a,b))
463#else
464#define islib() isv2_3lib()
465#define isdir() isv2_3dir()
466#undef chmod
467#define chmod(a,b) chmodv2_0(a,b)
468#endif
469
470/************************/
471/*  Function mapname()  */
472/************************/
473
474int mapname(__G__ renamed)
475    __GDEF
476    int renamed;
477/*
478 * returns:
479 *  MPN_OK          - no problem detected
480 *  MPN_INF_TRUNC   - caution (truncated filename)
481 *  MPN_INF_SKIP    - info "skip entry" (dir doesn't exist)
482 *  MPN_ERR_SKIP    - error -> skip entry
483 *  MPN_ERR_TOOLONG - error -> path is too long
484 *  MPN_NOMEM       - error (memory allocation failed) -> skip entry
485 *  [also MPN_VOL_LABEL, MPN_CREATED_DIR]
486 */
487{
488    char pathcomp[FILNAMSIZ];      /* path-component buffer */
489    char *pp, *cp=(char *)NULL;    /* character pointers */
490    char *lastsemi=(char *)NULL;   /* pointer to last semi-colon in pathcomp */
491#ifdef ACORN_FTYPE_NFS
492    char *lastcomma=(char *)NULL;  /* pointer to last comma in pathcomp */
493    RO_extra_block *ef_spark;      /* pointer Acorn FTYPE ef block */
494#endif
495    int killed_ddot = FALSE;       /* is set when skipping "../" pathcomp */
496    int error = MPN_OK;
497    register unsigned workch;      /* hold the character being tested */
498
499
500/*---------------------------------------------------------------------------
501    Initialize various pointers and counters and stuff.
502  ---------------------------------------------------------------------------*/
503
504    if (G.pInfo->vollabel)
505        return MPN_VOL_LABEL;   /* can't set disk volume labels in Unix */
506
507    /* can create path as long as not just freshening, or if user told us */
508    G.create_dirs = (!uO.fflag || renamed);
509
510    created_dir = FALSE;        /* not yet */
511
512    /* user gave full pathname:  don't prepend rootpath */
513    renamed_fullpath = (renamed && (*G.filename == '/'));
514
515    if (checkdir(__G__ (char *)NULL, INIT) == MPN_NOMEM)
516        return MPN_NOMEM;       /* initialize path buffer, unless no memory */
517
518    *pathcomp = '\0';           /* initialize translation buffer */
519    pp = pathcomp;              /* point to translation buffer */
520    if (uO.jflag)               /* junking directories */
521        cp = (char *)strrchr(G.filename, '/');
522    if (cp == (char *)NULL)     /* no '/' or not junking dirs */
523        cp = G.filename;        /* point to internal zipfile-member pathname */
524    else
525        ++cp;                   /* point to start of last component of path */
526
527/*---------------------------------------------------------------------------
528    Begin main loop through characters in filename.
529  ---------------------------------------------------------------------------*/
530
531    while ((workch = (uch)*cp++) != 0) {
532
533        switch (workch) {
534            case '/':             /* can assume -j flag not given */
535                *pp = '\0';
536                if (strcmp(pathcomp, ".") == 0) {
537                    /* don't bother appending "./" to the path */
538                    *pathcomp = '\0';
539                } else if (!uO.ddotflag && strcmp(pathcomp, "..") == 0) {
540                    /* "../" dir traversal detected, skip over it */
541                    *pathcomp = '\0';
542                    killed_ddot = TRUE;     /* set "show message" flag */
543                }
544                /* when path component is not empty, append it now */
545                if (*pathcomp != '\0' &&
546                    ((error = checkdir(__G__ pathcomp, APPEND_DIR))
547                     & MPN_MASK) > MPN_INF_TRUNC)
548                    return error;
549                pp = pathcomp;    /* reset conversion buffer for next piece */
550                lastsemi = (char *)NULL; /* leave direct. semi-colons alone */
551                break;
552
553#ifdef ACORN_FTYPE_NFS
554            case ',':             /* NFS filetype extension */
555                lastcomma = pp;
556                *pp++ = ',';      /* keep for now; may need to remove */
557                break;            /*  later, if requested */
558#endif
559
560            default:
561                if (isfnsym(workch) || workch == '.')
562                    *pp++ = (char)workch;
563                else
564                    *pp++ = '_';
565        } /* end switch */
566
567    } /* end while loop */
568
569    /* Show warning when stripping insecure "parent dir" path components */
570    if (killed_ddot && QCOND2) {
571        Info(slide, 0, ((char *)slide,
572          "warning:  skipped \"../\" path component(s) in %s\n",
573          FnFilter1(G.filename)));
574        if (!(error & ~MPN_MASK))
575            error = (error & MPN_MASK) | PK_WARN;
576    }
577
578/*---------------------------------------------------------------------------
579    Report if directory was created (and no file to create:  filename ended
580    in '/'), check name to be sure it exists, and combine path and name be-
581    fore exiting.
582  ---------------------------------------------------------------------------*/
583
584    if (G.filename[strlen(G.filename) - 1] == '/') {
585        checkdir(__G__ G.filename, GETPATH);
586        if (islib() && G.filename[strlen(G.filename) - 1] == '/')
587            G.filename[strlen(G.filename) - 1] = '\0';
588
589        if (created_dir) {
590            if (QCOND2) {
591                Info(slide, 0, ((char *)slide, "   creating: %s\n",
592                  FnFilter1(G.filename)));
593            }
594            /* set dir time (note trailing '/') */
595            return (error & ~MPN_MASK) | MPN_CREATED_DIR;
596        }
597        /* dir existed already; don't look for data to extract */
598        return (error & ~MPN_MASK) | MPN_INF_SKIP;
599    }
600
601    *pp = '\0';                   /* done with pathcomp:  terminate it */
602
603#ifdef ACORN_FTYPE_NFS
604    /* translate Acorn filetype information if asked to do so */
605    if (uO.acorn_nfs_ext &&
606        (ef_spark = (RO_extra_block *)
607                    getRISCOSexfield(G.extra_field, G.lrec.extra_field_length))
608        != (RO_extra_block *)NULL)
609    {
610        /* file *must* have a RISC OS extra field */
611        long ft = (long)makelong(ef_spark->loadaddr);
612        /*32-bit*/
613        if (lastcomma) {
614            pp = lastcomma + 1;
615            while (isxdigit((uch)(*pp))) ++pp;
616            if (pp == lastcomma+4 && *pp == '\0') *lastcomma='\0'; /* nuke */
617        }
618        if ((ft & 1<<31)==0) ft=0x000FFD00;
619        sprintf(pathcomp+strlen(pathcomp), ",%03x", (int)(ft>>8) & 0xFFF);
620    }
621#endif /* ACORN_FTYPE_NFS */
622
623    if (*pathcomp == '\0') {
624        Info(slide, 1, ((char *)slide, "mapname:  conversion of %s failed\n",
625          FnFilter1(G.filename)));
626        return (error & ~MPN_MASK) | MPN_ERR_SKIP;
627    }
628
629    checkdir(__G__ pathcomp, APPEND_NAME);  /* returns 1 if truncated: care? */
630    checkdir(__G__ G.filename, GETPATH);
631
632    return error;
633
634} /* end function mapname() */
635
636
637
638
639#if 0  /*========== NOTES ==========*/
640
641  extract-to dir:      a:path/
642  buildpath:           path1/path2/ ...   (NULL-terminated)
643  pathcomp:                filename
644
645  mapname():
646    loop over chars in zipfile member name
647      checkdir(path component, COMPONENT | CREATEDIR) --> map as required?
648        (d:/tmp/unzip/)                    (disk:[tmp.unzip.)
649        (d:/tmp/unzip/jj/)                 (disk:[tmp.unzip.jj.)
650        (d:/tmp/unzip/jj/temp/)            (disk:[tmp.unzip.jj.temp.)
651    finally add filename itself and check for existence? (could use with rename)
652        (d:/tmp/unzip/jj/temp/msg.outdir)  (disk:[tmp.unzip.jj.temp]msg.outdir)
653    checkdir(name, GETPATH)     -->  copy path to name and free space
654
655#endif /* 0 */
656
657
658
659
660/***********************/
661/* Function checkdir() */
662/***********************/
663
664int checkdir(__G__ pathcomp, flag)
665    __GDEF
666    char *pathcomp;
667    int flag;
668/*
669 * returns:
670 *  MPN_OK          - no problem detected
671 *  MPN_INF_TRUNC   - (on APPEND_NAME) truncated filename
672 *  MPN_INF_SKIP    - path doesn't exist, not allowed to create
673 *  MPN_ERR_SKIP    - path doesn't exist, tried to create and failed; or path
674 *                    exists and is not a directory, but is supposed to be
675 *  MPN_ERR_TOOLONG - path is too long
676 *  MPN_NOMEM       - can't allocate memory for filename buffers
677 */
678{
679    static int rootlen = 0;   /* length of rootpath */
680    static char *rootpath;    /* user's "extract-to" directory */
681    static char rootdisk[3];  /* user's "extract-to" disk */
682    static char *buildpath;   /* full path (so far) to extracted file */
683    static char *end;         /* pointer to end of buildpath ('\0') */
684
685#   define FN_MASK   7
686#   define FUNCTION  (flag & FN_MASK)
687
688
689
690/*---------------------------------------------------------------------------
691    APPEND_DIR:  append the path component to the path being built and check
692    for its existence.  If doesn't exist and we are creating directories, do
693    so for this one; else signal success or error as appropriate.
694  ---------------------------------------------------------------------------*/
695
696    if (FUNCTION == APPEND_DIR) {
697        int too_long = FALSE;
698#ifdef SHORT_NAMES
699        char *old_end = end;
700#endif
701
702        Trace((stderr, "appending dir segment [%s]\n", FnFilter1(pathcomp)));
703        while ((*end = *pathcomp++) != '\0')
704            ++end;
705#ifdef SHORT_NAMES   /* path components restricted to 14 chars, typically */
706        if ((end-old_end) > FILENAME_MAX)  /* GRR:  proper constant? */
707            *(end = old_end + FILENAME_MAX) = '\0';
708#endif
709
710        /* GRR:  could do better check, see if overrunning buffer as we go:
711         * check end-buildpath after each append, set warning variable if
712         * within 20 of FILNAMSIZ; then if var set, do careful check when
713         * appending.  Clear variable when begin new path. */
714
715        if ((end-buildpath) > FILNAMSIZ-3)  /* need '/', one-char name, '\0' */
716            too_long = TRUE;                /* check if extracting directory? */
717        Trace((stderr, "appending disk segment [%s]\n", FnFilter1(rootdisk)));
718        strcat(buildpath, rootdisk);
719        if (stat(buildpath, &G.statbuf)) {  /* path doesn't exist */
720            if (!G.create_dirs) { /* told not to create (freshening) */
721                free(buildpath);
722                return MPN_INF_SKIP;    /* path doesn't exist: nothing to do */
723            }
724            if (too_long) {
725                Info(slide, 1, ((char *)slide,
726                  "checkdir error:  path too long: %s\n",
727                  FnFilter1(buildpath)));
728                free(buildpath);
729                /* no room for filenames:  fatal */
730                return MPN_ERR_TOOLONG;
731            }
732            if (islib()) {
733                ulg size;
734#ifdef OLD_THEOS_EXTRA
735                if (isv2_0lib())
736                    size = v2_0extra()->filesize;
737                else
738#endif
739                {
740                    size = (ulg) G.extra_field[5] |
741                           ((ulg) G.extra_field[6] << 8) |
742                           ((ulg) G.extra_field[7] << 16) |
743                           ((ulg) G.extra_field[8] << 24);
744                }
745                if (makelib(buildpath, size / 64)) {
746                    Info(slide, 1, ((char*)slide,
747                      "checkdir error:  can't create library %s\n\
748                     unable to process %s.\n",
749                      FnFilter2(buildpath), FnFilter1(G.filename));
750                    free(buildpath);
751                    /* path didn't exist, tried to create, failed */
752                    return MPN_ERR_SKIP;
753                }
754            } else if (mkdir(buildpath) == -1) {   /* create the directory */
755                Info(slide, 1, ((char *)slide,
756                  "checkdir error:  cannot create %s\n\
757                 unable to process %s.\n",
758                  FnFilter2(buildpath), FnFilter1(G.filename));
759                free(buildpath);
760                /* path didn't exist, tried to create, failed */
761                return MPN_ERR_SKIP;
762            }
763            created_dir = TRUE;
764        } else if (!S_ISDIR(G.statbuf.st_mode) && isdir()) {
765            Info(slide, 1, ((char *)slide,
766              "checkdir error:  %s exists but is not directory\n\
767                 unable to process %s.\n",
768              FnFilter2(buildpath), FnFilter1(G.filename));
769            free(buildpath);
770            /* path existed but wasn't dir */
771            return MPN_ERR_SKIP;
772        } else if (!S_ISLIB(G.statbuf.st_mode) && islib()) {
773            Info(slide, 1, ((char *)slide,
774              "checkdir error:  %s exists but is not library\n\
775                 unable to process %s.\n",
776              FnFilter2(buildpath), FnFilter1(G.filename));
777            free(buildpath);
778            /* path existed but wasn't lib */
779            return MPN_ERR_SKIP;
780        }
781        if (too_long) {
782            Info(slide, 1, ((char *)slide,
783              "checkdir error:  path too long: %s\n", FnFilter1(buildpath)));
784            free(buildpath);
785            /* no room for filenames:  fatal */
786            return MPN_ERR_TOOLONG;
787        }
788        *end++ = '/';
789        *end = '\0';
790        Trace((stderr, "buildpath now = [%s]\n", FnFilter1(buildpath)));
791        return MPN_OK;
792
793    } /* end if (FUNCTION == APPEND_DIR) */
794
795/*---------------------------------------------------------------------------
796    GETPATH:  copy full path to the string pointed at by pathcomp, and free
797    buildpath.
798  ---------------------------------------------------------------------------*/
799
800    if (FUNCTION == GETPATH) {
801        strcpy(pathcomp, buildpath);
802        Trace((stderr, "getting and freeing path [%s]\n",
803          FnFilter1(pathcomp)));
804        free(buildpath);
805        buildpath = end = (char *)NULL;
806        return MPN_OK;
807    }
808
809/*---------------------------------------------------------------------------
810    APPEND_NAME:  assume the path component is the filename; append it and
811    return without checking for existence.
812  ---------------------------------------------------------------------------*/
813
814    if (FUNCTION == APPEND_NAME) {
815#ifdef SHORT_NAMES
816        char *old_end = end;
817#endif
818
819        Trace((stderr, "appending filename [%s]\n", FnFilter1(pathcomp)));
820        while ((*end = *pathcomp++) != '\0') {
821            ++end;
822#ifdef SHORT_NAMES  /* truncate name at 14 characters, typically */
823            if ((end-old_end) > FILENAME_MAX)      /* GRR:  proper constant? */
824                *(end = old_end + FILENAME_MAX) = '\0';
825#endif
826            if ((end-buildpath) >= FILNAMSIZ) {
827                *--end = '\0';
828                Info(slide, 0x201, ((char *)slide,
829                  "checkdir warning:  path too long; truncating\n\
830                   %s\n                -> %s\n",
831                  FnFilter1(G.filename), FnFilter2(buildpath)));
832                return MPN_INF_TRUNC;   /* filename truncated */
833            }
834        }
835        strcat(buildpath, rootdisk);
836        Trace((stderr, "buildpath now = [%s]\n", FnFilter1(buildpath)));
837        /* could check for existence here, prompt for new name... */
838        return MPN_OK;
839    }
840
841/*---------------------------------------------------------------------------
842    INIT:  allocate and initialize buffer space for the file currently being
843    extracted.  If file was renamed with an absolute path, don't prepend the
844    extract-to path.
845  ---------------------------------------------------------------------------*/
846
847/* GRR:  for VMS and TOPS-20, add up to 13 to strlen */
848
849    if (FUNCTION == INIT) {
850        Trace((stderr, "initializing buildpath to "));
851#ifdef ACORN_FTYPE_NFS
852        if ((buildpath = (char *)malloc(strlen(G.filename)+rootlen+
853                                        (uO.acorn_nfs_ext ? 5 : 1)))
854#else
855        if ((buildpath = (char *)malloc(strlen(G.filename)+rootlen+1))
856#endif
857            == (char *)NULL)
858            return MPN_NOMEM;
859        if ((rootlen > 0) && !renamed_fullpath) {
860            strcpy(buildpath, rootpath);
861            end = buildpath + rootlen;
862        } else {
863            *buildpath = '\0';
864            end = buildpath;
865        }
866        Trace((stderr, "[%s]\n", FnFilter1(buildpath)));
867        return MPN_OK;
868    }
869
870/*---------------------------------------------------------------------------
871    ROOT:  if appropriate, store the path in rootpath and create it if
872    necessary; else assume it's a zipfile member and return.  This path
873    segment gets used in extracting all members from every zipfile specified
874    on the command line.
875  ---------------------------------------------------------------------------*/
876
877#if (!defined(SFX) || defined(SFX_EXDIR))
878    if (FUNCTION == ROOT) {
879        if (pathcomp == (char *)NULL) {
880            rootlen = 0;
881            return MPN_OK;
882        }
883        if (rootlen > 0)        /* rootpath was already set, nothing to do */
884            return MPN_OK;
885        if ((rootlen = strlen(pathcomp)) > 0) {
886            int prepend_slash = 0;
887            char *tmproot, *p;
888
889            if (*pathcomp == ':') {
890                preprend_slash = 1;
891                rootlen++;
892            }
893            if ((tmproot = (char *)malloc(rootlen+2)) == (char *)NULL) {
894                rootlen = 0;
895                return MPN_NOMEM;
896            }
897            if (prepend_slash)
898                strcpy(tmproot, "/");
899            else
900                *tmproot = '\0';
901            strcat(tmproot, pathcomp);
902            Trace((stderr, "initializing root path to [%s]\n",
903              FnFilter1(tmproot)));
904            if (tmproot[rootlen-1] == '/') {
905                tmproot[--rootlen] = '\0';
906            }
907            if (rootlen > 0 && (SSTAT(tmproot, &G.statbuf) ||
908                                !S_ISDIR(G.statbuf.st_mode)))
909            {   /* path does not exist */
910                if (!G.create_dirs /* || iswild(tmproot) */ ) {
911                    free(tmproot);
912                    rootlen = 0;
913                    /* skip (or treat as stored file) */
914                    return MPN_INF_SKIP;
915                }
916                /* create the directory (could add loop here scanning tmproot
917                 * to create more than one level, but why really necessary?) */
918                if (mkdir(tmproot) == -1) {
919                    Info(slide, 1, ((char *)slide,
920                      "checkdir:  cannot create extraction directory: %s\n",
921                      FnFilter1(tmproot)));
922                    free(tmproot);
923                    rootlen = 0;
924                    /* path didn't exist, tried to create, and failed: */
925                    /* file exists, or 2+ subdir levels required */
926                    return MPN_ERR_SKIP;
927                }
928            }
929            /* split rootpath in path and disk */
930            if ((p = strchr(tmproot, ':')) != NULL) {
931                strncpy(rootdisk, p, 2);
932                rootdisk[2] = '\0';
933                *p = '\0';
934                rootlen = p - tmproot;
935            } else
936                rootdisk[0] = '\0';
937            if (rootpath[rootlen - 1] != '/') {
938                rootpath[rootlen++] = '/';
939                rootpath[rootlen] = '\0';
940            }
941            if ((rootpath = (char *)realloc(tmproot, rootlen+1)) == NULL) {
942                free(tmproot);
943                rootlen = 0;
944                return MPN_NOMEM;
945            }
946            Trace((stderr, "rootpath now = [%s], rootdisk now = [%s]\n",
947                FnFilter1(rootpath), FnFilter2(rootdisk)));
948        }
949        return MPN_OK;
950    }
951#endif /* !SFX || SFX_EXDIR */
952
953/*---------------------------------------------------------------------------
954    END:  free rootpath, immediately prior to program exit.
955  ---------------------------------------------------------------------------*/
956
957    if (FUNCTION == END) {
958        Trace((stderr, "freeing rootpath\n"));
959        if (rootlen > 0) {
960            free(rootpath);
961            rootlen = 0;
962        }
963        return MPN_OK;
964    }
965
966    return MPN_INVALID; /* should never reach */
967
968} /* end function checkdir() */
969
970
971
972
973#ifdef MORE
974
975/**************************/
976/* Function screenlines() */
977/**************************/
978
979int screenlines()
980{
981    return getpl(27) + 1;
982}
983
984#endif /* MORE */
985
986
987
988
989
990#if (!defined(MTS) || defined(SET_DIR_ATTRIB))
991static void get_extattribs OF((__GPRO__ iztimes *pzt));
992
993static int get_extattribs(__G__ pzt)
994    __GDEF
995    iztimes *pzt;
996{
997/*---------------------------------------------------------------------------
998    Convert from MSDOS-format local time and date to Unix-format 32-bit GMT
999    time:  adjust base year from 1980 to 1970, do usual conversions from
1000    yy/mm/dd hh:mm:ss to elapsed seconds, and account for timezone and day-
1001    light savings time differences.  If we have a Unix extra field, however,
1002    we're laughing:  both mtime and atime are ours.
1003  ---------------------------------------------------------------------------*/
1004#ifdef USE_EF_UT_TIME
1005    unsigned eb_izux_flg;
1006
1007    eb_izux_flg = (G.extra_field ? ef_scan_for_izux(G.extra_field,
1008                   G.lrec.extra_field_length, 0, G.lrec.last_mod_dos_datetime,
1009#ifdef IZ_CHECK_TZ
1010                   (G.tz_is_valid ? pzt : NULL),
1011#else
1012                   pzt,
1013#endif
1014                   z_uidgid) : 0);
1015    if (eb_izux_flg & EB_UT_FL_MTIME) {
1016        TTrace((stderr, "\nget_extattribs:  Unix e.f. modif. time = %ld\n",
1017          pzt->mtime));
1018    } else {
1019        pzt->mtime = dos_to_unix_time(G.lrec.last_mod_dos_datetime);
1020    }
1021    if (eb_izux_flg & EB_UT_FL_ATIME) {
1022        TTrace((stderr, "get_extattribs:  Unix e.f. access time = %ld\n",
1023          pzt->atime));
1024    } else {
1025        pzt->atime = pzt->mtime;
1026        TTrace((stderr, "\nget_extattribs:  modification/access times = %ld\n",
1027          pzt->mtime));
1028    }
1029#else
1030    pzt->mtime = dos_to_unix_time(G.lrec.last_mod_dos_datetime);
1031    pzt->atime = pzt->mtime;
1032#endif
1033}
1034#endif /* !MTS || SET_DIR_ATTRIB */
1035
1036
1037
1038#ifndef MTS
1039
1040/****************************/
1041/* Function close_outfile() */
1042/****************************/
1043
1044void close_outfile(__G)    /* GRR: change to return PK-style warning level */
1045    __GDEF
1046{
1047    union {
1048        iztimes t3;             /* mtime, atime, ctime */
1049        ztimbuf t2;             /* modtime, actime */
1050    } zt;
1051
1052/*---------------------------------------------------------------------------
1053    If symbolic links are supported, allocate a storage area, put the uncom-
1054    pressed "data" in it, and create the link.  Since we know it's a symbolic
1055    link to start with, we shouldn't have to worry about overflowing unsigned
1056    ints with unsigned longs.
1057  ---------------------------------------------------------------------------*/
1058
1059    fclose(G.outfile);
1060
1061    get_extattribs(__G__ &(zt.t3));
1062
1063    /* set the file's access and modification times */
1064    if (utime(G.filename, &(zt.t2))) {
1065        if (uO.qflag)
1066            Info(slide, 0x201, ((char *)slide,
1067              "warning:  cannot set times for %s\n", FnFilter1(G.filename)));
1068        else
1069            Info(slide, 0x201, ((char *)slide,
1070              " (warning) cannot set times"));
1071    }
1072
1073/*---------------------------------------------------------------------------
1074    Change the file permissions from default ones to those stored in the
1075    zipfile.
1076  ---------------------------------------------------------------------------*/
1077
1078    if (chmod(G.filename, 0xffff & G.pInfo->file_attr))
1079        perror("chmod (file attributes) error");
1080
1081/*---------------------------------------------------------------------------
1082    Change the file structure and set native .
1083  ---------------------------------------------------------------------------*/
1084
1085    if (isv2_3()) {
1086        chorg(G.filename, G.extra_field[9]);
1087        chlen(G.filename,
1088            (ush) G.extra_field[10] | ((ush) G.extra_field[11] << 8),
1089            (ush) G.extra_field[12] | ((ush) G.extra_field[13] << 8));
1090        chgrow(G.filename, G.extra_field[14]);
1091    }
1092#if OLD_THEOS_EXTRA
1093     else if (isv2_0()) {
1094        chorg(G.filename, G.pInfo->file_attr & 0xFF);
1095        chlen(G.filename, v2_0extra()->reclen, v2_0extra()->keylen);
1096        chgrow(G.filename, v2_0extra()->filegrow);
1097    }
1098#endif
1099} /* end function close_outfile() */
1100
1101#endif /* !MTS */
1102
1103
1104
1105
1106#ifdef SET_DIR_ATTRIB
1107/* messages of code for setting directory attributes */
1108static ZCONST char Far DirlistUidGidFailed[] =
1109  "warning:  cannot set UID %d and/or GID %d for %s\n";
1110static ZCONST char Far DirlistUtimeFailed[] =
1111  "warning:  cannot set modification, access times for %s\n";
1112#  ifndef NO_CHMOD
1113  static ZCONST char Far DirlistChmodFailed[] =
1114    "warning:  cannot set permissions for %s\n";
1115#  endif
1116
1117
1118int defer_dir_attribs(__G__ pd)
1119    __GDEF
1120    direntry **pd;
1121{
1122    uxdirattr *d_entry;
1123
1124    d_entry = (uxdirattr *)malloc(sizeof(uxdirattr) + strlen(G.filename));
1125    *pd = (direntry *)d_entry;
1126    if (d_entry == (uxdirattr *)NULL) {
1127        return PK_MEM;
1128    }
1129    d_entry->fn = d_entry->fnbuf;
1130    strcpy(d_entry->fn, G.filename);
1131
1132    d_entry->perms = G.pInfo->file_attr;
1133
1134    get_extattribs(__G__ &(d_entry->u.t3));
1135    return PK_OK;
1136} /* end function defer_dir_attribs() */
1137
1138
1139int set_direc_attribs(__G__ d)
1140    __GDEF
1141    direntry *d;
1142{
1143    int errval = PK_OK;
1144
1145    if (utime(d->fn, &UxAtt(d)->u.t2)) {
1146        Info(slide, 0x201, ((char *)slide,
1147          LoadFarString(DirlistUtimeFailed), FnFilter1(d->fn)));
1148        if (!errval)
1149            errval = PK_WARN;
1150    }
1151    if (chmod(d->fn, 0xffff & UxAtt(d)->perms)) {
1152        Info(slide, 0x201, ((char *)slide,
1153          LoadFarString(DirlistChmodFailed), FnFilter1(d->fn)));
1154        /* perror("chmod (file attributes) error"); */
1155        if (!errval)
1156            errval = PK_WARN;
1157    }
1158    return errval;
1159} /* end function set_direc_attribs() */
1160
1161#endif /* SET_DIR_ATTRIB */
1162
1163
1164
1165
1166#ifdef TIMESTAMP
1167
1168/***************************/
1169/*  Function stamp_file()  */
1170/***************************/
1171
1172int stamp_file(fname, modtime)
1173    ZCONST char *fname;
1174    time_t modtime;
1175{
1176    ztimbuf tp;
1177
1178    tp.modtime = tp.actime = modtime;
1179    return (utime(fname, &tp));
1180
1181} /* end function stamp_file() */
1182
1183#endif /* TIMESTAMP */
1184
1185
1186
1187
1188#ifndef SFX
1189
1190/************************/
1191/*  Function version()  */
1192/************************/
1193
1194void version(__G)
1195    __GDEF
1196{
1197    char buf1[40];
1198    extern char Far  CompiledWith[];
1199
1200    sprintf(slide, CompiledWith,
1201        "THEOS C ","5.28", "THEOS ", "4.x", " on ", __DATE__);
1202    (*G.message)((zvoid *)&G, slide, (ulg)strlen((char *)slide), 0);
1203
1204} /* end function version() */
1205
1206#endif /* !SFX */
1207