/*
  Copyright (c) 1990-2005 Info-ZIP.  All rights reserved.

  See the accompanying file LICENSE, version 2004-May-22 or later
  (the contents of which are also included in zip.h) for terms of use.
  If, for some reason, both of these files are missing, the Info-ZIP license
  also may be found at:  ftp://ftp.info-zip.org/pub/infozip/license.html
*/
/*
 * Yes this  file is necessary; the QDOS file system is the most
 * ludicrous known to man (even more so than VMS!).
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <dirent.h>
#include <unistd.h>

#include "zip.h"
#include "crypt.h"
#include "ttyio.h"

#ifdef QDOS

# include <qdos.h>

#if CRYPT

char *getp(m, p, n)
    ZCONST char *m;              /* prompt for password */
    char *p;                    /* return value: line input */
    int n;                      /* bytes available in p[] */
{
    int c;                      /* one-byte buffer for read() to use */
    int i;                      /* number of characters input */
    char *w;                    /* warning on retry */

    /* get password */
    w = "";
    sd_cure(getchid(0), -1);    /* enable cursor */
    do {
        fputs(w, stderr);       /* warning if back again */
        fputs(m, stderr);       /* display prompt and flush */
        fflush(stderr);
        i = 0;
        do {
            c = getch();
            if (c == 0xc2) {
                if (i > 0) {
                    i--; /* the `del' keys works */
                    fputs("\b \b", stderr);
                }
            }
            else if (i < n) {
                p[i++] = c;     /* truncate past n */
                if(c != '\n') putc('*', stderr);
            }
        } while (c != '\n');

        putc('\n', stderr);  fflush(stderr);
        w = "(line too long--try again)\n";
    } while (p[i-1] != '\n');

    p[i-1] = 0;                 /* terminate at newline */
    sd_curs(getchid(0), -1);    /* suppress cursor */
    return p;                   /* return pointer to password */

} /* end function getp() */

#endif /* CRYPT */


#define __attribute__(p)

int newname(char *, int, int);

#else /* !QDOS */
#define QDOS_FLMAX 36

short qlflag = 0;

struct qdirect  {
    long            d_length __attribute__ ((packed));  /* file length */
    unsigned char   d_access __attribute__ ((packed));  /* file access type */
    unsigned char   d_type __attribute__ ((packed));    /* file type */
    long            d_datalen __attribute__ ((packed)); /* data length */
    long            d_reserved __attribute__ ((packed));/* Unused */
    short           d_szname __attribute__ ((packed));  /* size of name */
    char            d_name[QDOS_FLMAX] __attribute__ ((packed));/* name area */
    long            d_update __attribute__ ((packed));  /* last update */
    long            d_refdate __attribute__ ((packed));
    long            d_backup __attribute__ ((packed));   /* EOD */
    } ;
#endif /* ?QDOS */

#define SHORTID 0x4afb          /* in big-endian order !! */
#define LONGID  "QDOS02"
#define EXTRALEN (sizeof(struct qdirect) + 8)

typedef struct
{
    unsigned short shortid __attribute__ ((packed));
    struct
    {
        unsigned char lo __attribute__ ((packed));
        unsigned char hi __attribute__ ((packed));
    } len __attribute__ ((packed));
    char        longid[8] __attribute__ ((packed));
    struct      qdirect     header __attribute__ ((packed));
} qdosextra;

#ifdef USE_EF_UT_TIME
local int GetExtraTime(struct zlist far *z, iztimes *z_utim, unsigned ut_flg);
#endif

#ifdef QDOS

#define rev_short(x) (x)
#define rev_long(x) (x)

char _prog_name[] = "zip";
char _copyright[] = "(c) Info-ZIP Group";
long _stack = 16*1024;
char *  _endmsg = NULL;

extern void consetup_title(chanid_t,struct WINDOWDEF *);
void (*_consetup)(chanid_t,struct WINDOWDEF *) = consetup_title;

struct WINDOWDEF _condetails =
{
    2,
    1,
    0,
    7,
    500,
    220,
    2,
    30
};

extern short qlwait;
extern short dtype;

#define CHECKDIR(p1) (((p1).d_type == dtype) && (((p1).d_length % 64) == 0))

char * stpcpy (char *d, ZCONST char *s)
{
    while(*d++ = *s++)
        ; /* Null loop */
    return d-1;
}

static jobid_t chowner(chanid_t chan)
{
    extern char *_sys_var;
    char *scht;
    long *cdb;
    long jid;

    scht = *((char **)(_sys_var + 0x78));
    cdb = *(long **)((long *)scht  + (chan & 0xffff));
    jid = *(cdb + 2);
    return jid;
}

void QDOSexit(void)
{
    jobid_t me,you;

    me = getpid();
    you = chowner(getchid(0));

    if((me == you) && ((qlflag & 4) == 0))
    {
        if(isatty(0) && isatty(2) && qlwait)
        {
            char c = 0;
            fputs("Press a key to exit", stderr);
            if((io_fbyte(getchid(0), qlwait, &c) == 0) && c == 27)
            {
                io_fbyte(getchid(0), -1, &c);
            }
        }
    }
    exit(0);
}

/* Access seems to be *always* broken in c68 */
/* Not accurate, just works */

int access (char *f, int mode)
{
    struct stat st;
    int fd;

    if((fd = stat(f, &st)) == 0)
    {
        switch(fd)
        {
        case F_OK:
            break;
        case R_OK:
            fd = (st.st_mode & 0444) == 0;
            break;
        case W_OK:
            fd = (st.st_mode & 0222) == 0;
            break;
        case X_OK:
            fd = (st.st_mode & 0111) == 0;
            break;
        default:
            fd = -1;
            break;
        }
    }
    return fd;
}

/* Fixup a Mickey Mouse file naming system */

char * Unix2ql (char *qlname, char **dot)
{
    static char path[64];
    char name[64];
    char *q, *r, *s;

    strcpy(name, qlname);
    if(*name == '~')
    {
        r = name+1;
        getcwd(path, sizeof(path));
        q = path + strlen(path);
        if(*(q-1) != '_')
        {
            *q++ = '_';
        }
    }
    else
    {
        q = path;
        r = name;
    }

    if(*r == '/')
    {
        r++;
    }

    strcpy(q, r);

    while (*q)
    {
        if(*q == '/' || *q == '.')
        {
            if(*q == '.' && dot)
            {
                *dot = name + (q - path);
            }
            *q = '_';
        }

        q++;
    }
    return path;
}

#if 0                                 /* Not used in ZIP */

GuessAltName(char *name, char *dot)
{
    if(dot)
    {
        *dot = '.';
    }
    else
    {
        if((dot = strrchr(name, '_')))
        {
            *dot = '.';
        }
    }
}

#endif /* 0 */

short devlen(char *p)
{
    char defpath[40];
    short deflen = 0, ok = 0;

    getcwd(defpath, sizeof(defpath));
    deflen = strlen(defpath);
    if(deflen)
    {
        if(strnicmp(p, defpath, deflen) == 0)
        {
            ok = 1;
        }
    }

    if(!ok)
    {
        if(isdirdev(p))
        {
            deflen = 5;
        }
        else
        {
            deflen = 0;
        }
    }
    return deflen;
}

char * ql2Unix (char *qlname)
{
    struct stat st;
    int sts;
    char *p, *r, *s, *ldp;
    char *pnam = NULL;
    static char path[64];
    short  deflen;
    char name[64];

    strcpy(name, qlname);
    strcpy(path, name);

    deflen = devlen(qlname);

    p = name + deflen;
    pnam = path + deflen;

    if(s = strrchr(p, '_'))
    {
        *s = 0;
        sts = stat(name, &st);
        if(deflen && sts ==0 && (st.st_mode & S_IFDIR))
        {
            *(path+(s-name)) = '/';
        }
        else
        {
            *(path+(s-name)) = '.';
        }
    }

    ldp = p;
    for(r = p; *r; r++)
    {
        if(r != ldp && *r == '_')
        {
            *r = 0;
            if(deflen)
            {
                sts = stat(name, &st);
            }
            else
                sts = -1;

            if(sts ==0 && (st.st_mode & S_IFDIR))
            {
                *(path+(r-name)) = '/';
                ldp = r + 1;
            }
            else
            {
                *(path+(r-name)) = '_';
            }
            *r = '_';
        }
    }
    return pnam;
}

char *LastDir(char *ws)
{
    char *p;
    char *q = ws;
    struct stat s;

    for(p = ws; *p; p++)
    {
        if(p != ws && *p == '_')
        {
            char c;

            p++;
            c = *p;
            *p = 0;
            if(stat(ws, &s) == 0 && S_ISDIR(s.st_mode))
            {
                q = p;
            }
            *p = c;
        }
    }
    return q;
}

# ifndef UTIL

static int add_dir(char * dnam)
{
    int e = ZE_OK;
    char *p;
    short nlen;

    nlen = strlen(dnam);
    if(p = malloc(nlen + 2))
    {
        strncpy (p, dnam, nlen);
        if(*(p+nlen) != '_')
        {
            *(p+nlen) = '_';
            *(p+nlen+1) = '\0';
        }
        if ((e = newname(p, 1, 0)) != ZE_OK)
        {
                free(p);
        }
    }
    else
    {
        e = ZE_MEM;
    }
    return e;
}

int qlwild (char *dnam, short dorecurse, short l)
{
    static char match[40] = {0};
    static char ddev[8] =  {0};
    static short nc;
    static short llen;
    static char base[40];

    int chid;
    struct qdirect qd;
    char *dp;
    int e = ZE_MISS;

    if (l == 0)
    {
        nc = 0;
        *base = '\0';
        if (isdirdev (dnam))
        {
            dp = dnam;
            strncpy (ddev, dnam, 5);
        }
        else
        {

            char *p;
            char temp[40];

            getcwd (temp, 40);

            llen = strlen(temp);
            p = (temp + llen - 1);
            if (*p != '_')
            {
                *p++ = '_';
                *p = 0;
            }

            strncpy (ddev, temp, 5);
            dp = base;
            p = stpcpy (dp, temp);
            strcpy (p, dnam);
        }

        {
            char *q = isshexp (dp);
            if(q)
            {
                strcpy (match, dp + 5);
                if (q)
                {
                    while (q != dp && *q != '_')
                    {
                        q--;
                    }
                    *(++q) = '\0';
                }
            }
            else
            {
                struct stat s;
                if (stat(dp, &s) == 0)
                {
                    if (!(s.st_mode & S_IFDIR))
                    {
                        return procname(dp, 0);
                    }
                }
                else
                {
                    return ZE_MISS;  /* woops, no wildcards! */
                }
            }

        }
    }
    else
    {
        dp = dnam;
    }

    if ((chid = io_open (dp, 4L)) > 0)
    {
        int id = 0;
        while (io_fstrg (chid, -1, &qd, 64) > 0)
        {
            short j;

            if (qd.d_szname)
            {
                if (CHECKDIR(qd))
                {
                    if(dorecurse)
                    {
                        char fnam[256], *p;

                        p = stpcpy (fnam, ddev);
                        strncpy (p, qd.d_name, qd.d_szname);
                        *(p + qd.d_szname) = 0;
                        e = qlwild (fnam, dorecurse, l+1);
                    }
                    else
                    {
                        continue;
                    }
                }
                else
                {
                    char nam[48];
                    strcpy(nam, ddev);

                    strncpy (nam + 5, qd.d_name, qd.d_szname);
                    *(nam + 5 + qd.d_szname) = 0;

                    if (MATCH (match, nam + 5, 0) == 1)
                    {
                        if(dirnames && l && id == 0)
                        {
                            id = 1;
                            if((e = add_dir(dp)) != ZE_OK)
                            {
                                return e;
                            }
                        }

                        if((e = procname(nam, 0)) == ZE_OK)
                        {
                            nc++;
                        }
                    }
                }
            }
        }
        io_close (chid);
    }

    if (l == 0)
    {
        *ddev = 0;
        *match = 0;
        e = (nc) ? ZE_OK : ZE_MISS;
    }
    return e;

}

int wild(char *p)
{
    return qlwild(p, recurse, 0);
}
# endif /* !UTIL */

/*
 * Return QDOS error, 0 if exec 1 if found but not exe or rel
 */
int qlstat(char *name, struct qdirect *qs, char *flag)
{
    int r;
    r = qstat(name, qs);
    if(r == 0)
    {
        if(qs->d_type == 0)
        {
            r = 1;
        }
        else if(CHECKDIR(*qs))
        {
            r = 255;
        }
    }
    return r;
}

#else /* !QDOS */

long rev_long (ulg l)
{
    uch cc[4];
    cc[0] = (uch)(l >> 24);
    cc[1] = (uch)((l >> 16) & 0xff);
    cc[2] = (uch)((l >> 8) & 0xff);
    cc[3] = (uch)(l & 0xff);
    return *(ulg *)cc;
}

short rev_short (ush s)
{
    uch cc[2];
    cc[0] = (uch)((s >> 8) & 0xff);
    cc[1] = (uch)(s & 0xff);
    return *(ush *)cc;
}

#define O_BINARY 0

int qlstat(char *name, struct qdirect *qs, char *flag)
{
    int r = -1;
    int n, fd;
    struct stat s;
    struct _ntc_
    {
        long id;
        long dlen;
    } ntc;

    *flag = 0;
    if((fd = open(name, O_RDONLY | O_BINARY)) > 0)
    {
        short nl;

        fstat(fd, &s);
        lseek(fd, -8, SEEK_END);
        read(fd, &ntc, 8);
        qs->d_length = rev_long(s.st_size);
        qs->d_update = rev_long(s.st_ctime + 283996800);

        nl = strlen(name);
        if(nl > QDOS_FLMAX)
        {
            nl = QDOS_FLMAX;
            *flag = 1;
        }
        qs->d_szname = rev_short(nl);
        memcpy(qs->d_name, name, nl);

        if(ntc.id == *(long *)"XTcc")
        {
            qs->d_datalen = ntc.dlen;    /* This is big endian */
            qs->d_type = 1;
            r = 0;
        }
        else
        {
            qs->d_type = 0;
            qs->d_datalen = 0;
            r = 1;
        }
        close(fd);
        return r;
    }
    else
    {
        fprintf(stderr, "Fails %d\n", fd);
        return r;
    }
}

#endif /* ?QDOS */

#ifdef USE_EF_UT_TIME

#define EB_L_UT_SIZE    (EB_HEADSIZE + eb_l_ut_len)
#define EB_C_UT_SIZE    (EB_HEADSIZE + eb_c_ut_len)

#ifdef UNIX
#define EB_L_UX2_SIZE     (EB_HEADSIZE + EB_UX2_MINLEN)
#define EB_C_UX2_SIZE     EB_HEADSIZE
#define EF_L_UT_UX2_SIZE  (EB_L_UT_SIZE + EB_L_UX2_SIZE)
#define EF_C_UT_UX2_SIZE  (EB_C_UT_SIZE + EB_C_UX2_SIZE)
#else
#define EF_L_UT_UX2_SIZE  EB_L_UT_SIZE
#define EF_C_UT_UX2_SIZE  EB_C_UT_SIZE
#endif

local int GetExtraTime(struct zlist far *z, iztimes *z_utim, unsigned ut_flg)
{
  char *eb_l_ptr;
  char *eb_c_ptr;
  char *eb_pt;
  extent eb_l_ut_len = 0;
  extent eb_c_ut_len = 0;

#ifdef UNIX
  struct stat s;

  /* For the full sized UT local field including the UID/GID fields, we
   * have to stat the file, again.  */
  if (stat(z->name, &s))
    return ZE_OPEN;
  /* update times in z_utim, stat() call might have changed atime... */
  z_utim->mtime = s.st_mtime;
  z_utim->atime = s.st_atime;
  z_utim->ctime = s.st_mtime;   /* best guess (st_ctime != creation time) */
#endif /* UNIX */

#ifdef IZ_CHECK_TZ
  if (!zp_tz_is_valid)
    ut_flg = 0;          /* disable UT e.f creation if no valid TZ info */
#endif
  if (ut_flg != 0) {
    if (ut_flg & EB_UT_FL_MTIME)
      eb_l_ut_len = eb_c_ut_len = 1;
    if (ut_flg & EB_UT_FL_ATIME)
      eb_l_ut_len++;
    if (ut_flg & EB_UT_FL_CTIME)
      eb_l_ut_len++;

    eb_l_ut_len = EB_UT_LEN(eb_l_ut_len);
    eb_c_ut_len = EB_UT_LEN(eb_c_ut_len);
  }

  if (EF_L_UT_UX2_SIZE > EB_HEADSIZE) {
    if(z->ext)
      eb_l_ptr = realloc(z->extra, (z->ext + EF_L_UT_UX2_SIZE));
    else
      eb_l_ptr = malloc(EF_L_UT_UX2_SIZE);

    if (eb_l_ptr == NULL)
      return ZE_MEM;

    if(z->cext)
      eb_c_ptr = realloc(z->cextra, (z->cext + EF_C_UT_UX2_SIZE));
    else
      eb_c_ptr = malloc(EF_C_UT_UX2_SIZE);

    if (eb_c_ptr == NULL)
      return ZE_MEM;

    z->extra = eb_l_ptr;
    eb_l_ptr += z->ext;
    z->ext += EF_L_UT_UX2_SIZE;

    if (ut_flg != 0) {
      eb_l_ptr[0]  = 'U';
      eb_l_ptr[1]  = 'T';
      eb_l_ptr[2]  = eb_l_ut_len;       /* length of data part of e.f. */
      eb_l_ptr[3]  = 0;
      eb_l_ptr[4]  = ut_flg;
      eb_pt = eb_l_ptr + 5;
      if (ut_flg & EB_UT_FL_MTIME) {
        *eb_pt++ = (char)(z_utim->mtime);
        *eb_pt++ = (char)(z_utim->mtime >> 8);
        *eb_pt++ = (char)(z_utim->mtime >> 16);
        *eb_pt++ = (char)(z_utim->mtime >> 24);
      }
      if (ut_flg & EB_UT_FL_ATIME) {
        *eb_pt++ = (char)(z_utim->atime);
        *eb_pt++ = (char)(z_utim->atime >> 8);
        *eb_pt++ = (char)(z_utim->atime >> 16);
        *eb_pt++ = (char)(z_utim->atime >> 24);
      }
      if (ut_flg & EB_UT_FL_CTIME) {
        *eb_pt++ = (char)(z_utim->ctime);
        *eb_pt++ = (char)(z_utim->ctime >> 8);
        *eb_pt++ = (char)(z_utim->ctime >> 16);
        *eb_pt++ = (char)(z_utim->ctime >> 24);
      }
    }
#ifdef UNIX
    else {
      eb_pt = eb_l_ptr;
    }
    *eb_pt++ = 'U';
    *eb_pt++ = 'x';
    *eb_pt++ = EB_UX2_MINLEN;            /* length of data part of local e.f. */
    *eb_pt++  = 0;
    *eb_pt++ = (char)(s.st_uid);
    *eb_pt++ = (char)(s.st_uid >> 8);
    *eb_pt++ = (char)(s.st_gid);
    *eb_pt++ = (char)(s.st_gid >> 8);
#endif /* UNIX */

    z->cextra = eb_c_ptr;
    eb_c_ptr += z->cext;
    z->cext += EF_C_UT_UX2_SIZE;

    if (ut_flg != 0) {
      memcpy(eb_c_ptr, eb_l_ptr, EB_C_UT_SIZE);
      eb_c_ptr[EB_LEN] = eb_c_ut_len;
    }
#ifdef UNIX
    memcpy(eb_c_ptr+EB_C_UT_SIZE, eb_l_ptr+EB_L_UT_SIZE, EB_C_UX2_SIZE);
    eb_c_ptr[EB_LEN+EB_C_UT_SIZE] = 0;
#endif /* UNIX */
  }

  return ZE_OK;
}

#endif /* USE_EF_UT_TIME */


int set_extra_field (struct zlist *z, iztimes *z_utim )
{
    int rv = 0;
    int last_rv = 0;
    char flag = 0;

    if ((qlflag & 3) != 1)
    {
        qdosextra       *lq, *cq;
        if ((lq = (qdosextra *) calloc(sizeof(qdosextra), 1)) == NULL)
            return ZE_MEM;
        if ((cq = (qdosextra *) calloc(sizeof(qdosextra), 1)) == NULL)
            return ZE_MEM;

        rv = qlstat(z->name, &(lq->header), &flag);

        if (rv == 0 || (rv == 1 && (qlflag & 2)))
        {
            lq->shortid = rev_short((short) SHORTID);
            lq->len.lo  = (unsigned char)(EXTRALEN & 0xff);
            lq->len.hi  = (unsigned char)(EXTRALEN >> 8);
            strcpy(lq->longid, LONGID);

            memcpy(cq, lq, sizeof(qdosextra));

            z->ext      =   sizeof(qdosextra);
            z->cext     =   sizeof(qdosextra);
            z->extra    =   (void *) lq;
            z->cextra   =   (void *) cq;
            fprintf (stderr, " %c",
                     lq->header.d_datalen ? '*' : '#');
        }
        else if (rv == -1)
        {
            fprintf(stderr,
                    "%s: warning: cannot stat %s, no file header added\n",
                    "zip", z->name);
        }
        if(flag)
        {
            fputs (" !", stderr);
        }
     }
    last_rv = (rv == -1 ? ZE_OPEN : ZE_OK);

#ifdef USE_EF_UT_TIME
# ifdef QDOS
#   define IZ_UT_FLAGS EB_UT_FL_MTIME
# endif
# ifdef UNIX
#   define IZ_UT_FLAGS (EB_UT_FL_MTIME | EB_UT_FL_ATIME)
# endif
# ifndef IZ_UT_FLAGS
#   define IZ_UT_FLAGS EB_UT_FL_MTIME
# endif

    rv = GetExtraTime(z, z_utim, IZ_UT_FLAGS);
    if (rv != ZE_OK)
        last_rv = rv;
#endif /* USE_EF_UT_TIME */

    return last_rv;
}
