public inbox for gentoo-commits@lists.gentoo.org
 help / color / mirror / Atom feed
From: "Alexandre Restovtsev" <tetromino@gmail.com>
To: gentoo-commits@lists.gentoo.org
Subject: [gentoo-commits] proj/openrc-settingsd:master commit in: src/
Date: Mon,  6 Feb 2012 10:24:55 +0000 (UTC)	[thread overview]
Message-ID: <52f4dc6163ecbc334f9ba949d6436f650ecf138d.tetromino@gentoo> (raw)

commit:     52f4dc6163ecbc334f9ba949d6436f650ecf138d
Author:     Alexandre Rostovtsev <tetromino <AT> gentoo <DOT> org>
AuthorDate: Mon Feb  6 10:19:08 2012 +0000
Commit:     Alexandre Restovtsev <tetromino <AT> gmail <DOT> com>
CommitDate: Mon Feb  6 10:19:08 2012 +0000
URL:        http://git.overlays.gentoo.org/gitweb/?p=proj/openrc-settingsd.git;a=commit;h=52f4dc61

Check polkit authorization asynchronously

The other alternative (spawning a thread for each authorization
request) seems less elegant, since such requests can take arbitrarily
long to complete.

---
 src/bus-utils.c |  137 +++++++++++++++++++++-------
 src/bus-utils.h |   13 ++-
 src/hostnamed.c |  275 ++++++++++++++++++++++++++++++++++++++-----------------
 3 files changed, 302 insertions(+), 123 deletions(-)

diff --git a/src/bus-utils.c b/src/bus-utils.c
index d3bc7b2..aef8037 100644
--- a/src/bus-utils.c
+++ b/src/bus-utils.c
@@ -20,44 +20,115 @@
 #include <gio/gio.h>
 #include <polkit/polkit.h>
 
+#include "bus-utils.h"
+
+struct check_polkit_data {
+    const gchar *unique_name;
+    const gchar *action_id;
+    gboolean user_interaction;
+    GAsyncReadyCallback callback;
+    gpointer user_data;
+
+    PolkitAuthority *authority;
+    PolkitSubject *subject;
+};
+
+void
+check_polkit_data_free (struct check_polkit_data *data)
+{
+    if (data == NULL)
+        return;
+
+    if (data->subject != NULL)
+        g_object_unref (data->subject);
+    if (data->authority != NULL)
+        g_object_unref (data->authority);
+    
+    g_free (data);
+}
+
 gboolean
-check_polkit (const gchar *unique_name,
-              const gchar *action_id,
-              const gboolean user_interaction,
-              GError **error)
+check_polkit_finish (GAsyncResult *res,
+                     GError **error)
 {
-    gboolean ret = FALSE;
-    GDBusConnection *connection = NULL;
-    PolkitAuthority *authority = NULL;
-    PolkitSubject *subject = NULL;
-    PolkitAuthorizationResult *result = NULL;
-
-    if ((authority = polkit_authority_get_sync (NULL, error)) == NULL)
-        goto end;
-
-    if (unique_name == NULL || action_id == NULL || 
-        (subject = polkit_system_bus_name_new (unique_name)) == NULL) {
-        g_propagate_error (error,
-                    g_error_new (POLKIT_ERROR, POLKIT_ERROR_FAILED,
-                                "Authorizing for '%s': failed sanity check", action_id));
-        goto end;
-    }
+    GSimpleAsyncResult *simple;
+
+    simple = G_SIMPLE_ASYNC_RESULT (res);
+    if (g_simple_async_result_propagate_error (simple, error))
+        return FALSE;
+
+    return g_simple_async_result_get_op_res_gboolean (simple);
+}
+
+static void
+check_polkit_authorization_cb (GObject *source_object,
+                               GAsyncResult *res,
+                               gpointer _data)
+{
+    struct check_polkit_data *data;
+    PolkitAuthorizationResult *result;
+    GSimpleAsyncResult *simple;
+    GError *err = NULL;
 
-    if ((result = polkit_authority_check_authorization_sync (authority, subject, action_id, NULL, (PolkitCheckAuthorizationFlags) user_interaction, NULL, error)) == NULL)
-        goto end;
+    data = (struct check_polkit_data *) _data;
+    if ((result = polkit_authority_check_authorization_finish (data->authority, res, &err)) == NULL) {
+        g_simple_async_report_take_gerror_in_idle (NULL, data->callback, data->user_data, err);
+        goto out;
+    }
  
-    if ((ret = polkit_authorization_result_get_is_authorized (result)) == FALSE) {
-        g_propagate_error (error,
-                    g_error_new (POLKIT_ERROR, POLKIT_ERROR_NOT_AUTHORIZED,
-                                "Authorizing for '%s': not authorized", action_id));
+    if (!polkit_authorization_result_get_is_authorized (result)) {
+        g_simple_async_report_error_in_idle (NULL, data->callback, data->user_data, POLKIT_ERROR, POLKIT_ERROR_NOT_AUTHORIZED, "Authorizing for '%s': not authorized", data->action_id);
+        goto out;
     }
-                                                             
-  end:
+    simple = g_simple_async_result_new (NULL, data->callback, data->user_data, check_polkit_async);
+    g_simple_async_result_set_op_res_gboolean (simple, TRUE);
+    g_simple_async_result_complete_in_idle (simple);
+    g_object_unref (simple);
+
+  out:
+    check_polkit_data_free (data);
     if (result != NULL)
         g_object_unref (result);
-    if (subject != NULL)
-        g_object_unref (subject);
-    if (authority != NULL)
-        g_object_unref (authority);
-    return ret;
+}
+
+static void
+check_polkit_authority_cb (GObject *source_object,
+                           GAsyncResult *res,
+                           gpointer _data)
+{
+    struct check_polkit_data *data;
+    GError *err = NULL;
+
+    data = (struct check_polkit_data *) _data;
+    if ((data->authority = polkit_authority_get_finish (res, &err)) == NULL) {
+        g_simple_async_report_take_gerror_in_idle (NULL, data->callback, data->user_data, err);
+        check_polkit_data_free (data);
+        return;
+    }
+    if (data->unique_name == NULL || data->action_id == NULL || 
+        (data->subject = polkit_system_bus_name_new (data->unique_name)) == NULL) {
+        g_simple_async_report_error_in_idle (NULL, data->callback, data->user_data, POLKIT_ERROR, POLKIT_ERROR_FAILED, "Authorizing for '%s': failed sanity check", data->action_id);
+        check_polkit_data_free (data);
+        return;
+    }
+    polkit_authority_check_authorization (data->authority, data->subject, data->action_id, NULL, (PolkitCheckAuthorizationFlags) data->user_interaction, NULL, check_polkit_authorization_cb, data);
+}
+
+void
+check_polkit_async (const gchar *unique_name,
+                    const gchar *action_id,
+                    const gboolean user_interaction,
+                    GAsyncReadyCallback callback,
+                    gpointer user_data)
+{
+    struct check_polkit_data *data;
+
+    data = g_new0 (struct check_polkit_data, 1);
+    data->unique_name = unique_name;
+    data->action_id = action_id;
+    data->user_interaction = user_interaction;
+    data->callback = callback;
+    data->user_data = user_data;
+
+    polkit_authority_get_async (NULL, check_polkit_authority_cb, data);
 }
\ No newline at end of file

diff --git a/src/bus-utils.h b/src/bus-utils.h
index 31a2ef1..f2e80ea 100644
--- a/src/bus-utils.h
+++ b/src/bus-utils.h
@@ -21,10 +21,15 @@
 
 #include <glib.h>
 
+void
+check_polkit_async (const gchar *unique_name,
+                    const gchar *action_id,
+                    const gboolean user_interaction,
+                    GAsyncReadyCallback callback,
+                    gpointer user_data);
+
 gboolean
-check_polkit (const gchar *unique_name,
-              const gchar *action_id,
-              const gboolean user_interaction,
-              GError **error);
+check_polkit_finish (GAsyncResult *res,
+                     GError **error);
 
 #endif
\ No newline at end of file

diff --git a/src/hostnamed.c b/src/hostnamed.c
index 4f95d04..c5914bd 100644
--- a/src/hostnamed.c
+++ b/src/hostnamed.c
@@ -36,6 +36,11 @@
 #define QUOTE(macro) #macro
 #define STR(macro) QUOTE(macro)
 
+struct invoked_name {
+    GDBusMethodInvocation *invocation;
+    gchar *name; /* newly allocated */
+};
+
 guint bus_id = 0;
 gboolean read_only = FALSE;
 
@@ -103,152 +108,250 @@ guess_icon_name ()
     icon_name = g_strdup ("computer");
 }
 
-static gboolean
-on_handle_set_hostname (OpenrcSettingsdHostnamedHostname1 *hostname1,
-                        GDBusMethodInvocation *invocation,
-                        const gchar *name,
-                        const gboolean user_interaction,
-                        gpointer user_data)
+static void
+on_handle_set_hostname_authorized_cb (GObject *source_object,
+                                      GAsyncResult *res,
+                                      gpointer user_data)
 {
     GError *err = NULL;
-
-    if (read_only) {
-        g_dbus_method_invocation_return_dbus_error (invocation,
-                                                    DBUS_ERROR_NOT_SUPPORTED,
-                                                    "openrc-settingsd hostnamed is in read-only mode");
-        goto end;
-    }
-
-    if (!check_polkit (g_dbus_method_invocation_get_sender (invocation), "org.freedesktop.hostname1.set-hostname", user_interaction, &err)) {
-        g_dbus_method_invocation_return_gerror (invocation, err);
-        goto end;
+    struct invoked_name *data;
+    
+    data = (struct invoked_name *) user_data;
+    if (!check_polkit_finish (res, &err)) {
+        g_dbus_method_invocation_return_gerror (data->invocation, err);
+        goto out;
     }
 
     G_LOCK (hostname);
     /* Don't allow an empty or invalid hostname */
-    if (!hostname_is_valid (name)) {
-        name = hostname;
-        if (!hostname_is_valid (name))
-            name = "localhost";
+    if (!hostname_is_valid (data->name)) {
+        if (data->name != NULL)
+            g_free (data->name);
+
+        if (hostname_is_valid (hostname))
+            data->name = g_strdup (hostname);
+        else
+            data->name = g_strdup ("localhost");
     }
-    if (sethostname (name, strlen(name))) {
+    if (sethostname (data->name, strlen(data->name))) {
         int errsv = errno;
-        g_dbus_method_invocation_return_dbus_error (invocation,
+        g_dbus_method_invocation_return_dbus_error (data->invocation,
                                                     DBUS_ERROR_FAILED,
                                                     strerror (errsv));
         G_UNLOCK (hostname);
-        goto end;
+        goto out;
     }
-    g_strlcpy (hostname, name, HOST_NAME_MAX + 1);
-    openrc_settingsd_hostnamed_hostname1_complete_set_hostname (hostname1, invocation);
+    g_strlcpy (hostname, data->name, HOST_NAME_MAX + 1);
+    openrc_settingsd_hostnamed_hostname1_complete_set_hostname (hostname1, data->invocation);
     openrc_settingsd_hostnamed_hostname1_set_hostname (hostname1, hostname);
     G_UNLOCK (hostname);
 
-  end:
-    return TRUE;
+  out:
+    g_free (data->name);
+    g_free (data);
+    if (err != NULL)
+        g_error_free (err);
 }
 
 static gboolean
-on_handle_set_static_hostname (OpenrcSettingsdHostnamedHostname1 *hostname1,
-                               GDBusMethodInvocation *invocation,
-                               const gchar *name,
-                               const gboolean user_interaction,
-                               gpointer user_data)
+on_handle_set_hostname (OpenrcSettingsdHostnamedHostname1 *hostname1,
+                        GDBusMethodInvocation *invocation,
+                        const gchar *name,
+                        const gboolean user_interaction,
+                        gpointer user_data)
 {
-    ShellUtilsTrivial *confd_file = NULL;
-    GError *err = NULL;
-
-    if (read_only) {
+    if (read_only)
         g_dbus_method_invocation_return_dbus_error (invocation,
                                                     DBUS_ERROR_NOT_SUPPORTED,
                                                     "openrc-settingsd hostnamed is in read-only mode");
-        goto end;
+    else {
+        struct invoked_name *data;
+        data = g_new0 (struct invoked_name, 1);
+        data->invocation = invocation;
+        data->name = g_strdup (name);
+        check_polkit_async (g_dbus_method_invocation_get_sender (invocation), "org.freedesktop.hostname1.set-hostname", user_interaction, on_handle_set_hostname_authorized_cb, data);
     }
 
-    if (!check_polkit (g_dbus_method_invocation_get_sender (invocation), "org.freedesktop.hostname1.set-static-hostname", user_interaction, &err)) {
-        g_dbus_method_invocation_return_gerror (invocation, err);
-        goto end;
+    return TRUE;
+}
+
+static void
+on_handle_set_static_hostname_authorized_cb (GObject *source_object,
+                                             GAsyncResult *res,
+                                             gpointer user_data)
+{
+    GError *err = NULL;
+    struct invoked_name *data;
+    
+    data = (struct invoked_name *) user_data;
+    if (!check_polkit_finish (res, &err)) {
+        g_dbus_method_invocation_return_gerror (data->invocation, err);
+        goto out;
     }
 
     G_LOCK (static_hostname);
     /* Don't allow an empty or invalid hostname */
-    if (!hostname_is_valid (name))
-        name = "localhost";
+    if (!hostname_is_valid (data->name)) {
+        if (data->name != NULL)
+            g_free (data->name);
+
+        data->name = g_strdup ("localhost");
+    }
 
-    if (!shell_utils_trivial_set_and_save (static_hostname_file, &err, "hostname", "HOSTNAME", name, NULL)) {
-        g_dbus_method_invocation_return_gerror (invocation, err);
+    if (!shell_utils_trivial_set_and_save (static_hostname_file, &err, "hostname", "HOSTNAME", data->name, NULL)) {
+        g_dbus_method_invocation_return_gerror (data->invocation, err);
         G_UNLOCK (static_hostname);
-        goto end;
+        goto out;
     }
 
     g_free (static_hostname);
-    static_hostname = g_strdup (name);
-    openrc_settingsd_hostnamed_hostname1_complete_set_static_hostname (hostname1, invocation);
+    static_hostname = data->name; /* data->name is g_strdup-ed already */;
+    openrc_settingsd_hostnamed_hostname1_complete_set_static_hostname (hostname1, data->invocation);
     openrc_settingsd_hostnamed_hostname1_set_static_hostname (hostname1, static_hostname);
     G_UNLOCK (static_hostname);
 
-  end:
-    shell_utils_trivial_free (confd_file);
+  out:
+    g_free (data);
     if (err != NULL)
         g_error_free (err);
+}
+
+static gboolean
+on_handle_set_static_hostname (OpenrcSettingsdHostnamedHostname1 *hostname1,
+                               GDBusMethodInvocation *invocation,
+                               const gchar *name,
+                               const gboolean user_interaction,
+                               gpointer user_data)
+{
+    if (read_only)
+        g_dbus_method_invocation_return_dbus_error (invocation,
+                                                    DBUS_ERROR_NOT_SUPPORTED,
+                                                    "openrc-settingsd hostnamed is in read-only mode");
+    else {
+        struct invoked_name *data;
+        data = g_new0 (struct invoked_name, 1);
+        data->invocation = invocation;
+        data->name = g_strdup (name);
+        check_polkit_async (g_dbus_method_invocation_get_sender (invocation), "org.freedesktop.hostname1.set-static-hostname", user_interaction, on_handle_set_static_hostname_authorized_cb, data);
+    }
 
     return TRUE; /* Always return TRUE to indicate signal has been handled */
 }
 
-static gboolean
-on_handle_set_machine_info (OpenrcSettingsdHostnamedHostname1 *hostname1,
-                            GDBusMethodInvocation *invocation,
-                            const gchar *name,
-                            const gboolean user_interaction,
-                            gpointer user_data)
+static void
+on_handle_set_pretty_hostname_authorized_cb (GObject *source_object,
+                                             GAsyncResult *res,
+                                             gpointer user_data)
 {
-    ShellUtilsTrivial *confd_file = NULL;
     GError *err = NULL;
-    gboolean is_pretty_hostname = GPOINTER_TO_INT(user_data);
+    struct invoked_name *data;
+    
+    data = (struct invoked_name *) user_data;
+    if (!check_polkit_finish (res, &err)) {
+        g_dbus_method_invocation_return_gerror (data->invocation, err);
+        goto out;
+    }
+
+    G_LOCK (machine_info);
+    /* Don't allow a null pretty hostname */
+    if (data->name == NULL)
+        data->name = g_strdup ("");
 
-    if (read_only) {
+    if (!shell_utils_trivial_set_and_save (machine_info_file, &err, "PRETTY_HOSTNAME", NULL, data->name, NULL)) {
+        g_dbus_method_invocation_return_gerror (data->invocation, err);
+        G_UNLOCK (machine_info);
+        goto out;
+    }
+
+    g_free (pretty_hostname);
+    pretty_hostname = data->name; /* data->name is g_strdup-ed already */
+    openrc_settingsd_hostnamed_hostname1_complete_set_pretty_hostname (hostname1, data->invocation);
+    openrc_settingsd_hostnamed_hostname1_set_pretty_hostname (hostname1, pretty_hostname);
+    G_UNLOCK (machine_info);
+
+  out:
+    g_free (data);
+    if (err != NULL)
+        g_error_free (err);
+}
+
+static gboolean
+on_handle_set_pretty_hostname (OpenrcSettingsdHostnamedHostname1 *hostname1,
+                               GDBusMethodInvocation *invocation,
+                               const gchar *name,
+                               const gboolean user_interaction,
+                               gpointer user_data)
+{
+    if (read_only)
         g_dbus_method_invocation_return_dbus_error (invocation,
                                                     DBUS_ERROR_NOT_SUPPORTED,
                                                     "openrc-settingsd hostnamed is in read-only mode");
-        goto end;
+    else {
+        struct invoked_name *data;
+        data = g_new0 (struct invoked_name, 1);
+        data->invocation = invocation;
+        data->name = g_strdup (name);
+        check_polkit_async (g_dbus_method_invocation_get_sender (invocation), "org.freedesktop.hostname1.set-machine-info", user_interaction, on_handle_set_pretty_hostname_authorized_cb, data);
     }
 
-    if (!check_polkit (g_dbus_method_invocation_get_sender (invocation), "org.freedesktop.hostname1.set-machine-info", user_interaction, &err)) {
-        g_dbus_method_invocation_return_gerror (invocation, err);
-        goto end;
+    return TRUE; /* Always return TRUE to indicate signal has been handled */
+}
+
+static void
+on_handle_set_icon_name_authorized_cb (GObject *source_object,
+                                       GAsyncResult *res,
+                                       gpointer user_data)
+{
+    GError *err = NULL;
+    struct invoked_name *data;
+    
+    data = (struct invoked_name *) user_data;
+    if (!check_polkit_finish (res, &err)) {
+        g_dbus_method_invocation_return_gerror (data->invocation, err);
+        goto out;
     }
 
     G_LOCK (machine_info);
     /* Don't allow a null pretty hostname */
-    if (name == NULL)
-        name = "";
+    if (data->name == NULL)
+        data->name = g_strdup ("");
 
-    if ((is_pretty_hostname &&
-            !shell_utils_trivial_set_and_save (machine_info_file, &err, "PRETTY_HOSTNAME", NULL, name, NULL)) ||
-        (!is_pretty_hostname &&
-            !shell_utils_trivial_set_and_save (machine_info_file, &err, "ICON_NAME", NULL, name, NULL))) {
-        g_dbus_method_invocation_return_gerror (invocation, err);
+    if (!shell_utils_trivial_set_and_save (machine_info_file, &err, "ICON_NAME", NULL, data->name, NULL)) {
+        g_dbus_method_invocation_return_gerror (data->invocation, err);
         G_UNLOCK (machine_info);
-        goto end;
+        goto out;
     }
 
-    if (is_pretty_hostname) {
-        g_free (pretty_hostname);
-        pretty_hostname = g_strdup (name);
-        openrc_settingsd_hostnamed_hostname1_complete_set_pretty_hostname (hostname1, invocation);
-        openrc_settingsd_hostnamed_hostname1_set_pretty_hostname (hostname1, pretty_hostname);
-    } else {
-        g_free (icon_name);
-        icon_name = g_strdup (name);
-        openrc_settingsd_hostnamed_hostname1_complete_set_icon_name (hostname1, invocation);
-        openrc_settingsd_hostnamed_hostname1_set_icon_name (hostname1, icon_name);
-    }
+    g_free (icon_name);
+    icon_name = data->name; /* data->name is g_strdup-ed already */
+    openrc_settingsd_hostnamed_hostname1_complete_set_icon_name (hostname1, data->invocation);
+    openrc_settingsd_hostnamed_hostname1_set_icon_name (hostname1, icon_name);
     G_UNLOCK (machine_info);
 
-  end:
-    shell_utils_trivial_free (confd_file);
+  out:
+    g_free (data);
     if (err != NULL)
         g_error_free (err);
+}
+
+static gboolean
+on_handle_set_icon_name (OpenrcSettingsdHostnamedHostname1 *hostname1,
+                         GDBusMethodInvocation *invocation,
+                         const gchar *name,
+                         const gboolean user_interaction,
+                         gpointer user_data)
+{
+    if (read_only)
+        g_dbus_method_invocation_return_dbus_error (invocation,
+                                                    DBUS_ERROR_NOT_SUPPORTED,
+                                                    "openrc-settingsd hostnamed is in read-only mode");
+    else {
+        struct invoked_name *data;
+        data = g_new0 (struct invoked_name, 1);
+        data->invocation = invocation;
+        data->name = g_strdup (name);
+        check_polkit_async (g_dbus_method_invocation_get_sender (invocation), "org.freedesktop.hostname1.set-machine-info", user_interaction, on_handle_set_icon_name_authorized_cb, data);
+    }
 
     return TRUE; /* Always return TRUE to indicate signal has been handled */
 }
@@ -272,8 +375,8 @@ on_bus_acquired (GDBusConnection *connection,
 
     g_signal_connect (hostname1, "handle-set-hostname", G_CALLBACK (on_handle_set_hostname), NULL);
     g_signal_connect (hostname1, "handle-set-static-hostname", G_CALLBACK (on_handle_set_static_hostname), NULL);
-    g_signal_connect (hostname1, "handle-set-pretty-hostname", G_CALLBACK (on_handle_set_machine_info), GINT_TO_POINTER(TRUE));
-    g_signal_connect (hostname1, "handle-set-icon-name", G_CALLBACK (on_handle_set_machine_info), GINT_TO_POINTER(FALSE));
+    g_signal_connect (hostname1, "handle-set-pretty-hostname", G_CALLBACK (on_handle_set_pretty_hostname), NULL);
+    g_signal_connect (hostname1, "handle-set-icon-name", G_CALLBACK (on_handle_set_icon_name), NULL);
 
     if (!g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (hostname1),
                                            connection,



             reply	other threads:[~2012-02-06 10:25 UTC|newest]

Thread overview: 17+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2012-02-06 10:24 Alexandre Restovtsev [this message]
  -- strict thread matches above, loose matches on Subject: below --
2012-09-09  3:47 [gentoo-commits] proj/openrc-settingsd:master commit in: src/ Alexandre Rostovtsev
2012-09-08  0:20 Alexandre Restovtsev
2012-09-05 23:46 Alexandre Restovtsev
2012-09-05 21:19 Alexandre Restovtsev
2012-09-05 21:19 Alexandre Restovtsev
2012-09-05 21:19 Alexandre Restovtsev
2012-09-05 16:54 Alexandre Restovtsev
2012-03-19  3:03 Alexandre Restovtsev
2012-03-19  2:05 Alexandre Restovtsev
2012-03-18 11:02 Alexandre Restovtsev
2012-02-08  6:53 Alexandre Restovtsev
2012-02-08  5:57 Alexandre Restovtsev
2012-02-08  5:01 Alexandre Restovtsev
2012-02-05  7:52 Alexandre Restovtsev
2012-02-05  0:20 Alexandre Restovtsev
2012-01-29 23:22 Alexandre Restovtsev

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=52f4dc6163ecbc334f9ba949d6436f650ecf138d.tetromino@gentoo \
    --to=tetromino@gmail.com \
    --cc=gentoo-commits@lists.gentoo.org \
    --cc=gentoo-dev@lists.gentoo.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox