summaryrefslogtreecommitdiff
path: root/futility/updater_archive.c
diff options
context:
space:
mode:
Diffstat (limited to 'futility/updater_archive.c')
-rw-r--r--futility/updater_archive.c311
1 files changed, 311 insertions, 0 deletions
diff --git a/futility/updater_archive.c b/futility/updater_archive.c
new file mode 100644
index 00000000..dd36f101
--- /dev/null
+++ b/futility/updater_archive.c
@@ -0,0 +1,311 @@
+/*
+ * Copyright 2018 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.
+ *
+ * Accessing updater resources from an archive.
+ */
+
+#include <assert.h>
+#include <fts.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#ifdef HAVE_LIBZIP
+#include <zip.h>
+#endif
+
+#include "host_misc.h"
+#include "updater.h"
+#include "vb2_common.h"
+
+struct archive {
+ void *handle;
+
+ void * (*open)(const char *name);
+ int (*close)(void *handle);
+
+ int (*walk)(void *handle, void *arg,
+ int (*callback)(const char *path, void *arg));
+ int (*has_entry)(void *handle, const char *name);
+ int (*read_file)(void *handle, const char *fname,
+ uint8_t **data, uint32_t *size);
+};
+
+/* Callback for archive_open on a general file system. */
+static void *archive_fallback_open(const char *name)
+{
+ assert(name && *name);
+ return strdup(name);
+}
+
+/* Callback for archive_close on a general file system. */
+static int archive_fallback_close(void *handle)
+{
+ free(handle);
+ return 0;
+}
+
+/* Callback for archive_walk on a general file system. */
+static int archive_fallback_walk(
+ void *handle, void *arg,
+ int (*callback)(const char *path, void *arg))
+{
+ FTS *fts_handle;
+ FTSENT *ent;
+ char *fts_argv[2] = {};
+ char default_path[] = ".";
+ char *root = default_path;
+ size_t root_len;
+
+ if (handle)
+ root = (char *)handle;
+ root_len = strlen(root);
+ fts_argv[0] = root;
+
+ fts_handle = fts_open(fts_argv, FTS_NOCHDIR, NULL);
+ if (!fts_handle)
+ return -1;
+
+ while ((ent = fts_read(fts_handle)) != NULL) {
+ char *path = ent->fts_path + root_len;
+ while (*path == '/')
+ path++;
+ if (!*path)
+ continue;
+ if (callback(path, arg))
+ break;
+ }
+ return 0;
+}
+
+/* Callback for archive_has_entry on a general file system. */
+static int archive_fallback_has_entry(void *handle, const char *fname)
+{
+ int r;
+ const char *path = fname;
+ char *temp_path = NULL;
+
+ if (handle && *fname != '/') {
+ ASPRINTF(&temp_path, "%s/%s", (char *)handle, fname);
+ path = temp_path;
+ }
+
+ DEBUG("Checking %s", path);
+ r = access(path, R_OK);
+ free(temp_path);
+ return r == 0;
+}
+
+/* Callback for archive_read_file on a general file system. */
+static int archive_fallback_read_file(void *handle, const char *fname,
+ uint8_t **data, uint32_t *size)
+{
+ int r;
+ const char *path = fname;
+ char *temp_path = NULL;
+
+ *data = NULL;
+ *size = 0;
+ if (handle && *fname != '/') {
+ ASPRINTF(&temp_path, "%s/%s", (char *)handle, fname);
+ path = temp_path;
+ }
+ DEBUG("Reading %s", path);
+ r = vb2_read_file(path, data, size) != VB2_SUCCESS;
+ free(temp_path);
+ return r;
+}
+
+#ifdef HAVE_LIBZIP
+
+/* Callback for archive_open on a ZIP file. */
+static void *archive_zip_open(const char *name)
+{
+ return zip_open(name, 0, NULL);
+}
+
+/* Callback for archive_close on a ZIP file. */
+static int archive_zip_close(void *handle)
+{
+ struct zip *zip = (struct zip *)handle;
+
+ if (zip)
+ return zip_close(zip);
+ return 0;
+}
+
+/* Callback for archive_has_entry on a ZIP file. */
+static int archive_zip_has_entry(void *handle, const char *fname)
+{
+ struct zip *zip = (struct zip *)handle;
+ assert(zip);
+ return zip_name_locate(zip, fname, 0) != -1;
+}
+
+/* Callback for archive_walk on a ZIP file. */
+static int archive_zip_walk(
+ void *handle, void *arg,
+ int (*callback)(const char *name, void *arg))
+{
+ zip_int64_t num, i;
+ struct zip *zip = (struct zip *)handle;
+ assert(zip);
+
+ num = zip_get_num_entries(zip, 0);
+ if (num < 0)
+ return 1;
+ for (i = 0; i < num; i++) {
+ if (callback(zip_get_name(zip, i, 0), arg))
+ break;
+ }
+ return 0;
+}
+
+/* Callback for archive_zip_read_file on a ZIP file. */
+static int archive_zip_read_file(void *handle, const char *fname,
+ uint8_t **data, uint32_t *size)
+{
+ struct zip *zip = (struct zip *)handle;
+ struct zip_file *fp;
+ struct zip_stat stat;
+
+ assert(zip);
+ *data = NULL;
+ *size = 0;
+ zip_stat_init(&stat);
+ if (zip_stat(zip, fname, 0, &stat)) {
+ ERROR("Fail to stat entry in ZIP: %s", fname);
+ return 1;
+ }
+ fp = zip_fopen(zip, fname, 0);
+ if (!fp) {
+ ERROR("Failed to open entry in ZIP: %s", fname);
+ return 1;
+ }
+ *data = (uint8_t *)malloc(stat.size);
+ if (*data) {
+ if (zip_fread(fp, *data, stat.size) == stat.size) {
+ *size = stat.size;
+ } else {
+ ERROR("Failed to read entry in zip: %s", fname);
+ free(*data);
+ *data = NULL;
+ }
+ }
+ zip_fclose(fp);
+ return *data == NULL;
+}
+#endif
+
+/*
+ * Opens an archive from given path.
+ * The type of archive will be determined automatically.
+ * Returns a pointer to reference to archive (must be released by archive_close
+ * when not used), otherwise NULL on error.
+ */
+struct archive *archive_open(const char *path)
+{
+ struct stat path_stat;
+ struct archive *ar;
+
+ if (stat(path, &path_stat) != 0) {
+ ERROR("Cannot identify type of path: %s", path);
+ return NULL;
+ }
+
+ ar = (struct archive *)malloc(sizeof(*ar));
+ if (!ar) {
+ ERROR("Internal error: allocation failure.");
+ return NULL;
+ }
+
+ if (S_ISDIR(path_stat.st_mode)) {
+ DEBUG("Found directory, use fallback (fs) driver: %s", path);
+ /* Regular file system. */
+ ar->open = archive_fallback_open;
+ ar->close = archive_fallback_close;
+ ar->walk = archive_fallback_walk;
+ ar->has_entry = archive_fallback_has_entry;
+ ar->read_file = archive_fallback_read_file;
+ } else {
+#ifdef HAVE_LIBZIP
+ DEBUG("Found file, use ZIP driver: %s", path);
+ ar->open = archive_zip_open;
+ ar->close = archive_zip_close;
+ ar->walk = archive_zip_walk;
+ ar->has_entry = archive_zip_has_entry;
+ ar->read_file = archive_zip_read_file;
+#else
+ ERROR("Found file, but no drivers were enabled: %s", path);
+ free(ar);
+ return NULL;
+#endif
+ }
+ ar->handle = ar->open(path);
+ if (!ar->handle) {
+ ERROR("Failed to open archive: %s", path);
+ free(ar);
+ return NULL;
+ }
+ return ar;
+}
+
+/*
+ * Closes an archive reference.
+ * Returns 0 on success, otherwise non-zero as failure.
+ */
+int archive_close(struct archive *ar)
+{
+ int r = ar->close(ar->handle);
+ free(ar);
+ return r;
+}
+
+/*
+ * Checks if an entry (either file or directory) exists in archive.
+ * If entry name (fname) is an absolute path (/file), always check
+ * with real file system.
+ * Returns 1 if exists, otherwise 0
+ */
+int archive_has_entry(struct archive *ar, const char *name)
+{
+ if (!ar || *name == '/')
+ return archive_fallback_has_entry(NULL, name);
+ return ar->has_entry(ar->handle, name);
+}
+
+/*
+ * Traverses all entries within archive.
+ * For every entry, the path (relative the archive root) will be passed to
+ * callback function, until the callback returns non-zero.
+ * The arg argument will also be passed to callback.
+ * Be aware that some archive may not store "directory" type entries.
+ * Returns 0 on success otherwise non-zero as failure.
+ */
+int archive_walk(
+ struct archive *ar, void *arg,
+ int (*callback)(const char *path, void *arg))
+{
+ if (!ar)
+ return archive_fallback_walk(NULL, arg, callback);
+ return ar->walk(ar->handle, arg, callback);
+}
+
+/*
+ * Reads a file from archive.
+ * If entry name (fname) is an absolute path (/file), always read
+ * from real file system.
+ * Returns 0 on success (data and size reflects the file content),
+ * otherwise non-zero as failure.
+ */
+int archive_read_file(struct archive *ar, const char *fname,
+ uint8_t **data, uint32_t *size)
+{
+ if (!ar || *fname == '/')
+ return archive_fallback_read_file(NULL, fname, data, size);
+ return ar->read_file(ar->handle, fname, data, size);
+}