/* * prog_util.c - utility functions for programs * * Copyright 2016 Eric Biggers * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "prog_util.h" #include #include #include #ifdef _WIN32 # include #else # include # include #endif #ifndef O_BINARY # define O_BINARY 0 #endif #ifndef O_SEQUENTIAL # define O_SEQUENTIAL 0 #endif #ifndef O_NOFOLLOW # define O_NOFOLLOW 0 #endif #ifndef O_NONBLOCK # define O_NONBLOCK 0 #endif #ifndef O_NOCTTY # define O_NOCTTY 0 #endif /* The invocation name of the program (filename component only) */ const tchar *prog_invocation_name; /* Whether to suppress warning messages or not */ bool suppress_warnings; static void do_msg(const char *format, bool with_errno, va_list va) { int saved_errno = errno; fprintf(stderr, "%"TS": ", prog_invocation_name); vfprintf(stderr, format, va); if (with_errno) fprintf(stderr, ": %s\n", strerror(saved_errno)); else fprintf(stderr, "\n"); errno = saved_errno; } /* Print a message to standard error */ void msg(const char *format, ...) { va_list va; va_start(va, format); do_msg(format, false, va); va_end(va); } /* Print a message to standard error, including a description of errno */ void msg_errno(const char *format, ...) { va_list va; va_start(va, format); do_msg(format, true, va); va_end(va); } /* Same as msg(), but do nothing if 'suppress_warnings' has been set. */ void warn(const char *format, ...) { if (!suppress_warnings) { va_list va; va_start(va, format); do_msg(format, false, va); va_end(va); } } /* malloc() wrapper */ void * xmalloc(size_t size) { void *p = malloc(size); if (p == NULL && size == 0) p = malloc(1); if (p == NULL) msg("Out of memory"); return p; } /* * Retrieve a pointer to the filename component of the specified path. * * Note: this does not modify the path. Therefore, it is not guaranteed to work * properly for directories, since a path to a directory might have trailing * slashes. */ static const tchar * get_filename(const tchar *path) { const tchar *slash = tstrrchr(path, '/'); #ifdef _WIN32 const tchar *backslash = tstrrchr(path, '\\'); if (backslash != NULL && (slash == NULL || backslash > slash)) slash = backslash; #endif if (slash != NULL) return slash + 1; return path; } void begin_program(tchar *argv[]) { prog_invocation_name = get_filename(argv[0]); #ifdef FREESTANDING /* This allows testing freestanding library builds. */ libdeflate_set_memory_allocator(malloc, free); #endif } /* Create a copy of 'path' surrounded by double quotes */ static tchar * quote_path(const tchar *path) { size_t len = tstrlen(path); tchar *result; result = xmalloc((1 + len + 1 + 1) * sizeof(tchar)); if (result == NULL) return NULL; result[0] = '"'; tmemcpy(&result[1], path, len); result[1 + len] = '"'; result[1 + len + 1] = '\0'; return result; } /* Open a file for reading, or set up standard input for reading */ int xopen_for_read(const tchar *path, bool symlink_ok, struct file_stream *strm) { strm->mmap_token = NULL; strm->mmap_mem = NULL; if (path == NULL) { strm->is_standard_stream = true; strm->name = T("standard input"); strm->fd = STDIN_FILENO; #ifdef _WIN32 _setmode(strm->fd, O_BINARY); #endif return 0; } strm->is_standard_stream = false; strm->name = quote_path(path); if (strm->name == NULL) return -1; strm->fd = topen(path, O_RDONLY | O_BINARY | O_NONBLOCK | O_NOCTTY | (symlink_ok ? 0 : O_NOFOLLOW) | O_SEQUENTIAL); if (strm->fd < 0) { msg_errno("Can't open %"TS" for reading", strm->name); free(strm->name); return -1; } #if O_SEQUENTIAL == 0 && \ (defined(HAVE_POSIX_FADVISE) || \ /* fallback detection method for direct compilation */ \ (!defined(HAVE_CONFIG_H) && defined(POSIX_FADV_SEQUENTIAL))) (void)posix_fadvise(strm->fd, 0, 0, POSIX_FADV_SEQUENTIAL); #endif return 0; } /* Open a file for writing, or set up standard output for writing */ int xopen_for_write(const tchar *path, bool overwrite, struct file_stream *strm) { int ret = -1; strm->mmap_token = NULL; strm->mmap_mem = NULL; if (path == NULL) { strm->is_standard_stream = true; strm->name = T("standard output"); strm->fd = STDOUT_FILENO; #ifdef _WIN32 _setmode(strm->fd, O_BINARY); #endif return 0; } strm->is_standard_stream = false; strm->name = quote_path(path); if (strm->name == NULL) goto err; retry: strm->fd = topen(path, O_WRONLY | O_BINARY | O_NOFOLLOW | O_CREAT | O_EXCL, 0644); if (strm->fd < 0) { if (errno != EEXIST) { msg_errno("Can't open %"TS" for writing", strm->name); goto err; } if (!overwrite) { if (!isatty(STDERR_FILENO) || !isatty(STDIN_FILENO)) { warn("%"TS" already exists; use -f to overwrite", strm->name); ret = -2; /* warning only */ goto err; } fprintf(stderr, "%"TS": %"TS" already exists; " "overwrite? (y/n) ", prog_invocation_name, strm->name); if (getchar() != 'y') { msg("Not overwriting."); goto err; } } if (tunlink(path) != 0) { msg_errno("Unable to delete %"TS, strm->name); goto err; } goto retry; } return 0; err: free(strm->name); return ret; } /* Read the full contents of a file into memory */ static int read_full_contents(struct file_stream *strm) { size_t filled = 0; size_t capacity = 4096; char *buf; int ret; buf = xmalloc(capacity); if (buf == NULL) return -1; do { if (filled == capacity) { char *newbuf; if (capacity == SIZE_MAX) goto oom; capacity += MIN(SIZE_MAX - capacity, capacity); newbuf = realloc(buf, capacity); if (newbuf == NULL) goto oom; buf = newbuf; } ret = xread(strm, &buf[filled], capacity - filled); if (ret < 0) goto err; filled += ret; } while (ret != 0); strm->mmap_mem = buf; strm->mmap_size = filled; return 0; err: free(buf); return ret; oom: msg("Out of memory! %"TS" is too large to be processed by " "this program as currently implemented.", strm->name); ret = -1; goto err; } /* Map the contents of a file into memory */ int map_file_contents(struct file_stream *strm, u64 size) { if (size == 0) /* mmap isn't supported on empty files */ return read_full_contents(strm); if (size > SIZE_MAX) { msg("%"TS" is too large to be processed by this program", strm->name); return -1; } #ifdef _WIN32 strm->mmap_token = CreateFileMapping( (HANDLE)(intptr_t)_get_osfhandle(strm->fd), NULL, PAGE_READONLY, 0, 0, NULL); if (strm->mmap_token == NULL) { DWORD err = GetLastError(); if (err == ERROR_BAD_EXE_FORMAT) /* mmap unsupported */ return read_full_contents(strm); msg("Unable create file mapping for %"TS": Windows error %u", strm->name, (unsigned int)err); return -1; } strm->mmap_mem = MapViewOfFile((HANDLE)strm->mmap_token, FILE_MAP_READ, 0, 0, size); if (strm->mmap_mem == NULL) { msg("Unable to map %"TS" into memory: Windows error %u", strm->name, (unsigned int)GetLastError()); CloseHandle((HANDLE)strm->mmap_token); return -1; } #else /* _WIN32 */ strm->mmap_mem = mmap(NULL, size, PROT_READ, MAP_SHARED, strm->fd, 0); if (strm->mmap_mem == MAP_FAILED) { strm->mmap_mem = NULL; if (errno == ENODEV /* standard */ || errno == EINVAL /* macOS */) { /* mmap isn't supported on this file */ return read_full_contents(strm); } if (errno == ENOMEM) { msg("%"TS" is too large to be processed by this " "program", strm->name); } else { msg_errno("Unable to map %"TS" into memory", strm->name); } return -1; } #if defined(HAVE_POSIX_MADVISE) || \ /* fallback detection method for direct compilation */ \ (!defined(HAVE_CONFIG_H) && defined(POSIX_MADV_SEQUENTIAL)) (void)posix_madvise(strm->mmap_mem, size, POSIX_MADV_SEQUENTIAL); #endif strm->mmap_token = strm; /* anything that's not NULL */ #endif /* !_WIN32 */ strm->mmap_size = size; return 0; } /* * Read from a file, returning the full count to indicate all bytes were read, a * short count (possibly 0) to indicate EOF, or -1 to indicate error. */ ssize_t xread(struct file_stream *strm, void *buf, size_t count) { char *p = buf; size_t orig_count = count; while (count != 0) { ssize_t res = read(strm->fd, p, MIN(count, INT_MAX)); if (res == 0) break; if (res < 0) { if (errno == EAGAIN || errno == EINTR) continue; msg_errno("Error reading from %"TS, strm->name); return -1; } p += res; count -= res; } return orig_count - count; } /* Write to a file, returning 0 if all bytes were written or -1 on error */ int full_write(struct file_stream *strm, const void *buf, size_t count) { const char *p = buf; while (count != 0) { ssize_t res = write(strm->fd, p, MIN(count, INT_MAX)); if (res <= 0) { msg_errno("Error writing to %"TS, strm->name); return -1; } p += res; count -= res; } return 0; } /* Close a file, returning 0 on success or -1 on error */ int xclose(struct file_stream *strm) { int ret = 0; if (!strm->is_standard_stream) { if (close(strm->fd) != 0) { msg_errno("Error closing %"TS, strm->name); ret = -1; } free(strm->name); } if (strm->mmap_token != NULL) { #ifdef _WIN32 UnmapViewOfFile(strm->mmap_mem); CloseHandle((HANDLE)strm->mmap_token); #else munmap(strm->mmap_mem, strm->mmap_size); #endif strm->mmap_token = NULL; } else { free(strm->mmap_mem); } strm->mmap_mem = NULL; strm->fd = -1; strm->name = NULL; return ret; } /* * Parse the compression level given on the command line, returning the * compression level on success or -1 on error */ int parse_compression_level(tchar opt_char, const tchar *arg) { int level; if (arg == NULL) arg = T(""); if (opt_char < '0' || opt_char > '9') goto invalid; level = opt_char - '0'; if (arg[0] != '\0') { if (arg[0] < '0' || arg[0] > '9') goto invalid; if (arg[1] != '\0') /* Levels are at most 2 digits */ goto invalid; if (level == 0) /* Don't allow arguments like "-01" */ goto invalid; level = (level * 10) + (arg[0] - '0'); } if (level < 0 || level > 12) goto invalid; return level; invalid: msg("Invalid compression level: \"%"TC"%"TS"\". " "Must be an integer in the range [0, 12].", opt_char, arg); return -1; } /* Allocate a new DEFLATE compressor */ struct libdeflate_compressor * alloc_compressor(int level) { struct libdeflate_compressor *c; c = libdeflate_alloc_compressor(level); if (c == NULL) { msg_errno("Unable to allocate compressor with " "compression level %d", level); } return c; } /* Allocate a new DEFLATE decompressor */ struct libdeflate_decompressor * alloc_decompressor(void) { struct libdeflate_decompressor *d; d = libdeflate_alloc_decompressor(); if (d == NULL) msg_errno("Unable to allocate decompressor"); return d; }