Merge lp:~ted/ubuntu-app-launch/fdo-application-open into lp:ubuntu-app-launch/13.10

Proposed by Ted Gould
Status: Merged
Approved by: Ted Gould
Approved revision: 81
Merged at revision: 69
Proposed branch: lp:~ted/ubuntu-app-launch/fdo-application-open
Merge into: lp:ubuntu-app-launch/13.10
Diff against target: 338 lines (+302/-6)
3 files modified
CMakeLists.txt (+9/-0)
application.conf.in (+2/-6)
fdo-application-open.c (+291/-0)
To merge this branch: bzr merge lp:~ted/ubuntu-app-launch/fdo-application-open
Reviewer Review Type Date Requested Status
PS Jenkins bot (community) continuous-integration Approve
Indicator Applet Developers Pending
Review via email: mp+186887@code.launchpad.net

Commit message

On second activations send a message to the FD.o application interface

Description of the change

This branch changes the behavior so that when we detect an application that we're managing the single instance behavior of is already running we send a DBus message using the FD.o application interface to open the URLs that we have.

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Loïc Minier (lool) wrote :

* the variable name "connection" is confusing in get_pid_cb() and contact_app(), could we call it connection_name?
* please add a TODO in main and in parse_uris() to use something else than space for splitting URIs; in fact it might be better to just remove support for multiple uris in this new code

So IIUC, we're first scanning by matching PID (PID that we think we've launched with upstart) then comparing the DBus bus name of the target app to match the app name; I find this a bit ugly because:
a) pids are reused, even if it's unlikely that it happens quickly and at the right time, it's bad when you can't guarantee it didn't happen
b) app names are not unique, only namespace + app name is

I'd rather we use namespace + app name as the bus name we're searching for, and connect to that; seems simple and solid. But I guess this was discussed elsewhere since we have to use the same scheme in e.g. qtubuntu?

Revision history for this message
Loïc Minier (lool) wrote :

I forgot one thing, these constructs:
   if ! start application-click APP_ID="${APP_ID}" APP_URIS="${APP_URIS}"; then
[...]
    if ! start application-legacy APP_ID="${APP_ID}" INSTANCE_ID="" APP_URIS="${APP_URIS}" ; then
that either start an applicatin or consider an application is already running always bugged me as fragile because a real start failure would be handled as if the app is running.

Surely we can do better?

start's exit code doesn't distinguish between types of failures (exit 1 for unknown job and for already started job), so I suggest we use "status"; if it's start/running, we open another URL, if it's anything else we start it.

Revision history for this message
Ted Gould (ted) wrote :

On Wed, 2013-09-25 at 09:41 +0000, Loïc Minier wrote:

> * the variable name "connection" is confusing in get_pid_cb() and
> contact_app(), could we call it connection_name?

Fixed r76.

> * please add a TODO in main and in parse_uris() to use something else
> than space for splitting URIs; in fact it might be better to just
> remove support for multiple uris in this new code

Comment added r77. At this point I think it's better to be consistent.
I hope that, assuming fires get put out, to fix this later this week.

> So IIUC, we're first scanning by matching PID (PID that we think we've
> launched with upstart) then comparing the DBus bus name of the target
> app to match the app name; I find this a bit ugly because:
> a) pids are reused, even if it's unlikely that it happens quickly and
> at the right time, it's bad when you can't guarantee it didn't happen
> b) app names are not unique, only namespace + app name is
>
> I'd rather we use namespace + app name as the bus name we're searching
> for, and connect to that; seems simple and solid. But I guess this
> was discussed elsewhere since we have to use the same scheme in e.g.
> qtubuntu?

I think you might have misread the code slightly. We're not using any
well known names on DBus, those aren't allowed by confinement. We're
only using object paths on the bus. So it is only important that the
object path be unique for the connection, which it is likely that the
application name would be. Also, this is what is specified by the FD.o
application interface on where to put the object. They assume all
application names will become something like
"org.freedesktop.foo.desktop" eventually, and we handle that case, but
also the case of "foo.desktop".

The lack of well-known names (which really have their own set of race
conditions) is what makes us interested in PIDs. Unfortunately DBus
doesn't have a quick way to turn a PID into a connection. So we have to
get the list of connections and get their PIDs. Then we can know the
unique name of the PID of the Upstart job. And once we know the DBus
unique name for it, we can send it a message.

76. By Ted Gould

Changing connection variable name to be more clear

77. By Ted Gould

Adding comment on spaces so we know where to fix

Revision history for this message
Ted Gould (ted) wrote :

On Wed, 2013-09-25 at 11:02 +0000, Loïc Minier wrote:

> Surely we can do better?
>
> start's exit code doesn't distinguish between types of failures (exit
> 1 for unknown job and for already started job), so I suggest we use
> "status"; if it's start/running, we open another URL, if it's anything
> else we start it.

Yes, and I expect to fix this with the failure branch (which is also
needed for Unity) that I plan on starting today. Those constructs were
only meant as a quick way to unblock people so that we can more
completely handle errors.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Loïc Minier (lool) wrote :

So we had a quick hangout with Ted and we confirmed b) above and we're going to talk to security team about a) to see whether this race is important enough to plug and whether it's hard to plug.

Revision history for this message
Loïc Minier (lool) wrote :

[ Happy to top approve this once b) is done and I have tested some binaries :-) ]

Revision history for this message
Loïc Minier (lool) wrote :

So security team said there was no easy fix, our apparmor stuff doesn't allow that yet; they also said this wasn't severe enough (risk of leaking one URL under hard to meet conditions). The longer term fix would be to add API to dbus to find processes better and/or to upstart to find application statup timestamp.

78. By Ted Gould

Move the getting of the PID to make it less racy

79. By Ted Gould

Merge in trunk/dont-stop branch to avoid conflicts later

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
80. By Ted Gould

Use nih_dbus_path() to get the app path

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
81. By Ted Gould

Stealing two dbus paths from window-focus-request

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Loïc Minier (lool) wrote :

It seems really wrong to have the app version in there; we're trying to find an unique identifier for applications, not application + version. "appid" is a bit misleading here.

Revision history for this message
Ted Gould (ted) wrote :

Tested by ricmm with loicm's branch for the SDK. This is ready to land in trunk. Signing off and pulling everything in!

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'CMakeLists.txt'
2--- CMakeLists.txt 2013-09-23 20:41:53 +0000
3+++ CMakeLists.txt 2013-09-27 14:11:15 +0000
4@@ -157,6 +157,15 @@
5 target_link_libraries(zg-report-app ${ZEITGEIST_LIBRARIES})
6 install(TARGETS zg-report-app RUNTIME DESTINATION "${pkglibexecdir}")
7
8+#######################
9+# fdo-application-open
10+#######################
11+
12+add_executable(fdo-application-open fdo-application-open.c)
13+set_target_properties(fdo-application-open PROPERTIES OUTPUT_NAME "fdo-application-open")
14+target_link_libraries(fdo-application-open helpers upstart-launcher)
15+install(TARGETS fdo-application-open RUNTIME DESTINATION "${pkglibexecdir}")
16+
17 ####################
18 # application.conf
19 ####################
20
21=== modified file 'application.conf.in'
22--- application.conf.in 2013-09-25 16:31:57 +0000
23+++ application.conf.in 2013-09-27 14:11:15 +0000
24@@ -22,16 +22,12 @@
25
26 if [ ! -z $CLICK_DIR ] && [ -d $CLICK_DIR ] ; then
27 if ! start application-click APP_ID="${APP_ID}" APP_URIS="${APP_URIS}"; then
28- # TODO: This is pure evil.
29- stop application-click APP_ID="${APP_ID}"
30- start application-click APP_ID="${APP_ID}" APP_URIS="${APP_URIS}"
31+ @pkglibexecdir@/fdo-application-open "${APP_ID}" "${APP_URIS}"
32 fi
33 else
34 if @pkglibexecdir@/desktop-single $APP_ID ; then
35 if ! start application-legacy APP_ID="${APP_ID}" INSTANCE_ID="" APP_URIS="${APP_URIS}" ; then
36- # TODO: This is a little less, but still mostly evil.
37- stop application-legacy APP_ID="${APP_ID}" INSTANCE_ID="" || true
38- start application-legacy APP_ID="${APP_ID}" INSTANCE_ID="" APP_URIS="${APP_URIS}"
39+ @pkglibexecdir@/fdo-application-open "${APP_ID}" "${APP_URIS}"
40 fi
41 else
42 start application-legacy APP_ID="${APP_ID}" INSTANCE_ID=`date -u +%s` APP_URIS="${APP_URIS}"
43
44=== added file 'fdo-application-open.c'
45--- fdo-application-open.c 1970-01-01 00:00:00 +0000
46+++ fdo-application-open.c 2013-09-27 14:11:15 +0000
47@@ -0,0 +1,291 @@
48+/*
49+ * Copyright 2013 Canonical Ltd.
50+ *
51+ * This program is free software: you can redistribute it and/or modify it
52+ * under the terms of the GNU General Public License version 3, as published
53+ * by the Free Software Foundation.
54+ *
55+ * This program is distributed in the hope that it will be useful, but
56+ * WITHOUT ANY WARRANTY; without even the implied warranties of
57+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
58+ * PURPOSE. See the GNU General Public License for more details.
59+ *
60+ * You should have received a copy of the GNU General Public License along
61+ * with this program. If not, see <http://www.gnu.org/licenses/>.
62+ *
63+ * Authors:
64+ * Ted Gould <ted.gould@canonical.com>
65+ */
66+
67+#include <gio/gio.h>
68+#include <nih/alloc.h>
69+#include <libnih-dbus.h>
70+#include "libupstart-app-launch/upstart-app-launch.h"
71+#include "helpers.h"
72+
73+/* Globals */
74+GPid app_pid = 0;
75+GMainLoop * mainloop = NULL;
76+guint connections_open = 0;
77+const gchar * appid = NULL;
78+const gchar * input_uris = NULL;
79+GVariant * app_data = NULL;
80+gchar * dbus_path = NULL;
81+
82+/* Lower the connection count and process if it gets to zero */
83+static void
84+connection_count_dec (void)
85+{
86+ connections_open--;
87+ if (connections_open == 0) {
88+ g_main_loop_quit(mainloop);
89+ }
90+ return;
91+}
92+
93+/* Turn the input string into something we can send to apps */
94+static void
95+parse_uris (void)
96+{
97+ if (app_data != NULL) {
98+ /* Already done */
99+ return;
100+ }
101+
102+ /* TODO: Joining only with space could cause issues with breaking them
103+ back out. We don't have any cases of more than one today. But, this
104+ isn't good.
105+ https://bugs.launchpad.net/upstart-app-launch/+bug/1229354
106+ */
107+ GVariant * uris = NULL;
108+ gchar ** uri_split = g_strsplit(input_uris, " ", 0);
109+ if (uri_split[0] == NULL) {
110+ g_free(uri_split);
111+ uris = g_variant_new_array(G_VARIANT_TYPE_STRING, NULL, 0);
112+ } else {
113+ GVariantBuilder builder;
114+ g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY);
115+
116+ int i;
117+ for (i = 0; uri_split[i] != NULL; i++) {
118+ g_variant_builder_add_value(&builder, g_variant_new_take_string(uri_split[i]));
119+ }
120+ g_free(uri_split);
121+
122+ uris = g_variant_builder_end(&builder);
123+ }
124+
125+ GVariant * platform = g_variant_new_array(G_VARIANT_TYPE("{sv}"), NULL, 0);
126+
127+ GVariantBuilder tuple;
128+ g_variant_builder_init(&tuple, G_VARIANT_TYPE_TUPLE);
129+ g_variant_builder_add_value(&tuple, uris);
130+ g_variant_builder_add_value(&tuple, platform);
131+
132+ app_data = g_variant_builder_end(&tuple);
133+ g_variant_ref_sink(app_data);
134+
135+ return;
136+}
137+
138+/* Finds us our dbus path to use. Basically this is the name
139+ of the application with dots replaced by / and a / tacted on
140+ the front. This is recommended here:
141+
142+ http://standards.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#dbus
143+*/
144+static void
145+app_id_to_dbus_path (void)
146+{
147+ if (dbus_path != NULL) {
148+ return;
149+ }
150+
151+ dbus_path = nih_dbus_path(NULL, "", appid, NULL);
152+ g_debug("DBus Path: %s", dbus_path);
153+
154+ return;
155+}
156+
157+/* Finish the send and decrement the counter */
158+static void
159+send_open_cb (GObject * object, GAsyncResult * res, gpointer user_data)
160+{
161+ GError * error = NULL;
162+
163+ g_dbus_connection_call_finish(G_DBUS_CONNECTION(object), res, &error);
164+
165+ if (error != NULL) {
166+ /* Mostly just to free the error, but printing for debugging */
167+ g_debug("Unable to send Open: %s", error->message);
168+ g_error_free(error);
169+ }
170+
171+ connection_count_dec();
172+ return;
173+}
174+
175+/* Sends the Open message to the connection with the URIs we were given */
176+static void
177+contact_app (GDBusConnection * bus, const gchar * dbus_name)
178+{
179+ parse_uris();
180+ app_id_to_dbus_path();
181+
182+ /* Using the FD.o Application interface */
183+ g_dbus_connection_call(bus,
184+ dbus_name,
185+ dbus_path,
186+ "org.freedesktop.Application",
187+ "Open",
188+ app_data,
189+ NULL,
190+ G_DBUS_CALL_FLAGS_NONE,
191+ -1,
192+ NULL,
193+ send_open_cb, NULL);
194+
195+ g_debug("Sending Open request to: %s", dbus_name);
196+
197+ return;
198+}
199+
200+/* Gets the PID for a connection, and if it matches the one we're looking
201+ for then it tries to send a message to that connection */
202+static void
203+get_pid_cb (GObject * object, GAsyncResult * res, gpointer user_data)
204+{
205+ gchar * dbus_name = (gchar *)user_data;
206+ GError * error = NULL;
207+ GVariant * vpid = NULL;
208+
209+ vpid = g_dbus_connection_call_finish(G_DBUS_CONNECTION(object), res, &error);
210+
211+ if (error != NULL) {
212+ g_warning("Unable to query PID for dbus name '%s': %s", dbus_name, error->message);
213+ g_error_free(error);
214+ g_free(dbus_name);
215+
216+ /* Lowering the connection count, this one is terminal, even if in error */
217+ connection_count_dec();
218+ return;
219+ }
220+
221+ guint pid = 0;
222+ g_variant_get(vpid, "(u)", &pid);
223+ g_variant_unref(vpid);
224+
225+ if (pid == app_pid) {
226+ /* Trying to send a message to the connection */
227+ contact_app(G_DBUS_CONNECTION(object), dbus_name);
228+ } else {
229+ /* See if we can quit now */
230+ connection_count_dec();
231+ }
232+
233+ g_free(dbus_name);
234+
235+ return;
236+}
237+
238+int
239+main (int argc, char * argv[])
240+{
241+ if (argc != 3) {
242+ g_error("Should be called as: %s <app_id> <uri list>", argv[0]);
243+ return 1;
244+ }
245+
246+ appid = argv[1];
247+ input_uris = argv[2];
248+
249+ /* DBus tell us! */
250+ GError * error = NULL;
251+ GDBusConnection * session = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, &error);
252+ if (error != NULL) {
253+ g_error("Unable to get session bus");
254+ g_error_free(error);
255+ return 1;
256+ }
257+
258+ /* List all the connections on dbus. This sucks that we have to do
259+ this, but in the future we should add DBus API to do this lookup
260+ instead of having to do it with a bunch of requests */
261+ GVariant * listnames = g_dbus_connection_call_sync(session,
262+ "org.freedesktop.DBus",
263+ "/",
264+ "org.freedesktop.DBus",
265+ "ListNames",
266+ NULL,
267+ G_VARIANT_TYPE("(as)"),
268+ G_DBUS_CALL_FLAGS_NONE,
269+ -1,
270+ NULL,
271+ &error);
272+
273+ if (error != NULL) {
274+ g_warning("Unable to get list of names from DBus: %s", error->message);
275+ g_error_free(error);
276+ return 1;
277+ }
278+
279+ /* Next figure out what we're looking for (and if there is something to look for) */
280+ /* NOTE: We're getting the PID *after* the list of connections so
281+ that some new process can't come in, be the same PID as it's
282+ connection will not be in teh list we just got. */
283+ app_pid = upstart_app_launch_get_primary_pid(appid);
284+ if (app_pid == 0) {
285+ g_warning("Unable to find pid for app id '%s'", argv[1]);
286+ return 1;
287+ }
288+
289+ /* Allocate the mainloop now as we know we're going async */
290+ mainloop = g_main_loop_new(NULL, FALSE);
291+
292+ GVariant * names = g_variant_get_child_value(listnames, 0);
293+ GVariantIter iter;
294+ g_variant_iter_init(&iter, names);
295+ gchar * name = NULL;
296+
297+ while (g_variant_iter_loop(&iter, "s", &name)) {
298+ /* We only want to ask each connection once, this makes that so */
299+ if (!g_dbus_is_unique_name(name)) {
300+ continue;
301+ }
302+
303+ /* Get the PIDs */
304+ g_dbus_connection_call(session,
305+ "org.freedesktop.DBus",
306+ "/",
307+ "org.freedesktop.DBus",
308+ "GetConnectionUnixProcessID",
309+ g_variant_new("(s)", name),
310+ G_VARIANT_TYPE("(u)"),
311+ G_DBUS_CALL_FLAGS_NONE,
312+ -1,
313+ NULL,
314+ get_pid_cb, g_strdup(name));
315+
316+ connections_open++;
317+ }
318+
319+ g_variant_unref(names);
320+ g_variant_unref(listnames);
321+
322+ if (connections_open != 0) {
323+ g_main_loop_run(mainloop);
324+ }
325+
326+ if (app_data != NULL) {
327+ g_variant_unref(app_data);
328+ }
329+
330+ g_main_loop_unref(mainloop);
331+ g_object_unref(session);
332+
333+ if (dbus_path != NULL) {
334+ nih_free(dbus_path);
335+ }
336+
337+ return 0;
338+}

Subscribers

People subscribed via source and target branches