/*---------------------------------------------------------------------------

  file_io.c

  This file contains routines for doing direct input/output, file-related
  sorts of things.

  ---------------------------------------------------------------------------*/


#include "unzip.h"
#ifdef  MSWIN
#include <windows.h>
#include "wizunzip.h"
#include "replace.h"
#endif


/************************************/
/*  File_IO Local Prototypes, etc.  */
/************************************/

static int WriteBuffer __((int fd, unsigned char *buf, int len));
static int dos2unix __((unsigned char *buf, int len));

int CR_flag = 0;        /* when last char of buffer == CR (for dos2unix()) */





/*******************************/
/*  Function open_input_file() */
/*******************************/

int open_input_file()
{                               /* return non-0 if open failed */
    /*
     *  open the zipfile for reading and in BINARY mode to prevent cr/lf
     *  translation, which would corrupt the bitstreams
     */

#ifndef UNIX
    zipfd = open(zipfn, O_RDONLY | O_BINARY);
#else
    zipfd = open(zipfn, O_RDONLY);
#endif
    if (zipfd < 1) {
        fprintf(stderr, "error:  can't open zipfile [ %s ]\n", zipfn);
        return (1);
    }
    return 0;
}





/************************/
/*  Function readbuf()  */
/************************/

int readbuf(buf, size)
char *buf;
register unsigned size;
{                               /* return number of bytes read into buf */
    register int count;
    int n;

    n = size;
    while (size) {
        if (incnt == 0) {
            if ((incnt = read(zipfd, inbuf, INBUFSIZ)) <= 0)
                return (n-size);
            /* buffer ALWAYS starts on a block boundary:  */
            cur_zipfile_bufstart += INBUFSIZ;
            inptr = inbuf;
        }
        count = min(size, incnt);
        memcpy(buf, inptr, count);
        buf += count;
        inptr += count;
        incnt -= count;
        size -= count;
    }
    return (n);
}





/**********************************/
/*  Function create_output_file() */
/**********************************/

int create_output_file()
{                               /* return non-0 if creat failed */
    /*
     * Create the output file with default permissions.
     */
    extern int do_all;
    char answerbuf[10];
    UWORD holder;
      int already_exists;



    CR_flag = 0;                /* Hack to get CR at end of buffer working. */

#ifndef VMS  /* creates higher version number instead of overwriting (will
              * have to modify for VMS-style names with specific version
              * numbers:  check for failure on creat()??? */
    /*
     * check if the file exists, unless do_all
     */
      already_exists = open(filename, 0);
      if (already_exists >= 0)
              close(already_exists);          /* before you forget */
    if (!do_all) {
        if (already_exists >= 0) {     /* first close it, before you forget! */

            /* ask the user before blowing it away */
#ifdef MSWIN
		{
    	FARPROC lpProcReplace;
		int ReplaceDlgRetVal;	/* replace dialog return value		*/

    		ShowCursor(FALSE);      /* turn off cursor      */
    		SetCursor(hSaveCursor); /* restore the cursor   */
		    lpProcReplace = MakeProcInstance(Replace, hInst);
		    ReplaceDlgRetVal = DialogBoxParam(hInst, "Replace", hMainWnd, 
											lpProcReplace, (DWORD)(LPSTR)filename);
		    FreeProcInstance(lpProcReplace);
			switch (ReplaceDlgRetVal) {
			case IDM_REPLACE_YES:
				break;
			case IDM_REPLACE_ALL:
                do_all = 1;
				break;
			case IDM_REPLACE_NO:
                while (ReadByte(&holder));
    			hSaveCursor = SetCursor(hHourGlass);
    			ShowCursor(TRUE);       /* show it      */
                return 1;       /* it's done! */
			}
    		hSaveCursor = SetCursor(hHourGlass);
    		ShowCursor(TRUE);       /* show it      */
		}
#else
            fprintf(stderr, "replace %s, y-yes, n-no, a-all: ", filename);
#ifdef AMIGA
            fflush(stderr);
#endif
            fgets(answerbuf, 9, stdin);

            switch (answerbuf[0]) {
            case 'y':
            case 'Y':
                break;
            case 'a':
            case 'A':
                do_all = 1;
                break;
            case 'n':
            case 'N':
            default:
                while (ReadByte(&holder));
                return 1;       /* it's done! */
            }
#endif
        }
    }

#if defined(UNIX) && !defined(AMIGA)
    {
      int mask;
        if (already_exists >= 0 && unlink(filename) < 0)
              fprintf(stderr, "Can't unlink %s\n", filename);         /* So we own it */
      mask = umask(0);
      outfd = creat(filename, 0777 & f_attr);                  /* Unix */
      umask(mask);
    }
#else /* !UNIX || AMIGA */
     /* Some Unix archives yield impossible f_attr's !!!
        Also, creating a file read-only makes absolutely no sense here
        because we immediately close it and then open it using open()
        and O_RDWR which could not work then. */
    outfd = creat(filename, (S_IWRITE | S_IREAD) /* & f_attr */); /* PCs */
#endif /* ?(UNIX && !AMIGA) */
#else /* VMS */
    outfd = creat(filename, 0, "rfm=stmlf", "rat=cr");         /* VMS */
#endif /* ?VMS */

    if (outfd < 1) {
        fprintf(stderr, "Can't create output file:  %s\n", filename);
        return 1;
    }
    /*
     * close the newly created file and reopen it in BINARY mode to
     * disable all CR/LF translations
     */
#ifndef UNIX
#ifdef THINK_C
    /*
     * THINKC's stdio routines have the horrible habit of
     * making any file you open look like generic files
     * this code tells the OS that it's a text file
     */
    if (aflag) {
        fileParam pb;
        OSErr err;

        CtoPstr(filename);
        pb.ioNamePtr = filename;
        pb.ioVRefNum = 0;
        pb.ioFVersNum = 0;
        pb.ioFDirIndex = 0;
        err = PBGetFInfo(&pb,0);
        if (err == noErr) {
          pb.ioFlFndrInfo.fdCreator = 0x3F3F3F3F;
            pb.ioFlFndrInfo.fdType = 'TEXT';
            err = PBSetFInfo(&pb, 0);
        }
        PtoCstr(filename);
    }
#endif                          /* THINK_C */
    if (!aflag) {
        close(outfd);
        outfd = open(filename, O_RDWR | O_BINARY);
    }
#endif                          /* !UNIX */
    if (outfd < 1) {
        fprintf(stderr, "Can't open output: %s\n", filename);
        return 1;
    }
    return 0;
}





/*****************************/
/*  Function FillBitBuffer() */
/*****************************/

int FillBitBuffer(bits)
register int bits;
{
    /*
     * Get the bits that are left and read the next UWORD.  This
     * function is only used by the READBIT macro (which is used
     * by all of the uncompression routines).
     */
    register int result = bitbuf;
    UWORD temp;
    int sbits = bits_left;


    bits -= bits_left;

    /* read next UWORD of input */
    bits_left = ReadByte(&bitbuf);
    bits_left += ReadByte(&temp);

    bitbuf |= (temp << 8);
    if (bits_left == 0)
        zipeof = 1;

    /* get the remaining bits */
    result = result | (int) ((bitbuf & mask_bits[bits]) << sbits);
    bitbuf >>= bits;
    bits_left -= bits;
    return result;
}





/************************/
/*  Function ReadByte() */
/************************/

int ReadByte(x)
UWORD *x;
{
    /*
     * read a byte; return 8 if byte available, 0 if not
     */


    if (csize-- <= 0)
        return 0;

    if (incnt == 0) {
        if ((incnt = read(zipfd, inbuf, INBUFSIZ)) <= 0)
            return 0;
        /* buffer ALWAYS starts on a block boundary:  */
        cur_zipfile_bufstart += INBUFSIZ;
        inptr = inbuf;
    }
    *x = *inptr++;
    --incnt;
    return 8;
}



#ifdef FLUSH_AND_WRITE
/***************************/
/*  Function FlushOutput() */
/***************************/

int FlushOutput()
{                               /* return PK-type error code */
    /* flush contents of output buffer */
    /*
     * This combined version doesn't work, and I sure can't see why not...
     * probably something stupid, but how much can you screw up in 6 lines???
     * [optimization problem??]
     */
    int len;


    if (outcnt) {
        UpdateCRC(outbuf, outcnt);

        if (!tflag) {
            if (aflag)
                len = dos2unix(outbuf, outcnt);
#ifdef MSWIN
            if (_lwrite(outfd, outout, len) != len) {
#else
            if (write(outfd, outout, len) != len) {
#endif
                fprintf(stderr, "Fatal write error.\n");
                return (50);    /* 50:  disk full */
            }
        }
        outpos += outcnt;
        outcnt = 0;
        outptr = outbuf;
    }
    return (0);                 /* 0:  no error */
}

#else                           /* separate flush and write routines */
/***************************/
/*  Function FlushOutput() */
/***************************/

int FlushOutput()
{                               /* return PK-type error code */
    /* flush contents of output buffer */
    if (outcnt) {
        UpdateCRC(outbuf, outcnt);

        if (!tflag && WriteBuffer(outfd, outbuf, outcnt))
            return (50);        /* 50:  disk full */

        outpos += outcnt;
        outcnt = 0;
        outptr = outbuf;
    }
    return (0);                 /* 0:  no error */
}

/***************************/
/*  Function WriteBuffer() */
/***************************/

static int WriteBuffer(fd, buf, len)    /* return 0 if successful, 1 if not */
int fd;
unsigned char *buf;
int len;
{
    if (aflag)
        len = dos2unix(buf, len);
#ifdef MSWIN
	if (cflag)	/* if writing to console vs. actual file, write to Msg Window */
	{
		WriteBufferToMsgWin(outout, len, FALSE);
		return 0;
	}
    if (_lwrite(fd, outout, len) != len) {
#else
    if (write(fd, outout, len) != len) {
#endif
#ifdef DOS_OS2
        if (!cflag) {           /* ^Z treated as EOF, removed with -c */
#endif
            fprintf(stderr, "Fatal write error.\n");
            return (1);         /* FAILED */
#ifdef DOS_OS2
        }
#endif
    }
    return (0);
}

#endif




/************************/
/*  Function dos2unix() */
/************************/

static int dos2unix(buf, len)
unsigned char *buf;
int len;
{
    int new_len;
    int i;
#ifdef MSWIN
    unsigned char _far *walker;
#else
    unsigned char *walker;
#endif

    new_len = len;
    walker = outout;
#ifdef MACOS
    /*
     * Mac wants to strip LFs instead CRs from CRLF pairs
     */
    if (CR_flag && *buf == LF) {
        buf++;
        new_len--;
        len--;
        CR_flag = buf[len] == CR;
    }
    else
        CR_flag = buf[len - 1] == CR;
    for (i = 0; i < len; i += 1) {
        *walker++ = ascii_to_native(*buf);
        if (*buf == LF) walker[-1] = CR;
        if (*buf++ == CR && *buf == LF) {
            new_len--;
            buf++;
            i++;
        }
    }
#else
    if (CR_flag && *buf != LF)
        *walker++ = ascii_to_native(CR);
    CR_flag = buf[len - 1] == CR;
    for (i = 0; i < len; i += 1) {
        *walker++ = ascii_to_native(*buf);
        if (*buf++ == CR && *buf == LF) {
            new_len--;
            walker[-1] = ascii_to_native(*buf++);
            i++;
        }
    }
    /*
     * If the last character is a CR, then "ignore it" for now...
     */
    if (walker[-1] == ascii_to_native(CR))
        new_len--;
#endif
    return new_len;
}





#ifdef DOS_OS2

/***************************************/
/*  Function set_file_time_and_close() */
/***************************************/

void set_file_time_and_close()
 /*
  * MS-DOS AND OS/2 VERSION (Mac, Unix/VMS versions are below)
  *
  * Set the output file date/time stamp according to information from the
  * zipfile directory record for this member, then close the file.  This
  * is optional and can be deleted if your compiler does not easily support
  * setftime().
  */
{
/*---------------------------------------------------------------------------
    Allocate local variables needed by OS/2 and Turbo C.  [OK, OK, so it's
    a bogus comment...but this routine was getting way too cluttered and
    needed some visual separators.  Bleah.]
  ---------------------------------------------------------------------------*/

#ifdef OS2              /* (assuming only MSC or MSC-compatible compilers
                         * for this part) */

    union {
        FDATE fd;               /* system file date record */
        UWORD zdate;            /* date word */
    } ud;

    union {
        FTIME ft;               /* system file time record */
        UWORD ztime;            /* time word */
    } ut;

    FILESTATUS fs;

#else                           /* !OS2 */
#ifdef __TURBOC__

    union {
        struct ftime ft;        /* system file time record */
        struct {
            UWORD ztime;        /* date and time words */
            UWORD zdate;        /* .. same format as in .ZIP file */
        } zt;
    } td;

#endif                          /* __TURBOC__ */
#endif                          /* !OS2 */

/*---------------------------------------------------------------------------
     Do not attempt to set the time stamp on standard output.
  ---------------------------------------------------------------------------*/

    if (cflag) {
        close(outfd);
        return;
    }

/*---------------------------------------------------------------------------
    Copy and/or convert time and date variables, if necessary; then set the
    file time/date.
  ---------------------------------------------------------------------------*/

#ifdef OS2

    DosQFileInfo(outfd, 1, &fs, sizeof(fs));
    ud.zdate = lrec.last_mod_file_date;
    fs.fdateLastWrite = ud.fd;
    ut.ztime = lrec.last_mod_file_time;
    fs.ftimeLastWrite = ut.ft;
    DosSetFileInfo(outfd, 1, (PBYTE) &fs, sizeof(fs));

#else                           /* !OS2 */
#ifdef __TURBOC__

    td.zt.ztime = lrec.last_mod_file_time;
    td.zt.zdate = lrec.last_mod_file_date;
    setftime(outfd, &td.ft);

#else                           /* !__TURBOC__:  MSC MS-DOS */

    _dos_setftime(outfd, lrec.last_mod_file_date, lrec.last_mod_file_time);

#endif                          /* !__TURBOC__ */
#endif                          /* !OS2 */

/*---------------------------------------------------------------------------
    And finally we can close the file...at least everybody agrees on how to
    do *this*.  I think...
  ---------------------------------------------------------------------------*/

    close(outfd);
}





#else                           /* !DOS_OS2 */
#ifdef MACOS                    /* Mac first */

/***************************************/
/*  Function set_file_time_and_close() */
/***************************************/

void set_file_time_and_close()
 /*
  * MAC VERSION
  *
  * First close the output file, then set its date/time stamp according
  * to information from the zipfile directory record for this file.  [So
  * technically this should be called "close_file_and_set_time()", but
  * this way we can use the same prototype for either case, without extra
  * #ifdefs.  So there.]
  */
{
    long m_time;
    DateTimeRec dtr;
    ParamBlockRec pbr;
    OSErr err;

    if (outfd != 1)
    {
        close(outfd);

        /*
         * Macintosh bases all file modification times on the number of seconds
         * elapsed since Jan 1, 1904, 00:00:00.  Therefore, to maintain
         * compatibility with MS-DOS archives, which date from Jan 1, 1980,
         * with NO relation to GMT, the following conversions must be made:
         *      the Year (yr) must be incremented by 1980;
         *      and converted to seconds using the Mac routine Date2Secs(),
         *      almost similar in complexity to the Unix version :-)
         *                                     J. Lee
         */

        dtr.year = (((lrec.last_mod_file_date >> 9) & 0x7f) + 1980); /* dissect date */
        dtr.month = ((lrec.last_mod_file_date >> 5) & 0x0f);
        dtr.day = (lrec.last_mod_file_date & 0x1f);

        dtr.hour = ((lrec.last_mod_file_time >> 11) & 0x1f);      /* dissect time */
        dtr.minute = ((lrec.last_mod_file_time >> 5) & 0x3f);
        dtr.second = ((lrec.last_mod_file_time & 0x1f) * 2);
        Date2Secs(&dtr, &m_time);
        CtoPstr(filename);
        pbr.fileParam.ioNamePtr = filename;
        pbr.fileParam.ioVRefNum = pbr.fileParam.ioFVersNum = pbr.fileParam.ioFDirIndex = 0;
        err = PBGetFInfo(&pbr, 0L);
        pbr.fileParam.ioFlMdDat = pbr.fileParam.ioFlCrDat = m_time;
        if (err == noErr) {
            err = PBSetFInfo(&pbr, 0L);
        }
        if (err != noErr) {
            printf("Error, can't set the time for %s\n",filename);
        }

        /* set read-only perms if needed */
        if (err != noErr && f_attr != 0) {
            err = SetFLock(filename, 0);
        }
        PtoCstr(filename);
    }
}


/* #elif defined(AMIGA) */
#else
#ifdef AMIGA
#include <libraries/dos.h>
void set_file_time_and_close()
{
/* This routine is derived from the unix routine. In AmigaDos, the
 * counting begins 01-Jan-1978
 */
    long m_time;
    int yr, mo, dy, hh, mm, ss, leap, days = 0;
    struct DateStamp myadate;
    if (cflag)                  /* can't set time on stdout */
      return;
    close(outfd);
    yr = (((lrec.last_mod_file_date >> 9) & 0x7f) + 2); /* dissect date */
    mo = ((lrec.last_mod_file_date >> 5) & 0x0f);
    dy = ((lrec.last_mod_file_date & 0x1f) - 1);
    hh = ((lrec.last_mod_file_time >> 11) & 0x1f);      /* dissect time */
    mm = ((lrec.last_mod_file_time >> 5) & 0x3f);
    ss = ((lrec.last_mod_file_time & 0x1f) * 2);
    /* leap = # of leap years from 1978 up to but not including
       the current year */
    leap = ((yr + 1977) / 4);   /* Leap year base factor */
    /* How many days from 1978 to this year? */
    days = (yr * 365) + (leap - 492);
    switch (mo) {               /* calculate expired days this year */
    case 12:
      days += 30;
    case 11:
      days += 31;
    case 10:
      days += 30;
    case 9:
      days += 31;
    case 8:
      days += 31;
    case 7:
      days += 30;
    case 6:
      days += 31;
    case 5:
      days += 30;
    case 4:
      days += 31;
    case 3:
      days += 28;             /* account for leap years (2000 IS one) */
      if (((yr + 1978) % 4 == 0) && (yr + 1978) != 2100)  /* OK thru 2199 */
          ++days;
    case 2:
      days += 31;
    }
    myadate.ds_Days   =   days+dy-2;
    myadate.ds_Minute =   hh*60+mm;
    myadate.ds_Tick   =   ss*TICKS_PER_SECOND;
    if (!(SetFileDate(filename, &myadate)))
      fprintf(stderr, "Error, can't set the time for %s\n",filename);
}

#else                           /* !MACOS... */
#ifndef MTS                     /* && !MTS (can't do): only one left is UNIX */

/***************************************/
/*  Function set_file_time_and_close() */
/***************************************/

void set_file_time_and_close()
 /*
  * UNIX AND VMS VERSION (MS-DOS & OS/2, Mac versions are above)
  *
  * First close the output file, then set its date/time stamp according
  * to information from the zipfile directory record for this file.  [So
  * technically this should be called "close_file_and_set_time()", but
  * this way we can use the same prototype for either case, without extra
  * #ifdefs.  So there.]
  */
{
    long m_time;
    int yr, mo, dy, hh, mm, ss, leap, days = 0;
#ifdef VMS
    char timbuf[24];
    static char *month[] = {"JAN", "FEB", "MAR", "APR", "MAY", "JUN",
                            "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"};
    struct VMStimbuf {
      char *actime;             /* VMS revision date, ASCII format */
      char *modtime;            /* VMS creation date, ASCII format */
    } ascii_times;
#else /* !VMS */
    struct utimbuf {
      time_t atime;             /* New access time */
      time_t mtime;             /* New modification time */
    } tp;
#ifdef BSD
    static struct timeb tbp;
#else /* !BSD */
#ifdef AMIGA
    extern char *_TZ;
#else /* !AMIGA */
    extern long timezone;
#endif /* ?AMIGA */
#endif /* ?BSD */
#endif /* ?VMS */


    close(outfd);

    if (cflag)                  /* can't set time on stdout */
        return;

    /*
     * These date conversions look a little weird, so I'll explain.
     * UNIX bases all file modification times on the number of seconds
     * elapsed since Jan 1, 1970, 00:00:00 GMT.  Therefore, to maintain
     * compatibility with MS-DOS archives, which date from Jan 1, 1980,
     * with NO relation to GMT, the following conversions must be made:
     *      the Year (yr) must be incremented by 10;
     *      the Date (dy) must be decremented by 1;
     *      and the whole mess must be adjusted by TWO factors:
     *          relationship to GMT (ie.,Pacific Time adds 8 hrs.),
     *          and whether or not it is Daylight Savings Time.
     * Also, the usual conversions must take place to account for leap years,
     * etc.
     *                                     C. Seaman
     */

    yr = ((lrec.last_mod_file_date >> 9) & 0x7f) + 10; /* dissect date */
    mo = (lrec.last_mod_file_date >> 5) & 0x0f;
    dy = (lrec.last_mod_file_date & 0x1f) - 1;

    hh = (lrec.last_mod_file_time >> 11) & 0x1f;      /* dissect time */
    mm = (lrec.last_mod_file_time >> 5) & 0x3f;
    ss = (lrec.last_mod_file_time & 0x1f) * 2;

#ifdef VMS
    sprintf(timbuf, "%02d-%3s-%04d %02d:%02d:%02d.00", dy+1, month[mo-1],
      yr+1970, hh, mm, ss);

    ascii_times.actime = timbuf;
    ascii_times.modtime = timbuf;

    if ((mm = VMSmunch(filename, SET_TIMES, &ascii_times)) != RMS$_NMF)
        fprintf(stderr, "error %d:  can't set the time for %s\n", mm, filename);

#else /* !VMS */
    /* leap = # of leap years from 1970 up to but not including
       the current year */

    leap = ((yr + 1969) / 4);   /* Leap year base factor */

    /* How many days from 1970 to this year? */
    days = (yr * 365) + (leap - 492);

    switch (mo) {               /* calculate expired days this year */
    case 12:
        days += 30;
    case 11:
        days += 31;
    case 10:
        days += 30;
    case 9:
        days += 31;
    case 8:
        days += 31;
    case 7:
        days += 30;
    case 6:
        days += 31;
    case 5:
        days += 30;
    case 4:
        days += 31;
    case 3:
        days += 28;             /* account for leap years (2000 IS one) */
        if (((yr + 1970) % 4 == 0) && (yr + 1970) != 2100)  /* OK thru 2199 */
            ++days;
    case 2:
        days += 31;
    }

    /* convert date & time to seconds relative to 00:00:00, 01/01/1970 */
    m_time = ((days + dy) * 86400) + (hh * 3600) + (mm * 60) + ss;

#ifdef BSD
   ftime(&tbp);
   m_time += tbp.timezone * 60L;
#else /* !BSD */
#ifdef AMIGA
    _TZ = getenv("TZ");
#endif
    tzset();                            /* Set `timezone'. */
    m_time += timezone;                 /* account for timezone differences */
#endif /* ?BSD */

    if (localtime(&m_time)->tm_isdst)
        m_time -= 60L * 60L;            /* Adjust for daylight savings time */

    /* set the time stamp on the file */
    tp.mtime = m_time;                  /* Set modification time */
    tp.atime = m_time;                  /* Set access time */

    if (utime(filename, &tp))
        fprintf(stderr, "error:  can't set the time for %s\n",filename);
#endif /* ?VMS */
}

#endif                          /* ?MTS */
#endif                          /* ?MACOS */
#endif                          /* ?AMIGA */
#endif                          /* ?DOS_OS2 */

