diff options
Diffstat (limited to 'src/io.c')
-rw-r--r-- | src/io.c | 287 |
1 files changed, 240 insertions, 47 deletions
@@ -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; } |