<|start_filename|>src/rzip.c<|end_filename|>

#include <stdlib.h>
#include <time.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/time.h>

#ifdef _WIN32
#include <direct.h>		/* _mkdir */
#include <windows.h>
#endif

#include <Rinternals.h>

#include "miniz.h"
#include "zip.h"

SEXP R_zip_list(SEXP zipfile) {
  const char *czipfile = CHAR(STRING_ELT(zipfile, 0));
  size_t num_files;
  unsigned int i;
  SEXP result = R_NilValue;
  mz_bool status;
  mz_zip_archive zip_archive;

  FILE *fh;
  wchar_t *uzipfile = NULL;
  size_t uzipfile_len = 0;

#ifdef _WIN32
  if (zip__utf8_to_utf16(czipfile, &uzipfile, &uzipfile_len)) {
    if (uzipfile) free(uzipfile);
    error("Cannot convert zip file name to unicode");
  }
  fh = zip_long_wfopen(uzipfile, L"rb");
#else
  fh = fopen(czipfile, "rb");
#endif

  if (fh == NULL) {
    if (uzipfile) free(uzipfile);
    error("Cannot open zip file `%s`");
  }

  fseek(fh, 0, SEEK_END);
  size_t file_size = ftell(fh);
  fseek(fh, 0, SEEK_SET);

  memset(&zip_archive, 0, sizeof(zip_archive));
  status = mz_zip_reader_init_cfile(&zip_archive, fh, file_size, 0);
  if (!status) {
    fclose(fh);
    free(uzipfile);
    error("Cannot open zip file `%s`", czipfile);
  }

  num_files = mz_zip_reader_get_num_files(&zip_archive);
  result = PROTECT(allocVector(VECSXP, 7));
  SET_VECTOR_ELT(result, 0, allocVector(STRSXP, num_files));
  SET_VECTOR_ELT(result, 1, allocVector(REALSXP, num_files));
  SET_VECTOR_ELT(result, 2, allocVector(REALSXP, num_files));
  SET_VECTOR_ELT(result, 3, allocVector(INTSXP, num_files));
  SET_VECTOR_ELT(result, 4, allocVector(INTSXP, num_files));
  SET_VECTOR_ELT(result, 5, allocVector(INTSXP, num_files));
  SET_VECTOR_ELT(result, 6, allocVector(REALSXP, num_files));

  for (i = 0; i < num_files; i++) {
    mz_zip_archive_file_stat file_stat;
    mode_t mode;
    status = mz_zip_reader_file_stat (&zip_archive, i, &file_stat);
    if (!status) goto cleanup;

    SET_STRING_ELT(VECTOR_ELT(result, 0), i, mkChar(file_stat.m_filename));
    REAL(VECTOR_ELT(result, 1))[i] = file_stat.m_comp_size;
    REAL(VECTOR_ELT(result, 2))[i] = file_stat.m_uncomp_size;
    INTEGER(VECTOR_ELT(result, 3))[i] = (int) file_stat.m_time;
    zip_get_permissions(&file_stat, &mode);
    INTEGER(VECTOR_ELT(result, 4))[i] = (int) mode;
    INTEGER(VECTOR_ELT(result, 5))[i] = (int) file_stat.m_crc32;
    REAL(VECTOR_ELT(result, 6))[i] = (double) file_stat.m_local_header_ofs;
  }

  fclose(fh);
  free(uzipfile);
  mz_zip_reader_end(&zip_archive);
  UNPROTECT(1);
  return result;

 cleanup:
  fclose(fh);
  mz_zip_reader_end(&zip_archive);
  error("Cannot list zip entries, corrupt zip file?");
  return result;
}

void R_zip_error_handler(const char *reason, const char *file,
			 int line, int zip_errno, int eno) {
  error("zip error: `%s` in file `%s:%i`", reason, file, line);
}

SEXP R_zip_zip(SEXP zipfile, SEXP keys, SEXP files, SEXP dirs, SEXP mtime,
	       SEXP compression_level, SEXP append) {

  const char *czipfile = CHAR(STRING_ELT(zipfile, 0));
  const char **ckeys = 0, **cfiles = 0;
  int *cdirs = INTEGER(dirs);
  double *cmtimes = REAL(mtime);
  int ccompression_level = INTEGER(compression_level)[0];
  int cappend = LOGICAL(append)[0];
  int i, n = LENGTH(keys);

  /* The reason we allocate n+1 here is that otherwise R_alloc will
     return a NULL pointer for n == 0, and zip_unzip interprets that
     as extracting the whole archive. */

  ckeys  = (const char **) R_alloc(n + 1, sizeof(char*));
  cfiles = (const char **) R_alloc(n + 1, sizeof(char*));
  for (i = 0; i < n; i++) {
    ckeys [i] = CHAR(STRING_ELT(keys,  i));
    cfiles[i] = CHAR(STRING_ELT(files, i));
  }

  zip_set_error_handler(R_zip_error_handler);

  zip_zip(czipfile, n, ckeys, cfiles, cdirs, cmtimes, ccompression_level,
	  cappend);

  return R_NilValue;
}

SEXP R_zip_unzip(SEXP zipfile, SEXP files, SEXP overwrite, SEXP junkpaths,
		 SEXP exdir) {

  const char *czipfile = CHAR(STRING_ELT(zipfile, 0));
  int coverwrite = LOGICAL(overwrite)[0];
  int cjunkpaths = LOGICAL(junkpaths)[0];
  const char *cexdir = CHAR(STRING_ELT(exdir, 0));
  int allfiles = isNull(files);
  int i, n = allfiles ? 0 : LENGTH(files);
  const char **cfiles = 0;

  if (!isNull(files)) {
    /* The reason we allocate n+1 here is that otherwise R_alloc will
       return a NULL pointer for n == 0, and zip_unzip interprets that
       as extracting the whole archive. */
    cfiles = (const char**) R_alloc(n + 1, sizeof(char*));
    for (i = 0; i < n; i++) cfiles[i] = CHAR(STRING_ELT(files, i));
  }

  zip_set_error_handler(R_zip_error_handler);
  zip_unzip(czipfile, cfiles, n, coverwrite, cjunkpaths, cexdir);

  return R_NilValue;
}


#ifdef __APPLE__
#include <fcntl.h>
#include <unistd.h>
#endif


#ifdef _WIN32

int zip__utf8_to_utf16(const char* s, wchar_t** buffer,
                       size_t *buffer_size);

#endif

SEXP R_make_big_file(SEXP filename, SEXP mb) {

#ifdef _WIN32

  const char *cfilename = CHAR(STRING_ELT(filename, 0));
  LARGE_INTEGER li;

  wchar_t *wfilename = NULL;
  size_t wfilename_size = 0;

  if (zip__utf8_to_utf16(cfilename, &wfilename, &wfilename_size)) {
    error("utf8 -> utf16 conversion");
  }

  HANDLE h = CreateFileW(
    wfilename,
    GENERIC_WRITE,
    FILE_SHARE_DELETE,
    NULL,
    CREATE_NEW,
    FILE_ATTRIBUTE_NORMAL,
    NULL);
  if (h == INVALID_HANDLE_VALUE) {
    if (wfilename) free(wfilename);
    error("Cannot create big file");
  }

  li.QuadPart = INTEGER(mb)[0] * 1024.0 * 1024.0;
  li.LowPart = SetFilePointer(h, li.LowPart, &li.HighPart, FILE_BEGIN);

  if (0xffffffff == li.LowPart && GetLastError() != NO_ERROR) {
    CloseHandle(h);
    if (wfilename) free(wfilename);
    error("Cannot create big file");
  }

  if (!SetEndOfFile(h)) {
    CloseHandle(h);
    if (wfilename) free(wfilename);
    error("Cannot create big file");
  }

  if (wfilename) free(wfilename);
  CloseHandle(h);

#endif

#ifdef __APPLE__

  const char *cfilename = CHAR(STRING_ELT(filename, 0));
  int fd = open(cfilename, O_WRONLY | O_CREAT);
  double sz = INTEGER(mb)[0] * 1024.0 * 1024.0;
  fstore_t store = { F_ALLOCATECONTIG, F_PEOFPOSMODE, 0, (off_t) sz };
  // Try to get a continous chunk of disk space
  int ret = fcntl(fd, F_PREALLOCATE, &store);
  if (-1 == ret) {
    // OK, perhaps we are too fragmented, allocate non-continuous
    store.fst_flags = F_ALLOCATEALL;
    ret = fcntl(fd, F_PREALLOCATE, &store);
    if (-1 == ret) error("Cannot create big file");
  }

  if (ftruncate(fd, (off_t) sz)) {
    close(fd);
    error("Cannot create big file");
  }

  close(fd);

#endif

#ifndef _WIN32
#ifndef __APPLE__
  error("cannot create big file (only implemented for windows and macos");
#endif
#endif

  return R_NilValue;
}


<|start_filename|>src/winutils.c<|end_filename|>

#include <windows.h>
#include <direct.h>
#include <sys/stat.h>
#include <stdio.h>
#include "zip.h"

/* -------------------------------------------------------------- */

int zip__utf8_to_utf16(const char* s, wchar_t** buffer,
                       size_t *buffer_size) {
  int ws_len, r;

  ws_len = MultiByteToWideChar(
    /* CodePage =       */ CP_UTF8,
    /* dwFlags =        */ 0,
    /* lpMultiByteStr = */ s,
    /* cbMultiByte =    */ -1,
    /* lpWideCharStr =  */ NULL,
    /* cchWideChar =    */ 0);

  if (ws_len <= 0) { return GetLastError(); }

  if (*buffer == NULL) {
    /* Let's allocated something bigger, so no need to grow much */
    *buffer_size = ws_len > 255 ? ws_len : 255;
    *buffer = (wchar_t*) calloc(*buffer_size, sizeof(wchar_t));
  } else if (ws_len > *buffer_size) {
    *buffer_size = ws_len;
    *buffer = (wchar_t*) realloc(*buffer, ws_len * sizeof(wchar_t));
  }
  if (*buffer == NULL) { return ERROR_OUTOFMEMORY; }

  r = MultiByteToWideChar(
    /* CodePage =       */ CP_UTF8,
    /* dwFlags =        */ 0,
    /* lpMultiByteStr = */ s,
    /* cbMultiBytes =   */ -1,
    /* lpWideCharStr =  */ *buffer,
    /* cchWideChar =    */ ws_len);

  if (r != ws_len) { return GetLastError(); }

  return 0;
}

int zip__utf16_to_utf8(const wchar_t *ws, char** buffer, size_t *buffer_size) {

  int slen, r;

  slen = WideCharToMultiByte(CP_UTF8, 0, ws, -1, NULL, 0, NULL, NULL);

  if (slen <= 0) { return GetLastError(); }

  if (*buffer == NULL) {
    /* Let's allocated something bigger, so no need to grow much */
    *buffer_size = slen > 255 ? slen : 255;
    *buffer = (char*) calloc(*buffer_size, sizeof(char));
  } else if (slen > *buffer_size) {
    *buffer_size = slen;
    *buffer = (char*) realloc(*buffer, slen * sizeof(char));
  }
  if (*buffer == NULL) { return ERROR_OUTOFMEMORY; }

  r = WideCharToMultiByte(CP_UTF8, 0, ws, -1, *buffer, slen, NULL, NULL);

  if (r != slen) { return GetLastError(); }

  return 0;
}

FILE *zip_open_utf8(const char *filename, const wchar_t *mode,
                    wchar_t **buffer, size_t *buffer_size) {
  int ret = zip__utf8_to_utf16(filename, buffer, buffer_size);
  if (ret) return NULL;

  FILE *fh = _wfopen(*buffer, mode);
  return fh;
}


int zip_str_file_path(const char *cexdir, const char *key,
                      zip_char_t **buffer, size_t *buffer_size,
                      int cjunkpaths) {

  int need_size, len1, len2, i;
  wchar_t *newbuffer;

  if (cjunkpaths) {
    char *base = strrchr(key, '/');
    if (base) key = base;
  }

  len1 = MultiByteToWideChar(
    /* CodePage =       */ CP_UTF8,
    /* dwFlags =        */ 0,
    /* lpMultiByteStr = */ cexdir,
    /* cbMultiByte =    */ -1,
    /* lpWideCharStr =  */ NULL,
    /* cchWideChar =    */ 0
  );
  len2 = MultiByteToWideChar(
    /* CodePage =       */ CP_UTF8,
    /* dwFlags =        */ 0,
    /* lpMultiByteStr = */ key,
    /* cbMultiByte =    */ -1,
    /* lpWideCharStr =  */ NULL,
    /* cchWideChar =    */ 0
  );
  /* No need to +1 for '\\' because both have a terminating zero. */
  int offset = cjunkpaths ? 0 : 4;
  need_size = len1 + len2 + offset;

  if (*buffer == NULL) {
    *buffer_size = need_size > 256 ? need_size : 256;
    *buffer = calloc(*buffer_size, sizeof(zip_char_t));
    if (!*buffer) return 1;

  } else if (*buffer_size < need_size) {
    newbuffer = realloc((void*) *buffer, need_size * sizeof(zip_char_t));
    if (!newbuffer) return 1;

    *buffer = newbuffer;
    *buffer_size = need_size;
  }

  /* work around path length limitations */
  if (!cjunkpaths) {
    (*buffer)[0] = L'\\';
    (*buffer)[1] = L'\\';
    (*buffer)[2] = L'?';
    (*buffer)[3] = L'\\';
  }

  len1 = MultiByteToWideChar(
    /* CodePage =       */ CP_UTF8,
    /* dwFlags =        */ 0,
    /* lpMultiByteStr = */ cexdir,
    /* cbMultiByte =    */ -1,
    /* lpWideCharStr =  */ (*buffer) + offset,
    /* cchWideChar =    */ len1
  );
  len2 = MultiByteToWideChar(
    /* CodePage =       */ CP_UTF8,
    /* dwFlags =        */ 0,
    /* lpMultiByteStr = */ key,
    /* cbMultiByte =    */ -1,
    /* lpWideCharStr =  */ (*buffer) + len1 + offset,
    /* cchWideChar =    */ len2
  );

  (*buffer)[offset + len1 - 1] = L'\\';

  for (i = offset; i < need_size; i++) {
    if ((*buffer)[i] == L'/') (*buffer)[i] = L'\\';
  }

  return 0;
}

int zip_mkdirp(zip_char_t *path, int complete)  {
  wchar_t *p = path;
  BOOL status;
  DWORD err = 0;

  /* Skip \\?\ if any */
  if (*p == L'\\' && *(p+1) == L'\\' && *(p+2) == L'?' &&
      *(p+3) == L'\\') {
    p += 4;
  }

  /* Skip host part of an UNC path */
  if ((*p == L'\\' && *(p+1) == L'\\') ||
      (*p == L'/'  && *(p+1) == L'/' )) {
    p += 2;
    while (*p && *p != L'/' && *p != L'\\') p++;
  }

  /* Skip drive letter if any. */
  if (*(p+1) == L':' && (*(p+2) == L'/' || (*(p+2)) == L'\\')) {
    p += 3;
  }

  /* Iterate the string */
  for (; *p; p++) {
    if (*p == L'/' || *p == L'\\') {
      *p = L'\0';
      status = CreateDirectoryW(path, NULL);
      *p = L'\\';
      if (!status) {
        err = GetLastError();
        if (err != ERROR_ALREADY_EXISTS) return 1;
      }
    }
  }

  if (complete) {
    p--;
    if (*p == L'/' || *p == L'\\') *p = L'\0';
    status = CreateDirectoryW(path, NULL);
    if (*p == L'\0') *p = L'\\';
    if (!status) {
      err = GetLastError();
      if (err != ERROR_ALREADY_EXISTS) return 1;
    }
  }

  return 0;
}

int zip_file_exists(zip_char_t *filename) {
  DWORD attrib = GetFileAttributesW(filename);
  return attrib != INVALID_FILE_ATTRIBUTES;
}

int zip_set_mtime(const zip_char_t *filename, time_t mtime) {
  SYSTEMTIME st;
  FILETIME modft;
  struct tm *utctm;
  HANDLE hFile;
  time_t ftimei = (time_t) mtime;

  utctm = gmtime(&ftimei);
  if (!utctm) return 1;

  st.wYear         = (WORD) utctm->tm_year + 1900;
  st.wMonth        = (WORD) utctm->tm_mon + 1;
  st.wDayOfWeek    = (WORD) utctm->tm_wday;
  st.wDay          = (WORD) utctm->tm_mday;
  st.wHour         = (WORD) utctm->tm_hour;
  st.wMinute       = (WORD) utctm->tm_min;
  st.wSecond       = (WORD) utctm->tm_sec;
  st.wMilliseconds = (WORD) 1000*(mtime - ftimei);
  if (!SystemTimeToFileTime(&st, &modft)) return 1;

  hFile = CreateFileW(filename, GENERIC_WRITE, 0, NULL, OPEN_EXISTING,
                      FILE_FLAG_BACKUP_SEMANTICS, NULL);

  if (hFile == INVALID_HANDLE_VALUE) return 1;
  int res  = SetFileTime(hFile, NULL, NULL, &modft);
  CloseHandle(hFile);
  return res == 0; /* success is non-zero */
}

int zip_file_size(FILE *fh, mz_uint64 *size) {
  if (_fseeki64(fh, 0, SEEK_END)) return 1;
  *size = _ftelli64(fh);
  if (*size == -1) return 2;
  if (_fseeki64(fh, 0, SEEK_SET)) return 3;
  return 0;
}

FILE* zip_long_wfopen(const wchar_t *filename, const wchar_t *mode) {
  FILE* res = _wfopen(filename, mode);
  return res;
}
