/** * ostree_repo_static_delta_execute_offline: * @self: Repo * @dir_or_file: Path to a directory containing static delta data, or directly to the superblock * @skip_validation: If %TRUE, assume data integrity * @cancellable: Cancellable * @error: Error * * Given a directory representing an already-downloaded static delta * on disk, apply it, generating a new commit. The directory must be * named with the form "FROM-TO", where both are checksums, and it * must contain a file named "superblock", along with at least one part. */ gboolean ostree_repo_static_delta_execute_offline (OstreeRepo *self, GFile *dir_or_file, gboolean skip_validation, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; guint i, n; const char *dir_or_file_path = NULL; glnx_fd_close int meta_fd = -1; glnx_fd_close int dfd = -1; g_autoptr(GVariant) meta = NULL; g_autoptr(GVariant) headers = NULL; g_autoptr(GVariant) metadata = NULL; g_autoptr(GVariant) fallback = NULL; g_autofree char *to_checksum = NULL; g_autofree char *from_checksum = NULL; g_autofree char *basename = NULL; dir_or_file_path = gs_file_get_path_cached (dir_or_file); /* First, try opening it as a directory */ dfd = glnx_opendirat_with_errno (AT_FDCWD, dir_or_file_path, TRUE); if (dfd < 0) { if (errno != ENOTDIR) { glnx_set_error_from_errno (error); goto out; } else { g_autofree char *dir = dirname (g_strdup (dir_or_file_path)); basename = g_path_get_basename (dir_or_file_path); if (!glnx_opendirat (AT_FDCWD, dir, TRUE, &dfd, error)) goto out; } } else basename = g_strdup ("superblock"); meta_fd = openat (dfd, basename, O_RDONLY | O_CLOEXEC); if (meta_fd < 0) { glnx_set_error_from_errno (error); goto out; } if (!ot_util_variant_map_fd (meta_fd, 0, G_VARIANT_TYPE (OSTREE_STATIC_DELTA_SUPERBLOCK_FORMAT), FALSE, &meta, error)) goto out; /* Parsing OSTREE_STATIC_DELTA_SUPERBLOCK_FORMAT */ metadata = g_variant_get_child_value (meta, 0); /* Write the to-commit object */ { g_autoptr(GVariant) to_csum_v = NULL; g_autoptr(GVariant) from_csum_v = NULL; g_autoptr(GVariant) to_commit = NULL; gboolean have_to_commit; gboolean have_from_commit; to_csum_v = g_variant_get_child_value (meta, 3); if (!ostree_validate_structureof_csum_v (to_csum_v, error)) goto out; to_checksum = ostree_checksum_from_bytes_v (to_csum_v); from_csum_v = g_variant_get_child_value (meta, 2); if (g_variant_n_children (from_csum_v) > 0) { if (!ostree_validate_structureof_csum_v (from_csum_v, error)) goto out; from_checksum = ostree_checksum_from_bytes_v (from_csum_v); if (!ostree_repo_has_object (self, OSTREE_OBJECT_TYPE_COMMIT, from_checksum, &have_from_commit, cancellable, error)) goto out; if (!have_from_commit) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Commit %s, which is the delta source, is not in repository", from_checksum); goto out; } } if (!ostree_repo_has_object (self, OSTREE_OBJECT_TYPE_COMMIT, to_checksum, &have_to_commit, cancellable, error)) goto out; if (!have_to_commit) { g_autofree char *detached_path = _ostree_get_relative_static_delta_path (from_checksum, to_checksum, "commitmeta"); g_autoptr(GVariant) detached_data = NULL; detached_data = g_variant_lookup_value (metadata, detached_path, G_VARIANT_TYPE("a{sv}")); if (detached_data && !ostree_repo_write_commit_detached_metadata (self, to_checksum, detached_data, cancellable, error)) goto out; to_commit = g_variant_get_child_value (meta, 4); if (!ostree_repo_write_metadata (self, OSTREE_OBJECT_TYPE_COMMIT, to_checksum, to_commit, NULL, cancellable, error)) goto out; } } fallback = g_variant_get_child_value (meta, 7); if (g_variant_n_children (fallback) > 0) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Cannot execute delta offline: contains nonempty http fallback entries"); goto out; } headers = g_variant_get_child_value (meta, 6); n = g_variant_n_children (headers); for (i = 0; i < n; i++) { guint32 version; guint64 size; guint64 usize; const guchar *csum; char checksum[OSTREE_SHA256_STRING_LEN+1]; gboolean have_all; g_autoptr(GInputStream) part_in = NULL; g_autoptr(GVariant) inline_part_data = NULL; g_autoptr(GVariant) header = NULL; g_autoptr(GVariant) csum_v = NULL; g_autoptr(GVariant) objects = NULL; g_autoptr(GVariant) part = NULL; g_autofree char *deltapart_path = NULL; OstreeStaticDeltaOpenFlags delta_open_flags = skip_validation ? OSTREE_STATIC_DELTA_OPEN_FLAGS_SKIP_CHECKSUM : 0; header = g_variant_get_child_value (headers, i); g_variant_get (header, "(u@aytt@ay)", &version, &csum_v, &size, &usize, &objects); if (version > OSTREE_DELTAPART_VERSION) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Delta part has too new version %u", version); goto out; } if (!_ostree_repo_static_delta_part_have_all_objects (self, objects, &have_all, cancellable, error)) goto out; /* If we already have these objects, don't bother executing the * static delta. */ if (have_all) continue; csum = ostree_checksum_bytes_peek_validate (csum_v, error); if (!csum) goto out; ostree_checksum_inplace_from_bytes (csum, checksum); deltapart_path = _ostree_get_relative_static_delta_part_path (from_checksum, to_checksum, i); inline_part_data = g_variant_lookup_value (metadata, deltapart_path, G_VARIANT_TYPE("(yay)")); if (inline_part_data) { g_autoptr(GBytes) inline_part_bytes = g_variant_get_data_as_bytes (inline_part_data); part_in = g_memory_input_stream_new_from_bytes (inline_part_bytes); /* For inline parts, we don't checksum, because it's * included with the metadata, so we're not trying to * protect against MITM or such. Non-security related * checksums should be done at the underlying storage layer. */ delta_open_flags |= OSTREE_STATIC_DELTA_OPEN_FLAGS_SKIP_CHECKSUM; if (!_ostree_static_delta_part_open (part_in, inline_part_bytes, delta_open_flags, NULL, &part, cancellable, error)) goto out; } else { g_autofree char *relpath = g_strdup_printf ("%u", i); /* TODO avoid malloc here */ glnx_fd_close int part_fd = openat (dfd, relpath, O_RDONLY | O_CLOEXEC); if (part_fd < 0) { glnx_set_error_from_errno (error); g_prefix_error (error, "Opening deltapart '%s': ", deltapart_path); goto out; } part_in = g_unix_input_stream_new (part_fd, FALSE); if (!_ostree_static_delta_part_open (part_in, NULL, delta_open_flags, checksum, &part, cancellable, error)) goto out; } if (!_ostree_static_delta_part_execute (self, objects, part, skip_validation, NULL, cancellable, error)) { g_prefix_error (error, "Executing delta part %i: ", i); goto out; } } ret = TRUE; out: return ret; }
gboolean _ostree_static_delta_part_open (GInputStream *part_in, GBytes *inline_part_bytes, OstreeStaticDeltaOpenFlags flags, const char *expected_checksum, GVariant **out_part, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; const gboolean trusted = (flags & OSTREE_STATIC_DELTA_OPEN_FLAGS_VARIANT_TRUSTED) > 0; const gboolean skip_checksum = (flags & OSTREE_STATIC_DELTA_OPEN_FLAGS_SKIP_CHECKSUM) > 0; gsize bytes_read; guint8 comptype; g_autoptr(GChecksum) checksum = NULL; g_autoptr(GInputStream) checksum_in = NULL; g_autoptr(GVariant) ret_part = NULL; GInputStream *source_in; /* We either take a fd or a GBytes reference */ g_return_val_if_fail (G_IS_FILE_DESCRIPTOR_BASED (part_in) || inline_part_bytes != NULL, FALSE); g_return_val_if_fail (skip_checksum || expected_checksum != NULL, FALSE); if (!skip_checksum) { checksum = g_checksum_new (G_CHECKSUM_SHA256); checksum_in = (GInputStream*)ostree_checksum_input_stream_new (part_in, checksum); source_in = checksum_in; } else { source_in = part_in; } { guint8 buf[1]; /* First byte is compression type */ if (!g_input_stream_read_all (source_in, buf, sizeof(buf), &bytes_read, cancellable, error)) { g_prefix_error (error, "Reading initial compression flag byte: "); goto out; } comptype = buf[0]; } switch (comptype) { case 0: if (!inline_part_bytes) { int part_fd = g_file_descriptor_based_get_fd ((GFileDescriptorBased*)part_in); /* No compression, no checksums - a fast path */ if (!ot_util_variant_map_fd (part_fd, 1, G_VARIANT_TYPE (OSTREE_STATIC_DELTA_PART_PAYLOAD_FORMAT_V0), trusted, &ret_part, error)) goto out; } else { g_autoptr(GBytes) content_bytes = g_bytes_new_from_bytes (inline_part_bytes, 1, g_bytes_get_size (inline_part_bytes) - 1); ret_part = g_variant_new_from_bytes (G_VARIANT_TYPE (OSTREE_STATIC_DELTA_PART_PAYLOAD_FORMAT_V0), content_bytes, trusted); g_variant_ref_sink (ret_part); } if (!skip_checksum) g_checksum_update (checksum, g_variant_get_data (ret_part), g_variant_get_size (ret_part)); break; case 'x': { g_autofree char *tmppath = g_strdup ("/var/tmp/ostree-delta-XXXXXX"); g_autoptr(GConverter) decomp = (GConverter*) _ostree_lzma_decompressor_new (); g_autoptr(GInputStream) convin = g_converter_input_stream_new (source_in, decomp); g_autoptr(GOutputStream) unpacked_out = NULL; glnx_fd_close int unpacked_fd = -1; gssize n_bytes_written; unpacked_fd = g_mkstemp_full (tmppath, O_RDWR | O_CLOEXEC, 0640); if (unpacked_fd < 0) { glnx_set_error_from_errno (error); goto out; } /* Now make it autocleanup on process exit - in the future, we * should consider caching unpacked deltas as well. */ if (unlink (tmppath) < 0) { glnx_set_error_from_errno (error); goto out; } unpacked_out = g_unix_output_stream_new (unpacked_fd, FALSE); n_bytes_written = g_output_stream_splice (unpacked_out, convin, G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE | G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET, cancellable, error); if (n_bytes_written < 0) goto out; if (!ot_util_variant_map_fd (unpacked_fd, 0, G_VARIANT_TYPE (OSTREE_STATIC_DELTA_PART_PAYLOAD_FORMAT_V0), trusted, &ret_part, error)) goto out; } break; default: g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Invalid compression type '%u'", comptype); goto out; } if (checksum) { const char *actual_checksum = g_checksum_get_string (checksum); g_assert (expected_checksum != NULL); if (strcmp (actual_checksum, expected_checksum) != 0) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Checksum mismatch in static delta part; expected=%s actual=%s", expected_checksum, actual_checksum); goto out; } } ret = TRUE; *out_part = g_steal_pointer (&ret_part); out: return ret; }
gboolean _ostree_static_delta_part_open (GInputStream *part_in, GBytes *inline_part_bytes, OstreeStaticDeltaOpenFlags flags, const char *expected_checksum, GVariant **out_part, GCancellable *cancellable, GError **error) { const gboolean trusted = (flags & OSTREE_STATIC_DELTA_OPEN_FLAGS_VARIANT_TRUSTED) > 0; const gboolean skip_checksum = (flags & OSTREE_STATIC_DELTA_OPEN_FLAGS_SKIP_CHECKSUM) > 0; gsize bytes_read; guint8 comptype; g_autoptr(GChecksum) checksum = NULL; g_autoptr(GInputStream) checksum_in = NULL; GInputStream *source_in; /* We either take a fd or a GBytes reference */ g_return_val_if_fail (G_IS_FILE_DESCRIPTOR_BASED (part_in) || inline_part_bytes != NULL, FALSE); g_return_val_if_fail (skip_checksum || expected_checksum != NULL, FALSE); if (!skip_checksum) { checksum = g_checksum_new (G_CHECKSUM_SHA256); checksum_in = (GInputStream*)ostree_checksum_input_stream_new (part_in, checksum); source_in = checksum_in; } else { source_in = part_in; } { guint8 buf[1]; /* First byte is compression type */ if (!g_input_stream_read_all (source_in, buf, sizeof(buf), &bytes_read, cancellable, error)) return glnx_prefix_error (error, "Reading initial compression flag byte"); comptype = buf[0]; } g_autoptr(GVariant) ret_part = NULL; switch (comptype) { case 0: if (!inline_part_bytes) { int part_fd = g_file_descriptor_based_get_fd ((GFileDescriptorBased*)part_in); /* No compression, no checksums - a fast path */ if (!ot_util_variant_map_fd (part_fd, 1, G_VARIANT_TYPE (OSTREE_STATIC_DELTA_PART_PAYLOAD_FORMAT_V0), trusted, &ret_part, error)) return FALSE; } else { g_autoptr(GBytes) content_bytes = g_bytes_new_from_bytes (inline_part_bytes, 1, g_bytes_get_size (inline_part_bytes) - 1); ret_part = g_variant_new_from_bytes (G_VARIANT_TYPE (OSTREE_STATIC_DELTA_PART_PAYLOAD_FORMAT_V0), content_bytes, trusted); g_variant_ref_sink (ret_part); } if (!skip_checksum) g_checksum_update (checksum, g_variant_get_data (ret_part), g_variant_get_size (ret_part)); break; case 'x': { g_autoptr(GConverter) decomp = (GConverter*) _ostree_lzma_decompressor_new (); g_autoptr(GInputStream) convin = g_converter_input_stream_new (source_in, decomp); g_autoptr(GBytes) buf = ot_map_anonymous_tmpfile_from_content (convin, cancellable, error); if (!buf) return FALSE; ret_part = g_variant_new_from_bytes (G_VARIANT_TYPE (OSTREE_STATIC_DELTA_PART_PAYLOAD_FORMAT_V0), buf, FALSE); } break; default: return glnx_throw (error, "Invalid compression type '%u'", comptype); } if (checksum) { const char *actual_checksum = g_checksum_get_string (checksum); g_assert (expected_checksum != NULL); if (strcmp (actual_checksum, expected_checksum) != 0) return glnx_throw (error, "Checksum mismatch in static delta part; expected=%s actual=%s", expected_checksum, actual_checksum); } *out_part = g_steal_pointer (&ret_part); return TRUE; }