/* * Copyright © 2016 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Alexander Larsson */ #include "config.h" #include #include #include #include "libglnx.h" #include #include "flatpak-oci-registry.h" #include "flatpak-utils.h" #define MAX_JSON_SIZE (1024 * 1024) GLNX_DEFINE_CLEANUP_FUNCTION (void *, flatpak_local_free_write_archive, archive_write_free) #define free_write_archive __attribute__((cleanup (flatpak_local_free_write_archive))) static void flatpak_oci_registry_initable_iface_init (GInitableIface *iface); struct FlatpakOciRegistry { GObject parent; gboolean for_write; gboolean valid; char *uri; int tmp_dfd; /* Local repos */ int dfd; /* Remote repos */ SoupSession *soup_session; SoupURI *base_uri; }; typedef struct { GObjectClass parent_class; } FlatpakOciRegistryClass; enum { PROP_0, PROP_URI, PROP_FOR_WRITE, PROP_TMP_DFD, }; G_DEFINE_TYPE_WITH_CODE (FlatpakOciRegistry, flatpak_oci_registry, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, flatpak_oci_registry_initable_iface_init)) static void flatpak_oci_registry_finalize (GObject *object) { FlatpakOciRegistry *self = FLATPAK_OCI_REGISTRY (object); if (self->dfd != -1) close (self->dfd); g_clear_object (&self->soup_session); g_clear_pointer (&self->base_uri, soup_uri_free); g_free (self->uri); G_OBJECT_CLASS (flatpak_oci_registry_parent_class)->finalize (object); } static void flatpak_oci_registry_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { FlatpakOciRegistry *self = FLATPAK_OCI_REGISTRY (object); const char *uri; switch (prop_id) { case PROP_URI: /* Ensure the base uri ends with a / so relative urls work */ uri = g_value_get_string (value); if (g_str_has_prefix (uri, "/")) self->uri = g_strdup (uri); else self->uri = g_strconcat (uri, "/", NULL); break; case PROP_FOR_WRITE: self->for_write = g_value_get_boolean (value); break; case PROP_TMP_DFD: self->tmp_dfd = g_value_get_int (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void flatpak_oci_registry_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { FlatpakOciRegistry *self = FLATPAK_OCI_REGISTRY (object); switch (prop_id) { case PROP_URI: g_value_set_string (value, self->uri); break; case PROP_FOR_WRITE: g_value_set_boolean (value, self->for_write); break; case PROP_TMP_DFD: g_value_set_int (value, self->tmp_dfd); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void flatpak_oci_registry_class_init (FlatpakOciRegistryClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = flatpak_oci_registry_finalize; object_class->get_property = flatpak_oci_registry_get_property; object_class->set_property = flatpak_oci_registry_set_property; g_object_class_install_property (object_class, PROP_URI, g_param_spec_string ("uri", "", "", NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); g_object_class_install_property (object_class, PROP_TMP_DFD, g_param_spec_int ("tmp-dfd", "", "", -1, G_MAXINT, -1, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); g_object_class_install_property (object_class, PROP_FOR_WRITE, g_param_spec_boolean ("for-write", "", "", FALSE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); } static void flatpak_oci_registry_init (FlatpakOciRegistry *self) { self->dfd = -1; self->tmp_dfd = -1; } FlatpakOciRegistry * flatpak_oci_registry_new (const char *uri, gboolean for_write, int tmp_dfd, GCancellable *cancellable, GError **error) { FlatpakOciRegistry *oci_registry; oci_registry = g_initable_new (FLATPAK_TYPE_OCI_REGISTRY, cancellable, error, "uri", uri, "for-write", for_write, "tmp-dfd", tmp_dfd, NULL); return oci_registry; } static int local_open_file (int dfd, const char *subpath, GCancellable *cancellable, GError **error) { glnx_fd_close int fd = -1; do fd = openat (dfd, subpath, O_RDONLY | O_NONBLOCK | O_CLOEXEC | O_NOCTTY); while (G_UNLIKELY (fd == -1 && errno == EINTR)); if (fd == -1) { glnx_set_error_from_errno (error); return -1; } return glnx_steal_fd (&fd); } static GBytes * local_load_file (int dfd, const char *subpath, GCancellable *cancellable, GError **error) { glnx_fd_close int fd = -1; fd = local_open_file (dfd, subpath, cancellable, error); if (fd == -1) return NULL; return glnx_fd_readall_bytes (fd, cancellable, error); } static GBytes * remote_load_file (SoupSession *soup_session, SoupURI *base, const char *subpath, GCancellable *cancellable, GError **error) { g_autoptr(SoupURI) uri = NULL; g_autoptr(GBytes) bytes = NULL; g_autofree char *uri_s = NULL; uri = soup_uri_new_with_base (base, subpath); if (uri == NULL) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "Invalid relative url %s", subpath); return NULL; } uri_s = soup_uri_to_string (uri, FALSE); bytes = flatpak_load_http_uri (soup_session, uri_s, NULL, NULL, cancellable, error); if (bytes == NULL) return NULL; return g_steal_pointer (&bytes); } static GBytes * flatpak_oci_registry_load_file (FlatpakOciRegistry *self, const char *subpath, GCancellable *cancellable, GError **error) { if (self->dfd != -1) return local_load_file (self->dfd, subpath, cancellable, error); else return remote_load_file (self->soup_session, self->base_uri, subpath, cancellable, error); } static JsonNode * parse_json (GBytes *bytes, GCancellable *cancellable, GError **error) { g_autoptr(JsonParser) parser = NULL; JsonNode *root = NULL; parser = json_parser_new (); if (!json_parser_load_from_data (parser, g_bytes_get_data (bytes, NULL), g_bytes_get_size (bytes), error)) return NULL; root = json_parser_get_root (parser); if (root == NULL || !JSON_NODE_HOLDS_OBJECT (root)) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "Invalid json, no root object"); return NULL; } return json_node_ref (root); } static gboolean verify_oci_version (GBytes *oci_layout_bytes, GCancellable *cancellable, GError **error) { const char *version; g_autoptr(JsonNode) node = NULL; JsonObject *oci_layout; node = parse_json (oci_layout_bytes, cancellable, error); if (node == NULL) return FALSE; oci_layout = json_node_get_object (node); version = json_object_get_string_member (oci_layout, "imageLayoutVersion"); if (version == NULL) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "Unsupported oci repo: oci-layout version missing"); return FALSE; } if (strcmp (version, "1.0.0") != 0) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "Unsupported existing oci-layout version %s (only 1.0.0 supported)", version); return FALSE; } return TRUE; } static gboolean flatpak_oci_registry_ensure_local (FlatpakOciRegistry *self, gboolean for_write, GCancellable *cancellable, GError **error) { g_autoptr(GFile) dir = g_file_new_for_uri (self->uri); glnx_fd_close int local_dfd = -1; int dfd; g_autoptr(GError) local_error = NULL; g_autoptr(GBytes) oci_layout_bytes = NULL; if (self->dfd != -1) dfd = self->dfd; else { if (!glnx_opendirat (AT_FDCWD, flatpak_file_get_path_cached (dir), TRUE, &local_dfd, &local_error)) { if (for_write && g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) { g_clear_error (&local_error); if (!glnx_shutil_mkdir_p_at (AT_FDCWD, flatpak_file_get_path_cached (dir), 0755, cancellable, error)) return FALSE; if (!glnx_opendirat (AT_FDCWD, flatpak_file_get_path_cached (dir), TRUE, &local_dfd, error)) return FALSE; } else { g_propagate_error (error, g_steal_pointer (&local_error)); return FALSE; } } dfd = local_dfd; } if (for_write) { if (!glnx_shutil_mkdir_p_at (dfd, "blobs/sha256", 0755, cancellable, error)) return FALSE; if (!glnx_shutil_mkdir_p_at (dfd, "refs", 0755, cancellable, error)) return FALSE; } oci_layout_bytes = local_load_file (dfd, "oci-layout", cancellable, &local_error); if (oci_layout_bytes == NULL) { if (for_write && g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) { const char *new_layout_data = "{\"imageLayoutVersion\": \"1.0.0\"}"; g_clear_error (&local_error); if (!glnx_file_replace_contents_at (dfd, "oci-layout", (const guchar *)new_layout_data, strlen (new_layout_data), 0, cancellable, error)) return FALSE; } else { g_propagate_error (error, g_steal_pointer (&local_error)); return FALSE; } } else if (!verify_oci_version (oci_layout_bytes, cancellable, error)) return FALSE; if (self->dfd == -1 && local_dfd != -1) self->dfd = glnx_steal_fd (&local_dfd); return TRUE; } static gboolean flatpak_oci_registry_ensure_remote (FlatpakOciRegistry *self, gboolean for_write, GCancellable *cancellable, GError **error) { g_autoptr(SoupURI) baseuri = NULL; g_autoptr(GBytes) oci_layout_bytes = NULL; if (for_write) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "Writes are not supported for remote OCI registries"); return FALSE; } self->soup_session = flatpak_create_soup_session ("flatpak"); baseuri = soup_uri_new (self->uri); if (baseuri == NULL) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "Invalid url %s", self->uri); return FALSE; } oci_layout_bytes = remote_load_file (self->soup_session, baseuri, "oci-layout", cancellable, error); if (oci_layout_bytes == NULL) return FALSE; if (!verify_oci_version (oci_layout_bytes, cancellable, error)) return FALSE; self->base_uri = g_steal_pointer (&baseuri); return TRUE; } static gboolean flatpak_oci_registry_initable_init (GInitable *initable, GCancellable *cancellable, GError **error) { FlatpakOciRegistry *self = FLATPAK_OCI_REGISTRY (initable); gboolean res; if (self->tmp_dfd == -1 && !glnx_opendirat (AT_FDCWD, "/tmp", TRUE, &self->tmp_dfd, error)) return FALSE; if (g_str_has_prefix (self->uri, "file:/")) res = flatpak_oci_registry_ensure_local (self, self->for_write, cancellable, error); else res = flatpak_oci_registry_ensure_remote (self, self->for_write, cancellable, error); if (!res) return FALSE; self->valid = TRUE; return TRUE; } static void flatpak_oci_registry_initable_iface_init (GInitableIface *iface) { iface->init = flatpak_oci_registry_initable_init; } FlatpakOciRef * flatpak_oci_registry_load_ref (FlatpakOciRegistry *self, const char *ref, GCancellable *cancellable, GError **error) { g_autoptr(GBytes) bytes = NULL; g_autofree char *subpath = g_strdup_printf ("refs/%s", ref); g_autoptr(GError) local_error = NULL; g_assert (self->valid); bytes = flatpak_oci_registry_load_file (self, subpath, cancellable, &local_error); if (bytes == NULL) { if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "No tag '%s' found", ref); else g_propagate_error (error, g_steal_pointer (&local_error)); return NULL; } return (FlatpakOciRef *)flatpak_json_from_bytes (bytes, FLATPAK_TYPE_OCI_REF, error); } gboolean flatpak_oci_registry_set_ref (FlatpakOciRegistry *self, const char *ref, FlatpakOciRef *data, GCancellable *cancellable, GError **error) { g_autoptr(GBytes) bytes = NULL; g_autofree char *subpath = g_strdup_printf ("refs/%s", ref); g_assert (self->valid); bytes = flatpak_json_to_bytes (FLATPAK_JSON (data)); if (!glnx_file_replace_contents_at (self->dfd, subpath, g_bytes_get_data (bytes, NULL), g_bytes_get_size (bytes), 0, cancellable, error)) return FALSE; return TRUE; } static gboolean write_update_checksum (GOutputStream *out, gconstpointer data, gsize len, gsize *out_bytes_written, GChecksum *checksum, GCancellable *cancellable, GError **error) { if (out) { if (!g_output_stream_write_all (out, data, len, out_bytes_written, cancellable, error)) return FALSE; } else if (out_bytes_written) { *out_bytes_written = len; } if (checksum) g_checksum_update (checksum, data, len); return TRUE; } static gboolean splice_update_checksum (GOutputStream *out, GInputStream *in, GChecksum *checksum, GCancellable *cancellable, GError **error) { g_return_val_if_fail (out != NULL || checksum != NULL, FALSE); if (checksum != NULL) { gsize bytes_read, bytes_written; char buf[4096]; do { if (!g_input_stream_read_all (in, buf, sizeof(buf), &bytes_read, cancellable, error)) return FALSE; if (!write_update_checksum (out, buf, bytes_read, &bytes_written, checksum, cancellable, error)) return FALSE; } while (bytes_read > 0); } else if (out != NULL) { if (g_output_stream_splice (out, in, 0, cancellable, error) < 0) return FALSE; } return TRUE; } static char * checksum_fd (int fd, GCancellable *cancellable, GError **error) { g_autoptr(GChecksum) checksum = NULL; g_autoptr(GInputStream) in = g_unix_input_stream_new (fd, FALSE); checksum = g_checksum_new (G_CHECKSUM_SHA256); if (!splice_update_checksum (NULL, in, checksum, cancellable, error)) return NULL; return g_strdup (g_checksum_get_string (checksum)); } int flatpak_oci_registry_download_blob (FlatpakOciRegistry *self, const char *digest, FlatpakLoadUriProgress progress_cb, gpointer user_data, GCancellable *cancellable, GError **error) { g_autofree char *subpath = NULL; glnx_fd_close int fd = -1; g_assert (self->valid); if (!g_str_has_prefix (digest, "sha256:")) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "Unsupported digest type %s", digest); return -1; } subpath = g_strdup_printf ("blobs/sha256/%s", digest + strlen ("sha256:")); if (self->dfd != -1) { /* Local case, trust checksum */ fd = local_open_file (self->dfd, subpath, cancellable, error); if (fd == -1) return -1; } else { g_autoptr(SoupURI) uri = NULL; g_autofree char *uri_s = NULL; g_autofree char *checksum = NULL; g_autofree char *tmpfile_name = g_strdup_printf ("oci-layer-XXXXXX"); g_autoptr(GOutputStream) out_stream = NULL; /* remote case, download and verify */ uri = soup_uri_new_with_base (self->base_uri, subpath); if (uri == NULL) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "Invalid relative url %s", subpath); return -1; } uri_s = soup_uri_to_string (uri, FALSE); if (!flatpak_open_in_tmpdir_at (self->tmp_dfd, 0600, tmpfile_name, &out_stream, cancellable, error)) return -1; fd = local_open_file (self->tmp_dfd, tmpfile_name, cancellable, error); (void)unlinkat (self->tmp_dfd, tmpfile_name, 0); if (fd == -1) return -1; if (!flatpak_download_http_uri (self->soup_session, uri_s, out_stream, progress_cb, user_data, cancellable, error)) return -1; if (!g_output_stream_close (out_stream, cancellable, error)) return -1; checksum = checksum_fd (fd, cancellable, error); if (checksum == NULL) return -1; if (strcmp (checksum, digest + strlen ("sha256:")) != 0) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Checksum digest did not match (%s != %s)", digest, checksum); return -1; } lseek (fd, 0, SEEK_SET); } return glnx_steal_fd (&fd); } GBytes * flatpak_oci_registry_load_blob (FlatpakOciRegistry *self, const char *digest, GCancellable *cancellable, GError **error) { g_autofree char *subpath = NULL; g_assert (self->valid); if (!g_str_has_prefix (digest, "sha256:")) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "Unsupported digest type %s", digest); return NULL; } subpath = g_strdup_printf ("blobs/sha256/%s", digest + strlen ("sha256:")); return flatpak_oci_registry_load_file (self, subpath, cancellable, error); } char * flatpak_oci_registry_store_blob (FlatpakOciRegistry *self, GBytes *data, GCancellable *cancellable, GError **error) { g_autofree char *sha256 = g_compute_checksum_for_bytes (G_CHECKSUM_SHA256, data); g_autofree char *subpath = NULL; g_assert (self->valid); subpath = g_strdup_printf ("blobs/sha256/%s", sha256); if (!glnx_file_replace_contents_at (self->dfd, subpath, g_bytes_get_data (data, NULL), g_bytes_get_size (data), 0, cancellable, error)) return FALSE; return g_strdup_printf ("sha256:%s", sha256); } FlatpakOciRef * flatpak_oci_registry_store_json (FlatpakOciRegistry *self, FlatpakJson *json, GCancellable *cancellable, GError **error) { g_autoptr(GBytes) bytes = flatpak_json_to_bytes (json); g_autofree char *digest = NULL; digest = flatpak_oci_registry_store_blob (self, bytes, cancellable, error); if (digest == NULL) return NULL; return flatpak_oci_ref_new (FLATPAK_JSON_CLASS (FLATPAK_JSON_GET_CLASS (json))->mediatype, digest, g_bytes_get_size (bytes)); } FlatpakOciVersioned * flatpak_oci_registry_load_versioned (FlatpakOciRegistry *self, const char *digest, GCancellable *cancellable, GError **error) { g_autoptr(GBytes) bytes = NULL; g_assert (self->valid); bytes = flatpak_oci_registry_load_blob (self, digest, cancellable, error); if (bytes == NULL) return NULL; return flatpak_oci_versioned_from_json (bytes, error); } FlatpakOciManifest * flatpak_oci_registry_chose_image (FlatpakOciRegistry *self, const char *tag, char **out_digest, GCancellable *cancellable, GError **error) { g_autoptr(FlatpakOciVersioned) versioned = NULL; g_autoptr(FlatpakOciRef) ref = NULL; ref = flatpak_oci_registry_load_ref (self, tag, cancellable, error); if (ref == NULL) return NULL; versioned = flatpak_oci_registry_load_versioned (self, flatpak_oci_ref_get_digest (ref), cancellable, error); if (versioned == NULL) return NULL; if (FLATPAK_IS_OCI_MANIFEST_LIST (versioned)) { /* TODO: Handle and set manifest */ g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "OCI manifest lists are not supported"); return NULL; } if (out_digest != NULL) *out_digest = g_strdup (flatpak_oci_ref_get_digest (ref)); return g_steal_pointer (&versioned); } struct FlatpakOciLayerWriter { GObject parent; FlatpakOciRegistry *registry; GChecksum *uncompressed_checksum; GChecksum *compressed_checksum; struct archive *archive; GZlibCompressor *compressor; guint64 uncompressed_size; guint64 compressed_size; char *tmp_path; int tmp_fd; }; typedef struct { GObjectClass parent_class; } FlatpakOciLayerWriterClass; G_DEFINE_TYPE (FlatpakOciLayerWriter, flatpak_oci_layer_writer, G_TYPE_OBJECT) static gboolean propagate_libarchive_error (GError **error, struct archive *a) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "%s", archive_error_string (a)); return FALSE; } static void flatpak_oci_layer_writer_reset (FlatpakOciLayerWriter *self) { if (self->tmp_path) { (void) unlinkat (self->registry->dfd, self->tmp_path, 0); g_free (self->tmp_path); self->tmp_path = NULL; } if (self->tmp_fd != -1) { close (self->tmp_fd); self->tmp_fd = -1; } g_checksum_reset (self->uncompressed_checksum); g_checksum_reset (self->compressed_checksum); if (self->archive) { archive_write_free (self->archive); self->archive = NULL; } g_clear_object (&self->compressor); } static void flatpak_oci_layer_writer_finalize (GObject *object) { FlatpakOciLayerWriter *self = FLATPAK_OCI_LAYER_WRITER (object); flatpak_oci_layer_writer_reset (self); g_checksum_free (self->compressed_checksum); g_checksum_free (self->uncompressed_checksum); g_clear_object (&self->registry); G_OBJECT_CLASS (flatpak_oci_layer_writer_parent_class)->finalize (object); } static void flatpak_oci_layer_writer_class_init (FlatpakOciLayerWriterClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = flatpak_oci_layer_writer_finalize; } static void flatpak_oci_layer_writer_init (FlatpakOciLayerWriter *self) { self->uncompressed_checksum = g_checksum_new (G_CHECKSUM_SHA256); self->compressed_checksum = g_checksum_new (G_CHECKSUM_SHA256); } static int flatpak_oci_layer_writer_open_cb (struct archive *archive, void *client_data) { return ARCHIVE_OK; } static gssize flatpak_oci_layer_writer_compress (FlatpakOciLayerWriter *self, const void *buffer, size_t length, gboolean at_end) { guchar compressed_buffer[8192]; GConverterResult res; gsize total_bytes_read, bytes_read, bytes_written, to_write_len; guchar *to_write; g_autoptr(GError) local_error = NULL; GConverterFlags flags = 0; bytes_read = 0; total_bytes_read = 0; if (at_end) flags |= G_CONVERTER_INPUT_AT_END; do { res = g_converter_convert (G_CONVERTER (self->compressor), buffer, length, compressed_buffer, sizeof (compressed_buffer), flags, &bytes_read, &bytes_written, &local_error); if (res == G_CONVERTER_ERROR) { archive_set_error (self->archive, EIO, "%s", local_error->message); return -1; } g_checksum_update (self->uncompressed_checksum, buffer, bytes_read); g_checksum_update (self->compressed_checksum, compressed_buffer, bytes_written); self->uncompressed_size += bytes_read; self->compressed_size += bytes_written; to_write_len = bytes_written; to_write = compressed_buffer; while (to_write_len > 0) { ssize_t res = write (self->tmp_fd, to_write, to_write_len); if (res <= 0) { if (errno == EINTR) continue; archive_set_error (self->archive, errno, "Write error"); return -1; } to_write_len -= res; to_write += res; } total_bytes_read += bytes_read; } while ((length > 0 && bytes_read == 0) || /* Repeat if we consumed nothing */ (at_end && res != G_CONVERTER_FINISHED)); /* Or until finished if at_end */ return total_bytes_read; } static ssize_t flatpak_oci_layer_writer_write_cb (struct archive *archive, void *client_data, const void *buffer, size_t length) { FlatpakOciLayerWriter *self = FLATPAK_OCI_LAYER_WRITER (client_data); return flatpak_oci_layer_writer_compress (self, buffer, length, FALSE); } static int flatpak_oci_layer_writer_close_cb (struct archive *archive, void *client_data) { FlatpakOciLayerWriter *self = FLATPAK_OCI_LAYER_WRITER (client_data); gssize res; char buffer[1] = {0}; res = flatpak_oci_layer_writer_compress (self, &buffer, 0, TRUE); if (res < 0) return ARCHIVE_FATAL; return ARCHIVE_OK; } FlatpakOciLayerWriter * flatpak_oci_registry_write_layer (FlatpakOciRegistry *self, GCancellable *cancellable, GError **error) { g_autoptr(FlatpakOciLayerWriter) oci_layer_writer = NULL; free_write_archive struct archive *a = NULL; glnx_fd_close int tmp_fd = -1; g_autofree char *tmp_path = NULL; g_assert (self->valid); if (!self->for_write) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "Write not supported to registry"); return NULL; } oci_layer_writer = g_object_new (FLATPAK_TYPE_OCI_LAYER_WRITER, NULL); oci_layer_writer->registry = g_object_ref (self); if (!glnx_open_tmpfile_linkable_at (self->dfd, "blobs/sha256", O_WRONLY, &tmp_fd, &tmp_path, error)) return NULL; a = archive_write_new (); if (archive_write_set_format_gnutar (a) != ARCHIVE_OK || archive_write_add_filter_none (a) != ARCHIVE_OK) { propagate_libarchive_error (error, a); return NULL; } if (archive_write_open (a, oci_layer_writer, flatpak_oci_layer_writer_open_cb, flatpak_oci_layer_writer_write_cb, flatpak_oci_layer_writer_close_cb) != ARCHIVE_OK) { propagate_libarchive_error (error, a); return NULL; } flatpak_oci_layer_writer_reset (oci_layer_writer); oci_layer_writer->archive = g_steal_pointer (&a); oci_layer_writer->tmp_fd = glnx_steal_fd (&tmp_fd); oci_layer_writer->tmp_path = g_steal_pointer (&tmp_path); oci_layer_writer->compressor = g_zlib_compressor_new (G_ZLIB_COMPRESSOR_FORMAT_GZIP, -1); return g_steal_pointer (&oci_layer_writer); } gboolean flatpak_oci_layer_writer_close (FlatpakOciLayerWriter *self, char **uncompressed_digest_out, FlatpakOciRef **ref_out, GCancellable *cancellable, GError **error) { g_autofree char *path = NULL; if (archive_write_close (self->archive) != ARCHIVE_OK) return propagate_libarchive_error (error, self->archive); path = g_strdup_printf ("blobs/sha256/%s", g_checksum_get_string (self->compressed_checksum)); if (!glnx_link_tmpfile_at (self->registry->dfd, GLNX_LINK_TMPFILE_REPLACE, self->tmp_fd, self->tmp_path, self->registry->dfd, path, error)) return FALSE; close (self->tmp_fd); self->tmp_fd = -1; g_free (self->tmp_path); self->tmp_path = NULL; if (uncompressed_digest_out != NULL) *uncompressed_digest_out = g_strdup_printf ("sha256:%s", g_checksum_get_string (self->uncompressed_checksum)); if (ref_out != NULL) { g_autofree char *digest = g_strdup_printf ("sha256:%s", g_checksum_get_string (self->compressed_checksum)); *ref_out = flatpak_oci_ref_new (FLATPAK_OCI_MEDIA_TYPE_IMAGE_LAYER, digest, self->compressed_size); } return TRUE; } struct archive * flatpak_oci_layer_writer_get_archive (FlatpakOciLayerWriter *self) { return self->archive; }