/** * ostree_sysroot_get_merge_deployment: * @self: Sysroot * @osname: (allow-none): Operating system group * * Find the deployment to use as a configuration merge source; this is * the first one in the current deployment list which matches osname. * * Returns: (transfer full): Configuration merge deployment */ OstreeDeployment * ostree_sysroot_get_merge_deployment (OstreeSysroot *self, const char *osname) { g_return_val_if_fail (osname != NULL || self->booted_deployment != NULL, NULL); if (osname == NULL) osname = ostree_deployment_get_osname (self->booted_deployment); /* If we're booted into the OS into which we're deploying, then * merge the currently *booted* configuration, rather than the most * recently deployed. */ if (self->booted_deployment && g_strcmp0 (ostree_deployment_get_osname (self->booted_deployment), osname) == 0) { return g_object_ref (self->booted_deployment); } else { guint i; for (i = 0; i < self->deployments->len; i++) { OstreeDeployment *deployment = self->deployments->pdata[i]; if (strcmp (ostree_deployment_get_osname (deployment), osname) != 0) continue; return g_object_ref (deployment); } } return NULL; }
/** * ostree_sysroot_simple_write_deployment: * @sysroot: Sysroot * @osname: (allow-none): OS name * @new_deployment: Prepend this deployment to the list * @merge_deployment: (allow-none): Use this deployment for configuration merge * @flags: Flags controlling behavior * @cancellable: Cancellable * @error: Error * * Prepend @new_deployment to the list of deployments, commit, and * cleanup. By default, all other deployments for the given @osname * except the merge deployment and the booted deployment will be * garbage collected. * * If %OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_RETAIN is * specified, then all current deployments will be kept. */ gboolean ostree_sysroot_simple_write_deployment (OstreeSysroot *sysroot, const char *osname, OstreeDeployment *new_deployment, OstreeDeployment *merge_deployment, OstreeSysrootSimpleWriteDeploymentFlags flags, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; guint i; OstreeDeployment *booted_deployment = NULL; g_autoptr(GPtrArray) deployments = NULL; g_autoptr(GPtrArray) new_deployments = g_ptr_array_new_with_free_func (g_object_unref); gboolean retain = (flags & OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_RETAIN) > 0; deployments = ostree_sysroot_get_deployments (sysroot); booted_deployment = ostree_sysroot_get_booted_deployment (sysroot); if (osname == NULL && booted_deployment) osname = ostree_deployment_get_osname (booted_deployment); g_ptr_array_add (new_deployments, g_object_ref (new_deployment)); for (i = 0; i < deployments->len; i++) { OstreeDeployment *deployment = deployments->pdata[i]; /* Keep deployments with different osnames, as well as the * booted and merge deployments */ if (retain || (osname != NULL && strcmp (ostree_deployment_get_osname (deployment), osname) != 0) || ostree_deployment_equal (deployment, booted_deployment) || ostree_deployment_equal (deployment, merge_deployment)) { g_ptr_array_add (new_deployments, g_object_ref (deployment)); } } if (!ostree_sysroot_write_deployments (sysroot, new_deployments, cancellable, error)) goto out; if (!ostree_sysroot_cleanup (sysroot, cancellable, error)) goto out; ret = TRUE; out: return ret; }
GVariant * rpmostreed_commit_generate_cached_details_variant (OstreeDeployment *deployment, OstreeRepo *repo, const gchar *refspec, GError **error) { g_autoptr(GVariant) commit = NULL; g_autofree gchar *origin_refspec = NULL; g_autofree gchar *head = NULL; gboolean gpg_enabled; const gchar *osname; GVariant *sigs = NULL; /* floating variant */ GVariantDict dict; osname = ostree_deployment_get_osname (deployment); if (refspec) origin_refspec = g_strdup (refspec); else { g_autoptr(RpmOstreeOrigin) origin = NULL; origin = rpmostree_origin_parse_deployment (deployment, error); if (!origin) return NULL; origin_refspec = g_strdup (rpmostree_origin_get_refspec (origin)); } g_assert (origin_refspec); /* allow_noent=TRUE since the ref may have been deleted for a * rebase. */ if (!ostree_repo_resolve_rev (repo, origin_refspec, TRUE, &head, error)) return NULL; if (head == NULL) head = g_strdup (ostree_deployment_get_csum (deployment)); if (!ostree_repo_load_variant (repo, OSTREE_OBJECT_TYPE_COMMIT, head, &commit, error)) return NULL; sigs = rpmostreed_deployment_gpg_results (repo, origin_refspec, head, &gpg_enabled); g_variant_dict_init (&dict, NULL); if (osname != NULL) g_variant_dict_insert (&dict, "osname", "s", osname); g_variant_dict_insert (&dict, "checksum", "s", head); variant_add_commit_details (&dict, commit); g_variant_dict_insert (&dict, "origin", "s", origin_refspec); if (sigs != NULL) g_variant_dict_insert_value (&dict, "signatures", sigs); g_variant_dict_insert (&dict, "gpg-enabled", "b", gpg_enabled); return g_variant_dict_end (&dict); }
gboolean get_origin_refspec (OstreeDeployment *booted_deployment, gchar **out_refspec, GError **error) { GKeyFile *origin; g_autofree gchar *refspec = NULL; g_return_val_if_fail (OSTREE_IS_DEPLOYMENT (booted_deployment), FALSE); g_return_val_if_fail (out_refspec != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); origin = ostree_deployment_get_origin (booted_deployment); if (origin == NULL) { const gchar *osname = ostree_deployment_get_osname (booted_deployment); const gchar *booted = ostree_deployment_get_csum (booted_deployment); g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "No origin found for %s (%s), cannot upgrade", osname, booted); return FALSE; } refspec = g_key_file_get_string (origin, "origin", "refspec", error); if (refspec == NULL) return FALSE; *out_refspec = g_steal_pointer (&refspec); return TRUE; }
/* Get a currently unique (for this host) identifier for the * deployment; TODO - adding the deployment timestamp would make it * persistently unique, needs API in libostree. */ char * rpmostreed_deployment_generate_id (OstreeDeployment *deployment) { g_return_val_if_fail (OSTREE_IS_DEPLOYMENT (deployment), NULL); return g_strdup_printf ("%s-%s.%u", ostree_deployment_get_osname (deployment), ostree_deployment_get_csum (deployment), ostree_deployment_get_deployserial (deployment)); }
/** * ostree_sysroot_get_deployment_dirpath: * @self: Repo * @deployment: A deployment * * Note this function only returns a *relative* path - if you want * to access, it, you must either use fd-relative api such as openat(), * or concatenate it with the full ostree_sysroot_get_path(). * * Returns: (transfer full): Path to deployment root directory, relative to sysroot */ char * ostree_sysroot_get_deployment_dirpath (OstreeSysroot *self, OstreeDeployment *deployment) { return g_strdup_printf ("ostree/deploy/%s/deploy/%s.%d", ostree_deployment_get_osname (deployment), ostree_deployment_get_csum (deployment), ostree_deployment_get_deployserial (deployment)); }
gint rpmostreed_rollback_deployment_index (const gchar *name, OstreeSysroot *ot_sysroot, GError **error) { g_autoptr(GPtrArray) deployments = NULL; glnx_unref_object OstreeDeployment *merge_deployment = NULL; gint index_to_prepend = -1; gint merge_index = -1; gint previous_index = -1; guint i; merge_deployment = ostree_sysroot_get_merge_deployment (ot_sysroot, name); if (merge_deployment == NULL) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "No deployments found for os %s", name); goto out; } deployments = ostree_sysroot_get_deployments (ot_sysroot); if (deployments->len < 2) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Found %u deployments, at least 2 required for rollback", deployments->len); goto out; } g_assert (merge_deployment != NULL); for (i = 0; i < deployments->len; i++) { if (deployments->pdata[i] == merge_deployment) merge_index = i; if (g_strcmp0 (ostree_deployment_get_osname (deployments->pdata[i]), name) == 0 && deployments->pdata[i] != merge_deployment && previous_index < 0) { previous_index = i; } } g_assert (merge_index < deployments->len); g_assert (deployments->pdata[merge_index] == merge_deployment); /* If merge deployment is not booted assume we are using it. */ if (merge_index == 0 && previous_index > 0) index_to_prepend = previous_index; else index_to_prepend = merge_index; out: return index_to_prepend; }
char * rpmostreed_deployment_generate_id (OstreeDeployment *deployment) { const char *osname; guint hash; g_return_val_if_fail (OSTREE_IS_DEPLOYMENT (deployment), NULL); osname = ostree_deployment_get_osname (deployment); hash = ostree_deployment_hash (deployment); return g_strdup_printf ("%s_%u", osname, hash); }
gboolean ot_admin_builtin_status (int argc, char **argv, OstreeCommandInvocation *invocation, GCancellable *cancellable, GError **error) { const int is_tty = isatty (1); const char *red_bold_prefix = is_tty ? "\x1b[31m\x1b[1m" : ""; const char *red_bold_suffix = is_tty ? "\x1b[22m\x1b[0m" : ""; g_autoptr(GOptionContext) context = g_option_context_new (""); g_autoptr(OstreeSysroot) sysroot = NULL; if (!ostree_admin_option_context_parse (context, options, &argc, &argv, OSTREE_ADMIN_BUILTIN_FLAG_UNLOCKED, invocation, &sysroot, cancellable, error)) return FALSE; g_autoptr(OstreeRepo) repo = NULL; if (!ostree_sysroot_get_repo (sysroot, &repo, cancellable, error)) return FALSE; g_autoptr(GPtrArray) deployments = ostree_sysroot_get_deployments (sysroot); OstreeDeployment *booted_deployment = ostree_sysroot_get_booted_deployment (sysroot); g_autoptr(OstreeDeployment) pending_deployment = NULL; g_autoptr(OstreeDeployment) rollback_deployment = NULL; if (booted_deployment) ostree_sysroot_query_deployments_for (sysroot, NULL, &pending_deployment, &rollback_deployment); if (deployments->len == 0) { g_print ("No deployments.\n"); } else { for (guint i = 0; i < deployments->len; i++) { OstreeDeployment *deployment = deployments->pdata[i]; const char *ref = ostree_deployment_get_csum (deployment); /* Load the backing commit; shouldn't normally fail, but if it does, * we stumble on. */ g_autoptr(GVariant) commit = NULL; (void)ostree_repo_load_variant (repo, OSTREE_OBJECT_TYPE_COMMIT, ref, &commit, NULL); g_autoptr(GVariant) commit_metadata = NULL; if (commit) commit_metadata = g_variant_get_child_value (commit, 0); const char *version = NULL; const char *source_title = NULL; if (commit_metadata) { (void) g_variant_lookup (commit_metadata, OSTREE_COMMIT_META_KEY_VERSION, "&s", &version); (void) g_variant_lookup (commit_metadata, OSTREE_COMMIT_META_KEY_SOURCE_TITLE, "&s", &source_title); } GKeyFile *origin = ostree_deployment_get_origin (deployment); const char *deployment_status = ""; if (deployment == pending_deployment) deployment_status = " (pending)"; else if (deployment == rollback_deployment) deployment_status = " (rollback)"; g_print ("%c %s %s.%d%s\n", deployment == booted_deployment ? '*' : ' ', ostree_deployment_get_osname (deployment), ostree_deployment_get_csum (deployment), ostree_deployment_get_deployserial (deployment), deployment_status); if (version) g_print (" Version: %s\n", version); OstreeDeploymentUnlockedState unlocked = ostree_deployment_get_unlocked (deployment); switch (unlocked) { case OSTREE_DEPLOYMENT_UNLOCKED_NONE: break; default: g_print (" %sUnlocked: %s%s\n", red_bold_prefix, ostree_deployment_unlocked_state_to_string (unlocked), red_bold_suffix); } if (!origin) g_print (" origin: none\n"); else { g_autofree char *origin_refspec = g_key_file_get_string (origin, "origin", "refspec", NULL); if (!origin_refspec) g_print (" origin: <unknown origin type>\n"); else g_print (" origin refspec: %s\n", origin_refspec); if (source_title) g_print (" `- %s\n", source_title); } if (deployment_get_gpg_verify (deployment, repo)) { g_autoptr(GString) output_buffer = g_string_sized_new (256); /* Print any digital signatures on this commit. */ g_autoptr(GError) local_error = NULL; g_autoptr(OstreeGpgVerifyResult) result = ostree_repo_verify_commit_ext (repo, ref, NULL, NULL, cancellable, &local_error); /* G_IO_ERROR_NOT_FOUND just means the commit is not signed. */ if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) { g_clear_error (&local_error); continue; } else if (local_error != NULL) { g_propagate_error (error, g_steal_pointer (&local_error)); return FALSE; } const guint n_signatures = ostree_gpg_verify_result_count_all (result); for (guint jj = 0; jj < n_signatures; jj++) { ostree_gpg_verify_result_describe (result, jj, output_buffer, " GPG: ", OSTREE_GPG_SIGNATURE_FORMAT_DEFAULT); } g_print ("%s", output_buffer->str); } } } return TRUE; }
static gboolean sysroot_populate_deployments_unlocked (RpmostreedSysroot *self, gboolean *out_changed, GError **error) { gboolean ret = FALSE; OstreeDeployment *booted = NULL; /* owned by sysroot */ g_autofree gchar *booted_id = NULL; g_autoptr(GPtrArray) deployments = NULL; g_autoptr(GHashTable) seen_osnames = NULL; GHashTableIter iter; gpointer hashkey; gpointer value; GVariantBuilder builder; guint i; gboolean sysroot_changed; gboolean repo_changed; struct stat repo_new_stat; if (!ostree_sysroot_load_if_changed (self->ot_sysroot, &sysroot_changed, self->cancellable, error)) goto out; if (fstat (ostree_repo_get_dfd (self->repo), &repo_new_stat) < 0) { glnx_set_error_from_errno (error); goto out; } repo_changed = !(self->repo_last_stat.st_mtim.tv_sec == repo_new_stat.st_mtim.tv_sec && self->repo_last_stat.st_mtim.tv_nsec == repo_new_stat.st_mtim.tv_nsec); if (repo_changed) self->repo_last_stat = repo_new_stat; if (!(sysroot_changed || repo_changed)) { ret = TRUE; if (out_changed) *out_changed = FALSE; goto out; } g_debug ("loading deployments"); g_variant_builder_init (&builder, G_VARIANT_TYPE ("aa{sv}")); seen_osnames = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL); /* Updated booted property */ booted = ostree_sysroot_get_booted_deployment (self->ot_sysroot); if (booted) { const gchar *os = ostree_deployment_get_osname (booted); g_autofree gchar *path = rpmostreed_generate_object_path (BASE_DBUS_PATH, os, NULL); rpmostree_sysroot_set_booted (RPMOSTREE_SYSROOT (self), path); booted_id = rpmostreed_deployment_generate_id (booted); } else { rpmostree_sysroot_set_booted (RPMOSTREE_SYSROOT (self), "/"); } /* Add deployment interfaces */ deployments = ostree_sysroot_get_deployments (self->ot_sysroot); for (i = 0; deployments != NULL && i < deployments->len; i++) { GVariant *variant; OstreeDeployment *deployment = deployments->pdata[i]; const char *deployment_os; variant = rpmostreed_deployment_generate_variant (deployment, booted_id, self->repo, error); if (!variant) goto out; g_variant_builder_add_value (&builder, variant); deployment_os = ostree_deployment_get_osname (deployment); /* Have we not seen this osname instance before? If so, add it * now. */ if (!g_hash_table_contains (self->os_interfaces, deployment_os)) { RPMOSTreeOS *obj = rpmostreed_os_new (self->ot_sysroot, self->repo, deployment_os, self->transaction_monitor); g_hash_table_insert (self->os_interfaces, g_strdup (deployment_os), obj); } /* Owned by deployment, hash lifetime is smaller */ g_hash_table_add (seen_osnames, (char*)deployment_os); } /* Remove dead os paths */ g_hash_table_iter_init (&iter, self->os_interfaces); while (g_hash_table_iter_next (&iter, &hashkey, &value)) { if (!g_hash_table_contains (seen_osnames, hashkey)) { g_object_run_dispose (G_OBJECT (value)); g_hash_table_iter_remove (&iter); } } rpmostree_sysroot_set_deployments (RPMOSTREE_SYSROOT (self), g_variant_builder_end (&builder)); g_debug ("finished deployments"); ret = TRUE; if (out_changed) *out_changed = TRUE; out: return ret; }
GVariant * rpmostreed_deployment_generate_variant (OstreeDeployment *deployment, OstreeRepo *repo) { g_autoptr(GVariant) commit = NULL; g_autofree gchar *origin_refspec = NULL; g_autofree gchar *version_commit = NULL; g_autofree gchar *id = NULL; GVariant *sigs = NULL; /* floating variant */ GError *error = NULL; GVariantDict dict; guint64 timestamp = 0; const gchar *osname = ostree_deployment_get_osname (deployment); const gchar *csum = ostree_deployment_get_csum (deployment); gint serial = ostree_deployment_get_deployserial (deployment); id = rpmostreed_deployment_generate_id (deployment); if (ostree_repo_load_variant (repo, OSTREE_OBJECT_TYPE_COMMIT, csum, &commit, &error)) { g_autoptr(GVariant) metadata = NULL; timestamp = ostree_commit_get_timestamp (commit); metadata = g_variant_get_child_value (commit, 0); if (metadata != NULL) g_variant_lookup (metadata, "version", "s", &version_commit); } else { g_warning ("Error loading commit %s", error->message); } g_clear_error (&error); origin_refspec = rpmostreed_deployment_get_refspec (deployment); if (origin_refspec) sigs = rpmostreed_deployment_gpg_results (repo, origin_refspec, csum); g_variant_dict_init (&dict, NULL); g_variant_dict_insert (&dict, "id", "s", id); if (osname != NULL) g_variant_dict_insert (&dict, "osname", "s", osname); g_variant_dict_insert (&dict, "serial", "i", serial); g_variant_dict_insert (&dict, "checksum", "s", csum); if (version_commit != NULL) g_variant_dict_insert (&dict, "version", "s", version_commit); if (timestamp > 0) g_variant_dict_insert (&dict, "timestamp", "t", timestamp); if (origin_refspec != NULL) g_variant_dict_insert (&dict, "origin", "s", origin_refspec); if (sigs != NULL) g_variant_dict_insert_value (&dict, "signatures", sigs); return g_variant_dict_end (&dict); }
GVariant * rpmostreed_deployment_generate_variant (OstreeDeployment *deployment, const char *booted_id, OstreeRepo *repo, GError **error) { g_autoptr(GVariant) commit = NULL; g_autoptr(RpmOstreeOrigin) origin = NULL; g_autofree gchar *id = NULL; GVariant *sigs = NULL; /* floating variant */ GVariantDict dict; const gchar *osname = ostree_deployment_get_osname (deployment); const gchar *csum = ostree_deployment_get_csum (deployment); gint serial = ostree_deployment_get_deployserial (deployment); gboolean gpg_enabled = FALSE; if (!ostree_repo_load_variant (repo, OSTREE_OBJECT_TYPE_COMMIT, csum, &commit, error)) return NULL; id = rpmostreed_deployment_generate_id (deployment); origin = rpmostree_origin_parse_deployment (deployment, error); if (!origin) return NULL; g_variant_dict_init (&dict, NULL); g_variant_dict_insert (&dict, "id", "s", id); if (osname != NULL) g_variant_dict_insert (&dict, "osname", "s", osname); g_variant_dict_insert (&dict, "serial", "i", serial); g_variant_dict_insert (&dict, "checksum", "s", csum); if (rpmostree_origin_is_locally_assembled (origin)) { const char *parent = ostree_commit_get_parent (commit); g_assert (parent); g_variant_dict_insert (&dict, "base-checksum", "s", parent); sigs = rpmostreed_deployment_gpg_results (repo, rpmostree_origin_get_refspec (origin), parent, &gpg_enabled); } else sigs = rpmostreed_deployment_gpg_results (repo, rpmostree_origin_get_refspec (origin), csum, &gpg_enabled); variant_add_commit_details (&dict, commit); g_variant_dict_insert (&dict, "origin", "s", rpmostree_origin_get_refspec (origin)); if (rpmostree_origin_get_packages (origin) != NULL) g_variant_dict_insert (&dict, "packages", "^as", rpmostree_origin_get_packages (origin)); if (sigs != NULL) g_variant_dict_insert_value (&dict, "signatures", sigs); g_variant_dict_insert (&dict, "gpg-enabled", "b", gpg_enabled); g_variant_dict_insert (&dict, "unlocked", "s", ostree_deployment_unlocked_state_to_string (ostree_deployment_get_unlocked (deployment))); if (booted_id != NULL) g_variant_dict_insert (&dict, "booted", "b", g_strcmp0 (booted_id, id) == 0); return g_variant_dict_end (&dict); }
gboolean ot_admin_builtin_status (int argc, char **argv, GCancellable *cancellable, GError **error) { g_autoptr(GOptionContext) context = NULL; glnx_unref_object OstreeSysroot *sysroot = NULL; gboolean ret = FALSE; glnx_unref_object OstreeRepo *repo = NULL; OstreeDeployment *booted_deployment = NULL; g_autoptr(GPtrArray) deployments = NULL; const int is_tty = isatty (1); const char *red_bold_prefix = is_tty ? "\x1b[31m\x1b[1m" : ""; const char *red_bold_suffix = is_tty ? "\x1b[22m\x1b[0m" : ""; guint i; context = g_option_context_new ("List deployments"); if (!ostree_admin_option_context_parse (context, options, &argc, &argv, OSTREE_ADMIN_BUILTIN_FLAG_UNLOCKED, &sysroot, cancellable, error)) goto out; if (!ostree_sysroot_load (sysroot, cancellable, error)) goto out; if (!ostree_sysroot_get_repo (sysroot, &repo, cancellable, error)) goto out; deployments = ostree_sysroot_get_deployments (sysroot); booted_deployment = ostree_sysroot_get_booted_deployment (sysroot); if (deployments->len == 0) { g_print ("No deployments.\n"); } else { for (i = 0; i < deployments->len; i++) { OstreeDeployment *deployment = deployments->pdata[i]; GKeyFile *origin; const char *ref = ostree_deployment_get_csum (deployment); OstreeDeploymentUnlockedState unlocked = ostree_deployment_get_unlocked (deployment); g_autofree char *version = version_of_commit (repo, ref); glnx_unref_object OstreeGpgVerifyResult *result = NULL; GString *output_buffer; guint jj, n_signatures; GError *local_error = NULL; origin = ostree_deployment_get_origin (deployment); g_print ("%c %s %s.%d\n", deployment == booted_deployment ? '*' : ' ', ostree_deployment_get_osname (deployment), ostree_deployment_get_csum (deployment), ostree_deployment_get_deployserial (deployment)); if (version) g_print (" Version: %s\n", version); switch (unlocked) { case OSTREE_DEPLOYMENT_UNLOCKED_NONE: break; default: g_print (" %sUnlocked: %s%s\n", red_bold_prefix, ostree_deployment_unlocked_state_to_string (unlocked), red_bold_suffix); } if (!origin) g_print (" origin: none\n"); else { g_autofree char *origin_refspec = g_key_file_get_string (origin, "origin", "refspec", NULL); if (!origin_refspec) g_print (" origin: <unknown origin type>\n"); else g_print (" origin refspec: %s\n", origin_refspec); } if (deployment_get_gpg_verify (deployment, repo)) { /* Print any digital signatures on this commit. */ result = ostree_repo_verify_commit_ext (repo, ref, NULL, NULL, cancellable, &local_error); /* G_IO_ERROR_NOT_FOUND just means the commit is not signed. */ if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) { g_clear_error (&local_error); continue; } else if (local_error != NULL) { g_propagate_error (error, local_error); goto out; } output_buffer = g_string_sized_new (256); n_signatures = ostree_gpg_verify_result_count_all (result); for (jj = 0; jj < n_signatures; jj++) { ostree_gpg_verify_result_describe (result, jj, output_buffer, " GPG: ", OSTREE_GPG_SIGNATURE_FORMAT_DEFAULT); } g_print ("%s", output_buffer->str); g_string_free (output_buffer, TRUE); } } } ret = TRUE; out: return ret; }