/* * Copyright (C) 2013 Colin Walters * * SPDX-License-Identifier: LGPL-2.0+ * * This library 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 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 . */ #include "config.h" #include "ot-main.h" #include "ot-builtins.h" #include "ostree.h" #include "ostree-cmdprivate.h" #include "ot-main.h" #include "otutil.h" static char *opt_from_rev; static char *opt_to_rev; static char *opt_min_fallback_size; static char *opt_max_bsdiff_size; static char *opt_max_chunk_size; static char *opt_endianness; static char *opt_filename; static gboolean opt_empty; static gboolean opt_swap_endianness; static gboolean opt_inline; static gboolean opt_disable_bsdiff; static gboolean opt_if_not_exists; static char **opt_key_ids; static char *opt_sign_name; static char *opt_keysfilename; static char *opt_keysdir; #define BUILTINPROTO(name) static gboolean ot_static_delta_builtin_ ## name (int argc, char **argv, OstreeCommandInvocation *invocation, GCancellable *cancellable, GError **error) BUILTINPROTO(list); BUILTINPROTO(show); BUILTINPROTO(delete); BUILTINPROTO(generate); BUILTINPROTO(apply_offline); BUILTINPROTO(verify); BUILTINPROTO(indexes); BUILTINPROTO(reindex); #undef BUILTINPROTO static OstreeCommand static_delta_subcommands[] = { { "list", OSTREE_BUILTIN_FLAG_NONE, ot_static_delta_builtin_list, "List static delta files" }, { "show", OSTREE_BUILTIN_FLAG_NONE, ot_static_delta_builtin_show, "Dump information on a delta" }, { "delete", OSTREE_BUILTIN_FLAG_NONE, ot_static_delta_builtin_delete, "Remove a delta" }, { "generate", OSTREE_BUILTIN_FLAG_NONE, ot_static_delta_builtin_generate, "Generate static delta files" }, { "apply-offline", OSTREE_BUILTIN_FLAG_NONE, ot_static_delta_builtin_apply_offline, "Apply static delta file" }, { "verify", OSTREE_BUILTIN_FLAG_NONE, ot_static_delta_builtin_verify, "Verify static delta signatures" }, { "indexes", OSTREE_BUILTIN_FLAG_NONE, ot_static_delta_builtin_indexes, "List static delta indexes" }, { "reindex", OSTREE_BUILTIN_FLAG_NONE, ot_static_delta_builtin_reindex, "Regenerate static delta indexes" }, { NULL, 0, NULL, NULL } }; /* ATTENTION: * Please remember to update the bash-completion script (bash/ostree) and * man page (man/ostree-static-delta.xml) when changing the option list(s). */ static GOptionEntry generate_options[] = { { "from", 0, 0, G_OPTION_ARG_STRING, &opt_from_rev, "Create delta from revision REV", "REV" }, { "empty", 0, 0, G_OPTION_ARG_NONE, &opt_empty, "Create delta from scratch", NULL }, { "inline", 0, 0, G_OPTION_ARG_NONE, &opt_inline, "Inline delta parts into main delta", NULL }, { "to", 0, 0, G_OPTION_ARG_STRING, &opt_to_rev, "Create delta to revision REV", "REV" }, { "disable-bsdiff", 0, 0, G_OPTION_ARG_NONE, &opt_disable_bsdiff, "Disable use of bsdiff", NULL }, { "if-not-exists", 'n', 0, G_OPTION_ARG_NONE, &opt_if_not_exists, "Only generate if a delta does not already exist", NULL }, { "set-endianness", 0, 0, G_OPTION_ARG_STRING, &opt_endianness, "Choose metadata endianness ('l' or 'B')", "ENDIAN" }, { "swap-endianness", 0, 0, G_OPTION_ARG_NONE, &opt_swap_endianness, "Swap metadata endianness from host order", NULL }, { "min-fallback-size", 0, 0, G_OPTION_ARG_STRING, &opt_min_fallback_size, "Minimum uncompressed size in megabytes for individual HTTP request", NULL}, { "max-bsdiff-size", 0, 0, G_OPTION_ARG_STRING, &opt_max_bsdiff_size, "Maximum size in megabytes to consider bsdiff compression for input files", NULL}, { "max-chunk-size", 0, 0, G_OPTION_ARG_STRING, &opt_max_chunk_size, "Maximum size of delta chunks in megabytes", NULL}, { "filename", 0, 0, G_OPTION_ARG_FILENAME, &opt_filename, "Write the delta content to PATH (a directory). If not specified, the OSTree repository is used", "PATH"}, { "sign", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_key_ids, "Sign the delta with", "KEY_ID"}, { "sign-type", 0, 0, G_OPTION_ARG_STRING, &opt_sign_name, "Signature type to use (defaults to 'ed25519')", "NAME"}, #if defined(HAVE_LIBSODIUM) { "keys-file", 0, 0, G_OPTION_ARG_STRING, &opt_keysfilename, "Read key(s) from file", "NAME"}, #endif { NULL } }; static GOptionEntry apply_offline_options[] = { { "sign-type", 0, 0, G_OPTION_ARG_STRING, &opt_sign_name, "Signature type to use (defaults to 'ed25519')", "NAME"}, #if defined(HAVE_LIBSODIUM) { "keys-file", 0, 0, G_OPTION_ARG_STRING, &opt_keysfilename, "Read key(s) from file", "NAME"}, { "keys-dir", 0, 0, G_OPTION_ARG_STRING, &opt_keysdir, "Redefine system-wide directories with public and revoked keys for verification", "NAME"}, #endif { NULL } }; static GOptionEntry list_options[] = { { NULL } }; static GOptionEntry verify_options[] = { { "sign-type", 0, 0, G_OPTION_ARG_STRING, &opt_sign_name, "Signature type to use (defaults to 'ed25519')", "NAME"}, #if defined(HAVE_LIBSODIUM) { "keys-file", 0, 0, G_OPTION_ARG_STRING, &opt_keysfilename, "Read key(s) from file", "NAME"}, { "keys-dir", 0, 0, G_OPTION_ARG_STRING, &opt_keysdir, "Redefine system-wide directories with public and revoked keys for verification", "NAME"}, #endif { NULL } }; static GOptionEntry indexes_options[] = { { NULL } }; static GOptionEntry reindex_options[] = { { "to", 0, 0, G_OPTION_ARG_STRING, &opt_to_rev, "Only update delta index to revision REV", "REV" }, { NULL } }; static void static_delta_usage (char **argv, gboolean is_error) { OstreeCommand *command = static_delta_subcommands; void (*print_func) (const gchar *format, ...); if (is_error) print_func = g_printerr; else print_func = g_print; print_func ("Usage:\n"); print_func (" ostree static-delta [OPTION...] COMMAND\n\n"); print_func ("Builtin \"static-delta\" Commands:\n"); while (command->name) { if ((command->flags & OSTREE_BUILTIN_FLAG_HIDDEN) == 0) print_func (" %-17s%s\n", command->name, command->description ?: ""); command++; } print_func ("\n"); } static gboolean ot_static_delta_builtin_list (int argc, char **argv, OstreeCommandInvocation *invocation, GCancellable *cancellable, GError **error) { g_autoptr(OstreeRepo) repo = NULL; g_autoptr(GOptionContext) context = g_option_context_new (""); if (!ostree_option_context_parse (context, list_options, &argc, &argv, invocation, &repo, cancellable, error)) return FALSE; g_autoptr(GPtrArray) delta_names = NULL; if (!ostree_repo_list_static_delta_names (repo, &delta_names, cancellable, error)) return FALSE; if (delta_names->len == 0) g_print ("(No static deltas)\n"); else { for (guint i = 0; i < delta_names->len; i++) g_print ("%s\n", (char*)delta_names->pdata[i]); } return TRUE; } static gboolean ot_static_delta_builtin_indexes (int argc, char **argv, OstreeCommandInvocation *invocation, GCancellable *cancellable, GError **error) { g_autoptr(OstreeRepo) repo = NULL; g_autoptr(GOptionContext) context = g_option_context_new (""); if (!ostree_option_context_parse (context, indexes_options, &argc, &argv, invocation, &repo, cancellable, error)) return FALSE; g_autoptr(GPtrArray) indexes = NULL; if (!ostree_repo_list_static_delta_indexes (repo, &indexes, cancellable, error)) return FALSE; if (indexes->len == 0) g_print ("(No static deltas indexes)\n"); else { for (guint i = 0; i < indexes->len; i++) g_print ("%s\n", (char*)indexes->pdata[i]); } return TRUE; } static gboolean ot_static_delta_builtin_reindex (int argc, char **argv, OstreeCommandInvocation *invocation, GCancellable *cancellable, GError **error) { g_autoptr(GOptionContext) context = g_option_context_new (""); g_autoptr(OstreeRepo) repo = NULL; if (!ostree_option_context_parse (context, reindex_options, &argc, &argv, invocation, &repo, cancellable, error)) return FALSE; if (!ostree_repo_static_delta_reindex (repo, 0, opt_to_rev, cancellable, error)) return FALSE; return TRUE; } static gboolean ot_static_delta_builtin_show (int argc, char **argv, OstreeCommandInvocation *invocation, GCancellable *cancellable, GError **error) { g_autoptr(GOptionContext) context = g_option_context_new (""); g_autoptr(OstreeRepo) repo = NULL; if (!ostree_option_context_parse (context, list_options, &argc, &argv, invocation, &repo, cancellable, error)) return FALSE; if (argc < 3) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, "DELTA must be specified"); return FALSE; } const char *delta_id = argv[2]; if (!ostree_cmd__private__ ()->ostree_static_delta_dump (repo, delta_id, cancellable, error)) return FALSE; return TRUE; } static gboolean ot_static_delta_builtin_delete (int argc, char **argv, OstreeCommandInvocation *invocation, GCancellable *cancellable, GError **error) { g_autoptr(GOptionContext) context = g_option_context_new (""); g_autoptr(OstreeRepo) repo = NULL; if (!ostree_option_context_parse (context, list_options, &argc, &argv, invocation, &repo, cancellable, error)) return FALSE; if (argc < 3) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, "DELTA must be specified"); return FALSE; } const char *delta_id = argv[2]; if (!ostree_cmd__private__ ()->ostree_static_delta_delete (repo, delta_id, cancellable, error)) return FALSE; return TRUE; } static gboolean ot_static_delta_builtin_generate (int argc, char **argv, OstreeCommandInvocation *invocation, GCancellable *cancellable, GError **error) { g_autoptr(GOptionContext) context = g_option_context_new ("[TO]"); g_autoptr(OstreeRepo) repo = NULL; if (!ostree_option_context_parse (context, generate_options, &argc, &argv, invocation, &repo, cancellable, error)) return FALSE; if (!ostree_ensure_repo_writable (repo, error)) return FALSE; if (argc >= 3 && opt_to_rev == NULL) opt_to_rev = argv[2]; if (argc < 3 && opt_to_rev == NULL) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, "TO revision must be specified"); return FALSE; } else { const char *from_source; g_autofree char *from_resolved = NULL; g_autofree char *to_resolved = NULL; g_autofree char *from_parent_str = NULL; g_autoptr(GVariantBuilder) parambuilder = NULL; int endianness; g_assert (opt_to_rev); if (opt_empty) { if (opt_from_rev) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Cannot specify both --empty and --from=REV"); return FALSE; } from_source = NULL; } else if (opt_from_rev == NULL) { from_parent_str = g_strconcat (opt_to_rev, "^", NULL); from_source = from_parent_str; } else { from_source = opt_from_rev; } if (from_source) { if (!ostree_repo_resolve_rev (repo, from_source, FALSE, &from_resolved, error)) return FALSE; } if (!ostree_repo_resolve_rev (repo, opt_to_rev, FALSE, &to_resolved, error)) return FALSE; if (opt_if_not_exists) { gboolean does_exist; g_autofree char *delta_id = from_resolved ? g_strconcat (from_resolved, "-", to_resolved, NULL) : g_strdup (to_resolved); if (!ostree_cmd__private__ ()->ostree_static_delta_query_exists (repo, delta_id, &does_exist, cancellable, error)) return FALSE; if (does_exist) { g_print ("Delta %s already exists.\n", delta_id); return TRUE; } } if (opt_endianness) { if (strcmp (opt_endianness, "l") == 0) endianness = G_LITTLE_ENDIAN; else if (strcmp (opt_endianness, "B") == 0) endianness = G_BIG_ENDIAN; else { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Invalid endianness '%s'", opt_endianness); return FALSE; } } else endianness = G_BYTE_ORDER; if (opt_swap_endianness) { switch (endianness) { case G_LITTLE_ENDIAN: endianness = G_BIG_ENDIAN; break; case G_BIG_ENDIAN: endianness = G_LITTLE_ENDIAN; break; default: g_assert_not_reached (); } } parambuilder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}")); if (opt_min_fallback_size) g_variant_builder_add (parambuilder, "{sv}", "min-fallback-size", g_variant_new_uint32 (g_ascii_strtoull (opt_min_fallback_size, NULL, 10))); if (opt_max_bsdiff_size) g_variant_builder_add (parambuilder, "{sv}", "max-bsdiff-size", g_variant_new_uint32 (g_ascii_strtoull (opt_max_bsdiff_size, NULL, 10))); if (opt_max_chunk_size) g_variant_builder_add (parambuilder, "{sv}", "max-chunk-size", g_variant_new_uint32 (g_ascii_strtoull (opt_max_chunk_size, NULL, 10))); if (opt_disable_bsdiff) g_variant_builder_add (parambuilder, "{sv}", "bsdiff-enabled", g_variant_new_boolean (FALSE)); if (opt_inline) g_variant_builder_add (parambuilder, "{sv}", "inline-parts", g_variant_new_boolean (TRUE)); if (opt_filename) g_variant_builder_add (parambuilder, "{sv}", "filename", g_variant_new_bytestring (opt_filename)); g_variant_builder_add (parambuilder, "{sv}", "verbose", g_variant_new_boolean (TRUE)); if (opt_endianness || opt_swap_endianness) g_variant_builder_add (parambuilder, "{sv}", "endianness", g_variant_new_uint32 (endianness)); if (opt_key_ids || opt_keysfilename) { g_autoptr(GPtrArray) key_ids = g_ptr_array_new (); for (char **iter = opt_key_ids; iter != NULL && *iter != NULL; ++iter) g_ptr_array_add (key_ids, *iter); if (opt_keysfilename) { g_autoptr (GFile) keyfile = NULL; g_autoptr (GFileInputStream) key_stream_in = NULL; g_autoptr (GDataInputStream) key_data_in = NULL; if (!g_file_test (opt_keysfilename, G_FILE_TEST_IS_REGULAR)) { g_warning ("Can't open file '%s' with keys", opt_keysfilename); return glnx_throw (error, "File object '%s' is not a regular file", opt_keysfilename); } keyfile = g_file_new_for_path (opt_keysfilename); key_stream_in = g_file_read (keyfile, NULL, error); if (key_stream_in == NULL) return FALSE; key_data_in = g_data_input_stream_new (G_INPUT_STREAM(key_stream_in)); g_assert (key_data_in != NULL); /* Use simple file format with just a list of base64 public keys per line */ while (TRUE) { gsize len = 0; g_autofree char *line = g_data_input_stream_read_line (key_data_in, &len, NULL, error); g_autoptr (GVariant) sk = NULL; if (*error != NULL) return FALSE; if (line == NULL) break; // Pass the key as a string g_ptr_array_add (key_ids, g_strdup (line)); } } g_autoptr(GVariant) key_ids_v = g_variant_new_strv ((const char *const *)key_ids->pdata, key_ids->len); g_variant_builder_add (parambuilder, "{s@v}", "sign-key-ids", g_variant_new_variant (g_steal_pointer (&key_ids_v))); } opt_sign_name = opt_sign_name ?: OSTREE_SIGN_NAME_ED25519; g_variant_builder_add (parambuilder, "{sv}", "sign-name", g_variant_new_bytestring (opt_sign_name)); g_print ("Generating static delta:\n"); g_print (" From: %s\n", from_resolved ? from_resolved : "empty"); g_print (" To: %s\n", to_resolved); { g_autoptr(GVariant) params = g_variant_ref_sink (g_variant_builder_end (parambuilder)); if (!ostree_repo_static_delta_generate (repo, OSTREE_STATIC_DELTA_GENERATE_OPT_MAJOR, from_resolved, to_resolved, NULL, params, cancellable, error)) return FALSE; } } return TRUE; } static gboolean ot_static_delta_builtin_apply_offline (int argc, char **argv, OstreeCommandInvocation *invocation, GCancellable *cancellable, GError **error) { g_autoptr(GOptionContext) context = NULL; g_autoptr(OstreeRepo) repo = NULL; g_autoptr (OstreeSign) sign = NULL; char **key_ids; int n_key_ids; context = g_option_context_new (""); if (!ostree_option_context_parse (context, apply_offline_options, &argc, &argv, invocation, &repo, cancellable, error)) return FALSE; if (!ostree_ensure_repo_writable (repo, error)) return FALSE; if (argc < 3) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, "PATH must be specified"); return FALSE; } #if defined(HAVE_LIBSODIUM) /* Initialize crypto system */ opt_sign_name = opt_sign_name ?: OSTREE_SIGN_NAME_ED25519; #endif if (opt_sign_name) { sign = ostree_sign_get_by_name (opt_sign_name, error); if (!sign) return glnx_throw (error, "Signing type %s is not supported", opt_sign_name); key_ids = argv + 3; n_key_ids = argc - 3; for (int i = 0; i < n_key_ids; i++) { g_autoptr (GVariant) pk = g_variant_new_string(key_ids[i]); if (!ostree_sign_add_pk(sign, pk, error)) return FALSE; } if ((n_key_ids == 0) || opt_keysfilename) { g_autoptr (GVariantBuilder) builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}")); g_autoptr (GVariant) options = NULL; /* Use custom directory with public and revoked keys instead of system-wide directories */ if (opt_keysdir) g_variant_builder_add (builder, "{sv}", "basedir", g_variant_new_string (opt_keysdir)); /* The last chance for verification source -- system files */ if (opt_keysfilename) g_variant_builder_add (builder, "{sv}", "filename", g_variant_new_string (opt_keysfilename)); options = g_variant_builder_end (builder); if (!ostree_sign_load_pk (sign, options, error)) { /* If it fails to load system default public keys, consider there no signature engine */ if (!opt_keysdir && !opt_keysfilename) { g_clear_error(error); g_clear_object(&sign); } else return FALSE; } } } const char *patharg = argv[2]; g_autoptr(GFile) path = g_file_new_for_path (patharg); if (!ostree_repo_prepare_transaction (repo, NULL, cancellable, error)) return FALSE; if (!ostree_repo_static_delta_execute_offline_with_signature (repo, path, sign, FALSE, cancellable, error)) return FALSE; if (!ostree_repo_commit_transaction (repo, NULL, cancellable, error)) return FALSE; return TRUE; } static gboolean ot_static_delta_builtin_verify (int argc, char **argv, OstreeCommandInvocation *invocation, GCancellable *cancellable, GError **error) { g_autoptr (GOptionContext) context = g_option_context_new ("STATIC-DELTA-FILE [KEY-ID...]"); g_autoptr (OstreeRepo) repo = NULL; gboolean verified; char **key_ids; int n_key_ids; if (!ostree_option_context_parse (context, verify_options, &argc, &argv, invocation, &repo, cancellable, error)) return FALSE; if (argc < 3) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, "DELTA must be specified"); return FALSE; } opt_sign_name = opt_sign_name ?: OSTREE_SIGN_NAME_ED25519; const char *delta_id = argv[2]; g_autoptr (OstreeSign) sign = ostree_sign_get_by_name (opt_sign_name, error); if (!sign) { g_print("Sign-type not supported\n"); return FALSE; } key_ids = argv + 3; n_key_ids = argc - 3; for (int i = 0; i < n_key_ids; i++) { g_autoptr (GVariant) pk = g_variant_new_string(key_ids[i]); if (!ostree_sign_add_pk(sign, pk, error)) return FALSE; } if ((n_key_ids == 0) || opt_keysfilename) { g_autoptr (GVariantBuilder) builder = NULL; g_autoptr (GVariant) options = NULL; builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}")); /* Use custom directory with public and revoked keys instead of system-wide directories */ if (opt_keysdir) g_variant_builder_add (builder, "{sv}", "basedir", g_variant_new_string (opt_keysdir)); /* The last chance for verification source -- system files */ if (opt_keysfilename) g_variant_builder_add (builder, "{sv}", "filename", g_variant_new_string (opt_keysfilename)); options = g_variant_builder_end (builder); if (!ostree_sign_load_pk (sign, options, error)) return FALSE; } verified = ostree_repo_static_delta_verify_signature (repo, delta_id, sign, NULL, error); g_print ("Verification %s\n", verified ? "OK" : "fails"); return verified; } gboolean ostree_builtin_static_delta (int argc, char **argv, OstreeCommandInvocation *invocation, GCancellable *cancellable, GError **error) { gboolean want_help = FALSE; const char *cmdname = NULL; for (int i = 1; i < argc; i++) { if (argv[i][0] != '-') { cmdname = argv[i]; break; } else if (g_str_equal (argv[i], "--help") || g_str_equal (argv[i], "-h")) { want_help = TRUE; break; } } if (!cmdname && !want_help) { static_delta_usage (argv, TRUE); return glnx_throw (error, "No command specified"); } OstreeCommand *command = NULL; if (cmdname) { command = static_delta_subcommands; while (command->name) { if (g_strcmp0 (cmdname, command->name) == 0) break; command++; } } if (want_help && command == NULL) { static_delta_usage (argv, FALSE); return TRUE; /* Note early return */ } if (!command || !command->fn) { static_delta_usage (argv, TRUE); return glnx_throw (error, "Unknown \"static-delta\" subcommand '%s'", cmdname); } g_autofree char *prgname = g_strdup_printf ("%s %s", g_get_prgname (), cmdname); g_set_prgname (prgname); OstreeCommandInvocation sub_invocation = { .command = command }; return command->fn (argc, argv, &sub_invocation, cancellable, error); }