
/*
 * Author:
 *    Guido Draheim <guidod@gmx.de>
 *
 * Copyright (c) Guido Draheim, use under copyleft (LGPL,MPL)
 */

#include <zzip/lib.h> /* exported... */
#include <zzip/file.h>
#include <errno.h>
#include <stddef.h> /*offsetof */
#include <stdlib.h>
#include <string.h>

#ifdef ZZIP_HAVE_SYS_STAT_H
#include <sys/stat.h>
#else
#include <stdio.h>
#endif

#include <zzip/zzip32.h>
#include <zzip/__dirent.h>
#include <zzip/__param.h>

#ifndef offsetof
#pragma warning had to DEFINE offsetof as it was not in stddef.h
#define offsetof(T, M) ((unsigned) (&((T*) 0)->M))
#endif

#ifdef ZZIP_HAVE_SYS_STAT_H

/* MSVC does have IFbitmask but not the corresponding IStests */
#if ! defined S_ISDIR && defined S_IFDIR
#define S_ISDIR(_X_) ((_X_) &S_IFDIR)
#endif
#if ! defined S_ISREG && defined S_IFREG
#define S_ISREG(_X_) ((_X_) &S_IFREG)
#endif
#endif

/** rewind.
 *
 * This function is the equivalent of a => rewinddir(2) for a realdir or
 * the zipfile in place of a directory. The ZZIP_DIR handle returned from
 * => zzip_opendir has a flag saying realdir or zipfile. As for a zipfile,
 * the filenames will include the filesubpath, so take care.
 */
void
zzip_rewinddir(ZZIP_DIR* dir)
{
    if (! dir)
        return;

    if (USE_DIRENT && dir->realdir) {
        _zzip_rewinddir(dir->realdir);
        return;
    }

    if (dir->hdr0)
        dir->hdr = dir->hdr0;
    else
        dir->hdr = 0;
}

#if ! USE_DIRENT
#define real_readdir(_X_) 1
#else
static int
real_readdir(ZZIP_DIR* dir)
{
    struct stat    st = {0};
    char           filename[PATH_MAX];
    struct dirent* dirent = _zzip_readdir(dir->realdir);

    if (! dirent)
        return 0;

    dir->dirent.d_name = dirent->d_name;
    strcpy(filename, dir->realname);
    strcat(filename, "/");
    strcat(filename, dirent->d_name);

    if (stat(filename, &st) == -1)
        return -1;

    dir->dirent.d_csize = dir->dirent.st_size = st.st_size;

    if (st.st_mode) {
        if (! S_ISREG(st.st_mode)) {
            dir->dirent.d_compr = st.st_mode;
            dir->dirent.d_compr |= 0x80000000;
            /* makes it effectively negative,
             * but can still be fed to S_ISXXX(x) */
        }
        else {
            dir->dirent.d_compr = 0; /* stored */
        }
    }
    else {
        dir->dirent.d_compr = 0; /* stored */
    }

    return 1;
}
#endif

/** read dir.
 *
 * This function is the equivalent of a => readdir(2) for a realdir
 * or a zipfile referenced by the ZZIP_DIR returned from => zzip_opendir.
 *
 * The ZZIP_DIR handle (as returned by => zzip_opendir) contains a few more
 * entries than being copied into the ZZIP_DIRENT. The only valid fields in
 * a ZZIP_DIRENT are d_name (the file name), d_compr (compression), d_csize
 * (compressed size), st_size (uncompressed size).
 */
ZZIP_DIRENT*
zzip_readdir(ZZIP_DIR* dir)
{
    if (! dir) {
        errno = EBADF;
        return 0;
    }

    if (USE_DIRENT && dir->realdir) {
        if (! real_readdir(dir))
            return 0;
    }
    else {
        if (! dir->hdr)
            return 0;

        dir->dirent.d_name  = dir->hdr->d_name;
        dir->dirent.d_compr = dir->hdr->d_compr;

        dir->dirent.d_csize = dir->hdr->d_csize;
        dir->dirent.st_size = dir->hdr->d_usize;

        if (! dir->hdr->d_reclen)
            dir->hdr = 0;
        else
            dir->hdr = (struct zzip_dir_hdr*) ((char*) dir->hdr + dir->hdr->d_reclen);
    }
    return &dir->dirent;
}

/** => zzip_rewinddir
 * This function is the equivalent of => telldir(2) for a realdir or zipfile.
 */
zzip_off_t
zzip_telldir(ZZIP_DIR* dir)
{
    if (! dir) {
        errno = EBADF;
        return -1;
    }

    if (USE_DIRENT && dir->realdir) {
        return _zzip_telldir(dir->realdir);
    }
    else {
        return ((zzip_off_t) ((char*) dir->hdr - (char*) dir->hdr0));
    }
}

/** => zzip_rewinddir
 * This function is the equivalent of => seekdir(2) for a realdir or zipfile.
 */
void
zzip_seekdir(ZZIP_DIR* dir, zzip_off_t offset)
{
    if (! dir)
        return;

    if (USE_DIRENT && dir->realdir) {
        _zzip_seekdir(dir->realdir, offset);
    }
    else {
        dir->hdr = (struct zzip_dir_hdr*) (dir->hdr0 ? (char*) dir->hdr0 + (size_t) offset : 0);
    }
}

#ifndef EOVERFLOW
#define EOVERFLOW EFBIG
#endif

/** => zzip_rewinddir
 * This function is provided for users who can not use any largefile-mode.
 */
long
zzip_telldir32(ZZIP_DIR* dir)
{
    if (sizeof(zzip_off_t) == sizeof(long)) {
        return zzip_telldir(dir);
    }
    else {
        off_t off = zzip_telldir(dir);
        if (off >= 0) {
            register long off32 = off;
            if (off32 == off)
                return off32;
            errno = EOVERFLOW;
        }
        return -1;
    }
}

/** => zzip_rewinddir
 * This function is provided for users who can not use any largefile-mode.
 */
void
zzip_seekdir32(ZZIP_DIR* dir, long offset)
{
    zzip_seekdir(dir, offset);
}

#if defined ZZIP_LARGEFILE_RENAME && defined EOVERFLOW && defined PIC
/* DLL compatibility layer - so that 32bit code can link with a 64on32 too */

#undef zzip_seekdir /* zzip_seekdir64 */
#undef zzip_telldir /* zzip_telldir64 */

long
zzip_telldir(ZZIP_DIR* dir)
{
    return zzip_telldir32(dir);
}
void
zzip_seekdir(ZZIP_DIR* dir, long offset)
{
    zzip_seekdir32(dir, offset);
}
#endif

/** start usage.
 *
 * This function is the equivalent of => opendir(3) for a realdir or zipfile.
 *
 * This function has some magic - if the given argument-path
 * is a directory, it will wrap a real => opendir(3) into the ZZIP_DIR
 * structure. Otherwise it will divert to => zzip_dir_open which
 * can also attach a ".zip" extension if needed to find the archive.
 *
 * the error-code is mapped to => errno(3).
 */
ZZIP_DIR*
zzip_opendir(zzip_char_t* filename)
{
    return zzip_opendir_ext_io(filename, 0, 0, 0);
}

/** => zzip_opendir
 * This function uses explicit ext and io instead of the internal
 * defaults, setting them to zero is equivalent to => zzip_opendir
 */
ZZIP_DIR*
zzip_opendir_ext_io(zzip_char_t* filename, int o_modes, zzip_strings_t* ext, zzip_plugin_io_t io)
{
    zzip_error_t e;
    ZZIP_DIR*    dir;

#ifdef ZZIP_HAVE_SYS_STAT_H
    struct stat st;
#endif

    if (o_modes & (ZZIP_PREFERZIP | ZZIP_ONLYZIP))
        goto try_zzip;
try_real:

#ifdef ZZIP_HAVE_SYS_STAT_H
    if (stat(filename, &st) >= 0 && S_ISDIR(st.st_mode)) {
        if (USE_DIRENT) {
            _zzip_DIR* realdir = _zzip_opendir(filename);

            if (realdir) {
                if (! (dir = (ZZIP_DIR*) calloc(1, sizeof(*dir)))) {
                    _zzip_closedir(realdir);
                    return 0;
                }
                else {
                    dir->realdir  = realdir;
                    dir->realname = strdup(filename);
                    return dir;
                }
            }
        }
        return 0;
    }
#endif /* HAVE_SYS_STAT_H */

try_zzip:
    dir = zzip_dir_open_ext_io(filename, &e, ext, io);
    if (! dir && (o_modes & ZZIP_PREFERZIP))
        goto try_real;
    if (e)
        errno = zzip_errno(e);
    return dir;
}

/** stop usage.
 *
 * This function is the equivalent of => closedir(3) for a realdir or zipfile.
 *
 * This function is magic - if the given arg-ZZIP_DIR
 * is a real directory, it will call the real => closedir(3) and then
 * free the wrapping ZZIP_DIR structure. Otherwise it will divert
 * to => zzip_dir_close which will free the ZZIP_DIR structure.
 */
int
zzip_closedir(ZZIP_DIR* dir)
{
    if (! dir) {
        errno = EBADF;
        return -1;
    }

    if (USE_DIRENT && dir->realdir) {
        _zzip_closedir(dir->realdir);
        free(dir->realname);
        free(dir);
        return 0;
    }
    else {
        zzip_dir_close(dir);
        return 0;
    }
}
