/* See LICENSE file for copyright and license details. */
#include "common.h"
#ifdef GUNZIP_PATH
# define IS_GZIP() (size >= 18 && mem8[0] == 0x1f && mem8[1] == 0x8b)
#endif
#define IS_PSF_V1() (size >= 4 && mem8[0] == 0x36 && mem8[1] == 0x04)
#define IS_PSF_V2() (size >= 32 && mem8[0] == 0x72 && mem8[1] == 0xb5 && mem8[2] == 0x4a && mem8[3] == 0x86)
#if defined(GUNZIP_PATH)
# define WITH_DECOMPRESS
#endif
#ifdef WITH_DECOMPRESS
static int
decompress(const char *path, const void *mem, size_t size, void **memp, size_t *sizep)
{
const char *mem_in = mem;
char *mem_out = *memp, *new;
size_t off_in = 0, off_out = 0;
int i, n, mem_fds[2][2], err_fds[2], saved_errno;
struct pollfd pfds[2];
nfds_t npfds;
int status;
pid_t pid;
ssize_t r;
if (pipe(mem_fds[0]))
return -1;
if (pipe(mem_fds[1])) {
saved_errno = errno;
close(mem_fds[0][0]);
close(mem_fds[0][1]);
errno = saved_errno;
return -1;
}
if (pipe2(err_fds, O_CLOEXEC)) {
saved_errno = errno;
close(mem_fds[0][0]);
close(mem_fds[0][1]);
close(mem_fds[1][0]);
close(mem_fds[1][1]);
errno = saved_errno;
return -1;
}
switch ((pid = fork())) {
case -1:
saved_errno = errno;
close(mem_fds[0][0]);
close(mem_fds[0][1]);
close(mem_fds[1][0]);
close(mem_fds[1][1]);
close(err_fds[0]);
close(err_fds[1]);
errno = saved_errno;
return -1;
case 0:
close(err_fds[0]);
for (i = 0; i < 2; i++) {
if (mem_fds[i][i] != i) {
close(i);
dup2_again:
if (dup2(mem_fds[i][i], i) != i) {
if (errno == EINTR)
goto dup2_again;
write(err_fds[1], &errno, sizeof(errno));
return 255;
}
close(mem_fds[i][i]);
}
close(mem_fds[i][i ^ 1]);
}
execl(path, path, NULL);
write(err_fds[1], &errno, sizeof(errno));
return 255;
default:
break;
}
close(err_fds[1]);
close(mem_fds[0][0]);
close(mem_fds[1][1]);
memset(pfds, 0, sizeof(pfds));
pfds[0].fd = mem_fds[1][0];
pfds[1].fd = mem_fds[0][1];
pfds[0].events = POLLIN;
pfds[1].events = POLLOUT;
npfds = 2;
for (;;) {
n = poll(pfds, npfds, -1);
if (n <= 0) {
if (!n)
continue;
poll_loop_fail:
saved_errno = errno;
close(err_fds[1]);
if (mem_fds[0][1] >= 0)
close(mem_fds[0][1]);
close(mem_fds[1][0]);
kill(pid, SIGKILL);
waitpid(pid, NULL, 0);
free(mem_out);
errno = saved_errno;
return -1;
}
if (pfds[0].revents) {
if (*sizep == off_out) {
if (*sizep > SIZE_MAX - 4096) {
errno = ENOMEM;
goto poll_loop_fail;
}
new = realloc(mem_out, *sizep += 4096);
if (!new)
goto poll_loop_fail;
mem_out = new;
}
read_again:
r = read(pfds[0].fd, &mem_out[off_out], *sizep - off_out);
if (r <= 0) {
if (!r)
break;
if (errno == EINTR)
goto read_again;
goto poll_loop_fail;
}
off_out += (size_t)r;
}
if (npfds > 1 && pfds[1].revents) {
if (off_in == size) {
npfds -= 1;
close(mem_fds[0][1]);
mem_fds[0][1] = -1;
continue;
}
write_again:
r = write(pfds[1].fd, &mem_in[off_in], MIN(size - off_in, (size_t)1 << 16));
if (r <= 0) {
if (r < 0 && errno == EINTR)
goto write_again;
goto poll_loop_fail;
}
off_in += (size_t)r;
}
}
if (mem_fds[0][1] >= 0)
close(mem_fds[0][1]);
close(mem_fds[1][0]);
waitpid_again:
if (waitpid(pid, &status, 0) != pid) {
if (errno == EINTR)
goto waitpid_again;
saved_errno = errno;
close(err_fds[0]);
free(mem_out);
errno = saved_errno;
}
if (WIFEXITED(status) && WEXITSTATUS(status) == 255) {
if (read(err_fds[1], &errno, sizeof(errno)) == sizeof(errno)) {
goto child_failed;
} else {
errno = EIO;
goto child_failed;
}
} else if (status) {
errno = WIFEXITED(status) ? EBFONT : EPIPE;
goto child_failed;
} else if (off_in < size) {
errno = EPIPE;
child_failed:
saved_errno = errno;
close(err_fds[0]);
free(mem_out);
errno = saved_errno;
return -1;
}
close(err_fds[0]);
if (*sizep > off_out) {
new = realloc(mem_out, off_out);
if (new)
mem_out = new;
}
*memp = mem_out;
*sizep = off_out;
return 0;
}
#endif
int
libskrift_open_font___(LIBSKRIFT_FONT **fontp, const void *mem_static, void *mem_free, void *mem_unmap, size_t size)
{
const void *mem = mem_static ? mem_static : mem_free ? mem_free : mem_unmap;
const uint8_t *mem8 = mem;
void *new_mem = NULL;
#ifdef WITH_DECOMPRESS
void *free_on_failure = NULL;
void *old_free = NULL, *old_unmap = NULL;
size_t new_size = 0, old_size = size;
#endif
*fontp = calloc(1, sizeof(**fontp));
if (!*fontp)
return -1;
(*fontp)->refcount = 1;
#if defined(GUNZIP_PATH)
if (IS_GZIP()) {
if (decompress(GUNZIP_PATH, mem, size, &new_mem, &new_size))
return -1;
old_free = mem_free;
old_unmap = mem_unmap;
mem_static = NULL;
mem_unmap = NULL;
mem8 = mem = free_on_failure = mem_free = new_mem;
size = new_size;
}
#endif
if (IS_PSF_V1() || IS_PSF_V2()) {
/* TODO Convert to TTF */
}
(*fontp)->font = sft_loadmem(mem, size);
if (!(*fontp)->font) {
free(*fontp);
*fontp = NULL;
#ifdef WITH_DECOMPRESS
free(free_on_failure);
#endif
return -1;
}
if (mem_free) {
(*fontp)->memory_free = mem_free;
(*fontp)->memory_size = size;
} else if (mem_unmap) {
(*fontp)->memory_unmap = mem_unmap;
(*fontp)->memory_size = size;
}
#ifdef WITH_DECOMPRESS
if (old_free)
free(old_free);
else if (old_unmap)
munmap(old_unmap, old_size);
#endif
return 0;
}