static gboolean do_lookup (GResource *resource, const gchar *path, GResourceLookupFlags lookup_flags, gsize *size, guint32 *flags, const void **data, gsize *data_size, GError **error) { char *free_path = NULL; gsize path_len; gboolean res = FALSE; GVariant *value; path_len = strlen (path); if (path[path_len-1] == '/') { path = free_path = g_strdup (path); free_path[path_len-1] = 0; } value = gvdb_table_get_raw_value (resource->table, path); if (value == NULL) { g_set_error (error, G_RESOURCE_ERROR, G_RESOURCE_ERROR_NOT_FOUND, _("The resource at '%s' does not exist"), path); } else { guint32 _size, _flags; GVariant *array; g_variant_get (value, "(uu@ay)", &_size, &_flags, &array); _size = GUINT32_FROM_LE (_size); _flags = GUINT32_FROM_LE (_flags); if (size) *size = _size; if (flags) *flags = _flags; if (data) *data = g_variant_get_data (array); if (data_size) { /* Don't report trailing newline that non-compressed files has */ if (_flags & G_RESOURCE_FLAGS_COMPRESSED) *data_size = g_variant_get_size (array); else *data_size = g_variant_get_size (array) - 1; } g_variant_unref (array); g_variant_unref (value); res = TRUE; } g_free (free_path); return res; }
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; }
OstreeDeltaEndianness _ostree_delta_get_endianness (GVariant *superblock, gboolean *out_was_heuristic) { guint8 endianness_char; g_autoptr(GVariant) delta_meta = NULL; g_autoptr(GVariantDict) delta_metadict = NULL; guint64 total_size = 0; guint64 total_usize = 0; guint total_objects = 0; delta_meta = g_variant_get_child_value (superblock, 0); delta_metadict = g_variant_dict_new (delta_meta); if (out_was_heuristic) *out_was_heuristic = FALSE; if (g_variant_dict_lookup (delta_metadict, "ostree.endianness", "y", &endianness_char)) { switch (endianness_char) { case 'l': return OSTREE_DELTA_ENDIAN_LITTLE; case 'B': return OSTREE_DELTA_ENDIAN_BIG; default: return OSTREE_DELTA_ENDIAN_INVALID; } } if (out_was_heuristic) *out_was_heuristic = TRUE; { g_autoptr(GVariant) meta_entries = NULL; guint n_parts; guint i; gboolean is_byteswapped = FALSE; g_variant_get_child (superblock, 6, "@a" OSTREE_STATIC_DELTA_META_ENTRY_FORMAT, &meta_entries); n_parts = g_variant_n_children (meta_entries); for (i = 0; i < n_parts; i++) { g_autoptr(GVariant) objects = NULL; guint64 size, usize; guint n_objects; g_variant_get_child (meta_entries, i, "(u@aytt@ay)", NULL, NULL, &size, &usize, &objects); n_objects = (guint)(g_variant_get_size (objects) / OSTREE_STATIC_DELTA_OBJTYPE_CSUM_LEN); total_objects += n_objects; total_size += size; total_usize += usize; if (size > usize) { double ratio = ((double)size)/((double)usize); /* This should really never happen where compressing things makes it more than 50% bigger. */ if (ratio > 1.2) { is_byteswapped = TRUE; break; } } } if (!is_byteswapped) { /* If the average object size is greater than 4GiB, let's assume * we're dealing with opposite endianness. I'm fairly confident * no one is going to be shipping peta- or exa- byte size ostree * deltas, period. Past the gigabyte scale you really want * bittorrent or something. */ if ((total_size / total_objects) > G_MAXUINT32) { is_byteswapped = TRUE; } } if (is_byteswapped) { switch (G_BYTE_ORDER) { case G_BIG_ENDIAN: return OSTREE_DELTA_ENDIAN_LITTLE; case G_LITTLE_ENDIAN: return OSTREE_DELTA_ENDIAN_BIG; default: g_assert_not_reached (); } } return G_BYTE_ORDER; } }
gboolean _ostree_repo_static_delta_dump (OstreeRepo *self, const char *delta_id, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; g_autofree char *from = NULL; g_autofree char *to = NULL; g_autofree char *superblock_path = NULL; glnx_fd_close int superblock_fd = -1; g_autoptr(GVariant) delta_superblock = NULL; guint64 total_size = 0, total_usize = 0; guint64 total_fallback_size = 0, total_fallback_usize = 0; guint i; OstreeDeltaEndianness endianness; gboolean swap_endian = FALSE; _ostree_parse_delta_name (delta_id, &from, &to); superblock_path = _ostree_get_relative_static_delta_superblock_path (from, to); if (!ot_util_variant_map_at (self->repo_dir_fd, superblock_path, (GVariantType*)OSTREE_STATIC_DELTA_SUPERBLOCK_FORMAT, OT_VARIANT_MAP_TRUSTED, &delta_superblock, error)) goto out; g_print ("%s\n", g_variant_print (delta_superblock, 1)); g_print ("Delta: %s\n", delta_id); { const char *endianness_description; gboolean was_heuristic; endianness = _ostree_delta_get_endianness (delta_superblock, &was_heuristic); switch (endianness) { case OSTREE_DELTA_ENDIAN_BIG: if (was_heuristic) endianness_description = "big (heuristic)"; else endianness_description = "big"; if (G_BYTE_ORDER == G_LITTLE_ENDIAN) swap_endian = TRUE; break; case OSTREE_DELTA_ENDIAN_LITTLE: if (was_heuristic) endianness_description = "little (heuristic)"; else endianness_description = "little"; if (G_BYTE_ORDER == G_BIG_ENDIAN) swap_endian = TRUE; break; case OSTREE_DELTA_ENDIAN_INVALID: endianness_description = "invalid"; break; default: g_assert_not_reached (); } g_print ("Endianness: %s\n", endianness_description); } { guint64 ts; g_variant_get_child (delta_superblock, 1, "t", &ts); g_print ("Timestamp: %" G_GUINT64_FORMAT "\n", GUINT64_FROM_BE (ts)); } { g_autoptr(GVariant) recurse = NULL; g_variant_get_child (delta_superblock, 5, "@ay", &recurse); g_print ("Number of parents: %u\n", (guint)(g_variant_get_size (recurse) / (OSTREE_SHA256_DIGEST_LEN * 2))); } { g_autoptr(GVariant) fallback = NULL; guint n_fallback; g_variant_get_child (delta_superblock, 7, "@a" OSTREE_STATIC_DELTA_FALLBACK_FORMAT, &fallback); n_fallback = g_variant_n_children (fallback); g_print ("Number of fallback entries: %u\n", n_fallback); for (i = 0; i < n_fallback; i++) { guint64 size, usize; g_variant_get_child (fallback, i, "(y@aytt)", NULL, NULL, &size, &usize); size = maybe_swap_endian_u64 (swap_endian, size); usize = maybe_swap_endian_u64 (swap_endian, usize); total_fallback_size += size; total_fallback_usize += usize; } { g_autofree char *sizestr = g_format_size (total_fallback_size); g_autofree char *usizestr = g_format_size (total_fallback_usize); g_print ("Total Fallback Size: %" G_GUINT64_FORMAT " (%s)\n", total_fallback_size, sizestr); g_print ("Total Fallback Uncompressed Size: %" G_GUINT64_FORMAT " (%s)\n", total_fallback_usize, usizestr); } } { g_autoptr(GVariant) meta_entries = NULL; guint n_parts; g_variant_get_child (delta_superblock, 6, "@a" OSTREE_STATIC_DELTA_META_ENTRY_FORMAT, &meta_entries); n_parts = g_variant_n_children (meta_entries); g_print ("Number of parts: %u\n", n_parts); for (i = 0; i < n_parts; i++) { if (!show_one_part (self, swap_endian, from, to, meta_entries, i, &total_size, &total_usize, cancellable, error)) goto out; } } { g_autofree char *sizestr = g_format_size (total_size); g_autofree char *usizestr = g_format_size (total_usize); g_print ("Total Part Size: %" G_GUINT64_FORMAT " (%s)\n", total_size, sizestr); g_print ("Total Part Uncompressed Size: %" G_GUINT64_FORMAT " (%s)\n", total_usize, usizestr); } { guint64 overall_size = total_size + total_fallback_size; guint64 overall_usize = total_usize + total_fallback_usize; g_autofree char *sizestr = g_format_size (overall_size); g_autofree char *usizestr = g_format_size (overall_usize); g_print ("Total Size: %" G_GUINT64_FORMAT " (%s)\n", overall_size, sizestr); g_print ("Total Uncompressed Size: %" G_GUINT64_FORMAT " (%s)\n", overall_usize, usizestr); } ret = TRUE; out: return ret; }
static gboolean show_one_part (OstreeRepo *self, gboolean swap_endian, const char *from, const char *to, GVariant *meta_entries, guint i, guint64 *total_size_ref, guint64 *total_usize_ref, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; guint32 version; guint64 size, usize; g_autoptr(GVariant) objects = NULL; g_autoptr(GInputStream) part_in = NULL; g_autoptr(GVariant) part = NULL; g_autofree char *part_path = _ostree_get_relative_static_delta_part_path (from, to, i); gint part_fd = -1; g_variant_get_child (meta_entries, i, "(u@aytt@ay)", &version, NULL, &size, &usize, &objects); size = maybe_swap_endian_u64 (swap_endian, size); usize = maybe_swap_endian_u64 (swap_endian, usize); *total_size_ref += size; *total_usize_ref += usize; g_print ("PartMeta%u: nobjects=%u size=%" G_GUINT64_FORMAT " usize=%" G_GUINT64_FORMAT "\n", i, (guint)(g_variant_get_size (objects) / OSTREE_STATIC_DELTA_OBJTYPE_CSUM_LEN), size, usize); part_fd = openat (self->repo_dir_fd, part_path, O_RDONLY | O_CLOEXEC); if (part_fd < 0) { glnx_set_error_from_errno (error); goto out; } part_in = g_unix_input_stream_new (part_fd, FALSE); if (!_ostree_static_delta_part_open (part_in, NULL, OSTREE_STATIC_DELTA_OPEN_FLAGS_SKIP_CHECKSUM, NULL, &part, cancellable, error)) goto out; { g_autoptr(GVariant) modes = NULL; g_autoptr(GVariant) xattrs = NULL; g_autoptr(GVariant) blob = NULL; g_autoptr(GVariant) ops = NULL; OstreeDeltaExecuteStats stats = { { 0, }, }; g_variant_get (part, "(@a(uuu)@aa(ayay)@ay@ay)", &modes, &xattrs, &blob, &ops); g_print ("PartPayload%u: nmodes=%" G_GUINT64_FORMAT " nxattrs=%" G_GUINT64_FORMAT " blobsize=%" G_GUINT64_FORMAT " opsize=%" G_GUINT64_FORMAT "\n", i, (guint64)g_variant_n_children (modes), (guint64)g_variant_n_children (xattrs), (guint64)g_variant_n_children (blob), (guint64)g_variant_n_children (ops)); if (!_ostree_static_delta_part_execute (self, objects, part, TRUE, TRUE, &stats, cancellable, error)) goto out; { const guint *n_ops = stats.n_ops_executed; g_print ("PartPayloadOps%u: openspliceclose=%u open=%u write=%u setread=%u " "unsetread=%u close=%u bspatch=%u\n", i, n_ops[0], n_ops[1], n_ops[2], n_ops[3], n_ops[4], n_ops[5], n_ops[6]); } } 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; }
static gboolean handle_configure_remote (FlatpakSystemHelper *object, GDBusMethodInvocation *invocation, guint arg_flags, const gchar *arg_remote, const gchar *arg_config, GVariant *arg_gpg_key) { g_autoptr(FlatpakDir) system = dir_get_system (); g_autoptr(GError) error = NULL; g_autoptr(GKeyFile) config = g_key_file_new (); g_autofree char *group = g_strdup_printf ("remote \"%s\"", arg_remote); g_autoptr(GBytes) gpg_data = NULL; gboolean force_remove; g_debug ("ConfigureRemote %u %s", arg_flags, arg_remote); if (*arg_remote == 0 || strchr (arg_remote, '/') != NULL) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "Invalid remote name: %s", arg_remote); return TRUE; } if ((arg_flags & ~FLATPAK_HELPER_CONFIGURE_REMOTE_FLAGS_ALL) != 0) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "Unsupported flags enabled: 0x%x", (arg_flags & ~FLATPAK_HELPER_CONFIGURE_REMOTE_FLAGS_ALL)); return TRUE; } if (!g_key_file_load_from_data (config, arg_config, strlen (arg_config), G_KEY_FILE_NONE, &error)) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "Invalid config: %s\n", error->message); return TRUE; } if (!flatpak_dir_ensure_repo (system, NULL, &error)) { g_dbus_method_invocation_return_gerror (invocation, error); return TRUE; } if (g_variant_get_size (arg_gpg_key) > 0) gpg_data = g_variant_get_data_as_bytes (arg_gpg_key); force_remove = (arg_flags & FLATPAK_HELPER_CONFIGURE_REMOTE_FLAGS_FORCE_REMOVE) != 0; if (g_key_file_has_group (config, group)) { /* Add/Modify */ if (!flatpak_dir_modify_remote (system, arg_remote, config, gpg_data, NULL, &error)) { g_dbus_method_invocation_return_gerror (invocation, error); return TRUE; } } else { /* Remove */ if (!flatpak_dir_remove_remote (system, force_remove, arg_remote, NULL, &error)) { g_dbus_method_invocation_return_gerror (invocation, error); return TRUE; } } flatpak_system_helper_complete_configure_remote (object, invocation); return TRUE; }
gboolean _ostree_static_delta_part_execute_raw (OstreeRepo *repo, GVariant *objects, GVariant *part, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; guint8 *checksums_data; g_autoptr(GVariant) checksums = NULL; g_autoptr(GVariant) mode_dict = NULL; g_autoptr(GVariant) xattr_dict = NULL; g_autoptr(GVariant) payload = NULL; g_autoptr(GVariant) ops = NULL; StaticDeltaExecutionState statedata = { 0, }; StaticDeltaExecutionState *state = &statedata; guint n_executed = 0; state->repo = repo; state->async_error = error; if (!_ostree_static_delta_parse_checksum_array (objects, &checksums_data, &state->n_checksums, error)) goto out; state->checksums = checksums_data; g_assert (state->n_checksums > 0); g_variant_get (part, "(@a(uuu)@aa(ayay)@ay@ay)", &mode_dict, &xattr_dict, &payload, &ops); state->mode_dict = mode_dict; state->xattr_dict = xattr_dict; state->payload_data = g_variant_get_data (payload); state->payload_size = g_variant_get_size (payload); state->oplen = g_variant_n_children (ops); state->opdata = g_variant_get_data (ops); while (state->oplen > 0) { guint8 opcode; opcode = state->opdata[0]; state->oplen--; state->opdata++; switch (opcode) { case OSTREE_STATIC_DELTA_OP_OPEN_SPLICE_AND_CLOSE: if (!dispatch_open_splice_and_close (repo, state, cancellable, error)) goto out; break; case OSTREE_STATIC_DELTA_OP_OPEN: if (!dispatch_open (repo, state, cancellable, error)) goto out; break; case OSTREE_STATIC_DELTA_OP_WRITE: if (!dispatch_write (repo, state, cancellable, error)) goto out; break; case OSTREE_STATIC_DELTA_OP_SET_READ_SOURCE: if (!dispatch_set_read_source (repo, state, cancellable, error)) goto out; break; case OSTREE_STATIC_DELTA_OP_UNSET_READ_SOURCE: if (!dispatch_unset_read_source (repo, state, cancellable, error)) goto out; break; case OSTREE_STATIC_DELTA_OP_CLOSE: if (!dispatch_close (repo, state, cancellable, error)) goto out; break; case OSTREE_STATIC_DELTA_OP_BSPATCH: if (!dispatch_bspatch (repo, state, cancellable, error)) goto out; break; default: g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "Unknown opcode %u at offset %u", opcode, n_executed); goto out; } n_executed++; } if (state->caught_error) goto out; ret = TRUE; out: return ret; }
static void write_translations_dictionary (GList * licenses, const gchar * dict_filename) { /* maps C string => (dictionary of: locale => translation) */ GVariantBuilder array; /* maps C string => boolean (if it's in the dictionary already */ GHashTable *translations; GVariant *var; GList *l; FILE *f; /* sort langs for prettiness / to make variant dumps easier to read */ langs = g_list_sort (langs, (GCompareFunc) strcmp); g_variant_builder_init (&array, G_VARIANT_TYPE_ARRAY); translations = g_hash_table_new (g_str_hash, g_str_equal); for (l = licenses; l != NULL; l = l->next) { const gchar *en; License *license; license = l->data; if (license->packed_into_source) continue; /* add title + translations */ en = g_hash_table_lookup (license->titles, "en"); g_assert (en != NULL); /* check if we already have added translations for this string */ if (!g_hash_table_lookup (translations, (gpointer) en)) { GVariant *trans; trans = create_translation_dict (license->titles, en); if (trans != NULL) { g_variant_builder_add_value (&array, g_variant_new_dict_entry (g_variant_new_string (en), trans)); g_hash_table_insert (translations, (gpointer) en, GINT_TO_POINTER (TRUE)); } } /* add description + translations */ if (license->descriptions == NULL) continue; en = g_hash_table_lookup (license->descriptions, "en"); g_assert (en != NULL); /* check if we already have added translations for this string */ if (!g_hash_table_lookup (translations, (gpointer) en)) { GVariant *trans; trans = create_translation_dict (license->descriptions, en); if (trans != NULL) { g_variant_builder_add_value (&array, g_variant_new_dict_entry (g_variant_new_string (en), trans)); g_hash_table_insert (translations, (gpointer) en, GINT_TO_POINTER (TRUE)); } } } var = g_variant_builder_end (&array); f = fopen (dict_filename, "wb"); if (fwrite (g_variant_get_data (var), g_variant_get_size (var), 1, f) != 1) { g_error ("failed to write dict to file: %s", g_strerror (errno)); } fclose (f); g_printerr ("Wrote dictionary to %s, size: %u, type: %s\n", dict_filename, (guint) g_variant_get_size (var), (gchar *) g_variant_get_type (var)); g_variant_unref (var); g_hash_table_destroy (translations); }