| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464 |
- /* filemem.c - write to file/memory abstraction */
- /*
- libzint - the open source barcode library
- Copyright (C) 2023-2024 Robin Stuart <rstuart114@gmail.com>
- Redistribution and use in source and binary forms, with or without
- modification, are permitted provided that the following conditions
- are met:
- 1. Redistributions of source code must retain the above copyright
- notice, this list of conditions and the following disclaimer.
- 2. Redistributions in binary form must reproduce the above copyright
- notice, this list of conditions and the following disclaimer in the
- documentation and/or other materials provided with the distribution.
- 3. Neither the name of the project nor the names of its contributors
- may be used to endorse or promote products derived from this software
- without specific prior written permission.
- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
- FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
- OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
- LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
- OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- SUCH DAMAGE.
- */
- /* SPDX-License-Identifier: BSD-3-Clause */
- #include <assert.h>
- #include <errno.h>
- #include <limits.h>
- #include <stdarg.h>
- #ifdef _MSC_VER
- #include <io.h>
- #include <fcntl.h>
- #endif
- #include "filemem.h"
- #include "output.h"
- #define FM_PAGE_SIZE 0x8000 /* 32k */
- #ifndef EOVERFLOW
- #define EOVERFLOW EINVAL
- #endif
- #if defined(_MSC_VER) && _MSC_VER < 1800 /* `va_copy()` not before MSVC 2013 (C++ 12.0) */
- # define va_copy(dest, src) (dest = src)
- #else
- # if defined(ZINT_IS_C89) && !defined(va_copy)
- # ifdef __GNUC__
- # define va_copy __va_copy /* Available with clang as well */
- # else
- # define va_copy(dest, src) (dest = src) /* Will fail if array (need `*dest = *src`) or something else */
- # endif
- # endif
- #endif
- /* Helper to set `err` only if not already set, returning 0 always for convenience */
- static int fm_seterr(struct filemem *restrict const fmp, const int err) {
- if (fmp->err == 0) {
- fmp->err = err;
- }
- return 0;
- }
- /* Helper to set position, syncing end marker */
- 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;
- }
- }
- /* Helper to clear memory buffer and associates */
- 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
- }
- /* `fopen()` if file, setup memory buffer if BARCODE_MEMORY_FILE, returning 1 on success, 0 on failure */
- INTERNAL int 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);
- }
- 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 _MSC_VER
- 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 = out_fopen(symbol->outfile, mode))) {
- return fm_seterr(fmp, errno);
- }
- return 1;
- }
- /* Expand memory buffer, returning 1 on success, 0 on failure */
- 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) { /* Fits? */
- if (fmp->mempos + size <= fmp->mempos) { /* Check for overflow */
- 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) { /* Check for overflow */
- fm_clear_mem(fmp);
- return fm_seterr(fmp, EOVERFLOW);
- }
- /* Protect against very large files & (Linux) OOM killer - cf `raster_malloc()` in "raster.c" */
- if (new_size > 0x40000000 /*1GB*/ || !(new_mem = (unsigned char *) realloc(fmp->mem, new_size))) {
- fm_clear_mem(fmp);
- return fm_seterr(fmp, new_size > 0x40000000 ? EINVAL : ENOMEM);
- }
- fmp->mem = new_mem;
- fmp->memsize = new_size;
- return 1;
- }
- /* `fwrite()` to file or memory, returning 1 on success, 0 on failure */
- INTERNAL int 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;
- }
- /* `fputc()` to file or memory, returning 1 on success, 0 on failure */
- INTERNAL int 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;
- }
- /* `fputs()` to file or memory, returning 1 on success, 0 on failure */
- INTERNAL int 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
- /* Helper to `printf()` into mem buffer */
- static int fm_vprintf(struct filemem *restrict const fmp, const char *fmt, va_list ap) {
- va_list cpy;
- int size, check;
- /* Adapted from https://stackoverflow.com/a/52558247/664741 */
- #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);
- /* The clang-tidy warning is a bug https://github.com/llvm/llvm-project/issues/40656 */
- #ifdef FM_NO_VSNPRINTF
- size = vfprintf(fmp->fp_null, fmt, cpy); /* NOLINT(clang-analyzer-valist.Uninitialized) */
- #else
- size = vsnprintf(NULL, 0, fmt, cpy); /* NOLINT(clang-analyzer-valist.Uninitialized) */
- #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
- /* NOLINTNEXTLINE(clang-analyzer-valist.Uninitialized) - see above */
- check = vsprintf((char *) fmp->mem + fmp->mempos, fmt, ap);
- #else
- /* NOLINTNEXTLINE(clang-analyzer-valist.Uninitialized) - see above */
- 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;
- }
- /* `fprintf()` to file or memory, returning 1 on success, 0 on failure */
- INTERNAL int 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; /* NOLINT(clang-analyzer-valist.Uninitialized) - see above */
- va_end(ap);
- return ret ? 1 : fm_seterr(fmp, errno);
- }
- /* Output float without trailing zeroes to `fmp` with decimal pts `dp` (precision), returning 1 on success, 0 on
- failure */
- INTERNAL int fm_putsf(const char *prefix, const int dp, const float arg, struct filemem *restrict const fmp) {
- int i, end;
- char buf[256]; /* Assuming `dp` reasonable */
- const int len = sprintf(buf, "%.*f", dp, arg);
- assert(fmp);
- if (fmp->err) {
- return 0;
- }
- if (prefix && *prefix) {
- if (!fm_puts(prefix, fmp)) {
- return 0;
- }
- }
- /* Adapted from https://stackoverflow.com/a/36202854/664741 */
- 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 not digit or minus then decimal point */
- if (end == i + 1) {
- end = i;
- } else {
- buf[i] = '.'; /* Overwrite any locale-specific setting for decimal point */
- }
- buf[end] = '\0';
- break;
- }
- }
- return fm_puts(buf, fmp);
- }
- /* `fclose()` if file, set `symbol->memfile` & `symbol->memfile_size` if memory, returning 1 on success, 0 on
- failure */
- INTERNAL int 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; /* Now belongs to `symbol` */
- 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;
- }
- /* `fseek()` to file/memory offset, returning 1 if successful, 0 on failure */
- INTERNAL int 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)) { /* Check for over/underflow */
- 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;
- }
- /* `ftell()` returns current file/memory offset if successful, -1 on failure */
- INTERNAL long 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);
- /* On many Linux distros `ftell()` returns LONG_MAX not -1 on error */
- if (ret < 0 || ret == LONG_MAX) {
- (void) fm_seterr(fmp, errno);
- return -1;
- }
- return ret;
- }
- /* Return `err`, which uses `errno` values; if file and `err` not set, test `ferror()` also */
- INTERNAL int 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;
- }
- /* `fflush()` if file, no-op (apart from error checking) if memory, returning 1 on success, 0 on failure
- NOTE: don't use, included only for libpng compatibility */
- INTERNAL int 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;
- }
- /* vim: set ts=4 sw=4 et : */
|