zip_source_win32handle.c [plain text]
#include <wchar.h>
#include <stdlib.h>
#include <string.h>
#include "zipint.h"
#include "zipwin32.h"
static zip_int64_t _win32_read_file(void *state, void *data, zip_uint64_t len, zip_source_cmd_t cmd);
static int _win32_create_temp_file(_zip_source_win32_read_file_t *ctx);
static int _zip_filetime_to_time_t(FILETIME ft, time_t *t);
static int _zip_seek_win32_u(void *h, zip_uint64_t offset, int whence, zip_error_t *error);
static int _zip_seek_win32(void *h, zip_int64_t offset, int whence, zip_error_t *error);
static int _zip_win32_error_to_errno(unsigned long win32err);
static int _zip_stat_win32(void *h, zip_stat_t *st, _zip_source_win32_read_file_t *ctx);
ZIP_EXTERN zip_source_t *
zip_source_win32handle(zip_t *za, HANDLE h, zip_uint64_t start, zip_int64_t len)
{
if (za == NULL)
return NULL;
return zip_source_win32handle_create(h, start, len, &za->error);
}
ZIP_EXTERN zip_source_t *
zip_source_win32handle_create(HANDLE h, zip_uint64_t start, zip_int64_t length, zip_error_t *error)
{
if (h == INVALID_HANDLE_VALUE || length < -1) {
zip_error_set(error, ZIP_ER_INVAL, 0);
return NULL;
}
return _zip_source_win32_handle_or_name(NULL, h, start, length, 1, NULL, NULL, error);
}
zip_source_t *
_zip_source_win32_handle_or_name(const void *fname, HANDLE h, zip_uint64_t start, zip_int64_t len, int closep, const zip_stat_t *st, _zip_source_win32_file_ops_t *ops, zip_error_t *error)
{
_zip_source_win32_read_file_t *ctx;
zip_source_t *zs;
if (h == INVALID_HANDLE_VALUE && fname == NULL) {
zip_error_set(error, ZIP_ER_INVAL, 0);
return NULL;
}
if ((ctx = (_zip_source_win32_read_file_t *)malloc(sizeof(_zip_source_win32_read_file_t))) == NULL) {
zip_error_set(error, ZIP_ER_MEMORY, 0);
return NULL;
}
ctx->fname = NULL;
if (fname) {
if ((ctx->fname = ops->op_strdup(fname)) == NULL) {
zip_error_set(error, ZIP_ER_MEMORY, 0);
free(ctx);
return NULL;
}
}
ctx->ops = ops;
ctx->h = h;
ctx->start = start;
ctx->end = (len < 0 ? 0 : start + (zip_uint64_t)len);
ctx->closep = ctx->fname ? 1 : closep;
if (st) {
memcpy(&ctx->st, st, sizeof(ctx->st));
ctx->st.name = NULL;
ctx->st.valid &= ~ZIP_STAT_NAME;
}
else {
zip_stat_init(&ctx->st);
}
ctx->tmpname = NULL;
ctx->hout = INVALID_HANDLE_VALUE;
zip_error_init(&ctx->error);
ctx->supports = ZIP_SOURCE_SUPPORTS_READABLE | zip_source_make_command_bitmap(ZIP_SOURCE_SUPPORTS, ZIP_SOURCE_TELL, -1);
if (ctx->fname) {
HANDLE th;
th = ops->op_open(ctx);
if (th == INVALID_HANDLE_VALUE || GetFileType(th) == FILE_TYPE_DISK) {
ctx->supports = ZIP_SOURCE_SUPPORTS_WRITABLE;
}
if (th != INVALID_HANDLE_VALUE) {
CloseHandle(th);
}
}
else if (GetFileType(ctx->h) == FILE_TYPE_DISK) {
ctx->supports = ZIP_SOURCE_SUPPORTS_SEEKABLE;
}
if ((zs = zip_source_function_create(_win32_read_file, ctx, error)) == NULL) {
free(ctx->fname);
free(ctx);
return NULL;
}
return zs;
}
static zip_int64_t
_win32_read_file(void *state, void *data, zip_uint64_t len, zip_source_cmd_t cmd)
{
_zip_source_win32_read_file_t *ctx;
char *buf;
zip_uint64_t n;
DWORD i;
ctx = (_zip_source_win32_read_file_t *)state;
buf = (char *)data;
switch (cmd) {
case ZIP_SOURCE_BEGIN_WRITE:
if (ctx->fname == NULL) {
zip_error_set(&ctx->error, ZIP_ER_OPNOTSUPP, 0);
return -1;
}
return _win32_create_temp_file(ctx);
case ZIP_SOURCE_COMMIT_WRITE: {
if (!CloseHandle(ctx->hout)) {
ctx->hout = INVALID_HANDLE_VALUE;
zip_error_set(&ctx->error, ZIP_ER_WRITE, _zip_win32_error_to_errno(GetLastError()));
}
ctx->hout = INVALID_HANDLE_VALUE;
if (ctx->ops->op_rename_temp(ctx) < 0) {
zip_error_set(&ctx->error, ZIP_ER_RENAME, _zip_win32_error_to_errno(GetLastError()));
return -1;
}
free(ctx->tmpname);
ctx->tmpname = NULL;
return 0;
}
case ZIP_SOURCE_CLOSE:
if (ctx->fname) {
CloseHandle(ctx->h);
ctx->h = INVALID_HANDLE_VALUE;
}
return 0;
case ZIP_SOURCE_ERROR:
return zip_error_to_data(&ctx->error, data, len);
case ZIP_SOURCE_FREE:
free(ctx->fname);
free(ctx->tmpname);
if (ctx->closep && ctx->h != INVALID_HANDLE_VALUE)
CloseHandle(ctx->h);
free(ctx);
return 0;
case ZIP_SOURCE_OPEN:
if (ctx->fname) {
if ((ctx->h = ctx->ops->op_open(ctx)) == INVALID_HANDLE_VALUE) {
zip_error_set(&ctx->error, ZIP_ER_OPEN, _zip_win32_error_to_errno(GetLastError()));
return -1;
}
}
if (ctx->closep && ctx->start > 0) {
if (_zip_seek_win32_u(ctx->h, ctx->start, SEEK_SET, &ctx->error) < 0) {
return -1;
}
}
ctx->current = ctx->start;
return 0;
case ZIP_SOURCE_READ:
if (ctx->end > 0) {
n = ctx->end - ctx->current;
if (n > len) {
n = len;
}
}
else {
n = len;
}
if (n > SIZE_MAX)
n = SIZE_MAX;
if (!ctx->closep) {
if (_zip_seek_win32_u(ctx->h, ctx->current, SEEK_SET, &ctx->error) < 0) {
return -1;
}
}
if (!ReadFile(ctx->h, buf, (DWORD)n, &i, NULL)) {
zip_error_set(&ctx->error, ZIP_ER_READ, _zip_win32_error_to_errno(GetLastError()));
return -1;
}
ctx->current += i;
return (zip_int64_t)i;
case ZIP_SOURCE_REMOVE:
if (ctx->ops->op_remove(ctx->fname) < 0) {
zip_error_set(&ctx->error, ZIP_ER_REMOVE, _zip_win32_error_to_errno(GetLastError()));
return -1;
}
return 0;
case ZIP_SOURCE_ROLLBACK_WRITE:
if (ctx->hout) {
CloseHandle(ctx->hout);
ctx->hout = INVALID_HANDLE_VALUE;
}
ctx->ops->op_remove(ctx->tmpname);
free(ctx->tmpname);
ctx->tmpname = NULL;
return 0;
case ZIP_SOURCE_SEEK: {
zip_int64_t new_current;
int need_seek;
zip_source_args_seek_t *args = ZIP_SOURCE_GET_ARGS(zip_source_args_seek_t, data, len, &ctx->error);
if (args == NULL)
return -1;
need_seek = ctx->closep;
switch (args->whence) {
case SEEK_SET:
new_current = args->offset;
break;
case SEEK_END:
if (ctx->end == 0) {
LARGE_INTEGER zero;
LARGE_INTEGER new_offset;
if (_zip_seek_win32(ctx->h, args->offset, SEEK_END, &ctx->error) < 0) {
return -1;
}
zero.QuadPart = 0;
if (!SetFilePointerEx(ctx->h, zero, &new_offset, FILE_CURRENT)) {
zip_error_set(&ctx->error, ZIP_ER_SEEK, _zip_win32_error_to_errno(GetLastError()));
return -1;
}
new_current = new_offset.QuadPart;
need_seek = 0;
}
else {
new_current = (zip_int64_t)ctx->end + args->offset;
}
break;
case SEEK_CUR:
new_current = (zip_int64_t)ctx->current + args->offset;
break;
default:
zip_error_set(&ctx->error, ZIP_ER_INVAL, 0);
return -1;
}
if (new_current < 0 || (zip_uint64_t)new_current < ctx->start || (ctx->end != 0 && (zip_uint64_t)new_current > ctx->end)) {
zip_error_set(&ctx->error, ZIP_ER_INVAL, 0);
return -1;
}
ctx->current = (zip_uint64_t)new_current;
if (need_seek) {
if (_zip_seek_win32_u(ctx->h, ctx->current, SEEK_SET, &ctx->error) < 0) {
return -1;
}
}
return 0;
}
case ZIP_SOURCE_SEEK_WRITE: {
zip_source_args_seek_t *args;
args = ZIP_SOURCE_GET_ARGS(zip_source_args_seek_t, data, len, &ctx->error);
if (args == NULL) {
return -1;
}
if (_zip_seek_win32(ctx->hout, args->offset, args->whence, &ctx->error) < 0) {
return -1;
}
return 0;
}
case ZIP_SOURCE_STAT: {
if (len < sizeof(ctx->st))
return -1;
if (ctx->st.valid != 0)
memcpy(data, &ctx->st, sizeof(ctx->st));
else {
DWORD win32err;
zip_stat_t *st;
HANDLE h;
int success;
st = (zip_stat_t *)data;
if (ctx->h != INVALID_HANDLE_VALUE) {
h = ctx->h;
}
else {
h = ctx->ops->op_open(ctx);
if (h == INVALID_HANDLE_VALUE && GetLastError() == ERROR_FILE_NOT_FOUND) {
zip_error_set(&ctx->error, ZIP_ER_READ, ENOENT);
return -1;
}
}
success = _zip_stat_win32(h, st, ctx);
win32err = GetLastError();
if (h != ctx->h) {
CloseHandle(h);
}
if (success < 0) {
zip_error_set(&ctx->error, ZIP_ER_READ, _zip_win32_error_to_errno(win32err));
return -1;
}
}
return sizeof(ctx->st);
}
case ZIP_SOURCE_SUPPORTS:
return ctx->supports;
case ZIP_SOURCE_TELL:
return (zip_int64_t)ctx->current;
case ZIP_SOURCE_TELL_WRITE:
{
LARGE_INTEGER zero;
LARGE_INTEGER offset;
zero.QuadPart = 0;
if (!SetFilePointerEx(ctx->hout, zero, &offset, FILE_CURRENT)) {
zip_error_set(&ctx->error, ZIP_ER_TELL, _zip_win32_error_to_errno(GetLastError()));
return -1;
}
return offset.QuadPart;
}
case ZIP_SOURCE_WRITE:
{
DWORD ret;
if (!WriteFile(ctx->hout, data, (DWORD)len, &ret, NULL) || ret != len) {
zip_error_set(&ctx->error, ZIP_ER_WRITE, _zip_win32_error_to_errno(GetLastError()));
return -1;
}
return (zip_int64_t)ret;
}
default:
zip_error_set(&ctx->error, ZIP_ER_OPNOTSUPP, 0);
return -1;
}
}
static int
_win32_create_temp_file(_zip_source_win32_read_file_t *ctx)
{
zip_uint32_t value;
int i;
HANDLE th = INVALID_HANDLE_VALUE;
void *temp = NULL;
SECURITY_INFORMATION si;
SECURITY_ATTRIBUTES sa;
PSECURITY_DESCRIPTOR psd = NULL;
PSECURITY_ATTRIBUTES psa = NULL;
DWORD len;
BOOL success;
if (ctx->h != INVALID_HANDLE_VALUE && GetFileType(ctx->h) == FILE_TYPE_DISK) {
si = DACL_SECURITY_INFORMATION | UNPROTECTED_DACL_SECURITY_INFORMATION;
len = 0;
success = GetUserObjectSecurity(ctx->h, &si, NULL, len, &len);
if (!success && GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
if ((psd = (PSECURITY_DESCRIPTOR)malloc(len)) == NULL) {
zip_error_set(&ctx->error, ZIP_ER_MEMORY, 0);
return -1;
}
success = GetUserObjectSecurity(ctx->h, &si, psd, len, &len);
}
if (success) {
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.bInheritHandle = FALSE;
sa.lpSecurityDescriptor = psd;
psa = &sa;
}
}
value = GetTickCount();
for (i = 0; i < 1024 && th == INVALID_HANDLE_VALUE; i++) {
th = ctx->ops->op_create_temp(ctx, &temp, value + i, psa);
if (th == INVALID_HANDLE_VALUE && GetLastError() != ERROR_FILE_EXISTS)
break;
}
if (th == INVALID_HANDLE_VALUE) {
free(temp);
free(psd);
zip_error_set(&ctx->error, ZIP_ER_TMPOPEN, _zip_win32_error_to_errno(GetLastError()));
return -1;
}
free(psd);
ctx->hout = th;
ctx->tmpname = temp;
return 0;
}
static int
_zip_seek_win32_u(HANDLE h, zip_uint64_t offset, int whence, zip_error_t *error)
{
if (offset > ZIP_INT64_MAX) {
zip_error_set(error, ZIP_ER_SEEK, EOVERFLOW);
return -1;
}
return _zip_seek_win32(h, (zip_int64_t)offset, whence, error);
}
static int
_zip_seek_win32(HANDLE h, zip_int64_t offset, int whence, zip_error_t *error)
{
LARGE_INTEGER li;
DWORD method;
switch (whence) {
case SEEK_SET:
method = FILE_BEGIN;
break;
case SEEK_END:
method = FILE_END;
break;
case SEEK_CUR:
method = FILE_CURRENT;
break;
default:
zip_error_set(error, ZIP_ER_SEEK, EINVAL);
return -1;
}
li.QuadPart = (LONGLONG)offset;
if (!SetFilePointerEx(h, li, NULL, method)) {
zip_error_set(error, ZIP_ER_SEEK, _zip_win32_error_to_errno(GetLastError()));
return -1;
}
return 0;
}
static int
_zip_win32_error_to_errno(DWORD win32err)
{
switch (win32err) {
case ERROR_INVALID_PARAMETER:
return EINVAL;
case ERROR_FILE_NOT_FOUND:
return ENOENT;
case ERROR_INVALID_HANDLE:
return EBADF;
case ERROR_ACCESS_DENIED:
return EACCES;
case ERROR_FILE_EXISTS:
return EEXIST;
case ERROR_TOO_MANY_OPEN_FILES:
return EMFILE;
case ERROR_DISK_FULL:
return ENOSPC;
default:
return 0;
}
}
static int
_zip_stat_win32(HANDLE h, zip_stat_t *st, _zip_source_win32_read_file_t *ctx)
{
FILETIME mtimeft;
time_t mtime;
LARGE_INTEGER size;
int regularp;
if (!GetFileTime(h, NULL, NULL, &mtimeft)) {
zip_error_set(&ctx->error, ZIP_ER_READ, _zip_win32_error_to_errno(GetLastError()));
return -1;
}
if (_zip_filetime_to_time_t(mtimeft, &mtime) < 0) {
zip_error_set(&ctx->error, ZIP_ER_READ, ERANGE);
return -1;
}
regularp = 0;
if (GetFileType(h) == FILE_TYPE_DISK) {
regularp = 1;
}
if (!GetFileSizeEx(h, &size)) {
zip_error_set(&ctx->error, ZIP_ER_READ, _zip_win32_error_to_errno(GetLastError()));
return -1;
}
zip_stat_init(st);
st->mtime = mtime;
st->valid |= ZIP_STAT_MTIME;
if (ctx->end != 0) {
st->size = ctx->end - ctx->start;
st->valid |= ZIP_STAT_SIZE;
}
else if (regularp) {
st->size = (zip_uint64_t)size.QuadPart;
st->valid |= ZIP_STAT_SIZE;
}
return 0;
}
static int
_zip_filetime_to_time_t(FILETIME ft, time_t *t)
{
const zip_int64_t WINDOWS_TICK = 10000000LL;
const zip_int64_t SEC_TO_UNIX_EPOCH = 11644473600LL;
ULARGE_INTEGER li;
zip_int64_t secs;
time_t temp;
li.LowPart = ft.dwLowDateTime;
li.HighPart = ft.dwHighDateTime;
secs = (li.QuadPart / WINDOWS_TICK - SEC_TO_UNIX_EPOCH);
temp = (time_t)secs;
if (secs != (zip_int64_t)temp)
return -1;
*t = temp;
return 0;
}