summaryrefslogtreecommitdiff
path: root/src/io.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/io.c')
-rw-r--r--src/io.c347
1 files changed, 281 insertions, 66 deletions
diff --git a/src/io.c b/src/io.c
index a4dd962..720a9c5 100644
--- a/src/io.c
+++ b/src/io.c
@@ -7,6 +7,9 @@
* General Public License as published by the Free Software Foundation;
* either version 2.1 or (at your option) any later version.
*/
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
@@ -14,6 +17,7 @@
#include <stdarg.h>
#include <stdbool.h>
#include <stddef.h>
+#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -25,12 +29,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
@@ -47,22 +57,23 @@ struct HXdir {
static int mkdir_gen(const char *d, unsigned int mode)
{
struct stat sb;
- if (lstat(d, &sb) < 0) {
#if defined(_WIN32)
- if (mkdir(d) < 0)
+ if (mkdir(d) == 0)
#else
- if (mkdir(d, mode) < 0) /* use umask() for permissions */
+ if (mkdir(d, mode) == 0) /* use umask() for permissions */
#endif
- return -errno;
- } else {
+ return 1;
+ if (errno != EEXIST)
+ return -errno;
+ if (lstat(d, &sb) == 0) {
#if defined(_WIN32)
- if ((sb.st_mode & S_IFDIR) != S_IFDIR)
+ if (sb.st_mode & S_IFDIR)
#else
- if (!S_ISDIR(sb.st_mode))
+ if (S_ISDIR(sb.st_mode))
#endif
- return -errno;
+ return 0;
}
- return 1;
+ return -EEXIST;
}
EXPORT_SYMBOL struct HXdir *HXdir_open(const char *s)
@@ -159,46 +170,77 @@ 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);
+ free(buf);
+ va_end(argp);
+ 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);
+ free(buf);
+ va_end(argp);
+ 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);
+ free(buf);
+ return -(errno = saved_errno);
+ }
+ }
+ close(srcfd);
+ close(dstfd);
+ free(buf);
return 1;
}
@@ -228,8 +270,8 @@ EXPORT_SYMBOL int HX_copy_dir(const char *src, const char *dest,
continue;
snprintf(fsrc, MAXFNLEN, "%s/%s", src, fn);
snprintf(fdest, MAXFNLEN, "%s/%s", dest, fn);
-
- lstat(fsrc, &sb);
+ if (lstat(fsrc, &sb) < 0)
+ continue;
sb.st_mode &= 0777; /* clear SUID/GUID/Sticky bits */
if (S_ISREG(sb.st_mode)) {
@@ -242,17 +284,20 @@ EXPORT_SYMBOL int HX_copy_dir(const char *src, const char *dest,
memset(pt, '\0', MAXFNLEN);
if (readlink(fsrc, pt, MAXFNLEN - 1) < MAXFNLEN - 1)
if (symlink(pt, fdest) < 0)
- {};
+ /* ignore */;
} else if (S_ISBLK(sb.st_mode) || S_ISCHR(sb.st_mode)) {
- mknod(fdest, sb.st_mode, sb.st_dev);
+ if (mknod(fdest, sb.st_mode, sb.st_dev) < 0)
+ /* ignore */;
} else if (S_ISFIFO(sb.st_mode)) {
- mkfifo(fdest, sb.st_mode);
+ if (mkfifo(fdest, sb.st_mode) < 0)
+ /* ignore */;
}
if (lchown(fdest, uid, gid) < 0)
- {};
+ /* ignore */;
if (!S_ISLNK(sb.st_mode))
- chmod(fdest, sb.st_mode);
+ if (chmod(fdest, sb.st_mode) < 0)
+ /* ignore */;
}
HXdir_close(dt);
@@ -285,12 +330,12 @@ EXPORT_SYMBOL int HX_mkdir(const char *idir, unsigned int mode)
if (dir[i] == '/') {
strncpy(buf, dir, i);
buf[i] = '\0';
- if ((v = mkdir_gen(buf, mode)) <= 0)
+ if ((v = mkdir_gen(buf, mode)) < 0)
return v;
} else if (i == len - 1) {
strncpy(buf, dir, len);
buf[len] = '\0';
- if ((v = mkdir_gen(buf, mode)) <= 0)
+ if ((v = mkdir_gen(buf, mode)) < 0)
return v;
}
}
@@ -300,27 +345,43 @@ 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) {
+ (*target)[ret] = '\0'; // please cov-scan
+ HXmc_setlen(target, ret); // \0 set here anyway
+ 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;
}
/**
@@ -449,7 +510,7 @@ EXPORT_SYMBOL int HX_realpath(hxmc_t **dest_pptr, const char *path,
ret = HX_realpath_symres(&state, path);
if (ret == -EINVAL)
continue;
- else if (ret < 0)
+ else if (ret <= 0)
goto out;
path = state.path;
}
@@ -533,31 +594,185 @@ 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;
+ }
+ free(buf);
+ 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 + offset, bufsize - 1 - offset)) > 0) {
+ offset += rdret;
+ /*
+ * Make it so that the next read call is not called
+ * with an exceptionally small size.
+ */
+ if (bufsize - offset >= 4095)
+ 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;
+#ifdef HAVE_POSIX_FADVISE
+ if (fsize > 0 && posix_fadvise(fd, 0, fsize,
+ POSIX_FADV_SEQUENTIAL) != 0)
+ /* ignore */;
+#endif
+ 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;
}