Add new file utilities library

Start of osutils/file, with functions to read a file and write a
temporary file right now.

Intention of this is to provide common implementation of these
functions so we can stop duplicating across C projects.

Modeled the API for li_read_file off of vb2_read_file since it does
not suck to use and it's nice to align with an API we already have.

BUG=none
TEST=make run-tests

Change-Id: I552a3825526d9ff966a9b1cd305f7a0615f1882e
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/lithium/+/2151101
Tested-by: Jack Rosenthal <jrosenth@chromium.org>
Reviewed-by: Paul Fagerburg <pfagerburg@chromium.org>
Commit-Queue: Jack Rosenthal <jrosenth@chromium.org>
diff --git a/include/osutils/file.h b/include/osutils/file.h
new file mode 100644
index 0000000..c2f8cae
--- /dev/null
+++ b/include/osutils/file.h
@@ -0,0 +1,62 @@
+/* Copyright 2020 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef LITHIUM_OSUTILS_FILE_H_
+#define LITHIUM_OSUTILS_FILE_H_
+
+/**
+ * File Utilities
+ * ==============
+ */
+
+#include <stddef.h>
+
+/**
+ * Read a regular file (by name) into a newly allocated buffer. The
+ * buffer should be freed by the caller.
+ *
+ * :param path: The path to the file.
+ * :param data_ptr: Output pointer for newly allocated buffer.
+ *
+ * :return: On success, the number of bytes read. -1 on failure.
+ */
+ssize_t li_read_file(const char *path, char **data_ptr);
+
+/**
+ * Read a regular file (by file descriptor) into a newly allocated
+ * buffer. The buffer should be freed by the caller.
+ *
+ * :param fd: The file descriptor to read.
+ * :param data_ptr: Output pointer for newly allocated buffer.
+ *
+ * :return: On success, the number of bytes read. -1 on failure.
+ */
+ssize_t li_read_file_fd(int fd, char **data_ptr);
+
+/**
+ * Write a temporary file with the specified contents.
+ *
+ * :param buf: The buffer to write.
+ * :param buf_sz: The size of the buffer.
+ * :param path_out: Output pointer which will be set to the name of
+ * the file created. This parameter should be freed by the caller.
+ *
+ * :return: 0 on success, -1 on failure.
+ */
+int li_make_tmpfile(const void *buf, size_t buf_sz, char **path_out);
+
+/**
+ * Write all the bytes from a buffer into the file descriptor
+ * specified.
+ *
+ * :param fd: The file descriptor to write to.
+ * :param buf: The buffer to write.
+ * :param buf_sz: The size of the buffer.
+ *
+ * :return: 0 on success, -1 on failure.
+ */
+int li_write_all(int fd, const void *buf, size_t buf_sz);
+
+#endif /* LITHIUM_OSUTILS_FILE_H_ */
diff --git a/src/osutils/file.c b/src/osutils/file.c
new file mode 100644
index 0000000..e008364
--- /dev/null
+++ b/src/osutils/file.c
@@ -0,0 +1,126 @@
+/* Copyright 2020 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#define POSIX_C_SOURCE 200809L
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "osutils/file.h"
+
+ssize_t li_read_file(const char *path, char **data_ptr)
+{
+	ssize_t rv;
+	int fd;
+
+	if (!data_ptr) {
+		errno = EFAULT;
+		return -1;
+	}
+
+	fd = open(path, O_RDONLY);
+	if (fd < 0) {
+		*data_ptr = NULL;
+		return -1;
+	}
+
+	rv = li_read_file_fd(fd, data_ptr);
+	if (close(fd) < 0)
+		rv = -1;
+	return rv;
+}
+
+ssize_t li_read_file_fd(int fd, char **data_ptr)
+{
+	ssize_t read_rv;
+	size_t bytes_to_read;
+	char *buf;
+	char *bufptr;
+	off_t file_size;
+
+	if (!data_ptr) {
+		errno = EFAULT;
+		return -1;
+	}
+
+	*data_ptr = NULL;
+
+	if ((file_size = lseek(fd, 0, SEEK_END)) < 0)
+		return -1;
+
+	if (lseek(fd, 0, SEEK_SET) < 0)
+		return -1;
+
+	buf = malloc(file_size);
+	if (!buf)
+		return -1;
+
+	bytes_to_read = file_size;
+	bufptr = buf;
+	while (bytes_to_read) {
+		read_rv = read(fd, bufptr, bytes_to_read);
+		if (read_rv < 0)
+			return -1;
+		if (read_rv == 0)
+			break;
+		bytes_to_read -= read_rv;
+		bufptr += read_rv;
+	}
+
+	*data_ptr = buf;
+	return file_size;
+}
+
+int li_make_tmpfile(const void *buf, size_t buf_sz, char **path_out)
+{
+	int fd;
+	int rv;
+
+	if (!path_out) {
+		errno = EFAULT;
+		return -1;
+	}
+
+	*path_out = strdup(P_tmpdir "/li_tmp.XXXXXX");
+	fd = mkstemp(*path_out);
+	if (fd < 0)
+		goto fail;
+
+	rv = li_write_all(fd, buf, buf_sz);
+	if (rv < 0)
+		goto fail;
+	return rv;
+
+fail:
+	free(*path_out);
+	*path_out = NULL;
+	return -1;
+}
+
+int li_write_all(int fd, const void *buf, size_t buf_sz)
+{
+	ssize_t write_rv;
+
+	if (!buf && buf_sz > 0) {
+		errno = EFAULT;
+		return -1;
+	}
+
+	while (buf_sz > 0) {
+		write_rv = write(fd, buf, buf_sz);
+		if (write_rv < 0)
+			return -1;
+		buf_sz -= write_rv;
+		buf += write_rv;
+	}
+
+	return 0;
+}
diff --git a/src/osutils/file_tests.c b/src/osutils/file_tests.c
new file mode 100644
index 0000000..b4eda53
--- /dev/null
+++ b/src/osutils/file_tests.c
@@ -0,0 +1,46 @@
+/* Copyright 2020 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "osutils/file.h"
+#include "unit.h"
+
+#define TEST_DATA_FILE_CONTENTS "abcdefghijklmnopqrstuvwxyz1234567890"
+
+DEFTEST("lithium.osutils.file.make_tmpfile_and_read", {})
+{
+	char *contents = NULL;
+	char *file_path = NULL;
+	ssize_t size_read;
+
+	if (!EXPECT(li_make_tmpfile(TEST_DATA_FILE_CONTENTS,
+				    sizeof(TEST_DATA_FILE_CONTENTS),
+				    &file_path) == 0))
+		goto exit;
+
+	/* The file path should start with P_tmpdir (usually /tmp). */
+	EXPECT(!strncmp(file_path, P_tmpdir, strlen(P_tmpdir)));
+
+	/* We should read the number of bytes we wrote. */
+	size_read = li_read_file(file_path, &contents);
+	if (!EXPECT(size_read == sizeof(TEST_DATA_FILE_CONTENTS)))
+		goto exit;
+
+	/* The contents should match. */
+	EXPECT(!memcmp(contents, TEST_DATA_FILE_CONTENTS, size_read));
+
+exit:
+	/* We should be able to delete the temporary file when done. */
+	if (file_path)
+		EXPECT(unlink(file_path) == 0);
+
+	free(file_path);
+	free(contents);
+}