apple2ix/src/zlib-helpers.c

493 lines
14 KiB
C

/*
* Apple // emulator for *ix
*
* This software package is subject to the GNU General Public License
* version 3 or later (your choice) as published by the Free Software
* Foundation.
*
* Copyright 2013-2017 Aaron Culliney
*
*/
#include "common.h"
typedef enum a2gzip_t {
A2GZT_ERR = -1,
A2GZT_NOT_GZ = 0,
A2GZT_MAYBE_GZ = 1,
} a2gzip_t;
/* report a zlib or i/o error */
static const char* const _gzerr(gzFile gzf) {
if (gzf == NULL) {
return NULL;
}
int err_num = 0;
const char *reason = gzerror(gzf, &err_num);
if (err_num == Z_ERRNO) {
return strerror(err_num);
} else {
return reason;
}
}
static int _gzread_data(gzFile gzsource, uint8_t *buf, const unsigned int expected_bytescount) {
int bytescount = 0;
int maxtries = 10;
do {
int bytesread = gzread(gzsource, buf+bytescount, expected_bytescount-bytescount);
if (bytesread <= 0) {
if (--maxtries == 0) {
LOG("OOPS, giving up on gzread() ...");
break;
}
LOG("OOPS, gzread() ...");
usleep(100);
continue;
}
bytescount += bytesread;
if (bytescount >= expected_bytescount) {
bytescount = expected_bytescount;
break; // DONE
}
} while (1);
return bytescount;
}
static ssize_t _read_data(int fd_own, uint8_t *buf, const off_t expected_bytescount) {
ssize_t bytescount = 0;
int maxtries = 10;
do {
ssize_t bytesread = 0;
off_t len0 = expected_bytescount-bytescount;
unsigned int len = (unsigned int)len0;
if (UNLIKELY(len0 > UINT_MAX || len0 < 0)) {
assert(false);
}
TEMP_FAILURE_RETRY(bytesread = read(fd_own, buf+bytescount, len));
if (bytesread <= 0) {
if (--maxtries == 0) {
LOG("OOPS, giving up on read() ... (%s)", strerror(errno));
break;
}
LOG("OOPS, read() ... (%s)", strerror(errno));
usleep(100);
continue;
}
bytescount += bytesread;
if (bytescount >= expected_bytescount) {
bytescount = (int)expected_bytescount;
break; // DONE
}
} while (1);
return bytescount;
}
static ssize_t _write_data(int fd_own, uint8_t *buf, const unsigned int expected_bytescount) {
ssize_t bytescount = 0;
int maxtries = 10;
do {
ssize_t byteswritten = 0;
TEMP_FAILURE_RETRY(byteswritten = write(fd_own, buf+bytescount, expected_bytescount-bytescount));
if (byteswritten <= 0) {
if (--maxtries == 0) {
LOG("OOPS, giving up on write() ... (%s)", strerror(errno));
break;
}
LOG("OOPS, write ... (%s)", strerror(errno));
usleep(100);
continue;
}
bytescount += byteswritten;
if (bytescount >= expected_bytescount) {
bytescount = expected_bytescount;
break; // DONE
}
} while (1);
return bytescount;
}
static a2gzip_t _check_gzip_magick(int fd_own, const unsigned int expected_bytescount) {
a2gzip_t ret = A2GZT_ERR;
do {
uint8_t stkbuf[2];
off_t bytescount = _read_data(fd_own, &stkbuf[0], sizeof(stkbuf));
if (bytescount != sizeof(stkbuf)) {
LOG("OOPS, could not read file magick for file descriptor");
break;
}
bytescount = lseek(fd_own, 0L, SEEK_END);
if (bytescount == -1) {
LOG("OOPS, cannot seek to end of file descriptor! (%s)", strerror(errno));
break;
}
if (stkbuf[0] == 0x1f && stkbuf[1] == 0x8b) {
// heuristics : gzip magick matches, but could possibly be a disk image file that begins with those bytes ...
ret = A2GZT_MAYBE_GZ;
if (bytescount >= expected_bytescount) {
// most gzipped disk images are *likely* to compress smaller, but we shouldn't assume ...
LOG("OMG, found a large apparently gzipped disk image!");
}
} else {
// did not see the gzip magick, so this is not a gzip file, but we should check file length ...
if (bytescount >= expected_bytescount) {
ret = A2GZT_NOT_GZ;
} else {
LOG("OOPS, did not find gzip magick, and file is %lld bytes, not expected size of %u", (long long)bytescount, expected_bytescount);
}
}
} while (0);
return ret;
}
/* Inflate/uncompress file descriptor to buffer.
*
* Return NULL on success, or error string (possibly from zlib) on failure.
*/
const char *zlib_inflate_to_buffer(int fd, const unsigned int expected_bytescount, uint8_t *buf) {
gzFile gzsource = NULL;
int fd_own = -1;
assert(buf != NULL);
assert(expected_bytescount > 0);
ssize_t bytescount = 0;
char *err = NULL;
do {
TEMP_FAILURE_RETRY(fd_own = dup(fd)); // balance gzclose() behavior
if (fd_own == -1) {
LOG("OOPS, could not dup() file descriptor %d", fd);
break;
}
fd = -1;
// check gzip magick ...
a2gzip_t val = _check_gzip_magick(fd_own, expected_bytescount);
if (val == A2GZT_ERR) {
break;
}
if (lseek(fd_own, 0L, SEEK_SET) == -1) {
LOG("OOPS, cannot seek to start of file descriptor! (%s)", strerror(errno));
break;
}
if (val == A2GZT_NOT_GZ) {
// definitively not gzipped and expected size, all good!
bytescount = _read_data(fd_own, buf, expected_bytescount);
break;
}
// attempt inflate ...
gzsource = gzdopen(fd_own, "r");
if (gzsource == NULL) {
LOG("OOPS, cannot open file descriptor for reading");
break;
}
bytescount = _gzread_data(gzsource, buf, expected_bytescount);
if (bytescount != expected_bytescount) {
// could not gzread(), maybe it's not actually a gzip stream? ...
LOG("OOPS, did not gzread() expected_bytescount of %u ... apparently read %zd ... checking file length heuristic ...", expected_bytescount, bytescount);
if (lseek(fd_own, 0L, SEEK_SET) == -1) {
LOG("OOPS, cannot seek to start of file descriptor! (%s)", strerror(errno));
break;
}
bytescount = _read_data(fd_own, buf, expected_bytescount);
if (bytescount < expected_bytescount) {
LOG("OOPS, apparently corrupt gzipped file...");
} else {
LOG("File appears to be non-gzipped, proceeding to load ...");
bytescount = expected_bytescount;
}
break;
}
} while (0);
if (bytescount != expected_bytescount) {
LOG("OOPS did not read expected_bytescount of %u ... apparently read %zd", expected_bytescount, bytescount);
if (gzsource) {
err = (char *)_gzerr(gzsource);
}
if (!err) {
err = ZERR_UNKNOWN;
}
}
// clean up
if (gzsource) {
gzclose(gzsource);
// TEMP_FAILURE_RETRY(close(fd_own)); -- NOTE gzdopen() API with gzclose() above also closes the dup()'d fd
} else if (fd_own > 0) {
TEMP_FAILURE_RETRY(close(fd_own));
}
return err;
}
/* Inline inflate/uncompress buffer to file descriptor
*
* Return NULL on success, or error string (possibly from zlib) on failure.
*/
const char *zlib_inflate_inplace(int fd, const unsigned int expected_bytescount, bool *is_gzipped) {
gzFile gzsource = NULL;
int fd_own = -1;
uint8_t *buf = NULL;
*is_gzipped = false;
assert(expected_bytescount > 2);
off_t bytescount = 0;
do {
TEMP_FAILURE_RETRY(fd_own = dup(fd)); // balance gzclose()
if (fd_own == -1) {
LOG("OOPS, could not dup() file descriptor %d", fd);
break;
}
fd = -1;
// check gzip magick ...
a2gzip_t val = _check_gzip_magick(fd_own, expected_bytescount);
if (val == A2GZT_ERR) {
break;
} else if (val == A2GZT_NOT_GZ) {
// definitively not gzipped and expected size, all good!
bytescount = expected_bytescount;
break;
}
// attempt inflate ...
buf = MALLOC(expected_bytescount);
if (buf == NULL) {
LOG("OOPS, failed allocation of %d bytes", expected_bytescount);
break;
}
if (lseek(fd_own, 0L, SEEK_SET) == -1) {
LOG("OOPS, cannot seek to start of file descriptor! (%s)", strerror(errno));
break;
}
gzsource = gzdopen(fd_own, "r");
if (gzsource == NULL) {
LOG("OOPS, cannot open file file descriptor for reading");
break;
}
bytescount = _gzread_data(gzsource, buf, expected_bytescount);
if (bytescount != expected_bytescount) {
// could not gzread(), maybe it's not actually a gzip stream? ...
LOG("OOPS, did not in-place gzread() expected_bytescount of %u ... apparently read %lld ... checking file length heuristic ...", expected_bytescount, (long long)bytescount);
bytescount = lseek(fd_own, 0L, SEEK_END);
if (bytescount == -1) {
LOG("OOPS, cannot seek to end of file descriptor! (%s)", strerror(errno));
break;
}
if (bytescount < expected_bytescount) {
LOG("OOPS, apparently corrupt gzipped file...");
} else {
LOG("File appears to be non-gzipped, proceeding to load ...");
bytescount = expected_bytescount;
}
break;
}
// successfully read gzipped file ...
*is_gzipped = true;
// inplace write file
if (lseek(fd_own, 0L, SEEK_SET) == -1) {
LOG("OOPS, cannot seek to start of file descriptor! (%s)", strerror(errno));
break;
}
bytescount = _write_data(fd_own, buf, expected_bytescount);
if (bytescount == expected_bytescount) {
int ret = -1;
TEMP_FAILURE_RETRY(ret = ftruncate(fd_own, expected_bytescount));
if (ret == -1) {
LOG("OOPS, ftruncate file descriptor failed");
break;
}
}
} while (0);
char *err = NULL;
if (bytescount != expected_bytescount) {
LOG("OOPS, did not write expected_bytescount of %u ... apparently wrote %lld", expected_bytescount, (long long)bytescount);
if (gzsource) {
err = (char *)_gzerr(gzsource);
}
if (!err) {
err = ZERR_UNKNOWN;
}
}
// clean up
if (gzsource) {
gzclose(gzsource);
// TEMP_FAILURE_RETRY(close(fd_own)); -- NOTE gzdopen() API with gzclose() above also closes the dup()'d fd
} else if (fd_own > 0) {
TEMP_FAILURE_RETRY(close(fd_own));
}
if (buf) {
FREE(buf);
}
return err;
}
/* Deflate/compress source buffer to destination buffer.
*
* Return NULL on success, or error string (possibly from zlib) on failure.
*/
const char *zlib_deflate_buffer(const uint8_t *src, const unsigned int src_bytescount, uint8_t *dst, OUTPARM off_t *dst_size) {
char *gzPath = NULL;
gzFile gzdest = NULL;
int fd_own = -1;
off_t expected_bytescount = src_bytescount;
*dst_size = -1;
off_t bytescount = 0;
char *err = NULL;
do {
ASPRINTF(&gzPath, "%s/tmp.img", data_dir);
assert(gzPath != NULL);
TEMP_FAILURE_RETRY(fd_own = open(gzPath, O_RDWR|O_CREAT|O_TRUNC, S_IRUSR|S_IWUSR));
if (fd_own == -1) {
LOG("OOPS could not open temp image path : %s", gzPath);
break;
}
gzdest = gzdopen(fd_own, "w");
if (gzdest == NULL) {
LOG("OOPS, cannot open file descriptor for writing");
break;
}
do {
off_t len0 = expected_bytescount-bytescount;
unsigned int len = (unsigned int)len0;
if (UNLIKELY(len0 > UINT_MAX || len0 < 0)) {
assert(false);
}
int byteswritten = gzwrite(gzdest, src+bytescount, len);
if (byteswritten <= 0) {
LOG("OOPS, gzwrite() returned %d", byteswritten);
break;
}
bytescount += byteswritten;
if (bytescount >= expected_bytescount) {
bytescount = expected_bytescount;
break;
}
// do short writes happen? ...
} while (1);
if (bytescount != expected_bytescount) {
break;
}
if (gzflush(gzdest, Z_FINISH) != Z_OK) {
LOG("Error flushing compressed output : %s", err);
break;
}
// now read compressed data into buffer ...
{
off_t compressed_size = lseek(fd_own, 0L, SEEK_CUR);
if (compressed_size == -1) {
LOG("OOPS, cannot seek to current position! (%s)", strerror(errno));
break;
}
assert(/*compressed_size > 0 && */compressed_size < expected_bytescount);
expected_bytescount = compressed_size;
}
if (lseek(fd_own, 0L, SEEK_SET) == -1) {
LOG("OOPS, cannot seek to start of file descriptor! (%s)", strerror(errno));
break;
}
bytescount = _read_data(fd_own, dst, expected_bytescount);
if (bytescount != expected_bytescount) {
break;
}
*dst_size = expected_bytescount;
} while (0);
if (bytescount != expected_bytescount) {
LOG("OOPS, did not write/read expected number of bytes of %lld ... apparently wrote %lld", (long long)expected_bytescount, (long long)bytescount);
if (gzdest) {
err = (char *)_gzerr(gzdest);
}
if (!err) {
err = ZERR_UNKNOWN;
}
}
// clean up
if (gzdest) {
gzclose(gzdest);
// TEMP_FAILURE_RETRY(close(fd_own)); -- NOTE gzdopen() API with gzclose() above also closes the open()'d fd
} else if (fd_own > 0) {
TEMP_FAILURE_RETRY(close(fd_own));
}
if (gzPath) {
unlink(gzPath);
FREE(gzPath);
}
return err;
}