#include "prog_util.h"
#include <errno.h>
#include <fcntl.h>
#include <stdarg.h>
#ifdef _WIN32
# include <windows.h>
#else
# include <unistd.h>
# include <sys/mman.h>
#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
const tchar *prog_invocation_name;
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;
}
void
msg(const char *format, ...)
{
va_list va;
va_start(va, format);
do_msg(format, false, va);
va_end(va);
}
void
msg_errno(const char *format, ...)
{
va_list va;
va_start(va, format);
do_msg(format, true, va);
va_end(va);
}
void
warn(const char *format, ...)
{
if (!suppress_warnings) {
va_list va;
va_start(va, format);
do_msg(format, false, va);
va_end(va);
}
}
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;
}
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
libdeflate_set_memory_allocator(malloc, free);
#endif
}
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;
}
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) || \
\
(!defined(HAVE_CONFIG_H) && defined(POSIX_FADV_SEQUENTIAL)))
(void)posix_fadvise(strm->fd, 0, 0, POSIX_FADV_SEQUENTIAL);
#endif
return 0;
}
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;
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;
}
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;
}
int
map_file_contents(struct file_stream *strm, u64 size)
{
if (size == 0)
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)
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
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 ||
errno == EINVAL ) {
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) || \
\
(!defined(HAVE_CONFIG_H) && defined(POSIX_MADV_SEQUENTIAL))
(void)posix_madvise(strm->mmap_mem, size, POSIX_MADV_SEQUENTIAL);
#endif
strm->mmap_token = strm;
#endif
strm->mmap_size = size;
return 0;
}
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;
}
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;
}
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;
}
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')
goto invalid;
if (level == 0)
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;
}
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;
}
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;
}