#include <assert.h>
#include <errno.h>
#include <limits.h>
#include <stdarg.h>
#ifdef _WIN32
#include <io.h>
#include <fcntl.h>
#endif
#include "filemem.h"
#include "output.h"
#define FM_PAGE_SIZE 0x8000
#ifndef EOVERFLOW
#define EOVERFLOW EINVAL
#endif
#if defined(_MSC_VER) && _MSC_VER < 1800
# define va_copy(dest, src) (dest = src)
#else
# if defined(ZINT_IS_C89) && !defined(va_copy)
# ifdef __GNUC__
# define va_copy __va_copy
# else
# define va_copy(dest, src) (dest = src)
# endif
# endif
#endif
static int fm_seterr(struct filemem *restrict const fmp, const int err) {
if (fmp->err == 0) {
fmp->err = err;
}
return 0;
}
static void fm_setpos(struct filemem *restrict const fmp, const size_t pos) {
assert(pos <= fmp->memsize);
fmp->mempos = pos;
if (fmp->mempos > fmp->memend) {
fmp->memend = fmp->mempos;
}
}
static void fm_clear_mem(struct filemem *restrict const fmp) {
if (fmp->mem) {
free(fmp->mem);
fmp->mem = NULL;
}
fmp->memsize = fmp->mempos = fmp->memend = 0;
#ifdef FM_NO_VSNPRINTF
if (fmp->fp_null) {
(void) fclose(fmp->fp_null);
fmp->fp_null = NULL;
}
#endif
}
INTERNAL int zint_fm_open(struct filemem *restrict const fmp, struct zint_symbol *symbol, const char *mode) {
assert(fmp && symbol && mode);
fmp->fp = NULL;
fmp->mem = NULL;
fmp->memsize = fmp->mempos = fmp->memend = 0;
fmp->flags = symbol->output_options & (BARCODE_STDOUT | BARCODE_MEMORY_FILE);
fmp->err = 0;
#ifdef FM_NO_VSNPRINTF
fmp->fp_null = NULL;
#endif
if (fmp->flags & BARCODE_MEMORY_FILE) {
if (!(fmp->mem = (unsigned char *) malloc(FM_PAGE_SIZE))) {
return fm_seterr(fmp, ENOMEM);
}
#ifdef ZINT_SANITIZEM
memset(fmp->mem, 0, FM_PAGE_SIZE);
#endif
fmp->memsize = FM_PAGE_SIZE;
if (symbol->memfile) {
free(symbol->memfile);
symbol->memfile = NULL;
}
symbol->memfile_size = 0;
return 1;
}
if (fmp->flags & BARCODE_STDOUT) {
#ifdef _WIN32
if (strchr(mode, 'b') != NULL && _setmode(_fileno(stdout), _O_BINARY) == -1) {
return fm_seterr(fmp, errno);
}
#endif
fmp->fp = stdout;
return 1;
}
if (!(fmp->fp = zint_out_fopen(symbol->outfile, mode))) {
return fm_seterr(fmp, errno);
}
return 1;
}
static int fm_mem_expand(struct filemem *restrict const fmp, const size_t size) {
unsigned char *new_mem;
size_t new_size;
assert(fmp);
if (!fmp->mem) {
return fm_seterr(fmp, EINVAL);
}
if (size == 0) {
return 1;
}
if (fmp->mempos + size < fmp->memsize) {
if (fmp->mempos + size <= fmp->mempos) {
fm_clear_mem(fmp);
return fm_seterr(fmp, EOVERFLOW);
}
return 1;
}
new_size = fmp->memsize + (size < FM_PAGE_SIZE ? FM_PAGE_SIZE : size);
if (new_size <= fmp->memsize) {
fm_clear_mem(fmp);
return fm_seterr(fmp, EOVERFLOW);
}
if (new_size > 0x40000000 || !(new_mem = (unsigned char *) realloc(fmp->mem, new_size))) {
fm_clear_mem(fmp);
return fm_seterr(fmp, new_size > 0x40000000 ? EINVAL : ENOMEM);
}
#ifdef ZINT_SANITIZEM
memset(new_mem + fmp->memsize, 0, new_size - fmp->memsize);
#endif
fmp->mem = new_mem;
fmp->memsize = new_size;
return 1;
}
INTERNAL int zint_fm_write(const void *restrict ptr, const size_t size, const size_t nitems,
struct filemem *restrict const fmp) {
assert(fmp && ptr);
if (fmp->err) {
return 0;
}
if (size == 0 || nitems == 0) {
return 1;
}
if (fmp->flags & BARCODE_MEMORY_FILE) {
const size_t tot_size = size * nitems;
if (tot_size / size != nitems) {
return fm_seterr(fmp, EOVERFLOW);
}
if (!fm_mem_expand(fmp, tot_size)) {
return 0;
}
memcpy(fmp->mem + fmp->mempos, ptr, tot_size);
fm_setpos(fmp, fmp->mempos + tot_size);
return 1;
}
if (fwrite(ptr, size, nitems, fmp->fp) != nitems) {
return fm_seterr(fmp, errno);
}
return 1;
}
INTERNAL int zint_fm_putc(const int ch, struct filemem *restrict const fmp) {
assert(fmp);
if (fmp->err) {
return 0;
}
if (fmp->flags & BARCODE_MEMORY_FILE) {
if (!fm_mem_expand(fmp, 1)) {
return 0;
}
fmp->mem[fmp->mempos] = (unsigned char) ch;
fm_setpos(fmp, fmp->mempos + 1);
return 1;
}
if (fputc(ch, fmp->fp) == EOF) {
return fm_seterr(fmp, errno);
}
return 1;
}
INTERNAL int zint_fm_puts(const char *str, struct filemem *restrict const fmp) {
assert(fmp);
if (fmp->err) {
return 0;
}
if (fmp->flags & BARCODE_MEMORY_FILE) {
const size_t len = strlen(str);
if (!fm_mem_expand(fmp, len)) {
return 0;
}
memcpy(fmp->mem + fmp->mempos, str, len);
fm_setpos(fmp, fmp->mempos + len);
return 1;
}
if (fputs(str, fmp->fp) == EOF) {
return fm_seterr(fmp, errno);
}
return 1;
}
#ifdef FM_NO_VSNPRINTF
# ifdef _WIN32
# define DEV_NULL "NUL"
# else
# define DEV_NULL "/dev/null"
# endif
#endif
static int fm_vprintf(struct filemem *restrict const fmp, const char *fmt, va_list ap) {
va_list cpy;
int size, check;
#ifdef FM_NO_VSNPRINTF
if (!fmp->fp_null && !(fmp->fp_null = fopen(DEV_NULL, "wb"))) {
return fm_seterr(fmp, errno);
}
#endif
va_copy(cpy, ap);
#ifdef FM_NO_VSNPRINTF
size = vfprintf(fmp->fp_null, fmt, cpy);
#else
size = vsnprintf(NULL, 0, fmt, cpy);
#endif
va_end(cpy);
if (size < 0) {
return fm_seterr(fmp, errno);
}
if (!fm_mem_expand(fmp, size + 1)) {
return 0;
}
#ifdef FM_NO_VSNPRINTF
check = vsprintf((char *) fmp->mem + fmp->mempos, fmt, ap);
#else
check = vsnprintf((char *) fmp->mem + fmp->mempos, size + 1, fmt, ap);
#endif
(void)check;
assert(check == size);
fm_setpos(fmp, fmp->mempos + size);
return 1;
}
INTERNAL int zint_fm_printf(struct filemem *restrict const fmp, const char *fmt, ...) {
va_list ap;
int ret;
assert(fmp && fmt);
if (fmp->err) {
return 0;
}
if (fmp->flags & BARCODE_MEMORY_FILE) {
va_start(ap, fmt);
ret = fm_vprintf(fmp, fmt, ap);
va_end(ap);
return ret;
}
va_start(ap, fmt);
ret = vfprintf(fmp->fp, fmt, ap) >= 0;
va_end(ap);
return ret ? 1 : fm_seterr(fmp, errno);
}
INTERNAL int zint_fm_putsf(const char *prefix, const int dp, const float arg, struct filemem *restrict const fmp) {
int i, end;
char buf[256];
const int len = sprintf(buf, "%.*f", dp, arg);
assert(fmp);
if (fmp->err) {
return 0;
}
if (prefix && *prefix) {
if (!zint_fm_puts(prefix, fmp)) {
return 0;
}
}
for (i = len - 1, end = len; i >= 0; i--) {
if (buf[i] == '0') {
if (end == i + 1) {
end = i;
}
} else if (!z_isdigit(buf[i]) && buf[i] != '-') {
if (end == i + 1) {
end = i;
} else {
buf[i] = '.';
}
buf[end] = '\0';
break;
}
}
return zint_fm_puts(buf, fmp);
}
INTERNAL int zint_fm_close(struct filemem *restrict const fmp, struct zint_symbol *symbol) {
assert(fmp && symbol);
if (fmp->flags & BARCODE_MEMORY_FILE) {
if (fmp->err || !fmp->mem) {
fm_clear_mem(fmp);
return fm_seterr(fmp, EINVAL);
}
symbol->memfile_size = (int) fmp->mempos;
if ((size_t) symbol->memfile_size != fmp->mempos) {
fm_clear_mem(fmp);
symbol->memfile_size = 0;
return fm_seterr(fmp, EINVAL);
}
symbol->memfile = fmp->mem;
fmp->mem = NULL;
fm_clear_mem(fmp);
return 1;
}
if (fmp->err || !fmp->fp) {
if (!(fmp->flags & BARCODE_STDOUT) && fmp->fp) {
(void) fclose(fmp->fp);
}
return fm_seterr(fmp, EINVAL);
}
if (fmp->flags & BARCODE_STDOUT) {
if (fflush(fmp->fp) != 0) {
fmp->fp = NULL;
return fm_seterr(fmp, errno);
}
} else {
if (fclose(fmp->fp) != 0) {
fmp->fp = NULL;
return fm_seterr(fmp, errno);
}
}
fmp->fp = NULL;
return 1;
}
INTERNAL int zint_fm_seek(struct filemem *restrict const fmp, const long offset, const int whence) {
assert(fmp);
if (fmp->err) {
return 0;
}
if (fmp->flags & BARCODE_MEMORY_FILE) {
const size_t start = whence == SEEK_SET ? 0 : whence == SEEK_CUR ? fmp->mempos : fmp->memend;
const size_t new_pos = start + offset;
if ((offset > 0 && new_pos <= start) || (offset < 0 && new_pos >= start)) {
return fm_seterr(fmp, EINVAL);
}
if (!fm_mem_expand(fmp, new_pos)) {
return 0;
}
fm_setpos(fmp, new_pos);
return 1;
}
if (fseek(fmp->fp, offset, whence) != 0) {
return fm_seterr(fmp, errno);
}
return 1;
}
INTERNAL long zint_fm_tell(struct filemem *restrict const fmp) {
long ret;
assert(fmp);
if (fmp->err) {
return -1;
}
if (fmp->flags & BARCODE_MEMORY_FILE) {
if (!fmp->mem) {
(void) fm_seterr(fmp, ENOMEM);
return -1;
}
return (long) fmp->mempos;
}
ret = ftell(fmp->fp);
if (ret < 0 || ret == LONG_MAX) {
(void) fm_seterr(fmp, errno);
return -1;
}
return ret;
}
INTERNAL int zint_fm_error(struct filemem *restrict const fmp) {
assert(fmp);
if (fmp->err == 0 && !(fmp->flags & BARCODE_MEMORY_FILE) && ferror(fmp->fp)) {
(void) fm_seterr(fmp, EIO);
}
return fmp->err;
}
INTERNAL int zint_fm_flush(struct filemem *restrict const fmp) {
assert(fmp);
if (fmp->err) {
return 0;
}
if (fmp->flags & BARCODE_MEMORY_FILE) {
if (!fmp->mem) {
return fm_seterr(fmp, EINVAL);
}
return 1;
}
if (fflush(fmp->fp) == EOF) {
return fm_seterr(fmp, errno);
}
return 1;
}