these functions help with allocating buffers, aligning,
reading and writing files opened with O_DIRECT.
Signed-off-by: Claudio Fontana <cfontana(a)suse.de>
---
src/libvirt_private.syms | 8 ++
src/util/virfile.c | 249 +++++++++++++++++++++++++++++++++++++++
src/util/virfile.h | 11 ++
3 files changed, 268 insertions(+)
diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms
index bfedd85326..1b3930dfb4 100644
--- a/src/libvirt_private.syms
+++ b/src/libvirt_private.syms
@@ -2240,7 +2240,15 @@ virFileComparePaths;
virFileCopyACLs;
virFileDataSync;
virFileDeleteTree;
+virFileDirectAlign;
+virFileDirectBufferNew;
+virFileDirectCopyBuf;
virFileDirectFdFlag;
+virFileDirectRead;
+virFileDirectReadCopy;
+virFileDirectReadLim;
+virFileDirectWrite;
+virFileDirectWriteLim;
virFileExists;
virFileFclose;
virFileFdopen;
diff --git a/src/util/virfile.c b/src/util/virfile.c
index e4522b5f67..03a7cdc9bf 100644
--- a/src/util/virfile.c
+++ b/src/util/virfile.c
@@ -192,6 +192,210 @@ virFileDirectFdFlag(void)
return O_DIRECT ? O_DIRECT : -1;
}
+/**
+ * virFileDirectAlign: align a value, which may include a pointer.
+ *
+ * @value: the value that will be incremented to reach alignment
+ *
+ * Returns the aligned value.
+ */
+uintptr_t
+virFileDirectAlign(uintptr_t value)
+{
+ return (value + VIR_FILE_DIRECT_ALIGN_MASK) & ~VIR_FILE_DIRECT_ALIGN_MASK;
+}
+
+/**
+ * virFileDirectRead: perform a single aligned read.
+ * @fd: O_DIRECT file descriptor
+ * @buf: aligned buffer to read into
+ * @count: the desired read size. Note that if unaligned,
+ * extra bytes will be read based on alignment.
+ *
+ * Note that buf should be able to contain count plus the alignment!
+ *
+ * Returns < 0 and errno set on error, or the number of bytes read,
+ * which may be smaller or even greater than count.
+ */
+ssize_t
+virFileDirectRead(int fd, void *buf, size_t count)
+{
+ size_t aligned_count = virFileDirectAlign(count);
+ while (count > 0) {
+ ssize_t r = read(fd, buf, aligned_count);
+ if (r < 0 && errno == EINTR)
+ continue;
+ return r;
+ }
+ return 0;
+}
+
+/**
+ * virFileDirectReadLim: perform multiple aligned reads up to limit
+ * @fd: O_DIRECT file descriptor
+ * @buf: aligned buffer to read into
+ * @limit: the desired limit to read into buffer.
+ * Note that if unaligned, extra bytes will be read based on alignment.
+ *
+ * Note that buf should be able to contain limit plus the alignment!
+ *
+ * Compared with virFileDirectRead, this function reads potentially
+ * multiple times to fill up to buffer with up to limit bytes plus alignment,
+ * so on success it does not return a number of bytes smaller than limit.
+ *
+ * Returns < 0 and errno set on error,
+ * and on success the number of bytes read, which may be greater than limit
+ * due to alignment.
+ */
+ssize_t
+virFileDirectReadLim(int fd, void *buf, size_t lim)
+{
+ ssize_t nread = 0;
+
+ while (lim > 0) {
+ ssize_t r = virFileDirectRead(fd, buf, lim);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return nread;
+ buf = (char *)buf + r;
+ nread += r;
+ if (lim < r)
+ break;
+ lim -= r;
+ }
+ return nread;
+}
+
+/**
+ * virFileDirectCopyBuf: copy a buffer contents to destination in memory
+ * @buf: aligned buffer to copy from
+ * @count: amount of data to copy
+ * @dst: the destination in memory
+ * @dst_len: the maximum the destination can hold.
+ *
+ * copies up to count bytes from buf into dst, but not more than dst_len.
+ * increments buf pointer and dst pointer, as well as decrementing the
+ * maximum the destination can hold (dst_len).
+ *
+ * Returns the amount copied.
+ */
+size_t
+virFileDirectCopyBuf(void **buf, size_t count, void **dst, size_t *dst_len)
+{
+ size_t to_copy;
+
+ to_copy = count > *dst_len ? *dst_len : count;
+ memcpy(*dst, *buf, to_copy);
+ *dst_len -= to_copy;
+ *(char **)dst += to_copy;
+ *(char **)buf += to_copy;
+ return to_copy;
+}
+
+/**
+ * virFileDirectReadCopy: read an fd and copy contents to memory
+ * @fd: O_DIRECT file descriptor
+ * @buf: aligned buffer to read the fd into, and then copy from
+ * @buflen: size of the buffer
+ * @dst: the destination in memory
+ * @dst_len: the maximum the destination can hold.
+ *
+ * reads data from the fd file descriptor into the buffer,
+ * and then copy to the destination, filling it up to dst_len.
+ *
+ * Returns < 0 and errno set on error,
+ * or the number of bytes read, which may be past the requested dst_len,
+ * or may be smaller if the fd does not contain enough data.
+ *
+ * The buf pointer is updated to point to eventual exccess data in the buffer.
+ */
+ssize_t
+virFileDirectReadCopy(int fd, void **buf, size_t buflen, void *dst, size_t dst_len)
+{
+ ssize_t nread = 0;
+ void *d = dst;
+ char *s = *buf;
+
+ while (dst_len > 0) {
+ ssize_t rv;
+ *buf = s;
+ rv = virFileDirectReadLim(fd, s, dst_len < buflen ? dst_len : buflen);
+ if (rv < 0)
+ return rv;
+ if (rv == 0)
+ return nread; /* not enough data to fulfill request */
+
+ nread += rv; /* note, we might read past the requested len */
+ virFileDirectCopyBuf(buf, rv, &d, &dst_len);
+ }
+ return nread;
+}
+
+/**
+ * virFileDirectWrite: perform a single aligned write.
+ * @fd: O_DIRECT file descriptor to write to
+ * @buf: aligned buffer to write from
+ * @count: the desired write size. Note that if unaligned,
+ * extra 0 bytes will be written based on alignment.
+ *
+ * Returns < 0 and errno set on error, or the number of bytes written,
+ * which may be smaller or even greater than count.
+ */
+ssize_t
+virFileDirectWrite(int fd, void *buf, size_t count)
+{
+ size_t aligned_count = virFileDirectAlign(count);
+ if (aligned_count > count) {
+ memset((char *)buf + count, 0, aligned_count - count);
+ }
+ while (count > 0) {
+ ssize_t r = write(fd, buf, aligned_count); /* sc_avoid_write */
+ if (r < 0 && errno == EINTR)
+ continue;
+ return r;
+ }
+ return 0;
+}
+
+/**
+ * virFileDirectWriteLim: perform multiple aligned writes up to limit
+ * @fd: O_DIRECT file descriptor
+ * @buf: aligned buffer to write from
+ * @limit: the desired limit for the total write size
+ * Note that if unaligned, extra bytes will be written based on alignment.
+ *
+ * Note that buf should be able to contain limit plus the alignment!
+ *
+ * Compared with virFileDirectWrite, this function writes potentially
+ * multiple times to drain the buffer up to the limit bytes plus alignment,
+ * so on success it does not return a number of bytes smaller than limit.
+ *
+ * Returns < 0 and errno set on error,
+ * and on success the number of bytes written, which may be greater than limit
+ * due to alignment.
+ */
+
+ssize_t
+virFileDirectWriteLim(int fd, void *buf, size_t lim)
+{
+ ssize_t nwritten = 0;
+
+ while (lim > 0) {
+ ssize_t r = virFileDirectWrite(fd, buf, lim);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return nwritten;
+ buf = (char *)buf + r;
+ nwritten += r;
+ if (lim < r)
+ break;
+ lim -= r;
+ }
+ return nwritten;
+}
+
/* Opaque type for managing a wrapper around a fd. For now,
* read-write is not supported, just a single direction. */
struct _virFileWrapperFd {
@@ -202,6 +406,41 @@ struct _virFileWrapperFd {
#ifndef WIN32
+/**
+ * virFileDirectBufferNew: allocate a buffer and return the first
+ * block-aligned address in it.
+ *
+ * @alloc_base: pointer to the to-be-allocated memory buffer.
+ * @buflen: desired length, which should be greater than alignment.
+ *
+ * Allocate a memory area large enough to accommodate an aligned
+ * buffer of size buflen.
+ *
+ * On success, *alloc_base is set to the newly allocated memory,
+ * and the aligned buffer within it is returned.
+ *
+ * On failure, *alloc_base is set to NULL and the function
+ * returns NULL.
+ */
+void *
+virFileDirectBufferNew(void **alloc_base, size_t buflen)
+{
+ void *buf;
+ buflen = virFileDirectAlign(buflen);
+
+# if WITH_POSIX_MEMALIGN
+ if (posix_memalign(alloc_base, VIR_FILE_DIRECT_ALIGN_MASK + 1, buflen)) {
+ *alloc_base = NULL;
+ return NULL;
+ }
+ buf = *alloc_base;
+# else
+ *alloc_base = g_malloc(buflen + VIR_FILE_DIRECT_ALIGN_MASK);
+ buf = virFileDirectAlign((uintptr_t)*alloc_base);
+# endif
+ return buf;
+}
+
# ifdef __linux__
/**
@@ -372,6 +611,16 @@ virFileWrapperFdNew(int *fd, const char *name, unsigned int flags)
return NULL;
}
#else /* WIN32 */
+
+void *
+virFileDirectBufferNew(void **alloc_base G_GNUC_UNUSED,
+ size_t buflen G_GNUC_UNUSED)
+{
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("virFileDirectBufferNew unsupported on this platform"));
+ return NULL;
+}
+
virFileWrapperFd *
virFileWrapperFdNew(int *fd G_GNUC_UNUSED,
const char *name G_GNUC_UNUSED,
diff --git a/src/util/virfile.h b/src/util/virfile.h
index 8e378efe30..9af3dc5528 100644
--- a/src/util/virfile.h
+++ b/src/util/virfile.h
@@ -104,6 +104,17 @@ typedef struct _virFileWrapperFd virFileWrapperFd;
int virFileDirectFdFlag(void);
+#define VIR_FILE_DIRECT_ALIGN_MASK ((64 * 1024) - 1)
+
+void *virFileDirectBufferNew(void **alloc_base, size_t buflen);
+uintptr_t virFileDirectAlign(uintptr_t value);
+ssize_t virFileDirectRead(int fd, void *buf, size_t count);
+ssize_t virFileDirectWrite(int fd, void *buf, size_t count);
+ssize_t virFileDirectReadLim(int fd, void *buf, size_t lim);
+ssize_t virFileDirectWriteLim(int fd, void *buf, size_t lim);
+size_t virFileDirectCopyBuf(void **buf, size_t count, void **dst, size_t *dst_len);
+ssize_t virFileDirectReadCopy(int fd, void **buf, size_t buflen, void *dst, size_t
dst_len);
+
typedef enum {
VIR_FILE_WRAPPER_BYPASS_CACHE = (1 << 0),
VIR_FILE_WRAPPER_NON_BLOCKING = (1 << 1),
--
2.26.2