Merge lp:~ted/ubuntu-app-launch/fdo-application-open into lp:ubuntu-app-launch/13.10
- fdo-application-open
- Merge into trunk.13.10
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 |
Related bugs: |
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.
PS Jenkins bot (ps-jenkins) wrote : | # |
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?
Loïc Minier (lool) wrote : | # |
I forgot one thing, these constructs:
if ! start application-click APP_ID="${APP_ID}" APP_URIS=
[...]
if ! start application-legacy APP_ID="${APP_ID}" INSTANCE_ID="" APP_URIS=
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.
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.freedeskto
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.
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.
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:77
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
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.
Loïc Minier (lool) wrote : | # |
[ Happy to top approve this once b) is done and I have tested some binaries :-) ]
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.
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:79
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:80
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:81
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
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.
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
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 | +} |
PASSED: Continuous integration, rev:75 jenkins. qa.ubuntu. com/job/ upstart- app-launch- ci/88/ jenkins. qa.ubuntu. com/job/ upstart- app-launch- saucy-amd64- ci/90 jenkins. qa.ubuntu. com/job/ upstart- app-launch- saucy-armhf- ci/89 jenkins. qa.ubuntu. com/job/ upstart- app-launch- saucy-i386- ci/88
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild: s-jenkins: 8080/job/ upstart- app-launch- ci/88/rebuild
http://