summaryrefslogtreecommitdiff
path: root/src/io.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/io.c')
-rw-r--r--src/io.c287
1 files changed, 240 insertions, 47 deletions
diff --git a/src/io.c b/src/io.c
index a4dd962..b5bdc08 100644
--- a/src/io.c
+++ b/src/io.c
@@ -14,6 +14,7 @@
#include <stdarg.h>
#include <stdbool.h>
#include <stddef.h>
+#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -25,12 +26,18 @@
# include <dirent.h>
# include <unistd.h>
#endif
+#if __linux__
+# include <sys/sendfile.h>
+#endif
#include <libHX/ctype_helper.h>
#include <libHX/defs.h>
#include <libHX/io.h>
#include <libHX/misc.h>
#include <libHX/string.h>
#include "internal.h"
+#ifndef O_CLOEXEC
+# define O_CLOEXEC 0
+#endif
struct HXdir {
#if defined _WIN32
@@ -159,46 +166,72 @@ EXPORT_SYMBOL void HXdir_close(struct HXdir *d)
EXPORT_SYMBOL int HX_copy_file(const char *src, const char *dest,
unsigned int opts, ...)
{
- char buf[MAXLNLEN];
+ static const size_t bufsize = 0x10000;
+ void *buf;
unsigned int extra_flags = 0;
- int dd, eax = 0, sd, l;
+ int srcfd, dstfd;
- if ((sd = open(src, O_RDONLY | O_BINARY)) < 0)
+ buf = malloc(bufsize);
+ if (buf == nullptr)
return -errno;
+ srcfd = open(src, O_RDONLY | O_BINARY);
+ if (srcfd < 0) {
+ free(buf);
+ return -errno;
+ }
if (opts & HXF_KEEP)
extra_flags = O_EXCL;
- dd = open(dest, O_WRONLY | O_BINARY | O_CREAT | O_TRUNC |
- extra_flags, S_IRUGO | S_IWUGO);
- if (dd < 0) {
- eax = errno;
- close(sd);
- errno = eax;
- if (extra_flags != 0 && eax == EEXIST)
- return 1;
- return -errno;
+ dstfd = open(dest, O_WRONLY | O_BINARY | O_CREAT | O_TRUNC |
+ extra_flags, S_IRUGO | S_IWUGO);
+ if (dstfd < 0) {
+ int saved_errno = errno;
+ free(buf);
+ close(srcfd);
+ return -(errno = saved_errno);
}
- while ((l = read(sd, buf, MAXLNLEN)) > 0 && write(dd, buf, l) > 0)
- ;
- close(sd);
-
if (opts & (HXF_UID | HXF_GID)) {
struct stat sb;
long uid, gid;
va_list argp;
va_start(argp, opts);
- fstat(dd, &sb);
+ if (fstat(dstfd, &sb) < 0) {
+ int saved_errno = errno;
+ unlink(dest);
+ close(dstfd);
+ close(srcfd);
+ return -(errno = saved_errno);
+ }
uid = sb.st_uid;
gid = sb.st_gid;
if (opts & HXF_UID) uid = va_arg(argp, long);
if (opts & HXF_GID) gid = va_arg(argp, long);
- if (fchown(dd, uid, gid) < 0)
- {};
+ if (fchown(dstfd, uid, gid) < 0) {
+ int saved_errno = errno;
+ unlink(dest);
+ close(dstfd);
+ close(srcfd);
+ return -(errno = saved_errno);
+ }
va_end(argp);
}
- close(dd);
+
+ while (true) {
+ ssize_t rdret = HX_sendfile(dstfd, srcfd, SIZE_MAX);
+ if (rdret == 0)
+ break;
+ if (rdret < 0 && errno != EINTR) {
+ int saved_errno = errno;
+ close(srcfd);
+ close(dstfd);
+ return -(errno = saved_errno);
+ }
+ }
+ close(srcfd);
+ close(dstfd);
+ free(buf);
return 1;
}
@@ -300,27 +333,42 @@ EXPORT_SYMBOL int HX_mkdir(const char *idir, unsigned int mode)
/* Readlink - with a trailing zero (provided by HXmc) */
EXPORT_SYMBOL int HX_readlink(hxmc_t **target, const char *path)
{
- bool dnull = *target == NULL;
- char *tb;
- int ret;
+ bool allocate = *target == NULL;
+ size_t linkbuf_size;
- if (dnull) {
- *target = HXmc_meminit(NULL, PATH_MAX);
+ if (allocate) {
+ linkbuf_size = 32;
+ *target = HXmc_meminit(NULL, 32);
if (*target == NULL)
return -errno;
+ } else {
+ linkbuf_size = HXmc_length(*target);
}
- tb = *target;
- ret = readlink(path, tb, PATH_MAX);
- if (ret < 0) {
- ret = -errno;
- if (!dnull) {
- HXmc_free(*target);
- *target = NULL;
+ while (true) {
+ ssize_t ret = readlink(path, *target, linkbuf_size);
+ if (ret < 0) {
+ int saved_errno = errno;
+ if (allocate) {
+ HXmc_free(*target);
+ *target = nullptr;
+ }
+ return -(errno = saved_errno);
+ }
+ if (static_cast(size_t, ret) < linkbuf_size) {
+ HXmc_setlen(target, ret);
+ return ret;
+ }
+ linkbuf_size *= 2;
+ if (HXmc_setlen(target, linkbuf_size) == NULL) {
+ int saved_errno = errno;
+ if (allocate) {
+ HXmc_free(*target);
+ *target = nullptr;
+ }
+ return -(errno = saved_errno);
}
- return ret;
}
- HXmc_setlen(target, ret);
- return ret;
+ return 0;
}
/**
@@ -533,31 +581,176 @@ EXPORT_SYMBOL int HX_rrmdir(const char *dir)
EXPORT_SYMBOL ssize_t HXio_fullread(int fd, void *vbuf, size_t size)
{
char *buf = vbuf;
- size_t rem = size;
- ssize_t ret;
+ size_t done = 0;
+ if (size > SSIZE_MAX)
+ size = SSIZE_MAX;
- while (rem > 0) {
- ret = read(fd, buf, rem);
+ while (done < size) {
+ ssize_t ret = read(fd, buf, size - done);
if (ret < 0)
return ret;
- rem -= ret;
+ else if (ret == 0)
+ break;
+ done += ret;
buf += ret;
}
- return size;
+ return done;
}
EXPORT_SYMBOL ssize_t HXio_fullwrite(int fd, const void *vbuf, size_t size)
{
const char *buf = vbuf;
- size_t rem = size;
- ssize_t ret;
+ size_t done = 0;
+ if (size > SSIZE_MAX)
+ size = SSIZE_MAX;
- while (rem > 0) {
- ret = write(fd, buf, rem);
+ while (done < size) {
+ ssize_t ret = write(fd, buf, size - done);
if (ret < 0)
return ret;
- rem -= ret;
+ else if (ret == 0)
+ break;
+ done += ret;
buf += ret;
}
- return size;
+ return done;
+}
+
+#if __linux__
+static ssize_t HX_sendfile_linux(int dst, int src, size_t count)
+{
+ long pagesize = sysconf(_SC_PAGE_SIZE);
+ size_t xfersize;
+ ssize_t ret, xferd = 0;
+
+ if (pagesize < 0)
+ pagesize = 4096;
+ xfersize = SSIZE_MAX - pagesize;
+ if (count > xfersize)
+ count = xfersize;
+ while ((ret = sendfile(dst, src, nullptr, count)) > 0)
+ xferd += ret;
+ if (xferd > 0)
+ return xferd;
+ if (ret < 0)
+ return -errno;
+ return 0;
+}
+#endif
+
+static ssize_t HX_sendfile_rw(int dst, int src, size_t count)
+{
+ static const size_t bufsize = 0x10000;
+ size_t xferd = 0;
+ ssize_t ret;
+ void *buf = malloc(bufsize);
+ if (buf == nullptr)
+ return -ENOMEM;
+ if (count > SSIZE_MAX)
+ count = SSIZE_MAX;
+ while (count > 0) {
+ size_t readsize = bufsize;
+ if (count < readsize)
+ readsize = count;
+ ret = HXio_fullread(src, buf, readsize);
+ if (ret < 0) {
+ errno = -ret;
+ break;
+ }
+ ret = HXio_fullwrite(dst, buf, ret);
+ if (ret < 0) {
+ errno = -ret;
+ break;
+ }
+ xferd += ret;
+ count -= ret;
+ }
+ if (xferd > 0)
+ return xferd;
+ if (ret < 0)
+ return -errno;
+ return 0;
+}
+
+EXPORT_SYMBOL ssize_t HX_sendfile(int dst, int src, size_t count)
+{
+#if __linux__
+ ssize_t ret = HX_sendfile_linux(dst, src, count);
+ if (ret != -ENOSYS)
+ return ret;
+#endif
+ return HX_sendfile_rw(dst, src, count);
+}
+
+EXPORT_SYMBOL char *HX_slurp_fd(int fd, size_t *outsize)
+{
+ struct stat sb;
+ if (fstat(fd, &sb) < 0)
+ return NULL;
+ if (sb.st_size == 0) {
+ /* e.g. ttys (S_ISCHR) or special procfs files */
+ size_t bufsize = 4096, offset = 0;
+ char *buf = malloc(bufsize);
+ if (buf == nullptr)
+ return nullptr;
+ ssize_t rdret;
+ while ((rdret = read(fd, buf, bufsize - 1 - offset)) > 0) {
+ offset += rdret;
+ if (bufsize - offset >= 4095)
+ /* any value would work, but >=1 is not all that efficient */
+ continue;
+ if (bufsize > SSIZE_MAX)
+ /* No more doubling */
+ break;
+ bufsize *= 2;
+ void *nbuf = realloc(buf, bufsize + 1);
+ if (nbuf == nullptr) {
+ int se = errno;
+ free(buf);
+ errno = se;
+ return nullptr;
+ }
+ buf = nbuf;
+ }
+ buf[offset] = '\0';
+ if (outsize != nullptr)
+ *outsize = offset;
+ return buf;
+ }
+ size_t fsize = sb.st_size; /* may truncate from loff_t to size_t */
+ if (fsize == SIZE_MAX)
+ --fsize;
+ char *buf = malloc(fsize + 1);
+ if (buf == NULL)
+ return NULL;
+ ssize_t rdret = HXio_fullread(fd, buf, fsize);
+ if (rdret < 0) {
+ int se = errno;
+ free(buf);
+ errno = se;
+ return NULL;
+ }
+ buf[rdret] = '\0';
+ if (outsize != NULL)
+ *outsize = rdret;
+ return buf;
+}
+
+EXPORT_SYMBOL char *HX_slurp_file(const char *file, size_t *outsize)
+{
+ int fd = open(file, O_RDONLY | O_BINARY | O_CLOEXEC);
+ if (fd < 0)
+ return NULL;
+ size_t tmpsize;
+ if (outsize == NULL)
+ outsize = &tmpsize;
+ char *buf = HX_slurp_fd(fd, outsize);
+ if (buf == NULL) {
+ int se = errno;
+ close(fd);
+ errno = se;
+ return NULL;
+ }
+ close(fd);
+ return buf;
}