#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include <errno.h>
#include <limits.h>
#include <pthread.h>
#include "htslib/hfile.h"
#include "hfile_internal.h"
#ifndef ENOTSUP
#define ENOTSUP EINVAL
#endif
#ifndef EOVERFLOW
#define EOVERFLOW ERANGE
#endif
#ifndef EPROTONOSUPPORT
#define EPROTONOSUPPORT ENOSYS
#endif
#ifndef SSIZE_MAX
#define SSIZE_MAX LONG_MAX
#endif
hFILE *hfile_init(size_t struct_size, const char *mode, size_t capacity)
{
hFILE *fp = (hFILE *) malloc(struct_size);
if (fp == NULL) goto error;
if (capacity == 0) capacity = 32768;
if (strchr(mode, 'r') && capacity > 32768) capacity = 32768;
fp->buffer = (char *) malloc(capacity);
if (fp->buffer == NULL) goto error;
fp->begin = fp->end = fp->buffer;
fp->limit = &fp->buffer[capacity];
fp->offset = 0;
fp->at_eof = 0;
fp->mobile = 1;
fp->readonly = (strchr(mode, 'r') && ! strchr(mode, '+'));
fp->has_errno = 0;
return fp;
error:
hfile_destroy(fp);
return NULL;
}
hFILE *hfile_init_fixed(size_t struct_size, const char *mode,
char *buffer, size_t buf_filled, size_t buf_size)
{
hFILE *fp = (hFILE *) malloc(struct_size);
if (fp == NULL) return NULL;
fp->buffer = fp->begin = buffer;
fp->end = &fp->buffer[buf_filled];
fp->limit = &fp->buffer[buf_size];
fp->offset = 0;
fp->at_eof = 1;
fp->mobile = 0;
fp->readonly = (strchr(mode, 'r') && ! strchr(mode, '+'));
fp->has_errno = 0;
return fp;
}
static const struct hFILE_backend mem_backend;
void hfile_destroy(hFILE *fp)
{
int save = errno;
if (fp) free(fp->buffer);
free(fp);
errno = save;
}
static inline int writebuffer_is_nonempty(hFILE *fp)
{
return fp->begin > fp->end;
}
static ssize_t refill_buffer(hFILE *fp)
{
ssize_t n;
if (fp->mobile && fp->begin > fp->buffer) {
fp->offset += fp->begin - fp->buffer;
memmove(fp->buffer, fp->begin, fp->end - fp->begin);
fp->end = &fp->buffer[fp->end - fp->begin];
fp->begin = fp->buffer;
}
if (fp->at_eof || fp->end == fp->limit) n = 0;
else {
n = fp->backend->read(fp, fp->end, fp->limit - fp->end);
if (n < 0) { fp->has_errno = errno; return n; }
else if (n == 0) fp->at_eof = 1;
}
fp->end += n;
return n;
}
int hfile_set_blksize(hFILE *fp, size_t bufsiz) {
char *buffer;
ptrdiff_t curr_used;
if (!fp) return -1;
curr_used = (fp->begin > fp->end ? fp->begin : fp->end) - fp->buffer;
if (bufsiz == 0) bufsiz = 32768;
if (bufsiz < curr_used)
return -1;
if (!(buffer = (char *) realloc(fp->buffer, bufsiz))) return -1;
fp->begin = buffer + (fp->begin - fp->buffer);
fp->end = buffer + (fp->end - fp->buffer);
fp->buffer = buffer;
fp->limit = &fp->buffer[bufsiz];
return 0;
}
int hgetc2(hFILE *fp)
{
return (refill_buffer(fp) > 0)? (unsigned char) *(fp->begin++) : EOF;
}
ssize_t hgetdelim(char *buffer, size_t size, int delim, hFILE *fp)
{
char *found;
size_t n, copied = 0;
ssize_t got;
if (size < 1 || size > SSIZE_MAX) {
fp->has_errno = errno = EINVAL;
return -1;
}
if (writebuffer_is_nonempty(fp)) {
fp->has_errno = errno = EBADF;
return -1;
}
--size;
do {
n = fp->end - fp->begin;
if (n > size - copied) n = size - copied;
found = memchr(fp->begin, delim, n);
if (found != NULL) {
n = found - fp->begin + 1;
memcpy(buffer + copied, fp->begin, n);
buffer[n + copied] = '\0';
fp->begin += n;
return n + copied;
}
memcpy(buffer + copied, fp->begin, n);
fp->begin += n;
copied += n;
if (copied == size) {
buffer[copied] = '\0';
return copied;
}
got = refill_buffer(fp);
} while (got > 0);
if (got < 0) return -1;
buffer[copied] = '\0';
return copied;
}
char *hgets(char *buffer, int size, hFILE *fp)
{
if (size < 1) {
fp->has_errno = errno = EINVAL;
return NULL;
}
return hgetln(buffer, size, fp) > 0 ? buffer : NULL;
}
ssize_t hpeek(hFILE *fp, void *buffer, size_t nbytes)
{
size_t n = fp->end - fp->begin;
while (n < nbytes) {
ssize_t ret = refill_buffer(fp);
if (ret < 0) return ret;
else if (ret == 0) break;
else n += ret;
}
if (n > nbytes) n = nbytes;
memcpy(buffer, fp->begin, n);
return n;
}
ssize_t hread2(hFILE *fp, void *destv, size_t nbytes, size_t nread)
{
const size_t capacity = fp->limit - fp->buffer;
int buffer_invalidated = 0;
char *dest = (char *) destv;
dest += nread, nbytes -= nread;
while (nbytes * 2 >= capacity && !fp->at_eof) {
ssize_t n = fp->backend->read(fp, dest, nbytes);
if (n < 0) { fp->has_errno = errno; return n; }
else if (n == 0) fp->at_eof = 1;
else buffer_invalidated = 1;
fp->offset += n;
dest += n, nbytes -= n;
nread += n;
}
if (buffer_invalidated) {
fp->offset += fp->begin - fp->buffer;
fp->begin = fp->end = fp->buffer;
}
while (nbytes > 0 && !fp->at_eof) {
size_t n;
ssize_t ret = refill_buffer(fp);
if (ret < 0) return ret;
n = fp->end - fp->begin;
if (n > nbytes) n = nbytes;
memcpy(dest, fp->begin, n);
fp->begin += n;
dest += n, nbytes -= n;
nread += n;
}
return nread;
}
static ssize_t flush_buffer(hFILE *fp)
{
const char *buffer = fp->buffer;
while (buffer < fp->begin) {
ssize_t n = fp->backend->write(fp, buffer, fp->begin - buffer);
if (n < 0) { fp->has_errno = errno; return n; }
buffer += n;
fp->offset += n;
}
fp->begin = fp->buffer; return 0;
}
int hflush(hFILE *fp)
{
if (flush_buffer(fp) < 0) return EOF;
if (fp->backend->flush) {
if (fp->backend->flush(fp) < 0) { fp->has_errno = errno; return EOF; }
}
return 0;
}
int hputc2(int c, hFILE *fp)
{
if (flush_buffer(fp) < 0) return EOF;
*(fp->begin++) = c;
return c;
}
ssize_t hwrite2(hFILE *fp, const void *srcv, size_t totalbytes, size_t ncopied)
{
const char *src = (const char *) srcv;
ssize_t ret;
const size_t capacity = fp->limit - fp->buffer;
size_t remaining = totalbytes - ncopied;
src += ncopied;
ret = flush_buffer(fp);
if (ret < 0) return ret;
while (remaining * 2 >= capacity) {
ssize_t n = fp->backend->write(fp, src, remaining);
if (n < 0) { fp->has_errno = errno; return n; }
fp->offset += n;
src += n, remaining -= n;
}
memcpy(fp->begin, src, remaining);
fp->begin += remaining;
return totalbytes;
}
int hputs2(const char *text, size_t totalbytes, size_t ncopied, hFILE *fp)
{
return (hwrite2(fp, text, totalbytes, ncopied) >= 0)? 0 : EOF;
}
off_t hseek(hFILE *fp, off_t offset, int whence)
{
off_t curpos, pos;
if (writebuffer_is_nonempty(fp) && fp->mobile) {
int ret = flush_buffer(fp);
if (ret < 0) return ret;
}
curpos = htell(fp);
if (whence == SEEK_CUR) {
if (curpos + offset < 0) {
fp->has_errno = errno = (offset < 0)? EINVAL : EOVERFLOW;
return -1;
}
whence = SEEK_SET;
offset = curpos + offset;
}
else if (! fp->mobile && whence == SEEK_END) {
size_t length = fp->end - fp->buffer;
if (offset > 0 || -offset > length) {
fp->has_errno = errno = EINVAL;
return -1;
}
whence = SEEK_SET;
offset = length + offset;
}
if (whence == SEEK_SET && (! fp->mobile || fp->readonly) &&
offset >= fp->offset && offset - fp->offset <= fp->end - fp->buffer) {
fp->begin = &fp->buffer[offset - fp->offset];
return offset;
}
pos = fp->backend->seek(fp, offset, whence);
if (pos < 0) { fp->has_errno = errno; return pos; }
fp->begin = fp->end = fp->buffer;
fp->at_eof = 0;
fp->offset = pos;
return pos;
}
int hclose(hFILE *fp)
{
int err = fp->has_errno;
if (writebuffer_is_nonempty(fp) && hflush(fp) < 0) err = fp->has_errno;
if (fp->backend->close(fp) < 0) err = errno;
hfile_destroy(fp);
if (err) {
errno = err;
return EOF;
}
else return 0;
}
void hclose_abruptly(hFILE *fp)
{
int save = errno;
if (fp->backend->close(fp) < 0) { }
hfile_destroy(fp);
errno = save;
}
#ifndef _WIN32
#include <sys/socket.h>
#include <sys/stat.h>
#define HAVE_STRUCT_STAT_ST_BLKSIZE
#else
#include <winsock2.h>
#define HAVE_CLOSESOCKET
#define HAVE_SETMODE
#endif
#include <fcntl.h>
#include <unistd.h>
typedef struct {
hFILE base;
int fd;
unsigned is_socket:1;
} hFILE_fd;
static ssize_t fd_read(hFILE *fpv, void *buffer, size_t nbytes)
{
hFILE_fd *fp = (hFILE_fd *) fpv;
ssize_t n;
do {
n = fp->is_socket? recv(fp->fd, buffer, nbytes, 0)
: read(fp->fd, buffer, nbytes);
} while (n < 0 && errno == EINTR);
return n;
}
static ssize_t fd_write(hFILE *fpv, const void *buffer, size_t nbytes)
{
hFILE_fd *fp = (hFILE_fd *) fpv;
ssize_t n;
do {
n = fp->is_socket? send(fp->fd, buffer, nbytes, 0)
: write(fp->fd, buffer, nbytes);
} while (n < 0 && errno == EINTR);
#ifdef _WIN32
if (n < 0 && errno == EINVAL &&
GetLastError() == ERROR_NO_DATA &&
GetFileType((HANDLE)_get_osfhandle(fp->fd)) == FILE_TYPE_PIPE) {
raise(SIGTERM);
}
#endif
return n;
}
static off_t fd_seek(hFILE *fpv, off_t offset, int whence)
{
hFILE_fd *fp = (hFILE_fd *) fpv;
return lseek(fp->fd, offset, whence);
}
static int fd_flush(hFILE *fpv)
{
int ret = 0;
do {
#ifdef HAVE_FDATASYNC
hFILE_fd *fp = (hFILE_fd *) fpv;
ret = fdatasync(fp->fd);
#elif defined(HAVE_FSYNC)
hFILE_fd *fp = (hFILE_fd *) fpv;
ret = fsync(fp->fd);
#endif
if (ret < 0 && (errno == EINVAL || errno == ENOTSUP)) ret = 0;
} while (ret < 0 && errno == EINTR);
return ret;
}
static int fd_close(hFILE *fpv)
{
hFILE_fd *fp = (hFILE_fd *) fpv;
int ret;
do {
#ifdef HAVE_CLOSESOCKET
ret = fp->is_socket? closesocket(fp->fd) : close(fp->fd);
#else
ret = close(fp->fd);
#endif
} while (ret < 0 && errno == EINTR);
return ret;
}
static const struct hFILE_backend fd_backend =
{
fd_read, fd_write, fd_seek, fd_flush, fd_close
};
static size_t blksize(int fd)
{
#ifdef HAVE_STRUCT_STAT_ST_BLKSIZE
struct stat sbuf;
if (fstat(fd, &sbuf) != 0) return 0;
return sbuf.st_blksize;
#else
return 0;
#endif
}
static hFILE *hopen_fd(const char *filename, const char *mode)
{
hFILE_fd *fp = NULL;
int fd = open(filename, hfile_oflags(mode), 0666);
if (fd < 0) goto error;
fp = (hFILE_fd *) hfile_init(sizeof (hFILE_fd), mode, blksize(fd));
if (fp == NULL) goto error;
fp->fd = fd;
fp->is_socket = 0;
fp->base.backend = &fd_backend;
return &fp->base;
error:
if (fd >= 0) { int save = errno; (void) close(fd); errno = save; }
hfile_destroy((hFILE *) fp);
return NULL;
}
static hFILE *hpreload(hFILE *fp) {
hFILE *mem_fp;
char *buf = NULL;
off_t buf_sz = 0, buf_a = 0, buf_inc = 8192, len;
for (;;) {
if (buf_a - buf_sz < 5000) {
buf_a += buf_inc;
char *t = realloc(buf, buf_a);
if (!t) goto err;
buf = t;
if (buf_inc < 1000000) buf_inc *= 1.3;
}
len = hread(fp, buf+buf_sz, buf_a-buf_sz);
if (len > 0)
buf_sz += len;
else
break;
}
if (len < 0) goto err;
mem_fp = hfile_init_fixed(sizeof(hFILE), "r", buf, buf_sz, buf_a);
if (!mem_fp) goto err;
mem_fp->backend = &mem_backend;
if (hclose(fp) < 0) {
hclose_abruptly(mem_fp);
goto err;
}
return mem_fp;
err:
free(buf);
hclose_abruptly(fp);
return NULL;
}
static int is_preload_url_remote(const char *url){
return hisremote(url + 8); }
static hFILE *hopen_preload(const char *url, const char *mode){
hFILE* fp = hopen(url + 8, mode);
return hpreload(fp);
}
hFILE *hdopen(int fd, const char *mode)
{
hFILE_fd *fp = (hFILE_fd*) hfile_init(sizeof (hFILE_fd), mode, blksize(fd));
if (fp == NULL) return NULL;
fp->fd = fd;
fp->is_socket = (strchr(mode, 's') != NULL);
fp->base.backend = &fd_backend;
return &fp->base;
}
static hFILE *hopen_fd_fileuri(const char *url, const char *mode)
{
if (strncmp(url, "file://localhost/", 17) == 0) url += 16;
else if (strncmp(url, "file:///", 8) == 0) url += 7;
else { errno = EPROTONOSUPPORT; return NULL; }
#ifdef _WIN32
if (url[0] == '/' && url[2] == ':' && url[3] == '/') url++;
#endif
return hopen_fd(url, mode);
}
static hFILE *hopen_fd_stdinout(const char *mode)
{
int fd = (strchr(mode, 'r') != NULL)? STDIN_FILENO : STDOUT_FILENO;
#if defined HAVE_SETMODE && defined O_BINARY
if (setmode(fd, O_BINARY) < 0) return NULL;
#endif
return hdopen(fd, mode);
}
int hfile_oflags(const char *mode)
{
int rdwr = 0, flags = 0;
const char *s;
for (s = mode; *s; s++)
switch (*s) {
case 'r': rdwr = O_RDONLY; break;
case 'w': rdwr = O_WRONLY; flags |= O_CREAT | O_TRUNC; break;
case 'a': rdwr = O_WRONLY; flags |= O_CREAT | O_APPEND; break;
case '+': rdwr = O_RDWR; break;
#ifdef O_CLOEXEC
case 'e': flags |= O_CLOEXEC; break;
#endif
#ifdef O_EXCL
case 'x': flags |= O_EXCL; break;
#endif
default: break;
}
#ifdef O_BINARY
flags |= O_BINARY;
#endif
return rdwr | flags;
}
#include "hts_internal.h"
typedef struct {
hFILE base;
} hFILE_mem;
static off_t mem_seek(hFILE *fpv, off_t offset, int whence)
{
errno = EINVAL;
return -1;
}
static int mem_close(hFILE *fpv)
{
return 0;
}
static const struct hFILE_backend mem_backend =
{
NULL, NULL, mem_seek, NULL, mem_close
};
static int cmp_prefix(const char *key, const char *s)
{
while (*key)
if (tolower_c(*s) != *key) return +1;
else s++, key++;
return 0;
}
static hFILE *create_hfile_mem(char* buffer, const char* mode, size_t buf_filled, size_t buf_size)
{
hFILE_mem *fp = (hFILE_mem *) hfile_init_fixed(sizeof(hFILE_mem), mode, buffer, buf_filled, buf_size);
if (fp == NULL)
return NULL;
fp->base.backend = &mem_backend;
return &fp->base;
}
static hFILE *hopen_mem(const char *url, const char *mode)
{
size_t length, size;
char *buffer;
const char *data, *comma = strchr(url, ',');
if (comma == NULL) { errno = EINVAL; return NULL; }
data = comma+1;
if (strchr(mode, 'r') == NULL) { errno = EROFS; return NULL; }
if (comma - url >= 7 && cmp_prefix(";base64", &comma[-7]) == 0) {
size = hts_base64_decoded_length(strlen(data));
buffer = malloc(size);
if (buffer == NULL) return NULL;
hts_decode_base64(buffer, &length, data);
}
else {
size = strlen(data) + 1;
buffer = malloc(size);
if (buffer == NULL) return NULL;
hts_decode_percent(buffer, &length, data);
}
hFILE* hf;
if(!(hf = create_hfile_mem(buffer, mode, length, size))){
free(buffer);
return NULL;
}
return hf;
}
hFILE *hopenv_mem(const char *filename, const char *mode, va_list args)
{
char* buffer = va_arg(args, char*);
size_t sz = va_arg(args, size_t);
va_end(args);
hFILE* hf;
if(!(hf = create_hfile_mem(buffer, mode, sz, sz))){
free(buffer);
return NULL;
}
return hf;
}
char *hfile_mem_get_buffer(hFILE *file, size_t *length) {
if (file->backend != &mem_backend) {
errno = EINVAL;
return NULL;
}
if (length)
*length = file->buffer - file->limit;
return file->buffer;
}
char *hfile_mem_steal_buffer(hFILE *file, size_t *length) {
char *buf = hfile_mem_get_buffer(file, length);
if (buf)
file->buffer = NULL;
return buf;
}
int hfile_plugin_init_mem(struct hFILE_plugin *self)
{
static const struct hFILE_scheme_handler handler =
{NULL, hfile_always_remote, "mem", 2000 + 50, hopenv_mem};
self->name = "mem";
hfile_add_scheme_handler("mem", &handler);
return 0;
}
#include "htslib/khash.h"
KHASH_MAP_INIT_STR(scheme_string, const struct hFILE_scheme_handler *)
static khash_t(scheme_string) *schemes = NULL;
struct hFILE_plugin_list {
struct hFILE_plugin plugin;
struct hFILE_plugin_list *next;
};
static struct hFILE_plugin_list *plugins = NULL;
static pthread_mutex_t plugins_lock = PTHREAD_MUTEX_INITIALIZER;
static void hfile_exit()
{
pthread_mutex_lock(&plugins_lock);
kh_destroy(scheme_string, schemes);
while (plugins != NULL) {
struct hFILE_plugin_list *p = plugins;
if (p->plugin.destroy) p->plugin.destroy();
#ifdef ENABLE_PLUGINS
if (p->plugin.obj) close_plugin(p->plugin.obj);
#endif
plugins = p->next;
free(p);
}
pthread_mutex_unlock(&plugins_lock);
pthread_mutex_destroy(&plugins_lock);
}
static inline int priority(const struct hFILE_scheme_handler *handler)
{
return handler->priority % 1000;
}
void hfile_add_scheme_handler(const char *scheme,
const struct hFILE_scheme_handler *handler)
{
int absent;
khint_t k = kh_put(scheme_string, schemes, scheme, &absent);
if (absent || priority(handler) > priority(kh_value(schemes, k))) {
kh_value(schemes, k) = handler;
}
}
static int init_add_plugin(void *obj, int (*init)(struct hFILE_plugin *),
const char *pluginname)
{
struct hFILE_plugin_list *p = malloc (sizeof (struct hFILE_plugin_list));
if (p == NULL) abort();
p->plugin.api_version = 1;
p->plugin.obj = obj;
p->plugin.name = NULL;
p->plugin.destroy = NULL;
int ret = (*init)(&p->plugin);
if (ret != 0) {
hts_log_debug("Initialisation failed for plugin \"%s\": %d", pluginname, ret);
free(p);
return ret;
}
hts_log_debug("Loaded \"%s\"", pluginname);
p->next = plugins, plugins = p;
return 0;
}
static void load_hfile_plugins()
{
static const struct hFILE_scheme_handler
data = { hopen_mem, hfile_always_local, "built-in", 80 },
file = { hopen_fd_fileuri, hfile_always_local, "built-in", 80 },
preload = { hopen_preload, is_preload_url_remote, "built-in", 80 };
schemes = kh_init(scheme_string);
if (schemes == NULL) abort();
hfile_add_scheme_handler("data", &data);
hfile_add_scheme_handler("file", &file);
hfile_add_scheme_handler("preload", &preload);
init_add_plugin(NULL, hfile_plugin_init_net, "knetfile");
init_add_plugin(NULL, hfile_plugin_init_mem, "mem");
#ifdef ENABLE_PLUGINS
struct hts_path_itr path;
const char *pluginname;
hts_path_itr_setup(&path, NULL, NULL, "hfile_", 6, NULL, 0);
while ((pluginname = hts_path_itr_next(&path)) != NULL) {
void *obj;
int (*init)(struct hFILE_plugin *) = (int (*)(struct hFILE_plugin *))
load_plugin(&obj, pluginname, "hfile_plugin_init");
if (init) {
if (init_add_plugin(obj, init, pluginname) != 0)
close_plugin(obj);
}
}
#else
#ifdef HAVE_LIBCURL
init_add_plugin(NULL, hfile_plugin_init_libcurl, "libcurl");
#endif
#ifdef ENABLE_GCS
init_add_plugin(NULL, hfile_plugin_init_gcs, "gcs");
#endif
#ifdef ENABLE_S3
init_add_plugin(NULL, hfile_plugin_init_s3, "s3");
#endif
#endif
(void) atexit(hfile_exit);
}
static hFILE *hopen_unknown_scheme(const char *fname, const char *mode)
{
hFILE *fp = hopen_fd(fname, mode);
if (fp == NULL && errno == ENOENT) errno = EPROTONOSUPPORT;
return fp;
}
static const struct hFILE_scheme_handler *find_scheme_handler(const char *s)
{
static const struct hFILE_scheme_handler unknown_scheme =
{ hopen_unknown_scheme, hfile_always_local, "built-in", 0 };
char scheme[12];
int i;
for (i = 0; i < sizeof scheme; i++)
if (isalnum_c(s[i]) || s[i] == '+' || s[i] == '-' || s[i] == '.')
scheme[i] = tolower_c(s[i]);
else if (s[i] == ':') break;
else return NULL;
if (i <= 1 || i >= sizeof scheme) return NULL;
scheme[i] = '\0';
pthread_mutex_lock(&plugins_lock);
if (! schemes) load_hfile_plugins();
pthread_mutex_unlock(&plugins_lock);
khint_t k = kh_get(scheme_string, schemes, scheme);
return (k != kh_end(schemes))? kh_value(schemes, k) : &unknown_scheme;
}
hFILE *hopen(const char *fname, const char *mode, ...)
{
const struct hFILE_scheme_handler *handler = find_scheme_handler(fname);
if (handler) {
if (strchr(mode, ':') == NULL
|| handler->priority < 2000
|| handler->vopen == NULL) {
return handler->open(fname, mode);
}
else {
hFILE *fp;
va_list arg;
va_start(arg, mode);
fp = handler->vopen(fname, mode, arg);
va_end(arg);
return fp;
}
}
else if (strcmp(fname, "-") == 0) return hopen_fd_stdinout(mode);
else return hopen_fd(fname, mode);
}
int hfile_always_local (const char *fname) { return 0; }
int hfile_always_remote(const char *fname) { return 1; }
int hisremote(const char *fname)
{
const struct hFILE_scheme_handler *handler = find_scheme_handler(fname);
return handler? handler->isremote(fname) : 0;
}