Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a search CLI verb and DBus API #4478

Merged
merged 1 commit into from
Jun 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions man/rpm-ostree.xml
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,20 @@ Boston, MA 02111-1307, USA.
</listitem>
</varlistentry>

<varlistentry>
<term><command>search</command></term>

<listitem>
<para>
Takes one or more query terms as arguments. The packages are
searched within the enabled repositories in
<filename>/etc/yum.repos.d/</filename>. Packages can be
overlayed and removed using the <command>install</command>
and <command>uninstall</command> commands.
</para>
</listitem>
</varlistentry>

<varlistentry>
<term><command>rebase</command></term>

Expand Down
2 changes: 2 additions & 0 deletions src/app/libmain.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ static RpmOstreeCommand commands[] = {
"Overlay additional packages", rpmostree_builtin_install },
{ "uninstall", static_cast<RpmOstreeBuiltinFlags> (RPM_OSTREE_BUILTIN_FLAG_CONTAINER_CAPABLE),
"Remove overlayed additional packages", rpmostree_builtin_uninstall },
{ "search", static_cast<RpmOstreeBuiltinFlags> (RPM_OSTREE_BUILTIN_FLAG_CONTAINER_CAPABLE),
lukewarmtemp marked this conversation as resolved.
Show resolved Hide resolved
"Search for packages", rpmostree_builtin_search },
{ "override", static_cast<RpmOstreeBuiltinFlags> (RPM_OSTREE_BUILTIN_FLAG_LOCAL_CMD),
"Manage base package overrides", rpmostree_builtin_override },
{ "reset", static_cast<RpmOstreeBuiltinFlags> (RPM_OSTREE_BUILTIN_FLAG_SUPPORTS_PKG_INSTALLS),
Expand Down
1 change: 1 addition & 0 deletions src/app/rpmostree-builtins.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ BUILTINPROTO (internals);
BUILTINPROTO (container);
BUILTINPROTO (install);
BUILTINPROTO (uninstall);
BUILTINPROTO (search);
BUILTINPROTO (override);
BUILTINPROTO (kargs);
BUILTINPROTO (reset);
Expand Down
101 changes: 101 additions & 0 deletions src/app/rpmostree-pkg-builtins.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@

#include <gio/gunixfdlist.h>

#include <set>

static char *opt_osname;
static gboolean opt_reboot;
static gboolean opt_dry_run;
Expand Down Expand Up @@ -310,3 +312,102 @@ rpmostree_builtin_uninstall (int argc, char **argv, RpmOstreeCommandInvocation *
return pkg_change (invocation, sysroot_proxy, FALSE, (const char *const *)opt_install,
(const char *const *)argv, cancellable, error);
}

struct cstrless
lukewarmtemp marked this conversation as resolved.
Show resolved Hide resolved
{
bool
operator() (const gchar *a, const gchar *b) const
{
return strcmp (a, b) < 0;
}
};

gboolean
rpmostree_builtin_search (int argc, char **argv, RpmOstreeCommandInvocation *invocation,
GCancellable *cancellable, GError **error)
{
GOptionContext *context;
glnx_unref_object RPMOSTreeSysroot *sysroot_proxy = NULL;

context = g_option_context_new ("PACKAGE [PACKAGE...]");
g_option_context_add_main_entries (context, install_option_entry, NULL);
g_option_context_add_main_entries (context, uninstall_option_entry, NULL);

if (!rpmostree_option_context_parse (context, option_entries, &argc, &argv, invocation,
cancellable, NULL, NULL, &sysroot_proxy, error))
return FALSE;

if (argc < 2)
{
rpmostree_usage_error (context, "At least one PACKAGE must be specified", error);
return FALSE;
}

glnx_unref_object RPMOSTreeOS *os_proxy = NULL;

if (!rpmostree_load_os_proxy (sysroot_proxy, opt_osname, cancellable, &os_proxy, error))
return FALSE;

g_autoptr (GPtrArray) arg_names = g_ptr_array_new ();
for (guint i = 1; i < argc; i++)
{
g_ptr_array_add (arg_names, (char *)argv[i]);
}
g_ptr_array_add (arg_names, NULL);

g_autoptr (GVariant) out_packages = NULL;

if (!rpmostree_os_call_search_sync (os_proxy, (const char *const *)arg_names->pdata,
&out_packages, cancellable, error))
return FALSE;

g_autoptr (GVariantIter) iter1 = NULL;
g_variant_get (out_packages, "aa{sv}", &iter1);

g_autoptr (GVariantIter) iter2 = NULL;
std::set<const gchar *, cstrless> query_set;

while (g_variant_iter_loop (iter1, "a{sv}", &iter2))
{
const gchar *key;
const gchar *name;
const gchar *summary;
const gchar *query;
const gchar *match_group = "";

g_autoptr (GVariant) value = NULL;

while (g_variant_iter_loop (iter2, "{sv}", &key, &value))
{
if (strcmp (key, "key") == 0)
g_variant_get (value, "s", &query);
else if (strcmp (key, "name") == 0)
g_variant_get (value, "s", &name);
else if (strcmp (key, "summary") == 0)
g_variant_get (value, "s", &summary);
}

if (!query_set.count (query))
{
query_set.insert (query);

if (strcmp (query, "match_group_a") == 0)
match_group = "Summary & Name";
else if (strcmp (query, "match_group_b") == 0)
match_group = "Name";
else if (strcmp (query, "match_group_c") == 0)
match_group = "Summary";

g_print ("\n===== %s Matched =====\n", match_group);
}

g_print ("%s : %s\n", name, summary);
}

if (query_set.size () == 0)
{
g_print ("No matches found.");
}

return TRUE;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: missing an empty line here at the end of the file

6 changes: 6 additions & 0 deletions src/daemon/org.projectatomic.rpmostree1.xml
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,12 @@
<arg type="aa{sv}" name="packages" direction="out"/>
<annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="QVariantList"/>
</method>

<method name="Search">
<arg type="as" name="names" direction="in"/>
<arg type="aa{sv}" name="packages" direction="out"/>
<annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="QVariantList"/>
</method>
</interface>

<interface name="org.projectatomic.rpmostree1.OSExperimental">
Expand Down
112 changes: 111 additions & 1 deletion src/daemon/rpmostreed-os.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@
#include "rpmostreed-transaction.h"
#include "rpmostreed-utils.h"

#include <libdnf/hy-util.cpp>

#include <set>
#include <vector>

typedef struct _RpmostreedOSClass RpmostreedOSClass;

struct _RpmostreedOS
Expand Down Expand Up @@ -141,7 +146,7 @@ os_authorize_method (GDBusInterfaceSkeleton *interface, GDBusMethodInvocation *i
else if (g_strcmp0 (method_name, "GetDeploymentBootConfig") == 0
|| g_strcmp0 (method_name, "ListRepos") == 0
|| g_strcmp0 (method_name, "WhatProvides") == 0
|| g_strcmp0 (method_name, "GetPackages") == 0)
|| g_strcmp0 (method_name, "GetPackages") == 0 || g_strcmp0 (method_name, "Search") == 0)
{
/* Note: early return here because no need authentication
* for these methods
Expand Down Expand Up @@ -1138,6 +1143,110 @@ os_handle_get_packages (RPMOSTreeOS *interface, GDBusMethodInvocation *invocatio
return TRUE;
}

/* helper function to sort and search within a set of (const gchar *) */
struct cstrless
{
bool
operator() (const gchar *a, const gchar *b) const
{
return strcmp (a, b) < 0;
}
};

/* wrapper function to both query for packages and add them to the builder */
static void
query_results_to_builder (HyQuery query, GVariantBuilder *builder, const gchar *id)
{
std::set<const gchar *, cstrless> result_set;
g_autoptr (GPtrArray) pkglist = hy_query_run (query);
for (guint i = 0; i < pkglist->len && result_set.size () < 50; i++)
{
auto pkg = static_cast<DnfPackage *> (g_ptr_array_index (pkglist, i));
if (!result_set.count (dnf_package_get_name (pkg)))
{
os_add_package_info_to_builder (pkg, builder, id);
result_set.insert (dnf_package_get_name (pkg));
}
}
}

/* helper function to filter package query results */
static void
search_packages_by_filter (HyQuery query, GVariantBuilder *builder, const gchar *const *names,
std::vector<int> keynames, const gchar *id)
{
/* case insensitive exact match between search term and package */
hy_query_clear (query);
for (guint j = 0; j < keynames.size (); j++)
{
for (guint i = 0; names[i] != NULL; i++)
lukewarmtemp marked this conversation as resolved.
Show resolved Hide resolved
{
if (!hy_is_glob_pattern (names[i]))
{
hy_query_filter (query, keynames[j], HY_EQ | HY_ICASE, names[i]);
}
else
{
hy_query_filter (query, keynames[j], HY_GLOB | HY_ICASE, names[i]);
}
}
}
query_results_to_builder (query, builder, id);

/* case insensitive substring match of search term within package */
hy_query_clear (query);
for (guint j = 0; j < keynames.size (); j++)
{
for (guint i = 0; names[i] != NULL; i++)
{
if (!hy_is_glob_pattern (names[i]))
{
hy_query_filter (query, keynames[j], HY_SUBSTR | HY_ICASE, names[i]);
}
else
{
hy_query_filter (query, keynames[j], HY_GLOB | HY_ICASE, names[i]);
}
}
}
query_results_to_builder (query, builder, id);
}

static gboolean
os_handle_search (RPMOSTreeOS *interface, GDBusMethodInvocation *invocation,
const gchar *const *names)
{
GError *local_error = NULL;
g_autoptr (GCancellable) cancellable = NULL;

sd_journal_print (LOG_INFO, "Handling Search for caller %s",
g_dbus_method_invocation_get_sender (invocation));

g_autoptr (DnfContext) dnfctx
= os_create_dnf_context_simple (interface, TRUE, cancellable, &local_error);
if (dnfctx == NULL)
return os_throw_dbus_invocation_error (invocation, &local_error);

hy_autoquery HyQuery query = hy_query_create (dnf_context_get_sack (dnfctx));

GVariantBuilder builder;
g_variant_builder_init (&builder, (const GVariantType *)"aa{sv}");

std::vector<int> keynames_a = { HY_PKG_NAME, HY_PKG_SUMMARY };
search_packages_by_filter (query, &builder, names, keynames_a, "match_group_a");

std::vector<int> keynames_b = { HY_PKG_NAME };
search_packages_by_filter (query, &builder, names, keynames_b, "match_group_b");

std::vector<int> keynames_c = { HY_PKG_SUMMARY };
search_packages_by_filter (query, &builder, names, keynames_c, "match_group_c");

GVariant *pkgs_result = g_variant_builder_end (&builder);
g_dbus_method_invocation_return_value (invocation, g_variant_new ("(@aa{sv})", pkgs_result));
lukewarmtemp marked this conversation as resolved.
Show resolved Hide resolved

return TRUE;
}

/* This is an older variant of Cleanup, kept for backcompat */
static gboolean
os_handle_clear_rollback_target (RPMOSTreeOS *interface, GDBusMethodInvocation *invocation,
Expand Down Expand Up @@ -1813,6 +1922,7 @@ rpmostreed_os_iface_init (RPMOSTreeOSIface *iface)
iface->handle_finalize_deployment = os_handle_finalize_deployment;
iface->handle_what_provides = os_handle_what_provides;
iface->handle_get_packages = os_handle_get_packages;
iface->handle_search = os_handle_search;
/* legacy cleanup API; superseded by Cleanup() */
iface->handle_clear_rollback_target = os_handle_clear_rollback_target;
/* legacy deployment change API; superseded by UpdateDeployment() */
Expand Down