/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * Copyright (C) 2010-2015 Richard Hughes * * Licensed under the GNU General Public License Version 2 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or *(at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include #include #include "libdnf.h" #include "dnf-utils.h" #define DNF_ERROR_INVALID_ARGUMENTS 0 #define DNF_ERROR_NO_SUCH_CMD 1 typedef struct { DnfContext *context; GOptionContext *option_context; GPtrArray *cmd_array; gboolean value_only; gchar **filters; } DnfUtilPrivate; typedef gboolean(*DnfUtilPrivateCb) (DnfUtilPrivate *cmd, gchar **values, GError **error); typedef struct { gchar *name; gchar *arguments; gchar *description; DnfUtilPrivateCb callback; } DnfUtilItem; /** * dnf_cmd_item_free: **/ static void dnf_cmd_item_free(DnfUtilItem *item) { g_free(item->name); g_free(item->arguments); g_free(item->description); g_free(item); } /* * dnf_sort_command_name_cb: */ static gint dnf_sort_command_name_cb(DnfUtilItem **item1, DnfUtilItem **item2) { return g_strcmp0((*item1)->name,(*item2)->name); } /** * dnf_cmd_add: **/ static void dnf_cmd_add(GPtrArray *array, const gchar *name, const gchar *arguments, const gchar *description, DnfUtilPrivateCb callback) { guint i; DnfUtilItem *item; g_auto(GStrv) names = NULL; g_return_if_fail(name != NULL); g_return_if_fail(description != NULL); g_return_if_fail(callback != NULL); /* add each one */ names = g_strsplit(name, ",", -1); for (i = 0; names[i] != NULL; i++) { item = g_new0(DnfUtilItem, 1); item->name = g_strdup(names[i]); if (i == 0) { item->description = g_strdup(description); } else { item->description = g_strdup_printf("Alias to %s", names[0]); } item->arguments = g_strdup(arguments); item->callback = callback; g_ptr_array_add(array, item); } } /** * dnf_cmd_get_descriptions: **/ static gchar * dnf_cmd_get_descriptions(GPtrArray *array) { guint i; guint j; guint len; const guint max_len = 35; DnfUtilItem *item; GString *string; /* print each command */ string = g_string_new(""); for (i = 0; i < array->len; i++) { item = g_ptr_array_index(array, i); g_string_append(string, " "); g_string_append(string, item->name); len = strlen(item->name) + 2; if (item->arguments != NULL) { g_string_append(string, " "); g_string_append(string, item->arguments); len += strlen(item->arguments) + 1; } if (len < max_len) { for (j = len; j < max_len + 1; j++) g_string_append_c(string, ' '); g_string_append(string, item->description); g_string_append_c(string, '\n'); } else { g_string_append_c(string, '\n'); for (j = 0; j < max_len + 1; j++) g_string_append_c(string, ' '); g_string_append(string, item->description); g_string_append_c(string, '\n'); } } /* remove trailing newline */ if (string->len > 0) g_string_set_size(string, string->len - 1); return g_string_free(string, FALSE); } /** * dnf_cmd_run: **/ static gboolean dnf_cmd_run(DnfUtilPrivate *priv, const gchar *command, gchar **values, GError **error) { guint i; DnfUtilItem *item; g_autoptr(GString) string = NULL; /* find command */ for (i = 0; i < priv->cmd_array->len; i++) { item = g_ptr_array_index(priv->cmd_array, i); if (g_strcmp0(item->name, command) == 0) return item->callback(priv, values, error); } /* not found */ string = g_string_new(""); g_string_append(string, "Command not found, valid commands are:\n"); for (i = 0; i < priv->cmd_array->len; i++) { item = g_ptr_array_index(priv->cmd_array, i); g_string_append_printf(string, " * %s %s\n", item->name, item->arguments ? item->arguments : ""); } g_set_error_literal(error, DNF_ERROR, DNF_ERROR_NO_SUCH_CMD, string->str); return FALSE; } /** * dnf_cmd_install: **/ static gboolean dnf_cmd_install(DnfUtilPrivate *priv, gchar **values, GError **error) { guint i; if (g_strv_length(values) < 1) { g_set_error_literal(error, DNF_ERROR, DNF_ERROR_INVALID_ARGUMENTS, "Not enough arguments, " "expected package or group name"); return FALSE; } /* install each package */ if (!dnf_context_setup(priv->context, NULL, error)) return FALSE; for (i = 0; values[i] != NULL; i++) { if (!dnf_context_install(priv->context, values[i], error)) return FALSE; } return dnf_context_run(priv->context, NULL, error); } /** * dnf_cmd_reinstall: **/ static gboolean dnf_cmd_reinstall(DnfUtilPrivate *priv, gchar **values, GError **error) { guint i; DnfTransaction *transaction; if (g_strv_length(values) < 1) { g_set_error_literal(error, DNF_ERROR, DNF_ERROR_INVALID_ARGUMENTS, "Not enough arguments, " "expected package or group name"); return FALSE; } /* install each package */ if (!dnf_context_setup(priv->context, NULL, error)) return FALSE; /* relax duplicate checking */ transaction = dnf_context_get_transaction(priv->context); dnf_transaction_set_flags(transaction, DNF_TRANSACTION_FLAG_ALLOW_REINSTALL); /* reinstall each package */ for (i = 0; values[i] != NULL; i++) { if (!dnf_context_remove(priv->context, values[i], error)) return FALSE; if (!dnf_context_install(priv->context, values[i], error)) return FALSE; } return dnf_context_run(priv->context, NULL, error); } /** * dnf_cmd_remove: **/ static gboolean dnf_cmd_remove(DnfUtilPrivate *priv, gchar **values, GError **error) { guint i; if (g_strv_length(values) < 1) { g_set_error_literal(error, DNF_ERROR, DNF_ERROR_INVALID_ARGUMENTS, "Not enough arguments, " "expected package or group name"); return FALSE; } /* remove each package */ if (!dnf_context_setup(priv->context, NULL, error)) return FALSE; for (i = 0; values[i] != NULL; i++) { if (!dnf_context_remove(priv->context, values[i], error)) return FALSE; } return dnf_context_run(priv->context, NULL, error); } /** * dnf_cmd_refresh_source: **/ static gboolean dnf_cmd_refresh_source(DnfUtilPrivate *priv, DnfRepo *src, DnfState *state, GError **error) { DnfState *state_local; gboolean ret; guint cache_age; g_autoptr(GError) error_local = NULL; /* set steps */ if (!dnf_state_set_steps(state, error, 50, 50, -1)) return FALSE; /* check source */ state_local = dnf_state_get_child(state); g_print("Checking %s\n", dnf_repo_get_id(src)); cache_age = dnf_context_get_cache_age(priv->context); ret = dnf_repo_check(src, cache_age, state_local, &error_local); if (ret) return dnf_state_finished(state, error); /* done */ if (!dnf_state_done(state, error)) return FALSE; /* print error to console and continue */ g_print("Failed to check %s: %s\n", dnf_repo_get_id(src), error_local->message); g_clear_error(&error_local); if (!dnf_state_finished(state_local, error)) return FALSE; /* actually update source */ state_local = dnf_state_get_child(state); g_print("Updating %s\n", dnf_repo_get_id(src)); if (!dnf_repo_update(src, DNF_REPO_UPDATE_FLAG_IMPORT_PUBKEY, state_local, &error_local)) { if (g_error_matches(error_local, DNF_ERROR, DNF_ERROR_CANNOT_FETCH_SOURCE)) { g_print("Skipping repo: %s\n", error_local->message); return dnf_state_finished(state, error); } g_propagate_error(error, error_local); return FALSE; } /* done */ return dnf_state_done(state, error); } /** * dnf_cmd_refresh: **/ static gboolean dnf_cmd_refresh(DnfUtilPrivate *priv, gchar **values, GError **error) { GPtrArray *sources; DnfRepo *src; DnfState *state; DnfState *state_local; guint i; if (!dnf_context_setup(priv->context, NULL, error)) return FALSE; /* check and refresh each source in turn */ sources = dnf_context_get_repos(priv->context); state = dnf_context_get_state(priv->context); dnf_state_set_number_steps(state, sources->len); for (i = 0; i < sources->len; i++) { src = g_ptr_array_index(sources, i); if (dnf_repo_get_enabled(src) == DNF_REPO_ENABLED_NONE) continue; state_local = dnf_state_get_child(state); if (!dnf_cmd_refresh_source(priv, src, state_local, error)) return FALSE; if (!dnf_state_done(state, error)) return FALSE; } return TRUE; } /** * dnf_cmd_clean: **/ static gboolean dnf_cmd_clean(DnfUtilPrivate *priv, gchar **values, GError **error) { GPtrArray *sources; DnfRepo *src; DnfState *state; guint i; if (!dnf_context_setup(priv->context, NULL, error)) return FALSE; /* clean each source in turn */ sources = dnf_context_get_repos(priv->context); state = dnf_context_get_state(priv->context); dnf_state_set_number_steps(state, sources->len); for (i = 0; i < sources->len; i++) { src = g_ptr_array_index(sources, i); if (!dnf_repo_clean(src, error)) return FALSE; if (!dnf_state_done(state, error)) return FALSE; } return TRUE; } /** * dnf_cmd_update: **/ static gboolean dnf_cmd_update(DnfUtilPrivate *priv, gchar **values, GError **error) { guint i; if (g_strv_length(values) < 1) { g_set_error_literal(error, DNF_ERROR, DNF_ERROR_INVALID_ARGUMENTS, "Not enough arguments, " "expected package or group name"); return FALSE; } /* update each package */ if (!dnf_context_setup(priv->context, NULL, error)) return FALSE; for (i = 0; values[i] != NULL; i++) { if (!dnf_context_update(priv->context, values[i], error)) return FALSE; } return dnf_context_run(priv->context, NULL, error); } /** * dnf_cmd_ignore_cb: **/ static void dnf_cmd_ignore_cb(const gchar *log_domain, GLogLevelFlags log_level, const gchar *message, gpointer user_data) { } /** * main: **/ int main(int argc, char *argv[]) { DnfUtilPrivate *priv; gboolean ret; gboolean verbose = FALSE; gboolean version = FALSE; guint retval = 1; g_autofree gchar *opt_root = NULL; g_autofree gchar *opt_reposdir = NULL; g_auto(GStrv) opt_varsdir = NULL; g_autofree gchar *opt_cachedir = NULL; const GOptionEntry options[] = { { "reposdir", 0, 0, G_OPTION_ARG_STRING, &opt_reposdir, "Directory for yum repository files", NULL }, { "varsdir", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_varsdir, "Directory for additional yum repository variables", NULL }, { "root", 0, 0, G_OPTION_ARG_STRING, &opt_root, "Installation root", NULL }, { "cachedir", 0, 0, G_OPTION_ARG_STRING, &opt_cachedir, "Directory for caches", NULL }, { "verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose, "Show extra debugging information", NULL }, { "version", '\0', 0, G_OPTION_ARG_NONE, &version, "Show version", NULL }, { NULL} }; g_autoptr(GError) error = NULL; g_autofree gchar *cmd_descriptions = NULL; g_autofree gchar *filter = NULL; setlocale(LC_ALL, ""); /* create helper object */ priv = g_new0(DnfUtilPrivate, 1); /* add commands */ priv->cmd_array = g_ptr_array_new_with_free_func((GDestroyNotify) dnf_cmd_item_free); dnf_cmd_add(priv->cmd_array, "install", "[pkgname]", "Install a package or group name", dnf_cmd_install); dnf_cmd_add(priv->cmd_array, "reinstall", "[pkgname]", "Reinstall a package or group name", dnf_cmd_reinstall); dnf_cmd_add(priv->cmd_array, "remove", "[pkgname]", "Remove a package or group name", dnf_cmd_remove); dnf_cmd_add(priv->cmd_array, "update", "[pkgname]", "Update a package or group name", dnf_cmd_update); dnf_cmd_add(priv->cmd_array, "refresh", "[force]", "Refresh all the metadata", dnf_cmd_refresh); dnf_cmd_add(priv->cmd_array, "clean", NULL, "Clean all the metadata", dnf_cmd_clean); /* sort by command name */ g_ptr_array_sort(priv->cmd_array, (GCompareFunc) dnf_sort_command_name_cb); /* get a list of the commands */ priv->option_context = g_option_context_new(NULL); cmd_descriptions = dnf_cmd_get_descriptions(priv->cmd_array); g_option_context_set_summary(priv->option_context, cmd_descriptions); g_option_context_add_main_entries(priv->option_context, options, NULL); ret = g_option_context_parse(priv->option_context, &argc, &argv, &error); if (!ret) { g_print("Failed to parse arguments: %s\n", error->message); goto out; } if (!opt_reposdir) opt_reposdir = g_strdup("/etc/yum.repos.d"); /* add filter if specified */ priv->context = dnf_context_new(); if (opt_root) { dnf_context_set_install_root(priv->context, opt_root); if (!opt_cachedir) { g_print("Must specify --cachedir when using --root\n"); goto out; } } else if (!opt_cachedir) opt_cachedir = g_strdup("/var/cache/PackageKit"); { g_autofree char *metadatadir = g_build_filename(opt_cachedir, "metadata", NULL); g_autofree char *solvdir = g_build_filename(opt_cachedir, "hawkey", NULL); g_autofree char *lockdir = g_build_filename(opt_cachedir, "lock", NULL); dnf_context_set_cache_dir(priv->context, metadatadir); dnf_context_set_solv_dir(priv->context, solvdir); dnf_context_set_lock_dir(priv->context, lockdir); } dnf_context_set_repo_dir(priv->context, opt_reposdir); if (opt_varsdir != NULL) dnf_context_set_vars_dir(priv->context, opt_varsdir); dnf_context_set_check_disk_space(priv->context, TRUE); dnf_context_set_check_transaction(priv->context, TRUE); dnf_context_set_keep_cache(priv->context, FALSE); dnf_context_set_cache_age(priv->context, 24 * 60 * 60); /* set verbose? */ if (verbose) { g_setenv("G_MESSAGES_DEBUG", "all", FALSE); } else { g_log_set_handler(G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, dnf_cmd_ignore_cb, NULL); } /* get version */ if (version) { g_print("Version:\t%s\n", PACKAGE_VERSION); goto out; } /* run the specified command */ ret = dnf_cmd_run(priv, argv[1],(gchar**) &argv[2], &error); if (!ret) { if (g_error_matches(error, DNF_ERROR, DNF_ERROR_NO_SUCH_CMD)) { g_autofree gchar *tmp = NULL; tmp = g_option_context_get_help(priv->option_context, TRUE, NULL); g_print("%s", tmp); } else { g_print("%s\n", error->message); } goto out; } /* success */ retval = 0; out: if (priv != NULL) { if (priv->context != NULL) g_object_unref(priv->context); if (priv->cmd_array != NULL) g_ptr_array_unref(priv->cmd_array); g_strfreev(priv->filters); g_option_context_free(priv->option_context); g_free(priv); } return retval; }