many: reproduce inconsistent common id bug#16634
Conversation
Signed-off-by: Oliver Calder <oliver.calder@canonical.com>
|
FYI all started by playing with the rest API to avoid using snapd-info-test.c/*
Compile with:
gcc snapd-info-test.c -o snapd-test $(jhbuild run pkg-config --cflags --libs libsoup-3.0 json-glib-1.0 gio-unix-2.0) -g3
*/
#define HAVE_SNAPD_SUPPORT 1
#define SNAP_SECURITY_LABEL_PREFIX "snap."
#include <gio/gdesktopappinfo.h>
#include <libsoup/soup.h>
#include <json-glib/json-glib.h>
#include <stdint.h>
#include <inttypes.h>
#define SNAPD_SOCKET_PATH "/var/run/snapd.socket"
#if HAVE_SNAPD_SUPPORT
static SoupMessage *
make_snapd_message (const gchar *method, const gchar *path)
{
g_autofree gchar *uri = NULL;
SoupMessage *msg;
SoupMessageHeaders *request_headers;
uri = g_strconcat ("http://localhost", path, NULL);
msg = soup_message_new (method, uri);
request_headers = soup_message_get_request_headers (msg);
/* Disallow authentication via polkit. */
soup_message_headers_append (request_headers, "X-Allow-Interaction", "false");
return msg;
}
static JsonObject *
process_snapd_response_body (SoupMessage *msg,
GBytes *body,
GError **error)
{
const gchar *content_type;
g_autoptr(JsonParser) parser = NULL;
const gchar *body_data;
size_t body_length;
JsonNode *root;
JsonObject *response;
gint64 status_code;
g_autoptr(GError) internal_error = NULL;
content_type = soup_message_headers_get_one (
soup_message_get_response_headers (msg), "Content-Type");
if (g_strcmp0 (content_type, "application/json") != 0)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Invalid content type %s returned", content_type);
return NULL;
}
parser = json_parser_new ();
body_data = g_bytes_get_data (body, &body_length);
if (!json_parser_load_from_data (parser, body_data, body_length, &internal_error))
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to decode JSON content: %s", internal_error->message);
return NULL;
}
root = json_parser_get_root (parser);
if (!JSON_NODE_HOLDS_OBJECT (root))
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Returned JSON not an object");
return NULL;
}
response = json_node_get_object (root);
status_code = json_object_get_int_member (response, "status-code");
if (status_code != SOUP_STATUS_OK && status_code != SOUP_STATUS_ACCEPTED)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Invalid status code %"
G_GINT64_FORMAT, status_code);
return NULL;
}
return json_object_ref (response);
}
typedef struct {
GMainLoop *loop;
GBytes *response_body;
GError **error;
} SendMessageData;
static void
on_send_message_finished (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
SendMessageData *data = user_data;
SoupSession *session = SOUP_SESSION (source_object);
data->response_body = soup_session_send_and_read_finish (session, res,
data->error);
g_main_loop_quit (data->loop);
}
static JsonObject *
call_snapd_sync (const gchar *method, const gchar *path, GError **error)
{
g_autoptr(SoupMessage) msg = NULL;
g_autoptr(GBytes) response_body = NULL;
g_autoptr(GSocketAddress) address = g_unix_socket_address_new (SNAPD_SOCKET_PATH);
g_autoptr(GMainLoop) loop = g_main_loop_new (NULL, FALSE);
g_autoptr(GCancellable) cancellable = g_cancellable_new ();
SendMessageData send_message_data = { .loop = loop, .error = error };
msg = make_snapd_message (method, path);
SoupSession *session = soup_session_new_with_options ("remote-connectable", address, NULL);
soup_session_set_timeout (session, 1);
g_timeout_add_once (20, (GSourceOnceFunc) g_cancellable_cancel, cancellable);
soup_session_send_and_read_async (session, msg, G_PRIORITY_HIGH,
cancellable, on_send_message_finished,
&send_message_data);
g_main_loop_run (loop);
if (!(response_body = g_steal_pointer (&send_message_data.response_body)))
return NULL;
return process_snapd_response_body (msg, response_body, error);
}
JsonObject *
snapd_client_get_snap_sync (const gchar *name, GError **error)
{
g_autofree gchar *path = NULL;
g_autoptr(JsonObject) response = NULL;
JsonObject *result;
path = g_strconcat ("/v2/snaps/", name, NULL);
response = call_snapd_sync ("GET", path, error);
if (response == NULL)
return NULL;
result = json_object_get_object_member (response, "result");
if (result == NULL)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Invalid response to %s", path);
return NULL;
}
return json_object_ref (result);
}
static char *
snapd_get_application_app_id_sync (const gchar *snap_name,
const gchar *app_name,
GError **error)
{
g_print("Looking for snap name: '%s' app '%s'\n", snap_name, app_name);
g_autoptr (JsonObject) snap = NULL;
if (!(snap = snapd_client_get_snap_sync(snap_name, error)))
return NULL;
g_print("Got json object for snap '%p'\n", snap);
// {
// g_autofree gchar *snap_text = NULL;
// gsize snap_len = 0;
// JsonNode *node = NULL;
// node = json_node_new (JSON_NODE_OBJECT);
// json_node_init_object (node, snap);
// snap_text = json_to_string(node, TRUE);
// g_print ("%s\n", snap_text);
// json_node_free (node);
// }
JsonNode *apps_node = json_object_get_member (snap, "apps");
if (!apps_node || !JSON_NODE_HOLDS_ARRAY (apps_node))
{
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed parsing returned JSON");
return NULL;
}
JsonArray *apps_array = json_node_get_array (apps_node);
g_autoptr(GList) apps = json_array_get_elements (apps_array);
guint n_apps = json_array_get_length (apps_array);
for (guint i = 0; i < n_apps; ++i)
{
g_autofree char *app_id = NULL;
JsonNode *app_node;
JsonObject *app;
const char *name;
const char *desktop_file;
char *dot;
app_node = json_array_get_element (apps_array, i);
if (!JSON_NODE_HOLDS_OBJECT (app_node))
{
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed parsing returned JSON");
return NULL;
}
app = json_node_get_object (app_node);
name = json_object_get_string_member (app, "name");
desktop_file = json_object_get_string_member (app, "desktop-file");
g_print("Processing app '%s' with desktop file '%s'\n", name, desktop_file);
if (!name || !desktop_file)
continue;
if (g_strcmp0 (name, app_name) != 0)
continue;
app_id = g_path_get_basename (desktop_file);
dot = strrchr (app_id, '.');
if (dot)
*dot = '\0';
g_print("Snap '%s' has app '%s' with desktop file '%s'\n", snap_name, name, desktop_file);
return g_steal_pointer (&app_id);
}
return NULL;
}
#endif
typedef struct _MetaWindow
{
char *sandboxed_app_id;
} MetaWindow;
static gboolean
meta_window_update_snap_id (MetaWindow *window,
uint32_t pid)
{
g_autoptr (GError) error = NULL;
g_autoptr (GKeyFile) portal_info_key_file = NULL;
g_autofree char *security_label_filename = NULL;
g_autofree char *security_label_contents = NULL;
g_autofree char *desktop_file = NULL;
g_autofree char *portal_info = NULL;
g_autofree char *pid_str = NULL;
gsize i, security_label_contents_size = 0;
char *contents_start;
char *contents_end;
char *sandboxed_app_id;
char *sandboxed_app_name;
int portal_wait_status;
g_return_val_if_fail (pid != 0, FALSE);
g_return_val_if_fail (window->sandboxed_app_id == NULL, FALSE);
security_label_filename = g_strdup_printf ("/proc/%u/attr/current", pid);
if (!g_file_get_contents (security_label_filename,
&security_label_contents,
&security_label_contents_size,
NULL))
return FALSE;
if (!g_str_has_prefix (security_label_contents, SNAP_SECURITY_LABEL_PREFIX))
return FALSE;
/* We need to translate the security profile into the desktop-id.
* The profile is in the form of 'snap.name-space.binary-name (current)'
* while the desktop id will be name-space_binary-name.
*/
security_label_contents_size -= sizeof (SNAP_SECURITY_LABEL_PREFIX) - 1;
contents_start = security_label_contents + sizeof (SNAP_SECURITY_LABEL_PREFIX) - 1;
contents_end = strchr (contents_start, ' ');
if (contents_end)
security_label_contents_size = contents_end - contents_start;
for (i = 0; i < security_label_contents_size; ++i)
{
if (contents_start[i] == '.')
contents_start[i] = '_';
}
sandboxed_app_id = g_malloc0 (security_label_contents_size + 1);
memcpy (sandboxed_app_id, contents_start, security_label_contents_size);
window->sandboxed_app_id = g_steal_pointer (&sandboxed_app_id);
#ifdef HAVE_SNAPD_SUPPORT
g_autoptr (GDesktopAppInfo) app_info = NULL;
g_autofree char *desktop_id = NULL;
g_autofree char *app_id = NULL;
g_autofree char *snap_name = g_strdup (window->sandboxed_app_id);
char *snap_app = strchr (snap_name, '_');
desktop_id = g_strconcat (window->sandboxed_app_id, ".desktop", NULL);
g_print("Desktop ID: %s\n", desktop_id);
if (!snap_name || !snap_app ||
/* (app_info = g_desktop_app_info_new (desktop_id)) != NULL */ FALSE)
return TRUE;
*snap_app = '\0';
snap_app++;
g_clear_pointer (&desktop_id, g_free);
app_id = snapd_get_application_app_id_sync (snap_name, snap_app, &error);
if (app_id)
{
window->sandboxed_app_id = g_steal_pointer (&app_id);
}
else if (error)
{
g_warning ("Failed to get desktop file for snap '%s' app '%s': %s",
snap_name, snap_app, error->message);
}
return TRUE;
#else
return TRUE;
#endif
return FALSE;
}
int
main (int argc, char *argv[])
{
MetaWindow window = {0};
pid_t pid = argc > 1 ? (pid_t) atoi (argv[1]) : 0;
if (!meta_window_update_snap_id (&window, pid))
g_print ("Failed to get snap id\n");
else
g_print ("Got snap id: %s\n", window.sandboxed_app_id);
g_clear_pointer (&window.sandboxed_app_id, g_free);
return 0;
}And we do the nice dance: ❯ for i in $(seq 0 20); do ./snapd-test $(pidof ashpd-demo); done |grep "snap id"
Got snap id: ashpd-demo_ashpd-demo
Got snap id: ashpd-demo_ashpd-demo
Got snap id: com.belmoussaoui.ashpd.demo
Got snap id: com.belmoussaoui.ashpd.demo
Got snap id: com.belmoussaoui.ashpd.demo
Got snap id: com.belmoussaoui.ashpd.demo
Got snap id: ashpd-demo_ashpd-demo
Got snap id: com.belmoussaoui.ashpd.demo
Got snap id: com.belmoussaoui.ashpd.demo
Got snap id: ashpd-demo_ashpd-demo
Got snap id: com.belmoussaoui.ashpd.demo
Got snap id: ashpd-demo_ashpd-demo
Got snap id: com.belmoussaoui.ashpd.demo
Got snap id: com.belmoussaoui.ashpd.demo
Got snap id: com.belmoussaoui.ashpd.demo
Got snap id: com.belmoussaoui.ashpd.demo
Got snap id: com.belmoussaoui.ashpd.demo
Got snap id: ashpd-demo_ashpd-demo
Got snap id: ashpd-demo_ashpd-demo
Got snap id: com.belmoussaoui.ashpd.demo
Got snap id: ashpd-demo_ashpd-demoNo different from using for i in $(seq 0 20); do snap routine portal-info $(pidof ashpd-demo); done |grep "DesktopFile"
DesktopFile=com.belmoussaoui.ashpd.demo.desktop
DesktopFile=com.belmoussaoui.ashpd.demo.desktop
DesktopFile=ashpd-demo_ashpd-demo.desktop
DesktopFile=com.belmoussaoui.ashpd.demo.desktop
DesktopFile=ashpd-demo_ashpd-demo.desktop
DesktopFile=ashpd-demo_ashpd-demo.desktop
DesktopFile=com.belmoussaoui.ashpd.demo.desktop
DesktopFile=com.belmoussaoui.ashpd.demo.desktop
DesktopFile=ashpd-demo_ashpd-demo.desktop
DesktopFile=ashpd-demo_ashpd-demo.desktop
DesktopFile=com.belmoussaoui.ashpd.demo.desktop
DesktopFile=com.belmoussaoui.ashpd.demo.desktop
DesktopFile=com.belmoussaoui.ashpd.demo.desktop
DesktopFile=com.belmoussaoui.ashpd.demo.desktop
DesktopFile=ashpd-demo_ashpd-demo.desktop
DesktopFile=ashpd-demo_ashpd-demo.desktop
DesktopFile=com.belmoussaoui.ashpd.demo.desktop
DesktopFile=com.belmoussaoui.ashpd.demo.desktop
DesktopFile=ashpd-demo_ashpd-demo.desktop
DesktopFile=com.belmoussaoui.ashpd.demo.desktop
DesktopFile=ashpd-demo_ashpd-demo.desktopThe desktop file switches between the two defined. Note that this was not happening in 2.74.1, but because the file was named wrongly, after fixing it it show the issue: sudo mv /var/lib/snapd/desktop/applications/ashpd-demo_com.belmoussaoui.ashpd.demo.desktop \
/var/lib/snapd/desktop/applications/com.belmoussaoui.ashpd.demo.desktop |
|
Fri Feb 20 01:40:16 UTC 2026 Failures:Preparing:
Executing:
Restoring:
|
Oh. Of course, we're looping through Map values, they aren't ever orderd! |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #16634 +/- ##
==========================================
+ Coverage 77.50% 77.52% +0.02%
==========================================
Files 1355 1353 -2
Lines 186018 186090 +72
Branches 2449 2449
==========================================
+ Hits 144165 144268 +103
+ Misses 33130 33113 -17
+ Partials 8723 8709 -14
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Thanks for digging into this! In this case the app only has one desktop file ID specified in the plug, so I don't think that is the issue (although we should do something to make that more deterministic). But I think the real problem here is a race between the desktop file being available on the filesystem. Not sure how to mitigate that, but will think on it with the team |
|
@olivercalder can you clarify whether we still need it? If so, please rebase and ask for reviews. |
|
I forgot about this PR, but a fix for the issue is in #17016. In short, the test snap has two |
@3v1n0 is seeing the common ID flip between the one defined in the snap
desktop-filesplug and the default<snap>_...one. One way this manifests is that snap installs occasionally fail due to a mismatch. This PR adds a spread test to reproduce it and record the stacktraces at time of failure.Marco said:
In my experience on my local system (25.10 with snapd 2.74.1 from latest/beta) I see the flip-flopping too. So it's host-system dependent.
This PR should not be merged.
This issue is tracked internally by https://warthogs.atlassian.net/browse/SNAPDENG-36476