Merge lp:~ted/ubuntu-app-launch/install-root into lp:ubuntu-app-launch/16.10

Proposed by Ted Gould
Status: Superseded
Proposed branch: lp:~ted/ubuntu-app-launch/install-root
Merge into: lp:ubuntu-app-launch/16.10
Prerequisite: lp:~ted/ubuntu-app-launch/icon-basepath
Diff against target: 5232 lines (+3165/-1310)
36 files modified
docs/index.rst (+81/-10)
libubuntu-app-launch/CMakeLists.txt (+8/-0)
libubuntu-app-launch/application-impl-base.cpp (+17/-763)
libubuntu-app-launch/application-impl-base.h (+3/-69)
libubuntu-app-launch/application-impl-click.cpp (+12/-19)
libubuntu-app-launch/application-impl-legacy.cpp (+9/-48)
libubuntu-app-launch/application-impl-legacy.h (+0/-1)
libubuntu-app-launch/application-impl-libertine.cpp (+15/-22)
libubuntu-app-launch/application-impl-snap.cpp (+11/-18)
libubuntu-app-launch/application-info-desktop.cpp (+2/-1)
libubuntu-app-launch/application-info-desktop.h (+8/-0)
libubuntu-app-launch/application.cpp (+7/-0)
libubuntu-app-launch/jobs-base.cpp (+434/-0)
libubuntu-app-launch/jobs-base.h (+124/-0)
libubuntu-app-launch/jobs-systemd.cpp (+917/-0)
libubuntu-app-launch/jobs-systemd.h (+114/-0)
libubuntu-app-launch/jobs-upstart.cpp (+874/-0)
libubuntu-app-launch/jobs-upstart.h (+75/-0)
libubuntu-app-launch/registry-impl.cpp (+10/-255)
libubuntu-app-launch/registry-impl.h (+12/-11)
libubuntu-app-launch/registry.cpp (+7/-54)
libubuntu-app-launch/snapd-info.cpp (+1/-1)
libubuntu-app-launch/ubuntu-app-launch.cpp (+5/-1)
tests/CMakeLists.txt (+22/-15)
tests/exec-util-test.cc (+10/-0)
tests/jobs-base-test.cpp (+364/-0)
tests/libual-cpp-test.cc (+1/-0)
tests/libual-test.cc (+1/-0)
upstart-jobs/application-click.conf.in (+4/-5)
upstart-jobs/application-failed.conf.in (+1/-1)
upstart-jobs/application-legacy.conf.in (+4/-4)
upstart-jobs/application-snap.conf.in (+4/-5)
upstart-jobs/application.conf.in (+1/-1)
upstart-jobs/untrusted-helper-type-end.conf.in (+1/-1)
upstart-jobs/untrusted-helper.conf.in (+4/-5)
xmir-helper.c (+2/-0)
To merge this branch: bzr merge lp:~ted/ubuntu-app-launch/install-root
Reviewer Review Type Date Requested Status
Ted Gould (community) Needs Fixing
Marcus Tomlinson (community) Approve
unity-api-1-bot continuous-integration Approve
Review via email: mp+305762@code.launchpad.net

This proposal has been superseded by a proposal from 2016-11-01.

Commit message

Make UAL relocatable for Snappy installs

Description of the change

For things that it makes sense to override we have an envvar to override it. For the Upstart jobs we're just relying on ${SNAP} to be set.

Also added docs for all the envvars that we use.

To post a comment you must log in.
Revision history for this message
unity-api-1-bot (unity-api-1-bot) wrote :

PASSED: Continuous integration, rev:256
https://jenkins.canonical.com/unity-api-1/job/lp-ubuntu-app-launch-ci/99/
Executed test runs:
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build/647
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-0-fetch/653
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=vivid+overlay/469
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=vivid+overlay/469/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=xenial+overlay/469
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=xenial+overlay/469/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=yakkety/469
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=yakkety/469/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=vivid+overlay/469
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=vivid+overlay/469/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=xenial+overlay/469
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=xenial+overlay/469/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=yakkety/469
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=yakkety/469/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=vivid+overlay/469
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=vivid+overlay/469/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=xenial+overlay/469
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=xenial+overlay/469/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=yakkety/469
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=yakkety/469/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://jenkins.canonical.com/unity-api-1/job/lp-ubuntu-app-launch-ci/99/rebuild

review: Approve (continuous-integration)
Revision history for this message
Marcus Tomlinson (marcustomlinson) wrote :

Comforting to see that all code paths are covered in the tests. Also, your bug, your fix (extra comfort) :)

LGTM

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

So further testing on real systems revealed an interesting flaw. Basically by including $SNAP in the exec line it doesn't work. Why? Because to resolve the environment variable Upstart switches from doing an exec directly, to instead using a shell which then does the exec. Not a big deal, except that there seems to be a bug in the way it works with AppArmor. So Upstart switches profiles on exec, but that is after the binary is read, so the exec-line-exec binary doesn't need to be in the Application's apparmor profile. But when the shell goes first the shell itself is exec'ing under the application's profile, and thus needs permission.

This is, at some level, a bug in Upstart. Not sure how it should be fixed. Will have to have conversations with security taking into account the maintenance level of the various packages involved.

review: Needs Fixing
257. By Ted Gould

Merging the jobs-systemd branch

258. By Ted Gould

Update to latest systemd branch

259. By Ted Gould

Pulling through updates

260. By Ted Gould

Adding an environment variable for XMir Helper

261. By Ted Gould

Pulling through trunk

262. By Ted Gould

Restructure exec line building so we can add more

263. By Ted Gould

Adding extra environment in the snap world

264. By Ted Gould

Make sure we get the other snap environment variables

265. By Ted Gould

Switch to calling legacy-exec

266. By Ted Gould

Starting utilities to put together an environment setup

267. By Ted Gould

Better exit codes

268. By Ted Gould

Fleshing out our utilities

269. By Ted Gould

Get the binaries into a deb

270. By Ted Gould

Make the command line reference the outside util

271. By Ted Gould

Call the snappy-xmir util unconfined

272. By Ted Gould

Handle write() returns

273. By Ted Gould

Disable for a moment

274. By Ted Gould

Remove the snap setting libertine launch

275. By Ted Gould

Fix command name

276. By Ted Gould

Make sure we sleep forever

277. By Ted Gould

Get negative errors as well

278. By Ted Gould

Make sure the FD stays open

279. By Ted Gould

Provide a way to test and put the fd number on the command line

280. By Ted Gould

Some basic test scripts

281. By Ted Gould

Switch to an abstract socket

282. By Ted Gould

Make sure we don't overrun sun_path

283. By Ted Gould

Clear out the MIR_* variables

284. By Ted Gould

Getting the snappy-xmir tests into the test suite

285. By Ted Gould

Check for MIR_ variables

286. By Ted Gould

Making sure we clean up the envvars utils

287. By Ted Gould

Increase output and debugging info in tests

288. By Ted Gould

Read everything in, and then start processing it into individual variables

289. By Ted Gould

Cleanup magic numbers

290. By Ted Gould

Explicitly set the DBus address as well

291. By Ted Gould

Make sure to pad with zeros

292. By Ted Gould

Test for long AppIDs

293. By Ted Gould

Removing snap variables from the Upstart jobs. If we're under a SNAP we'll be using systemd

294. By Ted Gould

Use the path in the current snap as the place to find the helper

295. By Ted Gould

Fix formatting

296. By Ted Gould

We don't need to care about the SNAP_ variables because they'll be reinserted when we come through the snap commands

297. By Ted Gould

Use a build variable to get the path

298. By Ted Gould

Switching back to setting the libertine-launch path in non-snap cases

299. By Ted Gould

Allow the legacy-exec util to be overridden

300. By Ted Gould

Merge error

301. By Ted Gould

More magic numbers brought down to Earth

302. By Ted Gould

Bigger libs warning. Like really people, read this.

303. By Ted Gould

Fix the libertine-launch case for non-snaps

304. By Ted Gould

Removing QT and XDG variables, don't think we need them

305. By Ted Gould

Removing more variables

306. By Ted Gould

Upstream changes

307. By Ted Gould

Make sure we don't have a Mir socket before checking that we don't

308. By Ted Gould

Merge upstream updates

309. By Ted Gould

Update to trunk

310. By Ted Gould

Update to current jobs-systemd

311. By Ted Gould

Grab QT_ and XDG_ for legacy apps on deb installs

312. By Ted Gould

Switching functions

313. By Ted Gould

Merge Trunk

314. By Ted Gould

Merge changes to jobs-systemd

315. By Ted Gould

Put the wrong environment var into the documentation

316. By Ted Gould

Getting these variables figured out

317. By Ted Gould

Fix docs to include default path for LEGACY_EXEC

318. By Ted Gould

Cache snap env var

319. By Ted Gould

Make sure we only use references in loops

320. By Ted Gould

Don't store if we have the variable

321. By Ted Gould

Return const pointer

322. By Ted Gould

Handle larger possible pids

323. By Ted Gould

Make sure we're gettin' data

324. By Ted Gould

Move debug outside of the print loop

325. By Ted Gould

A read failure is a helper failure

326. By Ted Gould

If we don't have a MIR_SOCKET use the default one

327. By Ted Gould

Special case legacy apps on Unity8 from debs

328. By Ted Gould

Put on the dunce cap and sit in the corner

Unmerged revisions

328. By Ted Gould

Put on the dunce cap and sit in the corner

327. By Ted Gould

Special case legacy apps on Unity8 from debs

326. By Ted Gould

If we don't have a MIR_SOCKET use the default one

325. By Ted Gould

A read failure is a helper failure

324. By Ted Gould

Move debug outside of the print loop

323. By Ted Gould

Make sure we're gettin' data

322. By Ted Gould

Handle larger possible pids

321. By Ted Gould

Return const pointer

320. By Ted Gould

Don't store if we have the variable

319. By Ted Gould

Make sure we only use references in loops

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'docs/index.rst'
2--- docs/index.rst 2016-08-04 14:04:40 +0000
3+++ docs/index.rst 2016-11-01 14:53:56 +0000
4@@ -18,6 +18,47 @@
5
6 .. _Upstart: http://upstart.ubuntu.com/
7
8+
9+Environment Variables
10+=====================
11+
12+There are a few environment variables that can effect the behavior of UAL while
13+it is running.
14+
15+UBUNTU_APP_LAUNCH_CG_MANAGER_NAME
16+ The DBus name that CG Manager registers under if it is on the session bus.
17+
18+UBUNTU_APP_LAUNCH_CG_MANAGER_SESSION_BUS
19+ Tell UAL to look on the session bus for CG Manager.
20+
21+UBUNTU_APP_LAUNCH_DEMANGLER
22+ Path to the UAL demangler tool that will get the Mir FD for trusted prompt session.
23+
24+UBUNTU_APP_LAUNCH_DISABLE_SNAPD_TIMEOUT
25+ Wait as long as Snapd wants to return data instead of erroring after 100ms.
26+
27+UBUNTU_APP_LAUNCH_LEGACY_ROOT
28+ Set the path that represents the root for legacy applications.
29+
30+UBUNTU_APP_LAUNCH_LIBERTINE_LAUNCH
31+ Path to the libertine launch utility for setting up libertine containers and XMir based legacy apps.
32+
33+UBUNTU_APP_LAUNCH_LINK_FARM
34+ Path to the link farm that is created by Click of all the installed Click applications.
35+
36+UBUNTU_APP_LAUNCH_OOM_HELPER
37+ Path to the setuid helper that configures OOM values on application processes that we otherwise couldn't, mostly this is for Oxide.
38+
39+UBUNTU_APP_LAUNCH_OOM_PROC_PATH
40+ Path to look for the files to set OOM values, defaults to /proc.
41+
42+UBUNTU_APP_LAUNCH_SNAP_BASEDIR
43+ The place where snaps are installed in the system, /snap is the default.
44+
45+UBUNTU_APP_LAUNCH_SNAPD_SOCKET
46+ Path to the snapd socket.
47+
48+
49 API Documentation
50 =================
51
52@@ -146,6 +187,46 @@
53 :private-members:
54 :undoc-members:
55
56+Jobs Manager Base
57+-----------------
58+
59+.. doxygenclass:: ubuntu::app_launch::jobs::manager::Base
60+ :project: libubuntu-app-launch
61+ :members:
62+ :protected-members:
63+ :private-members:
64+ :undoc-members:
65+
66+Jobs Instance Base
67+------------------
68+
69+.. doxygenclass:: ubuntu::app_launch::jobs::instance::Base
70+ :project: libubuntu-app-launch
71+ :members:
72+ :protected-members:
73+ :private-members:
74+ :undoc-members:
75+
76+Jobs Manager Upstart
77+--------------------
78+
79+.. doxygenclass:: ubuntu::app_launch::jobs::manager::Upstart
80+ :project: libubuntu-app-launch
81+ :members:
82+ :protected-members:
83+ :private-members:
84+ :undoc-members:
85+
86+Jobs Instance Upstart
87+---------------------
88+
89+.. doxygenclass:: ubuntu::app_launch::jobs::instance::Upstart
90+ :project: libubuntu-app-launch
91+ :members:
92+ :protected-members:
93+ :private-members:
94+ :undoc-members:
95+
96 Registry Implementation
97 -----------------------
98
99@@ -176,16 +257,6 @@
100 :private-members:
101 :undoc-members:
102
103-Upstart Instance
104-----------------
105-
106-.. doxygenclass:: ubuntu::app_launch::app_impls::UpstartInstance
107- :project: libubuntu-app-launch
108- :members:
109- :protected-members:
110- :private-members:
111- :undoc-members:
112-
113 Quality
114 =======
115
116
117=== modified file 'libubuntu-app-launch/CMakeLists.txt'
118--- libubuntu-app-launch/CMakeLists.txt 2016-08-26 17:33:34 +0000
119+++ libubuntu-app-launch/CMakeLists.txt 2016-11-01 14:53:56 +0000
120@@ -17,6 +17,8 @@
121 set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility=hidden -Wpedantic")
122 add_definitions ( -DOOM_HELPER="${pkglibexecdir}/oom-adjust-setuid-helper" -DDEMANGLER_PATH="${pkglibexecdir}/socket-demangler" )
123 add_definitions ( -DLIBERTINE_LAUNCH="${CMAKE_INSTALL_FULL_BINDIR}/libertine-launch" )
124+add_definitions ( -DG_LOG_DOMAIN="ubuntu-app-launch" )
125+add_definitions ( -DUBUNTU_APP_LAUNCH_ARCH="${UBUNTU_APP_LAUNCH_ARCH}" )
126
127 set(LAUNCHER_HEADERS
128 ubuntu-app-launch.h
129@@ -52,6 +54,12 @@
130 helper-impl-click.cpp
131 glib-thread.h
132 glib-thread.cpp
133+jobs-base.h
134+jobs-base.cpp
135+jobs-systemd.h
136+jobs-systemd.cpp
137+jobs-upstart.h
138+jobs-upstart.cpp
139 )
140
141 set(LAUNCHER_SOURCES
142
143=== modified file 'libubuntu-app-launch/application-impl-base.cpp'
144--- libubuntu-app-launch/application-impl-base.cpp 2016-10-03 23:54:20 +0000
145+++ libubuntu-app-launch/application-impl-base.cpp 2016-11-01 14:53:56 +0000
146@@ -23,8 +23,6 @@
147 #include <map>
148 #include <numeric>
149
150-#include <upstart.h>
151-
152 #include "application-impl-base.h"
153 #include "helpers.h"
154 #include "registry-impl.h"
155@@ -97,768 +95,24 @@
156 return retval;
157 }
158
159-/** Checks to see if we have a primary PID for the instance */
160-bool UpstartInstance::isRunning()
161-{
162- return primaryPid() != 0;
163-}
164-
165-/** Uses Upstart to get the primary PID of the instance using Upstart's
166- DBus interface */
167-pid_t UpstartInstance::primaryPid()
168-{
169- auto jobpath = registry_->impl->upstartJobPath(job_);
170- if (jobpath.empty())
171- {
172- g_debug("Unable to get a valid job path");
173- return 0;
174- }
175-
176- return registry_->impl->thread.executeOnThread<pid_t>([this, &jobpath]() -> pid_t {
177- GError* error = nullptr;
178-
179- std::string instancename = std::string(appId_);
180- if (job_ != "application-click")
181- {
182- instancename += "-" + instance_;
183- }
184-
185- g_debug("Getting instance by name: %s", instance_.c_str());
186- GVariant* vinstance_path =
187- g_dbus_connection_call_sync(registry_->impl->_dbus.get(), /* connection */
188- DBUS_SERVICE_UPSTART, /* service */
189- jobpath.c_str(), /* object path */
190- DBUS_INTERFACE_UPSTART_JOB, /* iface */
191- "GetInstanceByName", /* method */
192- g_variant_new("(s)", instancename.c_str()), /* params */
193- G_VARIANT_TYPE("(o)"), /* return type */
194- G_DBUS_CALL_FLAGS_NONE, /* flags */
195- -1, /* timeout: default */
196- registry_->impl->thread.getCancellable().get(), /* cancellable */
197- &error);
198-
199- if (error != nullptr)
200- {
201- g_warning("Unable to get instance '%s' of job '%s': %s", instance_.c_str(), job_.c_str(), error->message);
202- g_error_free(error);
203- return 0;
204- }
205-
206- /* Jump rope to make this into a C++ type */
207- std::string instance_path;
208- gchar* cinstance_path = nullptr;
209- g_variant_get(vinstance_path, "(o)", &cinstance_path);
210- g_variant_unref(vinstance_path);
211- if (cinstance_path != nullptr)
212- {
213- instance_path = cinstance_path;
214- g_free(cinstance_path);
215- }
216-
217- if (instance_path.empty())
218- {
219- g_debug("No instance object for instance name: %s", instance_.c_str());
220- return 0;
221- }
222-
223- GVariant* props_tuple =
224- g_dbus_connection_call_sync(registry_->impl->_dbus.get(), /* connection */
225- DBUS_SERVICE_UPSTART, /* service */
226- instance_path.c_str(), /* object path */
227- "org.freedesktop.DBus.Properties", /* interface */
228- "GetAll", /* method */
229- g_variant_new("(s)", DBUS_INTERFACE_UPSTART_INSTANCE), /* params */
230- G_VARIANT_TYPE("(a{sv})"), /* return type */
231- G_DBUS_CALL_FLAGS_NONE, /* flags */
232- -1, /* timeout: default */
233- registry_->impl->thread.getCancellable().get(), /* cancellable */
234- &error);
235-
236- if (error != nullptr)
237- {
238- g_warning("Unable to name of properties '%s': %s", instance_path.c_str(), error->message);
239- g_error_free(error);
240- error = nullptr;
241- return 0;
242- }
243-
244- GVariant* props_dict = g_variant_get_child_value(props_tuple, 0);
245-
246- pid_t retval = 0;
247- GVariant* processes = g_variant_lookup_value(props_dict, "processes", G_VARIANT_TYPE("a(si)"));
248- if (processes != nullptr && g_variant_n_children(processes) > 0)
249- {
250-
251- GVariant* first_entry = g_variant_get_child_value(processes, 0);
252- GVariant* pidv = g_variant_get_child_value(first_entry, 1);
253-
254- retval = g_variant_get_int32(pidv);
255-
256- g_variant_unref(pidv);
257- g_variant_unref(first_entry);
258- }
259- else
260- {
261- g_debug("Unable to get 'processes' from properties of instance at path: %s", instance_path.c_str());
262- }
263-
264- g_variant_unref(props_dict);
265-
266- return retval;
267- });
268-}
269-
270-/** Generate the full name of the Upstart job for the job, the
271- instance and how all those fit together.
272-
273- Handles the special case of application-click which isn't designed
274- to have multi-instance apps.
275-*/
276-std::string UpstartInstance::upstartJobPath()
277-{
278- std::string path = job_ + "-" + std::string(appId_);
279- if (job_ != "application-click")
280- {
281- path += "-";
282- }
283- if (!instance_.empty())
284- {
285- path += instance_;
286- }
287-
288- return path;
289-}
290-
291-/** Looks at the PIDs in the instance cgroup and checks to see if @pid
292- is in the set.
293-
294- @param pid PID to look for
295-*/
296-bool UpstartInstance::hasPid(pid_t pid)
297-{
298- for (auto testpid : registry_->impl->pidsFromCgroup(upstartJobPath()))
299- if (pid == testpid)
300- return true;
301- return false;
302-}
303-
304-/** Gets the path to the log file for this instance */
305-std::string UpstartInstance::logPath()
306-{
307- std::string logfile = upstartJobPath() + ".log";
308-
309- gchar* cpath = g_build_filename(g_get_user_cache_dir(), "upstart", logfile.c_str(), nullptr);
310- std::string path(cpath);
311- g_free(cpath);
312-
313- return path;
314-}
315-
316-/** Returns all the PIDs that are in the cgroup for this application */
317-std::vector<pid_t> UpstartInstance::pids()
318-{
319- auto pids = registry_->impl->pidsFromCgroup(upstartJobPath());
320- g_debug("Got %d PIDs for AppID '%s'", int(pids.size()), std::string(appId_).c_str());
321- return pids;
322-}
323-
324-/** Pauses this application by sending SIGSTOP to all the PIDs in the
325- cgroup and tells Zeitgeist that we've left the application. */
326-void UpstartInstance::pause()
327-{
328- g_debug("Pausing application: %s", std::string(appId_).c_str());
329- registry_->impl->zgSendEvent(appId_, ZEITGEIST_ZG_LEAVE_EVENT);
330-
331- auto pids = forAllPids([this](pid_t pid) {
332- auto oomval = oom::paused();
333- g_debug("Pausing PID: %d (%d)", pid, int(oomval));
334- signalToPid(pid, SIGSTOP);
335- oomValueToPid(pid, oomval);
336- });
337-
338- pidListToDbus(pids, "ApplicationPaused");
339-}
340-
341-/** Resumes this application by sending SIGCONT to all the PIDs in the
342- cgroup and tells Zeitgeist that we're accessing the application. */
343-void UpstartInstance::resume()
344-{
345- g_debug("Resuming application: %s", std::string(appId_).c_str());
346- registry_->impl->zgSendEvent(appId_, ZEITGEIST_ZG_ACCESS_EVENT);
347-
348- auto pids = forAllPids([this](pid_t pid) {
349- auto oomval = oom::focused();
350- g_debug("Resuming PID: %d (%d)", pid, int(oomval));
351- signalToPid(pid, SIGCONT);
352- oomValueToPid(pid, oomval);
353- });
354-
355- pidListToDbus(pids, "ApplicationResumed");
356-}
357-
358-/** Stops this instance by asking Upstart to stop it. Upstart will then
359- send a SIGTERM and five seconds later start killing things. */
360-void UpstartInstance::stop()
361-{
362- if (!registry_->impl->thread.executeOnThread<bool>([this]() {
363-
364- g_debug("Stopping job %s app_id %s instance_id %s", job_.c_str(), std::string(appId_).c_str(),
365- instance_.c_str());
366-
367- auto jobpath = registry_->impl->upstartJobPath(job_);
368- if (jobpath.empty())
369- {
370- throw new std::runtime_error("Unable to get job path for Upstart job '" + job_ + "'");
371- }
372-
373- GVariantBuilder builder;
374- g_variant_builder_init(&builder, G_VARIANT_TYPE_TUPLE);
375- g_variant_builder_open(&builder, G_VARIANT_TYPE_ARRAY);
376-
377- g_variant_builder_add_value(
378- &builder, g_variant_new_take_string(g_strdup_printf("APP_ID=%s", std::string(appId_).c_str())));
379-
380- if (!instance_.empty())
381- {
382- g_variant_builder_add_value(
383- &builder, g_variant_new_take_string(g_strdup_printf("INSTANCE_ID=%s", instance_.c_str())));
384- }
385-
386- g_variant_builder_close(&builder);
387- g_variant_builder_add_value(&builder, g_variant_new_boolean(FALSE)); /* wait */
388-
389- GError* error = nullptr;
390- GVariant* stop_variant =
391- g_dbus_connection_call_sync(registry_->impl->_dbus.get(), /* Dbus */
392- DBUS_SERVICE_UPSTART, /* Upstart name */
393- jobpath.c_str(), /* path */
394- DBUS_INTERFACE_UPSTART_JOB, /* interface */
395- "Stop", /* method */
396- g_variant_builder_end(&builder), /* params */
397- nullptr, /* return */
398- G_DBUS_CALL_FLAGS_NONE, /* flags */
399- -1, /* timeout: default */
400- registry_->impl->thread.getCancellable().get(), /* cancellable */
401- &error); /* error (hopefully not) */
402-
403- g_clear_pointer(&stop_variant, g_variant_unref);
404-
405- if (error != nullptr)
406- {
407- g_warning("Unable to stop job %s app_id %s instance_id %s: %s", job_.c_str(),
408- std::string(appId_).c_str(), instance_.c_str(), error->message);
409- g_error_free(error);
410- return false;
411- }
412-
413- return true;
414- }))
415- {
416- g_warning("Unable to stop Upstart instance");
417- }
418-}
419-
420-/** Sets the OOM adjustment by getting the list of PIDs and writing
421- the value to each of their files in proc
422-
423- \param score OOM Score to set
424-*/
425-void UpstartInstance::setOomAdjustment(const oom::Score score)
426-{
427- forAllPids([this, &score](pid_t pid) { oomValueToPid(pid, score); });
428-}
429-
430-/** Figures out the path to the primary PID of the application and
431- then reads its OOM adjustment file. */
432-const oom::Score UpstartInstance::getOomAdjustment()
433-{
434- auto pid = primaryPid();
435- if (pid == 0)
436- {
437- throw std::runtime_error("No PID for application: " + std::string(appId_));
438- }
439-
440- auto path = pidToOomPath(pid);
441- GError* error = nullptr;
442- gchar* content = nullptr;
443-
444- g_file_get_contents(path.c_str(), /* path */
445- &content, /* data */
446- nullptr, /* size */
447- &error); /* error */
448-
449- if (error != nullptr)
450- {
451- auto serror = std::shared_ptr<GError>(error, g_error_free);
452- throw std::runtime_error("Unable to access OOM value for '" + std::string(appId_) + "' primary PID '" +
453- std::to_string(pid) + "' because: " + serror->message);
454- }
455-
456- auto score = static_cast<oom::Score>(std::atoi(content));
457- g_free(content);
458- return score;
459-}
460-
461-/** Go through the list of PIDs calling a function and handling
462- the issue with getting PIDs being a racey condition.
463-
464- \param eachPid Function to run on each PID
465-*/
466-std::vector<pid_t> UpstartInstance::forAllPids(std::function<void(pid_t)> eachPid)
467-{
468- std::set<pid_t> seenPids;
469- bool added = true;
470-
471- while (added)
472- {
473- added = false;
474- auto pidlist = pids();
475- for (auto pid : pidlist)
476- {
477- if (seenPids.insert(pid).second)
478- {
479- eachPid(pid);
480- added = true;
481- }
482- }
483- }
484-
485- return std::vector<pid_t>(seenPids.begin(), seenPids.end());
486-}
487-
488-/** Sends a signal to a PID with a warning if we can't send it.
489- We could throw an exception, but we can't handle it usefully anyway
490-
491- \param pid PID to send the signal to
492- \param signal signal to send
493-*/
494-void UpstartInstance::signalToPid(pid_t pid, int signal)
495-{
496- if (-1 == kill(pid, signal))
497- {
498- /* While that didn't work, we still want to try as many as we can */
499- g_warning("Unable to send signal %d to pid %d", signal, pid);
500- }
501-}
502-
503-/** Get the path to the PID's OOM adjust path, with allowing for an
504- override for testing using the environment variable
505- UBUNTU_APP_LAUNCH_OOM_PROC_PATH
506-
507- \param pid PID to build path for
508-*/
509-std::string UpstartInstance::pidToOomPath(pid_t pid)
510-{
511- static std::string procpath;
512- if (G_UNLIKELY(procpath.empty()))
513- {
514- /* Set by the test suite, probably not anyone else */
515- auto envvar = g_getenv("UBUNTU_APP_LAUNCH_OOM_PROC_PATH");
516- if (G_LIKELY(envvar == nullptr))
517- procpath = "/proc";
518- else
519- procpath = envvar;
520- }
521-
522- gchar* gpath = g_build_filename(procpath.c_str(), std::to_string(pid).c_str(), "oom_score_adj", nullptr);
523- std::string path = gpath;
524- g_free(gpath);
525- return path;
526-}
527-
528-/** Writes an OOM value to proc, assuming we have a string
529- in the outer loop
530-
531- \param pid PID to change the OOM value of
532- \param oomvalue OOM value to set
533-*/
534-void UpstartInstance::oomValueToPid(pid_t pid, const oom::Score oomvalue)
535-{
536- auto oomstr = std::to_string(static_cast<std::int32_t>(oomvalue));
537- auto path = pidToOomPath(pid);
538- FILE* adj = fopen(path.c_str(), "w");
539- int openerr = errno;
540-
541- if (adj == nullptr)
542- {
543- switch (openerr)
544- {
545- case ENOENT:
546- /* ENOENT happens a fair amount because of races, so it's not
547- worth printing a warning about */
548- return;
549- case EACCES:
550- {
551- /* We can get this error when trying to set the OOM value on
552- Oxide renderers because they're started by the sandbox and
553- don't have their adjustment value available for us to write.
554- We have a helper to deal with this, but it's kinda expensive
555- so we only use it when we have to. */
556- oomValueToPidHelper(pid, oomvalue);
557- return;
558- }
559- default:
560- g_warning("Unable to set OOM value for '%d' to '%s': %s", int(pid), oomstr.c_str(),
561- std::strerror(openerr));
562- return;
563- }
564- }
565-
566- size_t writesize = fwrite(oomstr.c_str(), 1, oomstr.size(), adj);
567- int writeerr = errno;
568- fclose(adj);
569-
570- if (writesize == oomstr.size())
571- return;
572-
573- if (writeerr != 0)
574- g_warning("Unable to set OOM value for '%d' to '%s': %s", int(pid), oomstr.c_str(), strerror(writeerr));
575+/** Generates an instance string based on the clock if we're a multi-instance
576+ application. */
577+std::string Base::getInstance(const std::shared_ptr<app_info::Desktop>& desktop)
578+{
579+ if (!desktop)
580+ {
581+ g_warning("Invalid desktop file passed to getInstance");
582+ return {};
583+ }
584+
585+ if (desktop->singleInstance())
586+ {
587+ return {};
588+ }
589 else
590- /* No error, but yet, wrong size. Not sure, what could cause this. */
591- g_debug("Unable to set OOM value for '%d' to '%s': Wrote %d bytes", int(pid), oomstr.c_str(), int(writesize));
592-}
593-
594-/** Use a setuid root helper for setting the oom value of
595- Chromium instances
596-
597- \param pid PID to change the OOM value of
598- \param oomvalue OOM value to set
599-*/
600-void UpstartInstance::oomValueToPidHelper(pid_t pid, const oom::Score oomvalue)
601-{
602- GError* error = nullptr;
603- std::string oomstr = std::to_string(static_cast<std::int32_t>(oomvalue));
604- std::string pidstr = std::to_string(pid);
605- std::array<const char*, 4> args = {OOM_HELPER, pidstr.c_str(), oomstr.c_str(), nullptr};
606-
607- g_debug("Excuting OOM Helper (pid: %d, score: %d): %s", int(pid), int(oomvalue),
608- std::accumulate(args.begin(), args.end(), std::string{},
609- [](const std::string& instr, const char* output) -> std::string {
610- if (instr.empty())
611- {
612- return output;
613- }
614- else if (output != nullptr)
615- {
616- return instr + " " + std::string(output);
617- }
618- else
619- {
620- return instr;
621- }
622- })
623- .c_str());
624-
625- g_spawn_async(nullptr, /* working dir */
626- (char**)(args.data()), /* args */
627- nullptr, /* env */
628- G_SPAWN_DEFAULT, /* flags */
629- nullptr, /* child setup */
630- nullptr, /* child setup userdata*/
631- nullptr, /* pid */
632- &error); /* error */
633-
634- if (error != nullptr)
635- {
636- g_warning("Unable to launch OOM helper '" OOM_HELPER "' on PID '%d': %s", pid, error->message);
637- g_error_free(error);
638- return;
639- }
640-}
641-
642-/** Send a signal that we've change the application. Do this on the
643- registry thread in an idle so that we don't block anyone.
644-
645- \param pids List of PIDs to turn into variants to send
646- \param signal Name of the DBus signal to send
647-*/
648-void UpstartInstance::pidListToDbus(const std::vector<pid_t>& pids, const std::string& signal)
649-{
650- auto registry = registry_;
651- auto lappid = appId_;
652-
653- registry_->impl->thread.executeOnThread([registry, lappid, pids, signal] {
654- auto vpids = std::shared_ptr<GVariant>(
655- [pids]() {
656- GVariant* pidarray = nullptr;
657-
658- if (pids.empty())
659- {
660- pidarray = g_variant_new_array(G_VARIANT_TYPE_UINT64, nullptr, 0);
661- g_variant_ref_sink(pidarray);
662- return pidarray;
663- }
664-
665- GVariantBuilder builder;
666- g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY);
667- for (auto pid : pids)
668- {
669- g_variant_builder_add_value(&builder, g_variant_new_uint64(pid));
670- }
671-
672- pidarray = g_variant_builder_end(&builder);
673- g_variant_ref_sink(pidarray);
674- return pidarray;
675- }(),
676- [](GVariant* var) { g_variant_unref(var); });
677-
678- GVariantBuilder params;
679- g_variant_builder_init(&params, G_VARIANT_TYPE_TUPLE);
680- g_variant_builder_add_value(&params, g_variant_new_string(std::string(lappid).c_str()));
681- g_variant_builder_add_value(&params, vpids.get());
682-
683- GError* error = nullptr;
684- g_dbus_connection_emit_signal(registry->impl->_dbus.get(), /* bus */
685- nullptr, /* destination */
686- "/", /* path */
687- "com.canonical.UbuntuAppLaunch", /* interface */
688- signal.c_str(), /* signal */
689- g_variant_builder_end(&params), /* params, the same */
690- &error); /* error */
691-
692- if (error != nullptr)
693- {
694- g_warning("Unable to emit signal '%s' for appid '%s': %s", signal.c_str(), std::string(lappid).c_str(),
695- error->message);
696- g_error_free(error);
697- }
698- else
699- {
700- g_debug("Emmitted '%s' to DBus", signal.c_str());
701- }
702- });
703-}
704-
705-/** Create a new Upstart Instance object that can track the job and
706- get information about it.
707-
708- \param appId Application ID
709- \param job Upstart job name
710- \param instance Upstart instance name
711- \param urls URLs sent to the application (only on launch today)
712- \param registry Registry of persistent connections to use
713-*/
714-UpstartInstance::UpstartInstance(const AppID& appId,
715- const std::string& job,
716- const std::string& instance,
717- const std::vector<Application::URL>& urls,
718- const std::shared_ptr<Registry>& registry)
719- : appId_(appId)
720- , job_(job)
721- , instance_(instance)
722- , urls_(urls)
723- , registry_(registry)
724-{
725- g_debug("Creating a new UpstartInstance for '%s' instance '%s'", std::string(appId_).c_str(), instance.c_str());
726-}
727-
728-/** Reformat a C++ vector of URLs into a C GStrv of strings
729-
730- \param urls Vector of URLs to make into C strings
731-*/
732-std::shared_ptr<gchar*> UpstartInstance::urlsToStrv(const std::vector<Application::URL>& urls)
733-{
734- if (urls.empty())
735- {
736- return {};
737- }
738-
739- auto array = g_array_new(TRUE, FALSE, sizeof(gchar*));
740-
741- for (auto url : urls)
742- {
743- auto str = g_strdup(url.value().c_str());
744- g_debug("Converting URL: %s", str);
745- g_array_append_val(array, str);
746- }
747-
748- return std::shared_ptr<gchar*>((gchar**)g_array_free(array, FALSE), g_strfreev);
749-}
750-
751-/** Small helper that we can new/delete to work better with C stuff */
752-struct StartCHelper
753-{
754- std::shared_ptr<UpstartInstance> ptr;
755-};
756-
757-/** Callback from starting an application. It checks to see whether the
758- app is already running. If it is already running then we need to send
759- the URLs to it via DBus.
760-
761- \param obj The GDBusConnection object
762- \param res Async result object
763- \param user_data A pointer to a StartCHelper structure
764-*/
765-void UpstartInstance::application_start_cb(GObject* obj, GAsyncResult* res, gpointer user_data)
766-{
767- auto data = static_cast<StartCHelper*>(user_data);
768- GError* error{nullptr};
769- GVariant* result{nullptr};
770-
771- tracepoint(ubuntu_app_launch, libual_start_message_callback, std::string(data->ptr->appId_).c_str());
772-
773- g_debug("Started Message Callback: %s", std::string(data->ptr->appId_).c_str());
774-
775- result = g_dbus_connection_call_finish(G_DBUS_CONNECTION(obj), res, &error);
776-
777- g_clear_pointer(&result, g_variant_unref);
778-
779- if (error != nullptr)
780- {
781- if (g_dbus_error_is_remote_error(error))
782- {
783- gchar* remote_error = g_dbus_error_get_remote_error(error);
784- g_debug("Remote error: %s", remote_error);
785- if (g_strcmp0(remote_error, "com.ubuntu.Upstart0_6.Error.AlreadyStarted") == 0)
786- {
787- auto urls = urlsToStrv(data->ptr->urls_);
788- second_exec(data->ptr->registry_->impl->_dbus.get(), /* DBus */
789- data->ptr->registry_->impl->thread.getCancellable().get(), /* cancellable */
790- data->ptr->primaryPid(), /* primary pid */
791- std::string(data->ptr->appId_).c_str(), /* appid */
792- urls.get()); /* urls */
793- }
794-
795- g_free(remote_error);
796- }
797- else
798- {
799- g_warning("Unable to emit event to start application: %s", error->message);
800- }
801- g_error_free(error);
802- }
803-
804- delete data;
805-}
806-
807-/** Launch an application and create a new UpstartInstance object to track
808- its progress.
809-
810- \param appId Application ID
811- \param job Upstart job name
812- \param instance Upstart instance name
813- \param urls URLs sent to the application (only on launch today)
814- \param registry Registry of persistent connections to use
815- \param mode Whether or not to setup the environment for testing
816- \param getenv A function to get additional environment variable when appropriate
817-*/
818-std::shared_ptr<UpstartInstance> UpstartInstance::launch(
819- const AppID& appId,
820- const std::string& job,
821- const std::string& instance,
822- const std::vector<Application::URL>& urls,
823- const std::shared_ptr<Registry>& registry,
824- launchMode mode,
825- std::function<std::list<std::pair<std::string, std::string>>(void)>& getenv)
826-{
827- if (appId.empty())
828- return {};
829-
830- return registry->impl->thread.executeOnThread<std::shared_ptr<UpstartInstance>>(
831- [&]() -> std::shared_ptr<UpstartInstance> {
832- std::string appIdStr{appId};
833- g_debug("Initializing params for an new UpstartInstance for: %s", appIdStr.c_str());
834-
835- tracepoint(ubuntu_app_launch, libual_start, appIdStr.c_str());
836-
837- int timeout = 1;
838- if (ubuntu::app_launch::Registry::Impl::isWatchingAppStarting())
839- {
840- timeout = 0;
841- }
842-
843- auto handshake = starting_handshake_start(appIdStr.c_str(), timeout);
844- if (handshake == nullptr)
845- {
846- g_warning("Unable to setup starting handshake");
847- }
848-
849- /* Figure out the DBus path for the job */
850- auto jobpath = registry->impl->upstartJobPath(job);
851-
852- /* Build up our environment */
853- auto env = getenv();
854-
855- env.emplace_back(std::make_pair("APP_ID", appIdStr)); /* Application ID */
856- env.emplace_back(std::make_pair("APP_LAUNCHER_PID", std::to_string(getpid()))); /* Who we are, for bugs */
857-
858- if (!urls.empty())
859- {
860- auto accumfunc = [](const std::string& prev, Application::URL thisurl) -> std::string {
861- gchar* gescaped = g_shell_quote(thisurl.value().c_str());
862- std::string escaped;
863- if (gescaped != nullptr)
864- {
865- escaped = gescaped;
866- g_free(gescaped);
867- }
868- else
869- {
870- g_warning("Unable to escape URL: %s", thisurl.value().c_str());
871- return prev;
872- }
873-
874- if (prev.empty())
875- {
876- return escaped;
877- }
878- else
879- {
880- return prev + " " + escaped;
881- }
882- };
883- auto urlstring = std::accumulate(urls.begin(), urls.end(), std::string{}, accumfunc);
884- env.emplace_back(std::make_pair("APP_URIS", urlstring));
885- }
886-
887- if (mode == launchMode::TEST)
888- {
889- env.emplace_back(std::make_pair("QT_LOAD_TESTABILITY", "1"));
890- }
891-
892- /* Convert to GVariant */
893- GVariantBuilder builder;
894- g_variant_builder_init(&builder, G_VARIANT_TYPE_TUPLE);
895-
896- g_variant_builder_open(&builder, G_VARIANT_TYPE_ARRAY);
897-
898- for (const auto& envvar : env)
899- {
900- g_variant_builder_add_value(&builder, g_variant_new_take_string(g_strdup_printf(
901- "%s=%s", envvar.first.c_str(), envvar.second.c_str())));
902- }
903-
904- g_variant_builder_close(&builder);
905- g_variant_builder_add_value(&builder, g_variant_new_boolean(TRUE));
906-
907- auto retval = std::make_shared<UpstartInstance>(appId, job, instance, urls, registry);
908- auto chelper = new StartCHelper{};
909- chelper->ptr = retval;
910-
911- tracepoint(ubuntu_app_launch, handshake_wait, appIdStr.c_str());
912- starting_handshake_wait(handshake);
913- tracepoint(ubuntu_app_launch, handshake_complete, appIdStr.c_str());
914-
915- /* Call the job start function */
916- g_debug("Asking Upstart to start task for: %s", appIdStr.c_str());
917- g_dbus_connection_call(registry->impl->_dbus.get(), /* bus */
918- DBUS_SERVICE_UPSTART, /* service name */
919- jobpath.c_str(), /* Path */
920- DBUS_INTERFACE_UPSTART_JOB, /* interface */
921- "Start", /* method */
922- g_variant_builder_end(&builder), /* params */
923- nullptr, /* return */
924- G_DBUS_CALL_FLAGS_NONE, /* flags */
925- -1, /* default timeout */
926- registry->impl->thread.getCancellable().get(), /* cancellable */
927- application_start_cb, /* callback */
928- chelper /* object */
929- );
930-
931- tracepoint(ubuntu_app_launch, libual_start_message_sent, appIdStr.c_str());
932-
933- return retval;
934- });
935+ {
936+ return std::to_string(g_get_real_time());
937+ }
938 }
939
940 } // namespace app_impls
941
942=== modified file 'libubuntu-app-launch/application-impl-base.h'
943--- libubuntu-app-launch/application-impl-base.h 2016-09-23 22:30:51 +0000
944+++ libubuntu-app-launch/application-impl-base.h 2016-11-01 14:53:56 +0000
945@@ -17,6 +17,7 @@
946 * Ted Gould <ted.gould@canonical.com>
947 */
948
949+#include "application-info-desktop.h"
950 #include "application.h"
951
952 extern "C" {
953@@ -43,6 +44,8 @@
954
955 bool hasInstances() override;
956
957+ std::string getInstance(const std::shared_ptr<app_info::Desktop>& desktop);
958+
959 protected:
960 /** Pointer to the registry so we can ask it for things */
961 std::shared_ptr<Registry> _registry;
962@@ -51,75 +54,6 @@
963 const std::string& pkgdir);
964 };
965
966-/** An object that represents an instance of a job on Upstart. This
967- then implements everything needed by the instance interface. Most
968- applications tie into this today and use it as the backend for
969- their instances. */
970-class UpstartInstance : public Application::Instance
971-{
972-public:
973- explicit UpstartInstance(const AppID& appId,
974- const std::string& job,
975- const std::string& instance,
976- const std::vector<Application::URL>& urls,
977- const std::shared_ptr<Registry>& registry);
978-
979- /* Query lifecycle */
980- bool isRunning() override;
981- pid_t primaryPid() override;
982- bool hasPid(pid_t pid) override;
983- std::string logPath() override;
984- std::vector<pid_t> pids() override;
985-
986- /* Manage lifecycle */
987- void pause() override;
988- void resume() override;
989- void stop() override;
990-
991- /* OOM Functions */
992- void setOomAdjustment(const oom::Score score) override;
993- const oom::Score getOomAdjustment() override;
994-
995- /** Flag for whether we should include the testing environment variables */
996- enum class launchMode
997- {
998- STANDARD, /**< Standard variable set */
999- TEST /**< Include testing environment vars */
1000- };
1001- static std::shared_ptr<UpstartInstance> launch(
1002- const AppID& appId,
1003- const std::string& job,
1004- const std::string& instance,
1005- const std::vector<Application::URL>& urls,
1006- const std::shared_ptr<Registry>& registry,
1007- launchMode mode,
1008- std::function<std::list<std::pair<std::string, std::string>>(void)>& getenv);
1009-
1010-private:
1011- /** Application ID */
1012- const AppID appId_;
1013- /** Upstart job name */
1014- const std::string job_;
1015- /** Instance ID environment value, empty if none */
1016- const std::string instance_;
1017- /** The URLs that this was launched for. Only valid on launched jobs, we
1018- should look at perhaps changing that. */
1019- std::vector<Application::URL> urls_;
1020- /** A link to the registry we're using for connections */
1021- std::shared_ptr<Registry> registry_;
1022-
1023- std::vector<pid_t> forAllPids(std::function<void(pid_t)> eachPid);
1024- void signalToPid(pid_t pid, int signal);
1025- std::string pidToOomPath(pid_t pid);
1026- void oomValueToPid(pid_t pid, const oom::Score oomvalue);
1027- void oomValueToPidHelper(pid_t pid, const oom::Score oomvalue);
1028- void pidListToDbus(const std::vector<pid_t>& pids, const std::string& signal);
1029- std::string upstartJobPath();
1030-
1031- static std::shared_ptr<gchar*> urlsToStrv(const std::vector<Application::URL>& urls);
1032- static void application_start_cb(GObject* obj, GAsyncResult* res, gpointer user_data);
1033-};
1034-
1035 } // namespace app_impls
1036 } // namespace app_launch
1037 } // namespace ubuntu
1038
1039=== modified file 'libubuntu-app-launch/application-impl-click.cpp'
1040--- libubuntu-app-launch/application-impl-click.cpp 2016-10-03 23:54:20 +0000
1041+++ libubuntu-app-launch/application-impl-click.cpp 2016-11-01 14:53:56 +0000
1042@@ -51,6 +51,8 @@
1043 std::tie(_keyfile, desktopPath_) = manifestAppDesktop(_manifest, appid.package, appid.appname, _clickDir);
1044 if (!_keyfile)
1045 throw std::runtime_error{"No keyfile found for click application: " + std::string(appid)};
1046+
1047+ g_debug("Application Click object for appid '%s'", std::string(appid).c_str());
1048 }
1049
1050 AppID Click::appId()
1051@@ -323,21 +325,8 @@
1052
1053 std::vector<std::shared_ptr<Application::Instance>> Click::instances()
1054 {
1055- std::vector<std::shared_ptr<Instance>> vect;
1056- std::string sappid = appId();
1057-
1058- for (auto instancename : _registry->impl->upstartInstancesForJob("application-click"))
1059- {
1060- /* There an be only one, but we want to make sure it is
1061- there or return an empty vector */
1062- if (sappid == instancename)
1063- {
1064- vect.emplace_back(std::make_shared<UpstartInstance>(appId(), "application-click", std::string{},
1065- std::vector<Application::URL>{}, _registry));
1066- break;
1067- }
1068- }
1069- return vect;
1070+ auto vbase = _registry->impl->jobs->instances(appId(), "application-click");
1071+ return std::vector<std::shared_ptr<Application::Instance>>(vbase.begin(), vbase.end());
1072 }
1073
1074 /** Grabs all the environment variables for the application to
1075@@ -350,26 +339,30 @@
1076 retval.emplace_back(std::make_pair("APP_DIR", _clickDir));
1077 retval.emplace_back(std::make_pair("APP_DESKTOP_FILE_PATH", desktopPath_));
1078
1079+ retval.emplace_back(std::make_pair("QML2_IMPORT_PATH", _clickDir + "/lib/" + UBUNTU_APP_LAUNCH_ARCH + "/qml"));
1080+
1081 info();
1082
1083 retval.emplace_back(std::make_pair("APP_XMIR_ENABLE", _info->xMirEnable().value() ? "1" : "0"));
1084 retval.emplace_back(std::make_pair("APP_EXEC", _info->execLine().value()));
1085
1086+ retval.emplace_back(std::make_pair("APP_EXEC_POLICY", std::string(appId())));
1087+
1088 return retval;
1089 }
1090
1091 std::shared_ptr<Application::Instance> Click::launch(const std::vector<Application::URL>& urls)
1092 {
1093 std::function<std::list<std::pair<std::string, std::string>>(void)> envfunc = [this]() { return launchEnv(); };
1094- return UpstartInstance::launch(appId(), "application-click", {}, urls, _registry,
1095- UpstartInstance::launchMode::STANDARD, envfunc);
1096+ return _registry->impl->jobs->launch(appId(), "application-click", {}, urls, jobs::manager::launchMode::STANDARD,
1097+ envfunc);
1098 }
1099
1100 std::shared_ptr<Application::Instance> Click::launchTest(const std::vector<Application::URL>& urls)
1101 {
1102 std::function<std::list<std::pair<std::string, std::string>>(void)> envfunc = [this]() { return launchEnv(); };
1103- return UpstartInstance::launch(appId(), "application-click", {}, urls, _registry, UpstartInstance::launchMode::TEST,
1104- envfunc);
1105+ return _registry->impl->jobs->launch(appId(), "application-click", {}, urls, jobs::manager::launchMode::TEST,
1106+ envfunc);
1107 }
1108
1109 } // namespace app_impls
1110
1111=== modified file 'libubuntu-app-launch/application-impl-legacy.cpp'
1112--- libubuntu-app-launch/application-impl-legacy.cpp 2016-10-03 23:54:20 +0000
1113+++ libubuntu-app-launch/application-impl-legacy.cpp 2016-11-01 14:53:56 +0000
1114@@ -33,9 +33,6 @@
1115 /** Path that snapd puts desktop files, we don't want to read those directly
1116 in the Legacy backend. We want to use the snap backend. */
1117 const std::string snappyDesktopPath{"/var/lib/snapd"};
1118-/** Special characters that could be an application name that
1119- would activate in a regex */
1120-const static std::regex regexCharacters("([\\.\\-])");
1121
1122 /***********************************
1123 Prototypes
1124@@ -78,13 +75,7 @@
1125 throw std::runtime_error{"Looking like a legacy app, but should be a Snap: " + appname.value()};
1126 }
1127
1128- /* Build a regex that'll match instances of the applications which
1129- roughly looks like: $(appid)-2345345
1130-
1131- It is important to filter out the special characters that are in
1132- the appid.
1133- */
1134- instanceRegex_ = std::regex("^(?:" + std::regex_replace(_appname.value(), regexCharacters, "\\$&") + ")\\-(\\d*)$");
1135+ g_debug("Application Legacy object for app '%s'", appname.value().c_str());
1136 }
1137
1138 std::tuple<std::string, std::shared_ptr<GKeyFile>, std::string> keyfileForApp(const AppID::AppName& name)
1139@@ -291,23 +282,8 @@
1140
1141 std::vector<std::shared_ptr<Application::Instance>> Legacy::instances()
1142 {
1143- std::vector<std::shared_ptr<Instance>> vect;
1144- auto startsWith = std::string(appId()) + "-";
1145-
1146- for (auto instance : _registry->impl->upstartInstancesForJob("application-legacy"))
1147- {
1148- std::smatch instanceMatch;
1149- g_debug("Looking at legacy instance: %s", instance.c_str());
1150- if (std::regex_match(instance, instanceMatch, instanceRegex_))
1151- {
1152- vect.emplace_back(std::make_shared<UpstartInstance>(appId(), "application-legacy", instanceMatch[1].str(),
1153- std::vector<Application::URL>{}, _registry));
1154- }
1155- }
1156-
1157- g_debug("Legacy app '%s' has %d instances", std::string(appId()).c_str(), int(vect.size()));
1158-
1159- return vect;
1160+ auto vbase = _registry->impl->jobs->instances(appId(), "application-legacy");
1161+ return std::vector<std::shared_ptr<Application::Instance>>(vbase.begin(), vbase.end());
1162 }
1163
1164 /** Grabs all the environment for a legacy app. Mostly this consists of
1165@@ -368,21 +344,6 @@
1166 return retval;
1167 }
1168
1169-/** Generates an instance string based on the clock if we're a multi-instance
1170- application. */
1171-std::string Legacy::getInstance()
1172-{
1173- auto single = g_key_file_get_boolean(_keyfile.get(), "Desktop Entry", "X-Ubuntu-Single-Instance", nullptr);
1174- if (single)
1175- {
1176- return {};
1177- }
1178- else
1179- {
1180- return std::to_string(g_get_real_time());
1181- }
1182-}
1183-
1184 /** Create an UpstartInstance for this AppID using the UpstartInstance launch
1185 function.
1186
1187@@ -390,12 +351,12 @@
1188 */
1189 std::shared_ptr<Application::Instance> Legacy::launch(const std::vector<Application::URL>& urls)
1190 {
1191- std::string instance = getInstance();
1192+ auto instance = getInstance(appinfo_);
1193 std::function<std::list<std::pair<std::string, std::string>>(void)> envfunc = [this, instance]() {
1194 return launchEnv(instance);
1195 };
1196- return UpstartInstance::launch(appId(), "application-legacy", instance, urls, _registry,
1197- UpstartInstance::launchMode::STANDARD, envfunc);
1198+ return _registry->impl->jobs->launch(appId(), "application-legacy", instance, urls,
1199+ jobs::manager::launchMode::STANDARD, envfunc);
1200 }
1201
1202 /** Create an UpstartInstance for this AppID using the UpstartInstance launch
1203@@ -405,12 +366,12 @@
1204 */
1205 std::shared_ptr<Application::Instance> Legacy::launchTest(const std::vector<Application::URL>& urls)
1206 {
1207- std::string instance = getInstance();
1208+ auto instance = getInstance(appinfo_);
1209 std::function<std::list<std::pair<std::string, std::string>>(void)> envfunc = [this, instance]() {
1210 return launchEnv(instance);
1211 };
1212- return UpstartInstance::launch(appId(), "application-legacy", instance, urls, _registry,
1213- UpstartInstance::launchMode::TEST, envfunc);
1214+ return _registry->impl->jobs->launch(appId(), "application-legacy", instance, urls, jobs::manager::launchMode::TEST,
1215+ envfunc);
1216 }
1217
1218 } // namespace app_impls
1219
1220=== modified file 'libubuntu-app-launch/application-impl-legacy.h'
1221--- libubuntu-app-launch/application-impl-legacy.h 2016-09-23 21:54:33 +0000
1222+++ libubuntu-app-launch/application-impl-legacy.h 2016-11-01 14:53:56 +0000
1223@@ -86,7 +86,6 @@
1224 std::regex instanceRegex_;
1225
1226 std::list<std::pair<std::string, std::string>> launchEnv(const std::string& instance);
1227- std::string getInstance();
1228 };
1229
1230 } // namespace app_impls
1231
1232=== modified file 'libubuntu-app-launch/application-impl-libertine.cpp'
1233--- libubuntu-app-launch/application-impl-libertine.cpp 2016-10-03 23:54:20 +0000
1234+++ libubuntu-app-launch/application-impl-libertine.cpp 2016-11-01 14:53:56 +0000
1235@@ -65,6 +65,12 @@
1236 if (!_keyfile)
1237 throw std::runtime_error{"Unable to find a keyfile for application '" + appname.value() + "' in container '" +
1238 container.value() + "'"};
1239+
1240+ appinfo_ = std::make_shared<app_info::Desktop>(_keyfile, _basedir, _container_path,
1241+ app_info::DesktopFlags::XMIR_DEFAULT, _registry);
1242+
1243+ g_debug("Application Libertine object for container '%s' app '%s'", container.value().c_str(),
1244+ appname.value().c_str());
1245 }
1246
1247 std::shared_ptr<GKeyFile> Libertine::keyfileFromPath(const std::string& pathname)
1248@@ -255,8 +261,7 @@
1249 }
1250 catch (std::runtime_error& e)
1251 {
1252- g_debug("Unable to create application for libertine appname '%s': %s",
1253- apps.get()[j], e.what());
1254+ g_debug("Unable to create application for libertine appname '%s': %s", apps.get()[j], e.what());
1255 }
1256 }
1257 }
1258@@ -266,27 +271,13 @@
1259
1260 std::shared_ptr<Application::Info> Libertine::info()
1261 {
1262- if (!appinfo_)
1263- {
1264- appinfo_ = std::make_shared<app_info::Desktop>(_keyfile, _basedir, _container_path,
1265- app_info::DesktopFlags::XMIR_DEFAULT, _registry);
1266- }
1267 return appinfo_;
1268 }
1269
1270 std::vector<std::shared_ptr<Application::Instance>> Libertine::instances()
1271 {
1272- std::vector<std::shared_ptr<Instance>> vect;
1273- std::string sappid = appId();
1274-
1275- for (auto instancename : _registry->impl->upstartInstancesForJob("application-legacy"))
1276- {
1277- if (std::equal(sappid.begin(), sappid.end(), instancename.begin()))
1278- vect.emplace_back(std::make_shared<UpstartInstance>(appId(), "application-legacy", std::string{},
1279- std::vector<Application::URL>{}, _registry));
1280- }
1281-
1282- return vect;
1283+ auto vbase = _registry->impl->jobs->instances(appId(), "application-legacy");
1284+ return std::vector<std::shared_ptr<Application::Instance>>(vbase.begin(), vbase.end());
1285 }
1286
1287 /** Grabs all the environment variables for the application to
1288@@ -327,16 +318,18 @@
1289
1290 std::shared_ptr<Application::Instance> Libertine::launch(const std::vector<Application::URL>& urls)
1291 {
1292+ auto instance = getInstance(appinfo_);
1293 std::function<std::list<std::pair<std::string, std::string>>(void)> envfunc = [this]() { return launchEnv(); };
1294- return UpstartInstance::launch(appId(), "application-legacy", {}, urls, _registry,
1295- UpstartInstance::launchMode::STANDARD, envfunc);
1296+ return _registry->impl->jobs->launch(appId(), "application-legacy", instance, urls,
1297+ jobs::manager::launchMode::STANDARD, envfunc);
1298 }
1299
1300 std::shared_ptr<Application::Instance> Libertine::launchTest(const std::vector<Application::URL>& urls)
1301 {
1302+ auto instance = getInstance(appinfo_);
1303 std::function<std::list<std::pair<std::string, std::string>>(void)> envfunc = [this]() { return launchEnv(); };
1304- return UpstartInstance::launch(appId(), "application-legacy", {}, urls, _registry,
1305- UpstartInstance::launchMode::TEST, envfunc);
1306+ return _registry->impl->jobs->launch(appId(), "application-legacy", instance, urls, jobs::manager::launchMode::TEST,
1307+ envfunc);
1308 }
1309
1310 } // namespace app_impls
1311
1312=== modified file 'libubuntu-app-launch/application-impl-snap.cpp'
1313--- libubuntu-app-launch/application-impl-snap.cpp 2016-10-03 23:54:20 +0000
1314+++ libubuntu-app-launch/application-impl-snap.cpp 2016-11-01 14:53:56 +0000
1315@@ -196,6 +196,8 @@
1316 }
1317
1318 info_ = std::make_shared<SnapInfo>(appid_, _registry, interface_, pkgInfo_->directory);
1319+
1320+ g_debug("Application Snap object for AppID '%s'", std::string(appid).c_str());
1321 }
1322
1323 /** Uses the findInterface() function to find the interface if we don't
1324@@ -229,7 +231,7 @@
1325 }
1326 catch (std::runtime_error& e)
1327 {
1328- g_warning("Unable to make Snap object for '%s': %s", std::string(id).c_str(), e.what());
1329+ g_debug("Unable to make Snap object for '%s': %s", std::string(id).c_str(), e.what());
1330 }
1331 }
1332 }
1333@@ -396,19 +398,8 @@
1334 /** Get all of the instances of this snap package that are running */
1335 std::vector<std::shared_ptr<Application::Instance>> Snap::instances()
1336 {
1337- std::vector<std::shared_ptr<Instance>> vect;
1338- auto startsWith = std::string(appid_) + "-";
1339-
1340- for (const auto& instance : _registry->impl->upstartInstancesForJob("application-snap"))
1341- {
1342- if (std::equal(startsWith.begin(), startsWith.end(), instance.begin()))
1343- {
1344- vect.emplace_back(std::make_shared<UpstartInstance>(appid_, "application-snap", std::string{},
1345- std::vector<Application::URL>{}, _registry));
1346- }
1347- }
1348-
1349- return vect;
1350+ auto vbase = _registry->impl->jobs->instances(appId(), "application-snap");
1351+ return std::vector<std::shared_ptr<Application::Instance>>(vbase.begin(), vbase.end());
1352 }
1353
1354 /** Return the launch environment for this snap. That includes whether
1355@@ -447,9 +438,10 @@
1356 */
1357 std::shared_ptr<Application::Instance> Snap::launch(const std::vector<Application::URL>& urls)
1358 {
1359+ auto instance = getInstance(info_);
1360 std::function<std::list<std::pair<std::string, std::string>>(void)> envfunc = [this]() { return launchEnv(); };
1361- return UpstartInstance::launch(appid_, "application-snap", {}, urls, _registry,
1362- UpstartInstance::launchMode::STANDARD, envfunc);
1363+ return _registry->impl->jobs->launch(appid_, "application-snap", instance, urls,
1364+ jobs::manager::launchMode::STANDARD, envfunc);
1365 }
1366
1367 /** Create a new instance of this Snap with a testing environment
1368@@ -459,9 +451,10 @@
1369 */
1370 std::shared_ptr<Application::Instance> Snap::launchTest(const std::vector<Application::URL>& urls)
1371 {
1372+ auto instance = getInstance(info_);
1373 std::function<std::list<std::pair<std::string, std::string>>(void)> envfunc = [this]() { return launchEnv(); };
1374- return UpstartInstance::launch(appid_, "application-snap", {}, urls, _registry, UpstartInstance::launchMode::TEST,
1375- envfunc);
1376+ return _registry->impl->jobs->launch(appid_, "application-snap", instance, urls, jobs::manager::launchMode::TEST,
1377+ envfunc);
1378 }
1379
1380 } // namespace app_impls
1381
1382=== modified file 'libubuntu-app-launch/application-info-desktop.cpp'
1383--- libubuntu-app-launch/application-info-desktop.cpp 2016-09-14 16:42:20 +0000
1384+++ libubuntu-app-launch/application-info-desktop.cpp 2016-11-01 14:53:56 +0000
1385@@ -253,7 +253,7 @@
1386 if (stringlistFromKeyfileContains(keyfile, "NotShowIn", xdg_current_desktop, false) ||
1387 !stringlistFromKeyfileContains(keyfile, "OnlyShowIn", xdg_current_desktop, true))
1388 {
1389- g_warning("Application is not shown in Unity");
1390+ g_debug("Application is not shown in current desktop: %s", xdg_current_desktop);
1391 // Exception removed for OTA11 as a temporary fix
1392 // throw std::runtime_error("Application is not shown in Unity");
1393 }
1394@@ -352,6 +352,7 @@
1395 , _xMirEnable(
1396 boolFromKeyfile<XMirEnable>(keyfile, "X-Ubuntu-XMir-Enable", (flags & DesktopFlags::XMIR_DEFAULT).any()))
1397 , _exec(stringFromKeyfile<Exec>(keyfile, "Exec"))
1398+ , _singleInstance(boolFromKeyfile<SingleInstance>(keyfile, "X-Ubuntu-Single-Instance", false))
1399 {
1400 }
1401
1402
1403=== modified file 'libubuntu-app-launch/application-info-desktop.h'
1404--- libubuntu-app-launch/application-info-desktop.h 2016-09-14 16:38:42 +0000
1405+++ libubuntu-app-launch/application-info-desktop.h 2016-11-01 14:53:56 +0000
1406@@ -106,6 +106,13 @@
1407 return _exec;
1408 }
1409
1410+ struct SingleInstanceTag;
1411+ typedef TypeTagger<SingleInstanceTag, bool> SingleInstance;
1412+ virtual SingleInstance singleInstance()
1413+ {
1414+ return _singleInstance;
1415+ }
1416+
1417 protected:
1418 std::shared_ptr<GKeyFile> _keyfile;
1419 std::string _basePath;
1420@@ -125,6 +132,7 @@
1421
1422 XMirEnable _xMirEnable;
1423 Exec _exec;
1424+ SingleInstance _singleInstance;
1425 };
1426
1427 } // namespace AppInfo
1428
1429=== modified file 'libubuntu-app-launch/application.cpp'
1430--- libubuntu-app-launch/application.cpp 2016-08-26 17:33:34 +0000
1431+++ libubuntu-app-launch/application.cpp 2016-11-01 14:53:56 +0000
1432@@ -28,6 +28,8 @@
1433 #include "application-impl-snap.h"
1434 #endif
1435 #include "application.h"
1436+#include "jobs-base.h"
1437+#include "registry-impl.h"
1438 #include "registry.h"
1439
1440 #include <functional>
1441@@ -46,6 +48,11 @@
1442 throw std::runtime_error("AppID is empty");
1443 }
1444
1445+ if (!registry->impl->jobs)
1446+ {
1447+ registry->impl->jobs = jobs::manager::Base::determineFactory(registry);
1448+ }
1449+
1450 if (app_impls::Click::hasAppId(appid, registry))
1451 {
1452 return std::make_shared<app_impls::Click>(appid, registry);
1453
1454=== added file 'libubuntu-app-launch/jobs-base.cpp'
1455--- libubuntu-app-launch/jobs-base.cpp 1970-01-01 00:00:00 +0000
1456+++ libubuntu-app-launch/jobs-base.cpp 2016-11-01 14:53:56 +0000
1457@@ -0,0 +1,434 @@
1458+/*
1459+ * Copyright © 2016 Canonical Ltd.
1460+ *
1461+ * This program is free software: you can redistribute it and/or modify it
1462+ * under the terms of the GNU General Public License version 3, as published
1463+ * by the Free Software Foundation.
1464+ *
1465+ * This program is distributed in the hope that it will be useful, but
1466+ * WITHOUT ANY WARRANTY; without even the implied warranties of
1467+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1468+ * PURPOSE. See the GNU General Public License for more details.
1469+ *
1470+ * You should have received a copy of the GNU General Public License along
1471+ * with this program. If not, see <http://www.gnu.org/licenses/>.
1472+ *
1473+ * Authors:
1474+ * Ted Gould <ted.gould@canonical.com>
1475+ */
1476+
1477+#include <algorithm>
1478+#include <cerrno>
1479+#include <cstring>
1480+
1481+#include "jobs-base.h"
1482+#include "jobs-systemd.h"
1483+#include "jobs-upstart.h"
1484+#include "registry-impl.h"
1485+
1486+namespace ubuntu
1487+{
1488+namespace app_launch
1489+{
1490+namespace jobs
1491+{
1492+namespace manager
1493+{
1494+
1495+Base::Base(const std::shared_ptr<Registry>& registry)
1496+ : registry_(registry)
1497+ , allJobs_{"application-click", "application-legacy", "application-snap"}
1498+{
1499+}
1500+
1501+std::shared_ptr<Base> Base::determineFactory(std::shared_ptr<Registry> registry)
1502+{
1503+ /* Checking to see if we have a user bus, that is only started
1504+ by systemd so we're in good shape if we have one. We're using
1505+ the path instead of the RUNTIME variable because we want to work
1506+ around the case of being relocated by the snappy environment */
1507+ if (g_file_test(SystemD::userBusPath().c_str(), G_FILE_TEST_EXISTS))
1508+ {
1509+ return std::make_shared<jobs::manager::SystemD>(registry);
1510+ }
1511+ else
1512+ {
1513+ return std::make_shared<jobs::manager::Upstart>(registry);
1514+ }
1515+}
1516+
1517+const std::set<std::string>& Base::getAllJobs()
1518+{
1519+ return allJobs_;
1520+}
1521+
1522+} // namespace manager
1523+
1524+namespace instance
1525+{
1526+
1527+Base::Base(const AppID& appId,
1528+ const std::string& job,
1529+ const std::string& instance,
1530+ const std::vector<Application::URL>& urls,
1531+ const std::shared_ptr<Registry>& registry)
1532+ : appId_(appId)
1533+ , job_(job)
1534+ , instance_(instance)
1535+ , urls_(urls)
1536+ , registry_(registry)
1537+{
1538+}
1539+
1540+/** Checks to see if we have a primary PID for the instance */
1541+bool Base::isRunning()
1542+{
1543+ return primaryPid() != 0;
1544+}
1545+
1546+/** Looks at the PIDs in the instance cgroup and checks to see if @pid
1547+ is in the set.
1548+
1549+ @param pid PID to look for
1550+*/
1551+bool Base::hasPid(pid_t pid)
1552+{
1553+ auto vpids = pids();
1554+ bool hasit = std::find(vpids.begin(), vpids.end(), pid) != vpids.end();
1555+ g_debug("Checking for PID %d on AppID '%s' result: %s", pid, std::string(appId_).c_str(), hasit ? "YES" : "NO");
1556+ return hasit;
1557+}
1558+
1559+/** Pauses this application by sending SIGSTOP to all the PIDs in the
1560+ cgroup and tells Zeitgeist that we've left the application. */
1561+void Base::pause()
1562+{
1563+ g_debug("Pausing application: %s", std::string(appId_).c_str());
1564+ registry_->impl->zgSendEvent(appId_, ZEITGEIST_ZG_LEAVE_EVENT);
1565+
1566+ auto pids = forAllPids([this](pid_t pid) {
1567+ auto oomval = oom::paused();
1568+ g_debug("Pausing PID: %d (%d)", pid, int(oomval));
1569+ signalToPid(pid, SIGSTOP);
1570+ oomValueToPid(pid, oomval);
1571+ });
1572+
1573+ pidListToDbus(pids, "ApplicationPaused");
1574+}
1575+
1576+/** Resumes this application by sending SIGCONT to all the PIDs in the
1577+ cgroup and tells Zeitgeist that we're accessing the application. */
1578+void Base::resume()
1579+{
1580+ g_debug("Resuming application: %s", std::string(appId_).c_str());
1581+ registry_->impl->zgSendEvent(appId_, ZEITGEIST_ZG_ACCESS_EVENT);
1582+
1583+ auto pids = forAllPids([this](pid_t pid) {
1584+ auto oomval = oom::focused();
1585+ g_debug("Resuming PID: %d (%d)", pid, int(oomval));
1586+ signalToPid(pid, SIGCONT);
1587+ oomValueToPid(pid, oomval);
1588+ });
1589+
1590+ pidListToDbus(pids, "ApplicationResumed");
1591+}
1592+
1593+/** Go through the list of PIDs calling a function and handling
1594+ the issue with getting PIDs being a racey condition.
1595+
1596+ \param eachPid Function to run on each PID
1597+*/
1598+std::vector<pid_t> Base::forAllPids(std::function<void(pid_t)> eachPid)
1599+{
1600+ std::set<pid_t> seenPids;
1601+ bool added = true;
1602+
1603+ while (added)
1604+ {
1605+ added = false;
1606+ auto pidlist = pids();
1607+ for (auto pid : pidlist)
1608+ {
1609+ if (seenPids.insert(pid).second)
1610+ {
1611+ eachPid(pid);
1612+ added = true;
1613+ }
1614+ }
1615+ }
1616+
1617+ return std::vector<pid_t>(seenPids.begin(), seenPids.end());
1618+}
1619+
1620+/** Send a signal that we've change the application. Do this on the
1621+ registry thread in an idle so that we don't block anyone.
1622+
1623+ \param pids List of PIDs to turn into variants to send
1624+ \param signal Name of the DBus signal to send
1625+*/
1626+void Base::pidListToDbus(const std::vector<pid_t>& pids, const std::string& signal)
1627+{
1628+ auto registry = registry_;
1629+ auto lappid = appId_;
1630+
1631+ registry_->impl->thread.executeOnThread([registry, lappid, pids, signal] {
1632+ auto vpids = std::shared_ptr<GVariant>(
1633+ [pids]() {
1634+ GVariant* pidarray = nullptr;
1635+
1636+ if (pids.empty())
1637+ {
1638+ pidarray = g_variant_new_array(G_VARIANT_TYPE_UINT64, nullptr, 0);
1639+ g_variant_ref_sink(pidarray);
1640+ return pidarray;
1641+ }
1642+
1643+ GVariantBuilder builder;
1644+ g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY);
1645+ for (auto pid : pids)
1646+ {
1647+ g_variant_builder_add_value(&builder, g_variant_new_uint64(pid));
1648+ }
1649+
1650+ pidarray = g_variant_builder_end(&builder);
1651+ g_variant_ref_sink(pidarray);
1652+ return pidarray;
1653+ }(),
1654+ [](GVariant* var) { g_variant_unref(var); });
1655+
1656+ GVariantBuilder params;
1657+ g_variant_builder_init(&params, G_VARIANT_TYPE_TUPLE);
1658+ g_variant_builder_add_value(&params, g_variant_new_string(std::string(lappid).c_str()));
1659+ g_variant_builder_add_value(&params, vpids.get());
1660+
1661+ GError* error = nullptr;
1662+ g_dbus_connection_emit_signal(registry->impl->_dbus.get(), /* bus */
1663+ nullptr, /* destination */
1664+ "/", /* path */
1665+ "com.canonical.UbuntuAppLaunch", /* interface */
1666+ signal.c_str(), /* signal */
1667+ g_variant_builder_end(&params), /* params, the same */
1668+ &error); /* error */
1669+
1670+ if (error != nullptr)
1671+ {
1672+ g_warning("Unable to emit signal '%s' for appid '%s': %s", signal.c_str(), std::string(lappid).c_str(),
1673+ error->message);
1674+ g_error_free(error);
1675+ }
1676+ else
1677+ {
1678+ g_debug("Emmitted '%s' to DBus", signal.c_str());
1679+ }
1680+ });
1681+}
1682+
1683+/** Sets the OOM adjustment by getting the list of PIDs and writing
1684+ the value to each of their files in proc
1685+
1686+ \param score OOM Score to set
1687+*/
1688+void Base::setOomAdjustment(const oom::Score score)
1689+{
1690+ forAllPids([this, &score](pid_t pid) { oomValueToPid(pid, score); });
1691+}
1692+
1693+/** Figures out the path to the primary PID of the application and
1694+ then reads its OOM adjustment file. */
1695+const oom::Score Base::getOomAdjustment()
1696+{
1697+ auto pid = primaryPid();
1698+ if (pid == 0)
1699+ {
1700+ throw std::runtime_error("No PID for application: " + std::string(appId_));
1701+ }
1702+
1703+ auto path = pidToOomPath(pid);
1704+ GError* error = nullptr;
1705+ gchar* content = nullptr;
1706+
1707+ g_file_get_contents(path.c_str(), /* path */
1708+ &content, /* data */
1709+ nullptr, /* size */
1710+ &error); /* error */
1711+
1712+ if (error != nullptr)
1713+ {
1714+ auto serror = std::shared_ptr<GError>(error, g_error_free);
1715+ throw std::runtime_error("Unable to access OOM value for '" + std::string(appId_) + "' primary PID '" +
1716+ std::to_string(pid) + "' because: " + serror->message);
1717+ }
1718+
1719+ auto score = static_cast<oom::Score>(std::atoi(content));
1720+ g_free(content);
1721+ return score;
1722+}
1723+
1724+/** Sends a signal to a PID with a warning if we can't send it.
1725+ We could throw an exception, but we can't handle it usefully anyway
1726+
1727+ \param pid PID to send the signal to
1728+ \param signal signal to send
1729+*/
1730+void Base::signalToPid(pid_t pid, int signal)
1731+{
1732+ if (-1 == kill(pid, signal))
1733+ {
1734+ /* While that didn't work, we still want to try as many as we can */
1735+ g_warning("Unable to send signal %d to pid %d", signal, pid);
1736+ }
1737+}
1738+
1739+/** Get the path to the PID's OOM adjust path, with allowing for an
1740+ override for testing using the environment variable
1741+ UBUNTU_APP_LAUNCH_OOM_PROC_PATH
1742+
1743+ \param pid PID to build path for
1744+*/
1745+std::string Base::pidToOomPath(pid_t pid)
1746+{
1747+ static std::string procpath;
1748+ if (G_UNLIKELY(procpath.empty()))
1749+ {
1750+ /* Set by the test suite, probably not anyone else */
1751+ auto envvar = g_getenv("UBUNTU_APP_LAUNCH_OOM_PROC_PATH");
1752+ if (G_LIKELY(envvar == nullptr))
1753+ procpath = "/proc";
1754+ else
1755+ procpath = envvar;
1756+ }
1757+
1758+ gchar* gpath = g_build_filename(procpath.c_str(), std::to_string(pid).c_str(), "oom_score_adj", nullptr);
1759+ std::string path = gpath;
1760+ g_free(gpath);
1761+ return path;
1762+}
1763+
1764+/** Writes an OOM value to proc, assuming we have a string
1765+ in the outer loop
1766+
1767+ \param pid PID to change the OOM value of
1768+ \param oomvalue OOM value to set
1769+*/
1770+void Base::oomValueToPid(pid_t pid, const oom::Score oomvalue)
1771+{
1772+ auto oomstr = std::to_string(static_cast<std::int32_t>(oomvalue));
1773+ auto path = pidToOomPath(pid);
1774+ FILE* adj = fopen(path.c_str(), "w");
1775+ int openerr = errno;
1776+
1777+ if (adj == nullptr)
1778+ {
1779+ switch (openerr)
1780+ {
1781+ case ENOENT:
1782+ /* ENOENT happens a fair amount because of races, so it's not
1783+ worth printing a warning about */
1784+ return;
1785+ case EACCES:
1786+ {
1787+ /* We can get this error when trying to set the OOM value on
1788+ Oxide renderers because they're started by the sandbox and
1789+ don't have their adjustment value available for us to write.
1790+ We have a helper to deal with this, but it's kinda expensive
1791+ so we only use it when we have to. */
1792+ oomValueToPidHelper(pid, oomvalue);
1793+ return;
1794+ }
1795+ default:
1796+ g_warning("Unable to set OOM value for '%d' to '%s': %s", int(pid), oomstr.c_str(),
1797+ std::strerror(openerr));
1798+ return;
1799+ }
1800+ }
1801+
1802+ size_t writesize = fwrite(oomstr.c_str(), 1, oomstr.size(), adj);
1803+ int writeerr = errno;
1804+ fclose(adj);
1805+
1806+ if (writesize == oomstr.size())
1807+ return;
1808+
1809+ if (writeerr != 0)
1810+ g_warning("Unable to set OOM value for '%d' to '%s': %s", int(pid), oomstr.c_str(), strerror(writeerr));
1811+ else
1812+ /* No error, but yet, wrong size. Not sure, what could cause this. */
1813+ g_debug("Unable to set OOM value for '%d' to '%s': Wrote %d bytes", int(pid), oomstr.c_str(), int(writesize));
1814+}
1815+
1816+/** Use a setuid root helper for setting the oom value of
1817+ Chromium instances
1818+
1819+ \param pid PID to change the OOM value of
1820+ \param oomvalue OOM value to set
1821+*/
1822+void Base::oomValueToPidHelper(pid_t pid, const oom::Score oomvalue)
1823+{
1824+ GError* error = nullptr;
1825+ std::string oomstr = std::to_string(static_cast<std::int32_t>(oomvalue));
1826+ std::string pidstr = std::to_string(pid);
1827+ std::array<const char*, 4> args = {OOM_HELPER, pidstr.c_str(), oomstr.c_str(), nullptr};
1828+
1829+ g_debug("Excuting OOM Helper (pid: %d, score: %d): %s", int(pid), int(oomvalue),
1830+ std::accumulate(args.begin(), args.end(), std::string{},
1831+ [](const std::string& instr, const char* output) -> std::string {
1832+ if (instr.empty())
1833+ {
1834+ return output;
1835+ }
1836+ else if (output != nullptr)
1837+ {
1838+ return instr + " " + std::string(output);
1839+ }
1840+ else
1841+ {
1842+ return instr;
1843+ }
1844+ })
1845+ .c_str());
1846+
1847+ g_spawn_async(nullptr, /* working dir */
1848+ (char**)(args.data()), /* args */
1849+ nullptr, /* env */
1850+ G_SPAWN_DEFAULT, /* flags */
1851+ nullptr, /* child setup */
1852+ nullptr, /* child setup userdata*/
1853+ nullptr, /* pid */
1854+ &error); /* error */
1855+
1856+ if (error != nullptr)
1857+ {
1858+ g_warning("Unable to launch OOM helper '" OOM_HELPER "' on PID '%d': %s", pid, error->message);
1859+ g_error_free(error);
1860+ return;
1861+ }
1862+}
1863+
1864+/** Reformat a C++ vector of URLs into a C GStrv of strings
1865+
1866+ \param urls Vector of URLs to make into C strings
1867+*/
1868+std::shared_ptr<gchar*> Base::urlsToStrv(const std::vector<Application::URL>& urls)
1869+{
1870+ if (urls.empty())
1871+ {
1872+ return {};
1873+ }
1874+
1875+ auto array = g_array_new(TRUE, FALSE, sizeof(gchar*));
1876+
1877+ for (auto url : urls)
1878+ {
1879+ auto str = g_strdup(url.value().c_str());
1880+ g_debug("Converting URL: %s", str);
1881+ g_array_append_val(array, str);
1882+ }
1883+
1884+ return std::shared_ptr<gchar*>((gchar**)g_array_free(array, FALSE), g_strfreev);
1885+}
1886+
1887+} // namespace instance
1888+
1889+} // namespace jobs
1890+} // namespace app_launch
1891+} // namespace ubuntu
1892
1893=== added file 'libubuntu-app-launch/jobs-base.h'
1894--- libubuntu-app-launch/jobs-base.h 1970-01-01 00:00:00 +0000
1895+++ libubuntu-app-launch/jobs-base.h 2016-11-01 14:53:56 +0000
1896@@ -0,0 +1,124 @@
1897+/*
1898+ * Copyright © 2016 Canonical Ltd.
1899+ *
1900+ * This program is free software: you can redistribute it and/or modify it
1901+ * under the terms of the GNU General Public License version 3, as published
1902+ * by the Free Software Foundation.
1903+ *
1904+ * This program is distributed in the hope that it will be useful, but
1905+ * WITHOUT ANY WARRANTY; without even the implied warranties of
1906+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1907+ * PURPOSE. See the GNU General Public License for more details.
1908+ *
1909+ * You should have received a copy of the GNU General Public License along
1910+ * with this program. If not, see <http://www.gnu.org/licenses/>.
1911+ *
1912+ * Authors:
1913+ * Ted Gould <ted.gould@canonical.com>
1914+ */
1915+
1916+#pragma once
1917+#include "application.h"
1918+#include <glib.h>
1919+#include <set>
1920+
1921+namespace ubuntu
1922+{
1923+namespace app_launch
1924+{
1925+namespace jobs
1926+{
1927+namespace instance
1928+{
1929+
1930+class Base : public Application::Instance
1931+{
1932+public:
1933+ Base(const AppID& appId,
1934+ const std::string& job,
1935+ const std::string& instance,
1936+ const std::vector<Application::URL>& urls,
1937+ const std::shared_ptr<Registry>& registry);
1938+ virtual ~Base() = default;
1939+
1940+ bool isRunning() override;
1941+ bool hasPid(pid_t pid) override;
1942+ void pause() override;
1943+ void resume() override;
1944+
1945+ /* OOM Functions */
1946+ void setOomAdjustment(const oom::Score score) override;
1947+ const oom::Score getOomAdjustment() override;
1948+
1949+protected:
1950+ /** Application ID */
1951+ const AppID appId_;
1952+ /** Upstart job name */
1953+ const std::string job_;
1954+ /** Instance ID environment value, empty if none */
1955+ const std::string instance_;
1956+ /** The URLs that this was launched for. Only valid on launched jobs, we
1957+ should look at perhaps changing that. */
1958+ std::vector<Application::URL> urls_;
1959+ /** A link to the registry we're using for connections */
1960+ std::shared_ptr<Registry> registry_;
1961+
1962+ static std::shared_ptr<gchar*> urlsToStrv(const std::vector<Application::URL>& urls);
1963+
1964+private:
1965+ std::vector<pid_t> forAllPids(std::function<void(pid_t)> eachPid);
1966+ void signalToPid(pid_t pid, int signal);
1967+ std::string pidToOomPath(pid_t pid);
1968+ void oomValueToPid(pid_t pid, const oom::Score oomvalue);
1969+ void oomValueToPidHelper(pid_t pid, const oom::Score oomvalue);
1970+ void pidListToDbus(const std::vector<pid_t>& pids, const std::string& signal);
1971+};
1972+
1973+} // namespace instance
1974+
1975+namespace manager
1976+{
1977+
1978+/** Flag for whether we should include the testing environment variables */
1979+enum class launchMode
1980+{
1981+ STANDARD, /**< Standard variable set */
1982+ TEST /**< Include testing environment vars */
1983+};
1984+
1985+class Base
1986+{
1987+public:
1988+ Base(const std::shared_ptr<Registry>& registry);
1989+ virtual ~Base() = default;
1990+
1991+ virtual std::shared_ptr<Application::Instance> launch(
1992+ const AppID& appId,
1993+ const std::string& job,
1994+ const std::string& instance,
1995+ const std::vector<Application::URL>& urls,
1996+ launchMode mode,
1997+ std::function<std::list<std::pair<std::string, std::string>>(void)>& getenv) = 0;
1998+
1999+ virtual std::shared_ptr<Application::Instance> existing(const AppID& appId,
2000+ const std::string& job,
2001+ const std::string& instance,
2002+ const std::vector<Application::URL>& urls) = 0;
2003+
2004+ virtual std::list<std::shared_ptr<Application>> runningApps() = 0;
2005+
2006+ virtual std::vector<std::shared_ptr<instance::Base>> instances(const AppID& appID, const std::string& job) = 0;
2007+
2008+ const std::set<std::string>& getAllJobs();
2009+
2010+ static std::shared_ptr<Base> determineFactory(std::shared_ptr<Registry> registry);
2011+
2012+protected:
2013+ std::weak_ptr<Registry> registry_;
2014+ std::set<std::string> allJobs_;
2015+};
2016+
2017+} // namespace manager
2018+} // namespace jobs
2019+} // namespace app_launch
2020+} // namespace ubuntu
2021
2022=== added file 'libubuntu-app-launch/jobs-systemd.cpp'
2023--- libubuntu-app-launch/jobs-systemd.cpp 1970-01-01 00:00:00 +0000
2024+++ libubuntu-app-launch/jobs-systemd.cpp 2016-11-01 14:53:56 +0000
2025@@ -0,0 +1,917 @@
2026+/*
2027+ * Copyright © 2016 Canonical Ltd.
2028+ *
2029+ * This program is free software: you can redistribute it and/or modify it
2030+ * under the terms of the GNU General Public License version 3, as published
2031+ * by the Free Software Foundation.
2032+ *
2033+ * This program is distributed in the hope that it will be useful, but
2034+ * WITHOUT ANY WARRANTY; without even the implied warranties of
2035+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
2036+ * PURPOSE. See the GNU General Public License for more details.
2037+ *
2038+ * You should have received a copy of the GNU General Public License along
2039+ * with this program. If not, see <http://www.gnu.org/licenses/>.
2040+ *
2041+ * Authors:
2042+ * Ted Gould <ted.gould@canonical.com>
2043+ */
2044+
2045+#include <algorithm>
2046+#include <gio/gio.h>
2047+#include <numeric>
2048+#include <regex>
2049+#include <sys/types.h>
2050+#include <unistd.h>
2051+
2052+#include "helpers.h"
2053+#include "jobs-systemd.h"
2054+#include "registry-impl.h"
2055+#include "second-exec-core.h"
2056+
2057+extern "C" {
2058+#include "ubuntu-app-launch-trace.h"
2059+}
2060+
2061+namespace ubuntu
2062+{
2063+namespace app_launch
2064+{
2065+namespace jobs
2066+{
2067+namespace instance
2068+{
2069+
2070+class SystemD : public Base
2071+{
2072+ friend class manager::SystemD;
2073+
2074+public:
2075+ explicit SystemD(const AppID& appId,
2076+ const std::string& job,
2077+ const std::string& instance,
2078+ const std::vector<Application::URL>& urls,
2079+ const std::shared_ptr<Registry>& registry);
2080+
2081+ /* Query lifecycle */
2082+ pid_t primaryPid() override;
2083+ std::string logPath() override;
2084+ std::vector<pid_t> pids() override;
2085+
2086+ /* Manage lifecycle */
2087+ void stop() override;
2088+
2089+}; // class SystemD
2090+
2091+SystemD::SystemD(const AppID& appId,
2092+ const std::string& job,
2093+ const std::string& instance,
2094+ const std::vector<Application::URL>& urls,
2095+ const std::shared_ptr<Registry>& registry)
2096+ : Base(appId, job, instance, urls, registry)
2097+{
2098+ g_debug("Creating a new SystemD for '%s' instance '%s'", std::string(appId).c_str(), instance.c_str());
2099+}
2100+
2101+pid_t SystemD::primaryPid()
2102+{
2103+ auto manager = std::dynamic_pointer_cast<manager::SystemD>(registry_->impl->jobs);
2104+ return manager->unitPrimaryPid(appId_, job_, instance_);
2105+}
2106+
2107+std::string SystemD::logPath()
2108+{
2109+ /* NOTE: We can never get this for systemd */
2110+ return {};
2111+}
2112+
2113+std::vector<pid_t> SystemD::pids()
2114+{
2115+ auto manager = std::dynamic_pointer_cast<manager::SystemD>(registry_->impl->jobs);
2116+ return manager->unitPids(appId_, job_, instance_);
2117+}
2118+
2119+void SystemD::stop()
2120+{
2121+ auto manager = std::dynamic_pointer_cast<manager::SystemD>(registry_->impl->jobs);
2122+ return manager->stopUnit(appId_, job_, instance_);
2123+}
2124+
2125+} // namespace instance
2126+
2127+namespace manager
2128+{
2129+
2130+static const std::string SYSTEMD_DBUS_ADDRESS{"org.freedesktop.systemd1"};
2131+static const std::string SYSTEMD_DBUS_IFACE_MANAGER{"org.freedesktop.systemd1.Manager"};
2132+static const std::string SYSTEMD_DBUS_PATH_MANAGER{"/org/freedesktop/systemd1"};
2133+static const std::string SYSTEMD_DBUS_IFACE_UNIT{"org.freedesktop.systemd1.Unit"};
2134+static const std::string SYSTEMD_DBUS_IFACE_SERVICE{"org.freedesktop.systemd1.Service"};
2135+
2136+SystemD::SystemD(std::shared_ptr<Registry> registry)
2137+ : Base(registry)
2138+{
2139+ auto cancel = registry->impl->thread.getCancellable();
2140+ userbus_ = registry->impl->thread.executeOnThread<std::shared_ptr<GDBusConnection>>([cancel]() {
2141+ GError* error = nullptr;
2142+ auto bus = std::shared_ptr<GDBusConnection>(
2143+ g_dbus_connection_new_for_address_sync(
2144+ ("unix:path=" + userBusPath()).c_str(), /* path to the user bus */
2145+ (GDBusConnectionFlags)(G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT |
2146+ G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION), /* It is a message bus */
2147+ nullptr, /* observer */
2148+ cancel.get(), /* cancellable from the thread */
2149+ &error), /* error */
2150+ [](GDBusConnection* bus) { g_clear_object(&bus); });
2151+
2152+ if (error != nullptr)
2153+ {
2154+ std::string message = std::string("Unable to connect to user bus: ") + error->message;
2155+ g_error_free(error);
2156+ throw std::runtime_error(message);
2157+ }
2158+
2159+ return bus;
2160+ });
2161+}
2162+
2163+SystemD::~SystemD()
2164+{
2165+}
2166+
2167+std::string SystemD::findEnv(const std::string& value, std::list<std::pair<std::string, std::string>>& env)
2168+{
2169+ std::string retval;
2170+ auto entry = std::find_if(env.begin(), env.end(),
2171+ [&value](std::pair<std::string, std::string>& entry) { return entry.first == value; });
2172+
2173+ if (entry != env.end())
2174+ {
2175+ retval = entry->second;
2176+ }
2177+
2178+ return retval;
2179+}
2180+
2181+std::vector<std::string> SystemD::parseExec(std::list<std::pair<std::string, std::string>>& env)
2182+{
2183+ auto exec = findEnv("APP_EXEC", env);
2184+ if (exec.empty())
2185+ {
2186+ g_debug("Application exec line is empty?!?!?");
2187+ return {};
2188+ }
2189+ auto uris = findEnv("APP_URIS", env);
2190+
2191+ auto execarray = desktop_exec_parse(exec.c_str(), uris.c_str());
2192+
2193+ std::vector<std::string> retval;
2194+ retval.reserve(execarray->len);
2195+ for (unsigned int i = 0; i < execarray->len; i++)
2196+ {
2197+ retval.emplace_back(g_array_index(execarray, gchar*, i));
2198+ }
2199+
2200+ g_array_set_clear_func(execarray, g_free);
2201+ g_array_free(execarray, FALSE); /* TODO: Not TRUE? */
2202+
2203+ /* See if we need the xmir helper */
2204+ if (findEnv("APP_XMIR_ENABLE", env) == "1" && getenv("DISPLAY") == nullptr)
2205+ {
2206+ retval.emplace(retval.begin(), findEnv("APP_ID", env));
2207+ retval.emplace(retval.begin(), XMIR_HELPER);
2208+ }
2209+
2210+ /* See if we're doing apparmor by hand */
2211+ auto appexecpolicy = findEnv("APP_EXEC_POLICY", env);
2212+ if (!appexecpolicy.empty() && appexecpolicy != "unconfined")
2213+ {
2214+ retval.emplace(retval.begin(), appexecpolicy);
2215+ retval.emplace(retval.begin(), "-p");
2216+ retval.emplace(retval.begin(), "aa-exec");
2217+ }
2218+
2219+ return retval;
2220+}
2221+
2222+/** Small helper that we can new/delete to work better with C stuff */
2223+struct StartCHelper
2224+{
2225+ std::shared_ptr<instance::SystemD> ptr;
2226+ std::shared_ptr<GDBusConnection> bus;
2227+};
2228+
2229+void SystemD::application_start_cb(GObject* obj, GAsyncResult* res, gpointer user_data)
2230+{
2231+ auto data = static_cast<StartCHelper*>(user_data);
2232+ GError* error{nullptr};
2233+ GVariant* result{nullptr};
2234+
2235+ tracepoint(ubuntu_app_launch, libual_start_message_callback, std::string(data->ptr->appId_).c_str());
2236+
2237+ g_debug("Started Message Callback: %s", std::string(data->ptr->appId_).c_str());
2238+
2239+ result = g_dbus_connection_call_finish(G_DBUS_CONNECTION(obj), res, &error);
2240+
2241+ g_clear_pointer(&result, g_variant_unref);
2242+
2243+ if (error != nullptr)
2244+ {
2245+ if (g_dbus_error_is_remote_error(error))
2246+ {
2247+ gchar* remote_error = g_dbus_error_get_remote_error(error);
2248+ g_debug("Remote error: %s", remote_error);
2249+ if (g_strcmp0(remote_error, "org.freedesktop.systemd1.UnitExists") == 0)
2250+ {
2251+ auto urls = instance::SystemD::urlsToStrv(data->ptr->urls_);
2252+ second_exec(data->bus.get(), /* DBus */
2253+ data->ptr->registry_->impl->thread.getCancellable().get(), /* cancellable */
2254+ data->ptr->primaryPid(), /* primary pid */
2255+ std::string(data->ptr->appId_).c_str(), /* appid */
2256+ urls.get()); /* urls */
2257+ }
2258+
2259+ g_free(remote_error);
2260+ }
2261+ else
2262+ {
2263+ g_warning("Unable to emit event to start application: %s", error->message);
2264+ }
2265+ g_error_free(error);
2266+ }
2267+
2268+ delete data;
2269+}
2270+
2271+void SystemD::copyEnv(const std::string& envname, std::list<std::pair<std::string, std::string>>& env)
2272+{
2273+ if (!findEnv(envname, env).empty())
2274+ {
2275+ g_debug("Already a value set for '%s' ignoring", envname.c_str());
2276+ return;
2277+ }
2278+
2279+ auto cvalue = getenv(envname.c_str());
2280+ g_debug("Copying Environment: %s", envname.c_str());
2281+ if (cvalue != nullptr)
2282+ {
2283+ std::string value = getenv(envname.c_str());
2284+ env.emplace_back(std::make_pair(envname, value));
2285+ }
2286+ else
2287+ {
2288+ g_debug("Unable to copy environment '%s'", envname.c_str());
2289+ }
2290+}
2291+
2292+void SystemD::copyEnvByPrefix(const std::string& prefix, std::list<std::pair<std::string, std::string>>& env)
2293+{
2294+ for (unsigned int i = 0; environ[i] != nullptr; i++)
2295+ {
2296+ if (g_str_has_prefix(environ[i], prefix.c_str()))
2297+ {
2298+ std::string envfull = environ[i];
2299+ std::string envname;
2300+ bool seenequal = false;
2301+ std::remove_copy_if(envfull.begin(), envfull.end(), std::back_inserter(envname),
2302+ [&seenequal](const char c) {
2303+ if (c == '=')
2304+ {
2305+ seenequal = true;
2306+ }
2307+ return seenequal;
2308+ });
2309+ copyEnv(envname, env);
2310+ }
2311+ }
2312+}
2313+
2314+std::shared_ptr<Application::Instance> SystemD::launch(
2315+ const AppID& appId,
2316+ const std::string& job,
2317+ const std::string& instance,
2318+ const std::vector<Application::URL>& urls,
2319+ launchMode mode,
2320+ std::function<std::list<std::pair<std::string, std::string>>(void)>& getenv)
2321+{
2322+ if (appId.empty())
2323+ return {};
2324+
2325+ auto registry = registry_.lock();
2326+ return registry->impl->thread.executeOnThread<std::shared_ptr<instance::SystemD>>(
2327+ [&]() -> std::shared_ptr<instance::SystemD> {
2328+ auto manager = std::dynamic_pointer_cast<manager::SystemD>(registry->impl->jobs);
2329+ std::string appIdStr{appId};
2330+ g_debug("Initializing params for an new instance::SystemD for: %s", appIdStr.c_str());
2331+
2332+ tracepoint(ubuntu_app_launch, libual_start, appIdStr.c_str());
2333+
2334+ int timeout = 1;
2335+ if (ubuntu::app_launch::Registry::Impl::isWatchingAppStarting())
2336+ {
2337+ timeout = 0;
2338+ }
2339+
2340+ auto handshake = starting_handshake_start(appIdStr.c_str(), timeout);
2341+ if (handshake == nullptr)
2342+ {
2343+ g_warning("Unable to setup starting handshake");
2344+ }
2345+
2346+ /* Figure out the unit name for the job */
2347+ auto unitname = unitName(SystemD::UnitInfo{appIdStr, job, instance});
2348+
2349+ /* Build up our environment */
2350+ auto env = getenv();
2351+
2352+ env.emplace_back(std::make_pair("APP_ID", appIdStr)); /* Application ID */
2353+ env.emplace_back(std::make_pair("APP_LAUNCHER_PID", std::to_string(getpid()))); /* Who we are, for bugs */
2354+
2355+ copyEnv("DISPLAY", env);
2356+ copyEnvByPrefix("DBUS_", env);
2357+ copyEnvByPrefix("MIR_", env);
2358+ copyEnvByPrefix("QT_", env);
2359+ copyEnvByPrefix("UBUNTU_", env);
2360+ copyEnvByPrefix("UNITY_", env);
2361+ copyEnvByPrefix("XDG_", env);
2362+
2363+ if (!urls.empty())
2364+ {
2365+ auto accumfunc = [](const std::string& prev, Application::URL thisurl) -> std::string {
2366+ gchar* gescaped = g_shell_quote(thisurl.value().c_str());
2367+ std::string escaped;
2368+ if (gescaped != nullptr)
2369+ {
2370+ escaped = gescaped;
2371+ g_free(gescaped);
2372+ }
2373+ else
2374+ {
2375+ g_warning("Unable to escape URL: %s", thisurl.value().c_str());
2376+ return prev;
2377+ }
2378+
2379+ if (prev.empty())
2380+ {
2381+ return escaped;
2382+ }
2383+ else
2384+ {
2385+ return prev + " " + escaped;
2386+ }
2387+ };
2388+ auto urlstring = std::accumulate(urls.begin(), urls.end(), std::string{}, accumfunc);
2389+ env.emplace_back(std::make_pair("APP_URIS", urlstring));
2390+ }
2391+
2392+ if (mode == launchMode::TEST)
2393+ {
2394+ env.emplace_back(std::make_pair("QT_LOAD_TESTABILITY", "1"));
2395+ }
2396+
2397+ /* Convert to GVariant */
2398+ GVariantBuilder builder;
2399+ g_variant_builder_init(&builder, G_VARIANT_TYPE_TUPLE);
2400+
2401+ g_variant_builder_add_value(&builder, g_variant_new_string(unitname.c_str()));
2402+ g_variant_builder_add_value(&builder, g_variant_new_string("replace")); // Job mode
2403+
2404+ /* Parameter Array */
2405+ g_variant_builder_open(&builder, G_VARIANT_TYPE_ARRAY);
2406+
2407+ /* Environment */
2408+ g_variant_builder_open(&builder, G_VARIANT_TYPE_TUPLE);
2409+ g_variant_builder_add_value(&builder, g_variant_new_string("Environment"));
2410+ g_variant_builder_open(&builder, G_VARIANT_TYPE_VARIANT);
2411+ g_variant_builder_open(&builder, G_VARIANT_TYPE_ARRAY);
2412+ for (const auto& envvar : env)
2413+ {
2414+ if (!envvar.first.empty() && !envvar.second.empty())
2415+ {
2416+ g_variant_builder_add_value(&builder, g_variant_new_take_string(g_strdup_printf(
2417+ "%s=%s", envvar.first.c_str(), envvar.second.c_str())));
2418+ // g_debug("Setting environment: %s=%s", envvar.first.c_str(), envvar.second.c_str());
2419+ }
2420+ }
2421+
2422+ g_variant_builder_close(&builder);
2423+ g_variant_builder_close(&builder);
2424+ g_variant_builder_close(&builder);
2425+
2426+ /* ExecStart */
2427+ auto commands = parseExec(env);
2428+ gchar* pathexec{nullptr};
2429+ if (!commands.empty() && ((pathexec = g_find_program_in_path(commands[0].c_str())) != nullptr))
2430+ {
2431+ g_variant_builder_open(&builder, G_VARIANT_TYPE_TUPLE);
2432+ g_variant_builder_add_value(&builder, g_variant_new_string("ExecStart"));
2433+ g_variant_builder_open(&builder, G_VARIANT_TYPE_VARIANT);
2434+ g_variant_builder_open(&builder, G_VARIANT_TYPE_ARRAY);
2435+
2436+ g_variant_builder_open(&builder, G_VARIANT_TYPE_TUPLE);
2437+ g_variant_builder_add_value(&builder, g_variant_new_take_string(pathexec));
2438+
2439+ g_variant_builder_open(&builder, G_VARIANT_TYPE_ARRAY);
2440+ for (auto param : commands)
2441+ {
2442+ g_variant_builder_add_value(&builder, g_variant_new_string(param.c_str()));
2443+ }
2444+ g_variant_builder_close(&builder);
2445+
2446+ g_variant_builder_add_value(&builder, g_variant_new_boolean(FALSE));
2447+
2448+ g_variant_builder_close(&builder);
2449+ g_variant_builder_close(&builder);
2450+ g_variant_builder_close(&builder);
2451+ g_variant_builder_close(&builder);
2452+ }
2453+
2454+ /* RemainAfterExit */
2455+ g_variant_builder_open(&builder, G_VARIANT_TYPE_TUPLE);
2456+ g_variant_builder_add_value(&builder, g_variant_new_string("RemainAfterExit"));
2457+ g_variant_builder_open(&builder, G_VARIANT_TYPE_VARIANT);
2458+ g_variant_builder_add_value(&builder, g_variant_new_boolean(FALSE));
2459+ g_variant_builder_close(&builder);
2460+ g_variant_builder_close(&builder);
2461+
2462+ /* Type */
2463+ g_variant_builder_open(&builder, G_VARIANT_TYPE_TUPLE);
2464+ g_variant_builder_add_value(&builder, g_variant_new_string("Type"));
2465+ g_variant_builder_open(&builder, G_VARIANT_TYPE_VARIANT);
2466+ g_variant_builder_add_value(&builder, g_variant_new_string("oneshot"));
2467+ g_variant_builder_close(&builder);
2468+ g_variant_builder_close(&builder);
2469+
2470+ /* Working Directory */
2471+ if (!findEnv("APP_DIR", env).empty())
2472+ {
2473+ g_variant_builder_open(&builder, G_VARIANT_TYPE_TUPLE);
2474+ g_variant_builder_add_value(&builder, g_variant_new_string("WorkingDirectory"));
2475+ g_variant_builder_open(&builder, G_VARIANT_TYPE_VARIANT);
2476+ g_variant_builder_add_value(&builder, g_variant_new_string(findEnv("APP_DIR", env).c_str()));
2477+ g_variant_builder_close(&builder);
2478+ g_variant_builder_close(&builder);
2479+ }
2480+
2481+ /* Parameter Array */
2482+ g_variant_builder_close(&builder);
2483+
2484+ /* Dependent Units (none) */
2485+ g_variant_builder_add_value(&builder, g_variant_new_array(G_VARIANT_TYPE("(sa(sv))"), nullptr, 0));
2486+
2487+ auto retval = std::make_shared<instance::SystemD>(appId, job, instance, urls, registry);
2488+ auto chelper = new StartCHelper{};
2489+ chelper->ptr = retval;
2490+ chelper->bus = manager->userbus_;
2491+
2492+ tracepoint(ubuntu_app_launch, handshake_wait, appIdStr.c_str());
2493+ starting_handshake_wait(handshake);
2494+ tracepoint(ubuntu_app_launch, handshake_complete, appIdStr.c_str());
2495+
2496+ /* Call the job start function */
2497+ g_debug("Asking systemd to start task for: %s", appIdStr.c_str());
2498+ g_dbus_connection_call(manager->userbus_.get(), /* bus */
2499+ SYSTEMD_DBUS_ADDRESS.c_str(), /* service name */
2500+ SYSTEMD_DBUS_PATH_MANAGER.c_str(), /* Path */
2501+ SYSTEMD_DBUS_IFACE_MANAGER.c_str(), /* interface */
2502+ "StartTransientUnit", /* method */
2503+ g_variant_builder_end(&builder), /* params */
2504+ G_VARIANT_TYPE("(o)"), /* return */
2505+ G_DBUS_CALL_FLAGS_NONE, /* flags */
2506+ -1, /* default timeout */
2507+ registry->impl->thread.getCancellable().get(), /* cancellable */
2508+ application_start_cb, /* callback */
2509+ chelper /* object */
2510+ );
2511+
2512+ tracepoint(ubuntu_app_launch, libual_start_message_sent, appIdStr.c_str());
2513+
2514+ return retval;
2515+ });
2516+}
2517+
2518+std::shared_ptr<Application::Instance> SystemD::existing(const AppID& appId,
2519+ const std::string& job,
2520+ const std::string& instance,
2521+ const std::vector<Application::URL>& urls)
2522+{
2523+ return std::make_shared<instance::SystemD>(appId, job, instance, urls, registry_.lock());
2524+}
2525+
2526+std::vector<std::shared_ptr<instance::Base>> SystemD::instances(const AppID& appID, const std::string& job)
2527+{
2528+ std::vector<std::shared_ptr<instance::Base>> instances;
2529+ auto registry = registry_.lock();
2530+ std::vector<Application::URL> urls;
2531+
2532+ for (const auto& unit : listUnits())
2533+ {
2534+ SystemD::UnitInfo unitinfo;
2535+
2536+ try
2537+ {
2538+ unitinfo = parseUnit(unit.id);
2539+ }
2540+ catch (std::runtime_error& e)
2541+ {
2542+ continue;
2543+ }
2544+
2545+ if (job != unitinfo.job)
2546+ {
2547+ continue;
2548+ }
2549+
2550+ if (std::string(appID) != unitinfo.appid)
2551+ {
2552+ continue;
2553+ }
2554+
2555+ instances.emplace_back(std::make_shared<instance::SystemD>(appID, job, unitinfo.inst, urls, registry));
2556+ }
2557+
2558+ g_debug("Found %d instances for AppID '%s'", int(instances.size()), std::string(appID).c_str());
2559+
2560+ return instances;
2561+}
2562+
2563+std::list<std::shared_ptr<Application>> SystemD::runningApps()
2564+{
2565+ auto allJobs = getAllJobs();
2566+ auto registry = registry_.lock();
2567+ std::set<std::string> appids;
2568+
2569+ for (const auto& unit : listUnits())
2570+ {
2571+ SystemD::UnitInfo unitinfo;
2572+
2573+ try
2574+ {
2575+ unitinfo = parseUnit(unit.id);
2576+ }
2577+ catch (std::runtime_error& e)
2578+ {
2579+ continue;
2580+ }
2581+
2582+ if (allJobs.find(unitinfo.job) == allJobs.end())
2583+ {
2584+ continue;
2585+ }
2586+
2587+ appids.insert(unitinfo.appid);
2588+ }
2589+
2590+ std::list<std::shared_ptr<Application>> apps;
2591+ for (const auto& appid : appids)
2592+ {
2593+ auto id = AppID::find(appid);
2594+ if (id.empty())
2595+ {
2596+ g_debug("Unable to handle AppID: %s", appid.c_str());
2597+ continue;
2598+ }
2599+
2600+ apps.emplace_back(Application::create(id, registry));
2601+ }
2602+
2603+ return apps;
2604+}
2605+
2606+std::string SystemD::userBusPath()
2607+{
2608+ auto cpath = getenv("UBUNTU_APP_LAUNCH_SYSTEMD_PATH");
2609+ if (cpath != nullptr)
2610+ {
2611+ return cpath;
2612+ }
2613+ return std::string{"/run/user/"} + std::to_string(getuid()) + std::string{"/bus"};
2614+}
2615+
2616+std::list<SystemD::UnitEntry> SystemD::listUnits()
2617+{
2618+ auto registry = registry_.lock();
2619+ return registry->impl->thread.executeOnThread<std::list<SystemD::UnitEntry>>([this, registry]() {
2620+ GError* error{nullptr};
2621+ std::list<SystemD::UnitEntry> ret;
2622+
2623+ GVariant* callt = g_dbus_connection_call_sync(userbus_.get(), /* user bus */
2624+ SYSTEMD_DBUS_ADDRESS.c_str(), /* bus name */
2625+ SYSTEMD_DBUS_PATH_MANAGER.c_str(), /* path */
2626+ SYSTEMD_DBUS_IFACE_MANAGER.c_str(), /* interface */
2627+ "ListUnits", /* method */
2628+ nullptr, /* params */
2629+ G_VARIANT_TYPE("(a(ssssssouso))"), /* ret type */
2630+ G_DBUS_CALL_FLAGS_NONE, /* flags */
2631+ -1, /* timeout */
2632+ registry->impl->thread.getCancellable().get(), /* cancellable */
2633+ &error);
2634+
2635+ if (error != nullptr)
2636+ {
2637+ auto message = std::string{"Unable to list SystemD units: "} + error->message;
2638+ g_error_free(error);
2639+ throw std::runtime_error(message);
2640+ }
2641+
2642+ GVariant* call = g_variant_get_child_value(callt, 0);
2643+ g_variant_unref(callt);
2644+
2645+ const gchar* id;
2646+ const gchar* description;
2647+ const gchar* loadState;
2648+ const gchar* activeState;
2649+ const gchar* subState;
2650+ const gchar* following;
2651+ const gchar* path;
2652+ guint32 jobId;
2653+ const gchar* jobType;
2654+ const gchar* jobPath;
2655+ auto iter = g_variant_iter_new(call);
2656+ while (g_variant_iter_loop(iter, "(&s&s&s&s&s&s&ou&s&o)", &id, &description, &loadState, &activeState,
2657+ &subState, &following, &path, &jobId, &jobType, &jobPath))
2658+ {
2659+ ret.emplace_back(SystemD::UnitEntry{id, description, loadState, activeState, subState, following, path,
2660+ jobId, jobType, jobPath});
2661+ }
2662+
2663+ g_variant_iter_free(iter);
2664+ g_variant_unref(call);
2665+
2666+ return ret;
2667+ });
2668+}
2669+
2670+/* TODO: Application job names */
2671+const std::regex unitNaming{
2672+ "^ubuntu\\-app\\-launch\\-(application\\-(?:click|legacy|snap))\\-(.*)\\-([0-9]*)\\.service$"};
2673+
2674+SystemD::UnitInfo SystemD::parseUnit(const std::string& unit)
2675+{
2676+ std::smatch match;
2677+ if (!std::regex_match(unit, match, unitNaming))
2678+ {
2679+ throw std::runtime_error{"Unable to parse unit name: " + unit};
2680+ }
2681+
2682+ return {match[2].str(), match[1].str(), match[3].str()};
2683+}
2684+
2685+std::string SystemD::unitName(const SystemD::UnitInfo& info)
2686+{
2687+ return std::string{"ubuntu-app-launch-"} + info.job + "-" + info.appid + "-" + info.inst + ".service";
2688+}
2689+
2690+/** Function that uses and maintains the cache of the paths for units
2691+ on the systemd dbus connection. If we already have the entry in the
2692+ cache we just return the path and this function is fast. If not we have
2693+ to ask systemd for it and that can take a bit longer.
2694+
2695+ After getting the data we throw a small background task in to clean
2696+ up the cache if it has more than 50 entries. We delete those who
2697+ haven't be used for an hour.
2698+*/
2699+std::string SystemD::unitPath(const std::string& unitName)
2700+{
2701+ auto registry = registry_.lock();
2702+ std::string retval;
2703+
2704+ if (true)
2705+ {
2706+ /* Create a context for the gaurd */
2707+ std::lock_guard<std::mutex> guard(unitPathsMutex_);
2708+ auto iter = std::find_if(unitPaths_.begin(), unitPaths_.end(),
2709+ [&unitName](const SystemD::UnitPath& entry) { return entry.unitName == unitName; });
2710+
2711+ if (iter != unitPaths_.end())
2712+ {
2713+ retval = iter->unitPath;
2714+ iter->timeStamp = std::chrono::system_clock::now();
2715+ }
2716+ }
2717+
2718+ if (retval.empty())
2719+ {
2720+ retval = registry->impl->thread.executeOnThread<std::string>([this, registry, unitName]() {
2721+ std::string path;
2722+ GError* error{nullptr};
2723+ GVariant* call =
2724+ g_dbus_connection_call_sync(userbus_.get(), /* user bus */
2725+ SYSTEMD_DBUS_ADDRESS.c_str(), /* bus name */
2726+ SYSTEMD_DBUS_PATH_MANAGER.c_str(), /* path */
2727+ SYSTEMD_DBUS_IFACE_MANAGER.c_str(), /* interface */
2728+ "GetUnit", /* method */
2729+ g_variant_new("(s)", unitName.c_str()), /* params */
2730+ G_VARIANT_TYPE("(o)"), /* ret type */
2731+ G_DBUS_CALL_FLAGS_NONE, /* flags */
2732+ -1, /* timeout */
2733+ registry->impl->thread.getCancellable().get(), /* cancellable */
2734+ &error);
2735+
2736+ if (error != nullptr)
2737+ {
2738+ auto message = std::string{"Unable to get SystemD unit path for '"} + unitName + std::string{"': "} +
2739+ error->message;
2740+ g_error_free(error);
2741+ throw std::runtime_error(message);
2742+ }
2743+
2744+ /* Parse variant */
2745+ gchar* gpath = nullptr;
2746+ g_variant_get(call, "(o)", &gpath);
2747+ if (gpath != nullptr)
2748+ {
2749+ std::lock_guard<std::mutex> guard(unitPathsMutex_);
2750+ path = gpath;
2751+ unitPaths_.emplace_back(SystemD::UnitPath{unitName, path, std::chrono::system_clock::now()});
2752+ }
2753+
2754+ g_variant_unref(call);
2755+
2756+ return path;
2757+ });
2758+ }
2759+
2760+ /* Queue a possible cleanup */
2761+ if (unitPaths_.size() > 50)
2762+ {
2763+ /* TODO: We should look at UnitRemoved as well */
2764+ /* TODO: Add to cache on UnitNew */
2765+ registry->impl->thread.executeOnThread([this] {
2766+ std::lock_guard<std::mutex> guard(unitPathsMutex_);
2767+ std::remove_if(unitPaths_.begin(), unitPaths_.end(), [](const SystemD::UnitPath& entry) -> bool {
2768+ auto age = std::chrono::system_clock::now() - entry.timeStamp;
2769+ return age > std::chrono::hours{1};
2770+ });
2771+ });
2772+ }
2773+
2774+ return retval;
2775+}
2776+
2777+pid_t SystemD::unitPrimaryPid(const AppID& appId, const std::string& job, const std::string& instance)
2778+{
2779+ auto registry = registry_.lock();
2780+ auto unitname = unitName(SystemD::UnitInfo{appId, job, instance});
2781+ auto unitpath = unitPath(unitname);
2782+
2783+ return registry->impl->thread.executeOnThread<pid_t>([this, registry, unitname, unitpath]() {
2784+ GError* error{nullptr};
2785+ GVariant* call = g_dbus_connection_call_sync(
2786+ userbus_.get(), /* user bus */
2787+ SYSTEMD_DBUS_ADDRESS.c_str(), /* bus name */
2788+ unitpath.c_str(), /* path */
2789+ "org.freedesktop.DBus.Properties", /* interface */
2790+ "Get", /* method */
2791+ g_variant_new("(ss)", SYSTEMD_DBUS_IFACE_SERVICE.c_str(), "MainPID"), /* params */
2792+ G_VARIANT_TYPE("(v)"), /* ret type */
2793+ G_DBUS_CALL_FLAGS_NONE, /* flags */
2794+ -1, /* timeout */
2795+ registry->impl->thread.getCancellable().get(), /* cancellable */
2796+ &error);
2797+
2798+ if (error != nullptr)
2799+ {
2800+ auto message =
2801+ std::string{"Unable to get SystemD PID for '"} + unitname + std::string{"': "} + error->message;
2802+ g_error_free(error);
2803+ throw std::runtime_error(message);
2804+ }
2805+
2806+ /* Parse variant */
2807+ GVariant* vpid{nullptr};
2808+ g_variant_get(call, "(v)", &vpid);
2809+ g_variant_unref(call);
2810+
2811+ pid_t pid;
2812+ pid = g_variant_get_uint32(vpid);
2813+ g_variant_unref(vpid);
2814+
2815+ return pid;
2816+ });
2817+}
2818+
2819+std::vector<pid_t> SystemD::unitPids(const AppID& appId, const std::string& job, const std::string& instance)
2820+{
2821+ auto registry = registry_.lock();
2822+ auto unitname = unitName(SystemD::UnitInfo{appId, job, instance});
2823+ auto unitpath = unitPath(unitname);
2824+
2825+ auto cgrouppath = registry->impl->thread.executeOnThread<std::string>([this, registry, unitname, unitpath]() {
2826+ GError* error{nullptr};
2827+ GVariant* call = g_dbus_connection_call_sync(
2828+ userbus_.get(), /* user bus */
2829+ SYSTEMD_DBUS_ADDRESS.c_str(), /* bus name */
2830+ unitpath.c_str(), /* path */
2831+ "org.freedesktop.DBus.Properties", /* interface */
2832+ "Get", /* method */
2833+ g_variant_new("(ss)", SYSTEMD_DBUS_IFACE_SERVICE.c_str(), "ControlGroup"), /* params */
2834+ G_VARIANT_TYPE("(v)"), /* ret type */
2835+ G_DBUS_CALL_FLAGS_NONE, /* flags */
2836+ -1, /* timeout */
2837+ registry->impl->thread.getCancellable().get(), /* cancellable */
2838+ &error);
2839+
2840+ if (error != nullptr)
2841+ {
2842+ auto message = std::string{"Unable to get SystemD Control Group for '"} + unitname + std::string{"': "} +
2843+ error->message;
2844+ g_error_free(error);
2845+ throw std::runtime_error(message);
2846+ }
2847+
2848+ /* Parse variant */
2849+ GVariant* vstring = nullptr;
2850+ g_variant_get(call, "(v)", &vstring);
2851+ g_variant_unref(call);
2852+
2853+ if (vstring == nullptr)
2854+ {
2855+ return std::string{};
2856+ }
2857+
2858+ std::string group;
2859+ auto ggroup = g_variant_get_string(vstring, nullptr);
2860+ if (ggroup != nullptr)
2861+ {
2862+ group = ggroup;
2863+ }
2864+ g_variant_unref(vstring);
2865+
2866+ return group;
2867+ });
2868+
2869+ gchar* fullpath = g_build_filename("/sys", "fs", "cgroup", "systemd", cgrouppath.c_str(), "tasks", nullptr);
2870+ gchar* pidstr = nullptr;
2871+ GError* error = nullptr;
2872+
2873+ g_debug("Getting PIDs from %s", fullpath);
2874+ g_file_get_contents(fullpath, &pidstr, nullptr, &error);
2875+ g_free(fullpath);
2876+
2877+ if (error != nullptr)
2878+ {
2879+ auto message = std::string{"Unable to read cgroup PID list: "} + error->message;
2880+ g_error_free(error);
2881+ throw std::runtime_error(message);
2882+ }
2883+
2884+ gchar** pidlines = g_strsplit(pidstr, "\n", -1);
2885+ g_free(pidstr);
2886+ std::vector<pid_t> pids;
2887+
2888+ for (auto i = 0; pidlines[i] != nullptr; i++)
2889+ {
2890+ const gchar* pidline = pidlines[i];
2891+ if (pidline[0] != '\n')
2892+ {
2893+ auto pid = std::atoi(pidline);
2894+ if (pid != 0)
2895+ {
2896+ pids.emplace_back(pid);
2897+ }
2898+ }
2899+ }
2900+
2901+ g_strfreev(pidlines);
2902+
2903+ return pids;
2904+}
2905+
2906+void SystemD::stopUnit(const AppID& appId, const std::string& job, const std::string& instance)
2907+{
2908+ auto registry = registry_.lock();
2909+ auto unitname = unitName(SystemD::UnitInfo{appId, job, instance});
2910+
2911+ registry->impl->thread.executeOnThread<bool>([this, registry, unitname] {
2912+ GError* error{nullptr};
2913+ GVariant* call = g_dbus_connection_call_sync(userbus_.get(), /* user bus */
2914+ SYSTEMD_DBUS_ADDRESS.c_str(), /* bus name */
2915+ SYSTEMD_DBUS_PATH_MANAGER.c_str(), /* path */
2916+ SYSTEMD_DBUS_IFACE_MANAGER.c_str(), /* interface */
2917+ "StopUnit", /* method */
2918+ g_variant_new("(ss)", unitname.c_str(), "fail"), /* params */
2919+ G_VARIANT_TYPE("(o)"), /* ret type */
2920+ G_DBUS_CALL_FLAGS_NONE, /* flags */
2921+ -1, /* timeout */
2922+ registry->impl->thread.getCancellable().get(), /* cancellable */
2923+ &error);
2924+
2925+ if (error != nullptr)
2926+ {
2927+ auto message =
2928+ std::string{"Unable to get SystemD to stop '"} + unitname + std::string{"': "} + error->message;
2929+ g_error_free(error);
2930+ throw std::runtime_error(message);
2931+ }
2932+
2933+ g_variant_unref(call);
2934+
2935+ return true;
2936+ });
2937+}
2938+
2939+} // namespace manager
2940+} // namespace jobs
2941+} // namespace app_launch
2942+} // namespace ubuntu
2943
2944=== added file 'libubuntu-app-launch/jobs-systemd.h'
2945--- libubuntu-app-launch/jobs-systemd.h 1970-01-01 00:00:00 +0000
2946+++ libubuntu-app-launch/jobs-systemd.h 2016-11-01 14:53:56 +0000
2947@@ -0,0 +1,114 @@
2948+/*
2949+ * Copyright © 2016 Canonical Ltd.
2950+ *
2951+ * This program is free software: you can redistribute it and/or modify it
2952+ * under the terms of the GNU General Public License version 3, as published
2953+ * by the Free Software Foundation.
2954+ *
2955+ * This program is distributed in the hope that it will be useful, but
2956+ * WITHOUT ANY WARRANTY; without even the implied warranties of
2957+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
2958+ * PURPOSE. See the GNU General Public License for more details.
2959+ *
2960+ * You should have received a copy of the GNU General Public License along
2961+ * with this program. If not, see <http://www.gnu.org/licenses/>.
2962+ *
2963+ * Authors:
2964+ * Ted Gould <ted.gould@canonical.com>
2965+ */
2966+
2967+#pragma once
2968+
2969+#include "jobs-base.h"
2970+#include <chrono>
2971+#include <gio/gio.h>
2972+#include <map>
2973+#include <mutex>
2974+
2975+namespace ubuntu
2976+{
2977+namespace app_launch
2978+{
2979+namespace jobs
2980+{
2981+namespace manager
2982+{
2983+
2984+class SystemD : public Base
2985+{
2986+public:
2987+ SystemD(std::shared_ptr<Registry> registry);
2988+ virtual ~SystemD();
2989+
2990+ virtual std::shared_ptr<Application::Instance> launch(
2991+ const AppID& appId,
2992+ const std::string& job,
2993+ const std::string& instance,
2994+ const std::vector<Application::URL>& urls,
2995+ launchMode mode,
2996+ std::function<std::list<std::pair<std::string, std::string>>(void)>& getenv) override;
2997+ virtual std::shared_ptr<Application::Instance> existing(const AppID& appId,
2998+ const std::string& job,
2999+ const std::string& instance,
3000+ const std::vector<Application::URL>& urls) override;
3001+
3002+ virtual std::list<std::shared_ptr<Application>> runningApps() override;
3003+
3004+ virtual std::vector<std::shared_ptr<instance::Base>> instances(const AppID& appID, const std::string& job) override;
3005+
3006+ static std::string userBusPath();
3007+
3008+ pid_t unitPrimaryPid(const AppID& appId, const std::string& job, const std::string& instance);
3009+ std::vector<pid_t> unitPids(const AppID& appId, const std::string& job, const std::string& instance);
3010+ void stopUnit(const AppID& appId, const std::string& job, const std::string& instance);
3011+
3012+private:
3013+ std::shared_ptr<GDBusConnection> userbus_;
3014+
3015+ /* ssssssouso */
3016+ struct UnitEntry
3017+ {
3018+ std::string id;
3019+ std::string description;
3020+ std::string loadState;
3021+ std::string activeState;
3022+ std::string subState;
3023+ std::string following;
3024+ std::string path;
3025+ std::uint32_t jobId;
3026+ std::string jobType;
3027+ std::string jobPath;
3028+ };
3029+ std::list<UnitEntry> listUnits();
3030+
3031+ struct UnitInfo
3032+ {
3033+ std::string appid;
3034+ std::string job;
3035+ std::string inst;
3036+ };
3037+ UnitInfo parseUnit(const std::string& unit);
3038+ std::string unitName(const UnitInfo& info);
3039+
3040+ struct UnitPath
3041+ {
3042+ std::string unitName;
3043+ std::string unitPath;
3044+ std::chrono::time_point<std::chrono::system_clock> timeStamp;
3045+ };
3046+ std::list<UnitPath> unitPaths_;
3047+ std::mutex unitPathsMutex_;
3048+ std::string unitPath(const std::string& unitName);
3049+
3050+ static std::string findEnv(const std::string& value, std::list<std::pair<std::string, std::string>>& env);
3051+ static void copyEnv(const std::string& envname, std::list<std::pair<std::string, std::string>>& env);
3052+ static void copyEnvByPrefix(const std::string& prefix, std::list<std::pair<std::string, std::string>>& env);
3053+
3054+ static std::vector<std::string> parseExec(std::list<std::pair<std::string, std::string>>& env);
3055+ static void application_start_cb(GObject* obj, GAsyncResult* res, gpointer user_data);
3056+};
3057+
3058+} // namespace manager
3059+} // namespace jobs
3060+} // namespace app_launch
3061+} // namespace ubuntu
3062
3063=== added file 'libubuntu-app-launch/jobs-upstart.cpp'
3064--- libubuntu-app-launch/jobs-upstart.cpp 1970-01-01 00:00:00 +0000
3065+++ libubuntu-app-launch/jobs-upstart.cpp 2016-11-01 14:53:56 +0000
3066@@ -0,0 +1,874 @@
3067+/*
3068+ * Copyright © 2016 Canonical Ltd.
3069+ *
3070+ * This program is free software: you can redistribute it and/or modify it
3071+ * under the terms of the GNU General Public License version 3, as published
3072+ * by the Free Software Foundation.
3073+ *
3074+ * This program is distributed in the hope that it will be useful, but
3075+ * WITHOUT ANY WARRANTY; without even the implied warranties of
3076+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
3077+ * PURPOSE. See the GNU General Public License for more details.
3078+ *
3079+ * You should have received a copy of the GNU General Public License along
3080+ * with this program. If not, see <http://www.gnu.org/licenses/>.
3081+ *
3082+ * Authors:
3083+ * Ted Gould <ted.gould@canonical.com>
3084+ */
3085+
3086+#include <algorithm>
3087+#include <cerrno>
3088+#include <cstring>
3089+#include <map>
3090+#include <numeric>
3091+#include <regex>
3092+
3093+#include <cgmanager/cgmanager.h>
3094+#include <upstart.h>
3095+
3096+#include "helpers.h"
3097+#include "registry-impl.h"
3098+#include "second-exec-core.h"
3099+
3100+extern "C" {
3101+#include "ubuntu-app-launch-trace.h"
3102+}
3103+
3104+#include "jobs-upstart.h"
3105+
3106+namespace ubuntu
3107+{
3108+namespace app_launch
3109+{
3110+namespace jobs
3111+{
3112+namespace instance
3113+{
3114+
3115+/** An object that represents an instance of a job on Upstart. This
3116+ then implements everything needed by the instance interface. Most
3117+ applications tie into this today and use it as the backend for
3118+ their instances. */
3119+class Upstart : public Base
3120+{
3121+public:
3122+ explicit Upstart(const AppID& appId,
3123+ const std::string& job,
3124+ const std::string& instance,
3125+ const std::vector<Application::URL>& urls,
3126+ const std::shared_ptr<Registry>& registry);
3127+
3128+ /* Query lifecycle */
3129+ pid_t primaryPid() override;
3130+ std::string logPath() override;
3131+ std::vector<pid_t> pids() override;
3132+
3133+ /* Manage lifecycle */
3134+ void stop() override;
3135+
3136+ /* C Callback */
3137+ static void application_start_cb(GObject* obj, GAsyncResult* res, gpointer user_data);
3138+
3139+private:
3140+ std::string upstartJobPath(const std::string& job);
3141+ std::string upstartName();
3142+};
3143+
3144+/** Uses Upstart to get the primary PID of the instance using Upstart's
3145+ DBus interface */
3146+pid_t Upstart::primaryPid()
3147+{
3148+ auto jobpath = upstartJobPath(job_);
3149+ if (jobpath.empty())
3150+ {
3151+ g_debug("Unable to get a valid job path");
3152+ return 0;
3153+ }
3154+
3155+ return registry_->impl->thread.executeOnThread<pid_t>([this, &jobpath]() -> pid_t {
3156+ GError* error = nullptr;
3157+
3158+ std::string instancename = std::string(appId_);
3159+ if (job_ != "application-click")
3160+ {
3161+ instancename += "-" + instance_;
3162+ }
3163+
3164+ g_debug("Getting instance by name: %s", instance_.c_str());
3165+ GVariant* vinstance_path =
3166+ g_dbus_connection_call_sync(registry_->impl->_dbus.get(), /* connection */
3167+ DBUS_SERVICE_UPSTART, /* service */
3168+ jobpath.c_str(), /* object path */
3169+ DBUS_INTERFACE_UPSTART_JOB, /* iface */
3170+ "GetInstanceByName", /* method */
3171+ g_variant_new("(s)", instancename.c_str()), /* params */
3172+ G_VARIANT_TYPE("(o)"), /* return type */
3173+ G_DBUS_CALL_FLAGS_NONE, /* flags */
3174+ -1, /* timeout: default */
3175+ registry_->impl->thread.getCancellable().get(), /* cancellable */
3176+ &error);
3177+
3178+ if (error != nullptr)
3179+ {
3180+ g_warning("Unable to get instance '%s' of job '%s': %s", instance_.c_str(), job_.c_str(), error->message);
3181+ g_error_free(error);
3182+ return 0;
3183+ }
3184+
3185+ /* Jump rope to make this into a C++ type */
3186+ std::string instance_path;
3187+ gchar* cinstance_path = nullptr;
3188+ g_variant_get(vinstance_path, "(o)", &cinstance_path);
3189+ g_variant_unref(vinstance_path);
3190+ if (cinstance_path != nullptr)
3191+ {
3192+ instance_path = cinstance_path;
3193+ g_free(cinstance_path);
3194+ }
3195+
3196+ if (instance_path.empty())
3197+ {
3198+ g_debug("No instance object for instance name: %s", instance_.c_str());
3199+ return 0;
3200+ }
3201+
3202+ GVariant* props_tuple =
3203+ g_dbus_connection_call_sync(registry_->impl->_dbus.get(), /* connection */
3204+ DBUS_SERVICE_UPSTART, /* service */
3205+ instance_path.c_str(), /* object path */
3206+ "org.freedesktop.DBus.Properties", /* interface */
3207+ "GetAll", /* method */
3208+ g_variant_new("(s)", DBUS_INTERFACE_UPSTART_INSTANCE), /* params */
3209+ G_VARIANT_TYPE("(a{sv})"), /* return type */
3210+ G_DBUS_CALL_FLAGS_NONE, /* flags */
3211+ -1, /* timeout: default */
3212+ registry_->impl->thread.getCancellable().get(), /* cancellable */
3213+ &error);
3214+
3215+ if (error != nullptr)
3216+ {
3217+ g_warning("Unable to name of properties '%s': %s", instance_path.c_str(), error->message);
3218+ g_error_free(error);
3219+ error = nullptr;
3220+ return 0;
3221+ }
3222+
3223+ GVariant* props_dict = g_variant_get_child_value(props_tuple, 0);
3224+
3225+ pid_t retval = 0;
3226+ GVariant* processes = g_variant_lookup_value(props_dict, "processes", G_VARIANT_TYPE("a(si)"));
3227+ if (processes != nullptr && g_variant_n_children(processes) > 0)
3228+ {
3229+
3230+ GVariant* first_entry = g_variant_get_child_value(processes, 0);
3231+ GVariant* pidv = g_variant_get_child_value(first_entry, 1);
3232+
3233+ retval = g_variant_get_int32(pidv);
3234+
3235+ g_variant_unref(pidv);
3236+ g_variant_unref(first_entry);
3237+ }
3238+ else
3239+ {
3240+ g_debug("Unable to get 'processes' from properties of instance at path: %s", instance_path.c_str());
3241+ }
3242+
3243+ g_variant_unref(props_dict);
3244+
3245+ return retval;
3246+ });
3247+}
3248+
3249+/** Generate the full name of the Upstart job for the job, the
3250+ instance and how all those fit together.
3251+
3252+ Handles the special case of application-click which isn't designed
3253+ to have multi-instance apps.
3254+*/
3255+std::string Upstart::upstartName()
3256+{
3257+ std::string path = job_ + "-" + std::string(appId_);
3258+ if (job_ != "application-click")
3259+ {
3260+ path += "-";
3261+ }
3262+ if (!instance_.empty())
3263+ {
3264+ path += instance_;
3265+ }
3266+
3267+ return path;
3268+}
3269+
3270+/** Gets the path to the log file for this instance */
3271+std::string Upstart::logPath()
3272+{
3273+ std::string logfile = upstartName() + ".log";
3274+
3275+ gchar* cpath = g_build_filename(g_get_user_cache_dir(), "upstart", logfile.c_str(), nullptr);
3276+ std::string path(cpath);
3277+ g_free(cpath);
3278+
3279+ return path;
3280+}
3281+
3282+/** Returns all the PIDs that are in the cgroup for this application */
3283+std::vector<pid_t> Upstart::pids()
3284+{
3285+ auto manager = std::dynamic_pointer_cast<manager::Upstart>(registry_->impl->jobs);
3286+ auto pids = manager->pidsFromCgroup(upstartName());
3287+ g_debug("Got %d PIDs for AppID '%s'", int(pids.size()), std::string(appId_).c_str());
3288+ return pids;
3289+}
3290+
3291+/** Stops this instance by asking Upstart to stop it. Upstart will then
3292+ send a SIGTERM and five seconds later start killing things. */
3293+void Upstart::stop()
3294+{
3295+ if (!registry_->impl->thread.executeOnThread<bool>([this]() {
3296+ auto manager = std::dynamic_pointer_cast<manager::Upstart>(registry_->impl->jobs);
3297+
3298+ g_debug("Stopping job %s app_id %s instance_id %s", job_.c_str(), std::string(appId_).c_str(),
3299+ instance_.c_str());
3300+
3301+ auto jobpath = manager->upstartJobPath(job_);
3302+ if (jobpath.empty())
3303+ {
3304+ throw new std::runtime_error("Unable to get job path for Upstart job '" + job_ + "'");
3305+ }
3306+
3307+ GVariantBuilder builder;
3308+ g_variant_builder_init(&builder, G_VARIANT_TYPE_TUPLE);
3309+ g_variant_builder_open(&builder, G_VARIANT_TYPE_ARRAY);
3310+
3311+ g_variant_builder_add_value(
3312+ &builder, g_variant_new_take_string(g_strdup_printf("APP_ID=%s", std::string(appId_).c_str())));
3313+
3314+ if (!instance_.empty())
3315+ {
3316+ g_variant_builder_add_value(
3317+ &builder, g_variant_new_take_string(g_strdup_printf("INSTANCE_ID=%s", instance_.c_str())));
3318+ }
3319+
3320+ g_variant_builder_close(&builder);
3321+ g_variant_builder_add_value(&builder, g_variant_new_boolean(FALSE)); /* wait */
3322+
3323+ GError* error = nullptr;
3324+ GVariant* stop_variant =
3325+ g_dbus_connection_call_sync(registry_->impl->_dbus.get(), /* Dbus */
3326+ DBUS_SERVICE_UPSTART, /* Upstart name */
3327+ jobpath.c_str(), /* path */
3328+ DBUS_INTERFACE_UPSTART_JOB, /* interface */
3329+ "Stop", /* method */
3330+ g_variant_builder_end(&builder), /* params */
3331+ nullptr, /* return */
3332+ G_DBUS_CALL_FLAGS_NONE, /* flags */
3333+ -1, /* timeout: default */
3334+ registry_->impl->thread.getCancellable().get(), /* cancellable */
3335+ &error); /* error (hopefully not) */
3336+
3337+ g_clear_pointer(&stop_variant, g_variant_unref);
3338+
3339+ if (error != nullptr)
3340+ {
3341+ g_warning("Unable to stop job %s app_id %s instance_id %s: %s", job_.c_str(),
3342+ std::string(appId_).c_str(), instance_.c_str(), error->message);
3343+ g_error_free(error);
3344+ return false;
3345+ }
3346+
3347+ return true;
3348+ }))
3349+ {
3350+ g_warning("Unable to stop Upstart instance");
3351+ }
3352+}
3353+
3354+/** Create a new Upstart Instance object that can track the job and
3355+ get information about it.
3356+
3357+ \param appId Application ID
3358+ \param job Upstart job name
3359+ \param instance Upstart instance name
3360+ \param urls URLs sent to the application (only on launch today)
3361+ \param registry Registry of persistent connections to use
3362+*/
3363+Upstart::Upstart(const AppID& appId,
3364+ const std::string& job,
3365+ const std::string& instance,
3366+ const std::vector<Application::URL>& urls,
3367+ const std::shared_ptr<Registry>& registry)
3368+ : Base(appId, job, instance, urls, registry)
3369+{
3370+ g_debug("Creating a new Upstart for '%s' instance '%s'", std::string(appId).c_str(), instance.c_str());
3371+}
3372+
3373+/** Small helper that we can new/delete to work better with C stuff */
3374+struct StartCHelper
3375+{
3376+ std::shared_ptr<Upstart> ptr;
3377+};
3378+
3379+/** Callback from starting an application. It checks to see whether the
3380+ app is already running. If it is already running then we need to send
3381+ the URLs to it via DBus.
3382+
3383+ \param obj The GDBusConnection object
3384+ \param res Async result object
3385+ \param user_data A pointer to a StartCHelper structure
3386+*/
3387+void Upstart::application_start_cb(GObject* obj, GAsyncResult* res, gpointer user_data)
3388+{
3389+ auto data = static_cast<StartCHelper*>(user_data);
3390+ GError* error{nullptr};
3391+ GVariant* result{nullptr};
3392+
3393+ tracepoint(ubuntu_app_launch, libual_start_message_callback, std::string(data->ptr->appId_).c_str());
3394+
3395+ g_debug("Started Message Callback: %s", std::string(data->ptr->appId_).c_str());
3396+
3397+ result = g_dbus_connection_call_finish(G_DBUS_CONNECTION(obj), res, &error);
3398+
3399+ g_clear_pointer(&result, g_variant_unref);
3400+
3401+ if (error != nullptr)
3402+ {
3403+ if (g_dbus_error_is_remote_error(error))
3404+ {
3405+ gchar* remote_error = g_dbus_error_get_remote_error(error);
3406+ g_debug("Remote error: %s", remote_error);
3407+ if (g_strcmp0(remote_error, "com.ubuntu.Upstart0_6.Error.AlreadyStarted") == 0)
3408+ {
3409+ auto urls = urlsToStrv(data->ptr->urls_);
3410+ second_exec(data->ptr->registry_->impl->_dbus.get(), /* DBus */
3411+ data->ptr->registry_->impl->thread.getCancellable().get(), /* cancellable */
3412+ data->ptr->primaryPid(), /* primary pid */
3413+ std::string(data->ptr->appId_).c_str(), /* appid */
3414+ urls.get()); /* urls */
3415+ }
3416+
3417+ g_free(remote_error);
3418+ }
3419+ else
3420+ {
3421+ g_warning("Unable to emit event to start application: %s", error->message);
3422+ }
3423+ g_error_free(error);
3424+ }
3425+
3426+ delete data;
3427+}
3428+
3429+std::string Upstart::upstartJobPath(const std::string& job)
3430+{
3431+ auto manager = std::dynamic_pointer_cast<manager::Upstart>(registry_->impl->jobs);
3432+ return manager->upstartJobPath(job);
3433+}
3434+
3435+} // namespace instances
3436+
3437+namespace manager
3438+{
3439+
3440+Upstart::Upstart(std::shared_ptr<Registry> registry)
3441+ : Base(registry)
3442+{
3443+}
3444+
3445+Upstart::~Upstart()
3446+{
3447+}
3448+
3449+/** Launch an application and create a new Upstart instance object to track
3450+ its progress.
3451+
3452+ \param appId Application ID
3453+ \param job Upstart job name
3454+ \param instance Upstart instance name
3455+ \param urls URLs sent to the application (only on launch today)
3456+ \param mode Whether or not to setup the environment for testing
3457+ \param getenv A function to get additional environment variable when appropriate
3458+*/
3459+std::shared_ptr<Application::Instance> Upstart::launch(
3460+ const AppID& appId,
3461+ const std::string& job,
3462+ const std::string& instance,
3463+ const std::vector<Application::URL>& urls,
3464+ launchMode mode,
3465+ std::function<std::list<std::pair<std::string, std::string>>(void)>& getenv)
3466+{
3467+ if (appId.empty())
3468+ return {};
3469+
3470+ auto registry = registry_.lock();
3471+ return registry->impl->thread.executeOnThread<std::shared_ptr<instance::Upstart>>(
3472+ [&]() -> std::shared_ptr<instance::Upstart> {
3473+ auto manager = std::dynamic_pointer_cast<manager::Upstart>(registry->impl->jobs);
3474+ std::string appIdStr{appId};
3475+ g_debug("Initializing params for an new instance::Upstart for: %s", appIdStr.c_str());
3476+
3477+ tracepoint(ubuntu_app_launch, libual_start, appIdStr.c_str());
3478+
3479+ int timeout = 1;
3480+ if (ubuntu::app_launch::Registry::Impl::isWatchingAppStarting())
3481+ {
3482+ timeout = 0;
3483+ }
3484+
3485+ auto handshake = starting_handshake_start(appIdStr.c_str(), timeout);
3486+ if (handshake == nullptr)
3487+ {
3488+ g_warning("Unable to setup starting handshake");
3489+ }
3490+
3491+ /* Figure out the DBus path for the job */
3492+ auto jobpath = manager->upstartJobPath(job);
3493+
3494+ /* Build up our environment */
3495+ auto env = getenv();
3496+
3497+ env.emplace_back(std::make_pair("APP_ID", appIdStr)); /* Application ID */
3498+ env.emplace_back(std::make_pair("APP_LAUNCHER_PID", std::to_string(getpid()))); /* Who we are, for bugs */
3499+
3500+ if (!urls.empty())
3501+ {
3502+ auto accumfunc = [](const std::string& prev, Application::URL thisurl) -> std::string {
3503+ gchar* gescaped = g_shell_quote(thisurl.value().c_str());
3504+ std::string escaped;
3505+ if (gescaped != nullptr)
3506+ {
3507+ escaped = gescaped;
3508+ g_free(gescaped);
3509+ }
3510+ else
3511+ {
3512+ g_warning("Unable to escape URL: %s", thisurl.value().c_str());
3513+ return prev;
3514+ }
3515+
3516+ if (prev.empty())
3517+ {
3518+ return escaped;
3519+ }
3520+ else
3521+ {
3522+ return prev + " " + escaped;
3523+ }
3524+ };
3525+ auto urlstring = std::accumulate(urls.begin(), urls.end(), std::string{}, accumfunc);
3526+ env.emplace_back(std::make_pair("APP_URIS", urlstring));
3527+ }
3528+
3529+ if (mode == launchMode::TEST)
3530+ {
3531+ env.emplace_back(std::make_pair("QT_LOAD_TESTABILITY", "1"));
3532+ }
3533+
3534+ /* Convert to GVariant */
3535+ GVariantBuilder builder;
3536+ g_variant_builder_init(&builder, G_VARIANT_TYPE_TUPLE);
3537+
3538+ g_variant_builder_open(&builder, G_VARIANT_TYPE_ARRAY);
3539+
3540+ for (const auto& envvar : env)
3541+ {
3542+ g_variant_builder_add_value(&builder, g_variant_new_take_string(g_strdup_printf(
3543+ "%s=%s", envvar.first.c_str(), envvar.second.c_str())));
3544+ }
3545+
3546+ g_variant_builder_close(&builder);
3547+ g_variant_builder_add_value(&builder, g_variant_new_boolean(TRUE));
3548+
3549+ auto retval = std::make_shared<instance::Upstart>(appId, job, instance, urls, registry);
3550+ auto chelper = new instance::StartCHelper{};
3551+ chelper->ptr = retval;
3552+
3553+ tracepoint(ubuntu_app_launch, handshake_wait, appIdStr.c_str());
3554+ starting_handshake_wait(handshake);
3555+ tracepoint(ubuntu_app_launch, handshake_complete, appIdStr.c_str());
3556+
3557+ /* Call the job start function */
3558+ g_debug("Asking Upstart to start task for: %s", appIdStr.c_str());
3559+ g_dbus_connection_call(registry->impl->_dbus.get(), /* bus */
3560+ DBUS_SERVICE_UPSTART, /* service name */
3561+ jobpath.c_str(), /* Path */
3562+ DBUS_INTERFACE_UPSTART_JOB, /* interface */
3563+ "Start", /* method */
3564+ g_variant_builder_end(&builder), /* params */
3565+ nullptr, /* return */
3566+ G_DBUS_CALL_FLAGS_NONE, /* flags */
3567+ -1, /* default timeout */
3568+ registry->impl->thread.getCancellable().get(), /* cancellable */
3569+ instance::Upstart::application_start_cb, /* callback */
3570+ chelper /* object */
3571+ );
3572+
3573+ tracepoint(ubuntu_app_launch, libual_start_message_sent, appIdStr.c_str());
3574+
3575+ return retval;
3576+ });
3577+}
3578+
3579+/** Special characters that could be an application name that
3580+ would activate in a regex */
3581+const static std::regex regexCharacters("([\\.\\-])");
3582+
3583+std::shared_ptr<Application::Instance> Upstart::existing(const AppID& appId,
3584+ const std::string& job,
3585+ const std::string& instance,
3586+ const std::vector<Application::URL>& urls)
3587+{
3588+ return std::make_shared<instance::Upstart>(appId, job, instance, urls, registry_.lock());
3589+}
3590+
3591+std::vector<std::shared_ptr<instance::Base>> Upstart::instances(const AppID& appID, const std::string& job)
3592+{
3593+ std::vector<std::shared_ptr<instance::Base>> vect;
3594+ auto startsWith = std::string(appID);
3595+ if (job != "application-click")
3596+ {
3597+ startsWith += "-";
3598+ }
3599+
3600+ auto regexstr =
3601+ std::string{"^(?:"} + std::regex_replace(startsWith, regexCharacters, "\\$&") + std::string{")(\\d*)$"};
3602+ auto instanceRegex = std::regex(regexstr);
3603+
3604+ for (auto instance : upstartInstancesForJob(job))
3605+ {
3606+ std::smatch instanceMatch;
3607+ g_debug("Looking at job '%s' instance: %s", job.c_str(), instance.c_str());
3608+ if (std::regex_match(instance, instanceMatch, instanceRegex))
3609+ {
3610+ auto app = existing(appID, job, instanceMatch[1].str(), {});
3611+ vect.emplace_back(std::dynamic_pointer_cast<instance::Base>(app));
3612+ }
3613+ }
3614+
3615+ g_debug("App '%s' has %d instances", std::string(appID).c_str(), int(vect.size()));
3616+
3617+ return vect;
3618+}
3619+
3620+/** Initialize the CGManager connection, including a timeout to disconnect
3621+ as CGManager doesn't free resources entirely well. So it's better if
3622+ we connect and disconnect occationally */
3623+void Upstart::initCGManager()
3624+{
3625+ if (cgManager_)
3626+ return;
3627+
3628+ std::promise<std::shared_ptr<GDBusConnection>> promise;
3629+ auto future = promise.get_future();
3630+ auto registry = registry_.lock();
3631+
3632+ registry->impl->thread.executeOnThread([this, &promise, &registry]() {
3633+ bool use_session_bus = g_getenv("UBUNTU_APP_LAUNCH_CG_MANAGER_SESSION_BUS") != nullptr;
3634+ if (use_session_bus)
3635+ {
3636+ /* For working dbusmock */
3637+ g_debug("Connecting to CG Manager on session bus");
3638+ promise.set_value(registry->impl->_dbus);
3639+ return;
3640+ }
3641+
3642+ auto cancel =
3643+ std::shared_ptr<GCancellable>(g_cancellable_new(), [](GCancellable* cancel) { g_clear_object(&cancel); });
3644+
3645+ /* Ensure that we do not wait for more than a second */
3646+ registry->impl->thread.timeoutSeconds(std::chrono::seconds{1},
3647+ [cancel]() { g_cancellable_cancel(cancel.get()); });
3648+
3649+ g_dbus_connection_new_for_address(
3650+ CGMANAGER_DBUS_PATH, /* cgmanager path */
3651+ G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT, /* flags */
3652+ nullptr, /* Auth Observer */
3653+ cancel.get(), /* Cancellable */
3654+ [](GObject* obj, GAsyncResult* res, gpointer data) -> void {
3655+ GError* error = nullptr;
3656+ auto promise = reinterpret_cast<std::promise<std::shared_ptr<GDBusConnection>>*>(data);
3657+
3658+ auto gcon = g_dbus_connection_new_for_address_finish(res, &error);
3659+ if (error != nullptr)
3660+ {
3661+ g_error_free(error);
3662+ }
3663+
3664+ auto con = std::shared_ptr<GDBusConnection>(gcon, [](GDBusConnection* con) { g_clear_object(&con); });
3665+ promise->set_value(con);
3666+ },
3667+ &promise);
3668+ });
3669+
3670+ cgManager_ = future.get();
3671+ registry->impl->thread.timeoutSeconds(std::chrono::seconds{10}, [this]() { cgManager_.reset(); });
3672+}
3673+
3674+/** Get a list of PIDs from a CGroup, uses the CGManager connection to list
3675+ all of the PIDs. It is important to note that this is an IPC call, so it can
3676+ by its nature, be racy. Once the message has been sent the group can change.
3677+ You should take that into account in your usage of it. */
3678+std::vector<pid_t> Upstart::pidsFromCgroup(const std::string& jobpath)
3679+{
3680+ initCGManager();
3681+ auto lmanager = cgManager_; /* Grab a local copy so we ensure it lasts through our lifetime */
3682+ auto registry = registry_.lock();
3683+
3684+ return registry->impl->thread.executeOnThread<std::vector<pid_t>>([&jobpath, lmanager]() -> std::vector<pid_t> {
3685+ GError* error = nullptr;
3686+ const gchar* name = g_getenv("UBUNTU_APP_LAUNCH_CG_MANAGER_NAME");
3687+ std::string groupname;
3688+ if (!jobpath.empty())
3689+ {
3690+ groupname = "upstart/" + jobpath;
3691+ }
3692+
3693+ g_debug("Looking for cg manager '%s' group '%s'", name, groupname.c_str());
3694+
3695+ GVariant* vtpids = g_dbus_connection_call_sync(
3696+ lmanager.get(), /* connection */
3697+ name, /* bus name for direct connection is NULL */
3698+ "/org/linuxcontainers/cgmanager", /* object */
3699+ "org.linuxcontainers.cgmanager0_0", /* interface */
3700+ "GetTasksRecursive", /* method */
3701+ g_variant_new("(ss)", "freezer", groupname.empty() ? "" : groupname.c_str()), /* params */
3702+ G_VARIANT_TYPE("(ai)"), /* output */
3703+ G_DBUS_CALL_FLAGS_NONE, /* flags */
3704+ -1, /* default timeout */
3705+ nullptr, /* cancellable */
3706+ &error); /* error */
3707+
3708+ if (error != nullptr)
3709+ {
3710+ g_warning("Unable to get PID list from cgroup manager: %s", error->message);
3711+ g_error_free(error);
3712+ return {};
3713+ }
3714+
3715+ GVariant* vpids = g_variant_get_child_value(vtpids, 0);
3716+ GVariantIter iter;
3717+ g_variant_iter_init(&iter, vpids);
3718+ gint32 pid;
3719+ std::vector<pid_t> pids;
3720+
3721+ while (g_variant_iter_loop(&iter, "i", &pid))
3722+ {
3723+ pids.push_back(pid);
3724+ }
3725+
3726+ g_variant_unref(vpids);
3727+ g_variant_unref(vtpids);
3728+
3729+ return pids;
3730+ });
3731+}
3732+
3733+/** Looks to find the Upstart object path for a specific Upstart job. This first
3734+ checks the cache, and otherwise does the lookup on DBus. */
3735+std::string Upstart::upstartJobPath(const std::string& job)
3736+{
3737+ try
3738+ {
3739+ return upstartJobPathCache_.at(job);
3740+ }
3741+ catch (std::out_of_range& e)
3742+ {
3743+ auto registry = registry_.lock();
3744+ auto path = registry->impl->thread.executeOnThread<std::string>([this, &job, &registry]() -> std::string {
3745+ GError* error = nullptr;
3746+ GVariant* job_path_variant =
3747+ g_dbus_connection_call_sync(registry->impl->_dbus.get(), /* connection */
3748+ DBUS_SERVICE_UPSTART, /* service */
3749+ DBUS_PATH_UPSTART, /* path */
3750+ DBUS_INTERFACE_UPSTART, /* iface */
3751+ "GetJobByName", /* method */
3752+ g_variant_new("(s)", job.c_str()), /* params */
3753+ G_VARIANT_TYPE("(o)"), /* return */
3754+ G_DBUS_CALL_FLAGS_NONE, /* flags */
3755+ -1, /* timeout: default */
3756+ registry->impl->thread.getCancellable().get(), /* cancellable */
3757+ &error); /* error */
3758+
3759+ if (error != nullptr)
3760+ {
3761+ g_warning("Unable to find job '%s': %s", job.c_str(), error->message);
3762+ g_error_free(error);
3763+ return {};
3764+ }
3765+
3766+ gchar* job_path = nullptr;
3767+ g_variant_get(job_path_variant, "(o)", &job_path);
3768+ g_variant_unref(job_path_variant);
3769+
3770+ if (job_path != nullptr)
3771+ {
3772+ std::string path(job_path);
3773+ g_free(job_path);
3774+ return path;
3775+ }
3776+ else
3777+ {
3778+ return {};
3779+ }
3780+ });
3781+
3782+ upstartJobPathCache_[job] = path;
3783+ return path;
3784+ }
3785+}
3786+
3787+/** Queries Upstart to get all the instances of a given job. This
3788+ can take a while as the number of dbus calls is n+1. It is
3789+ rare that apps have many instances though. */
3790+std::list<std::string> Upstart::upstartInstancesForJob(const std::string& job)
3791+{
3792+ std::string jobpath = upstartJobPath(job);
3793+ if (jobpath.empty())
3794+ {
3795+ return {};
3796+ }
3797+
3798+ auto registry = registry_.lock();
3799+ return registry->impl->thread.executeOnThread<std::list<std::string>>(
3800+ [this, &job, &jobpath, &registry]() -> std::list<std::string> {
3801+ GError* error = nullptr;
3802+ GVariant* instance_tuple =
3803+ g_dbus_connection_call_sync(registry->impl->_dbus.get(), /* connection */
3804+ DBUS_SERVICE_UPSTART, /* service */
3805+ jobpath.c_str(), /* object path */
3806+ DBUS_INTERFACE_UPSTART_JOB, /* iface */
3807+ "GetAllInstances", /* method */
3808+ nullptr, /* params */
3809+ G_VARIANT_TYPE("(ao)"), /* return type */
3810+ G_DBUS_CALL_FLAGS_NONE, /* flags */
3811+ -1, /* timeout: default */
3812+ registry->impl->thread.getCancellable().get(), /* cancellable */
3813+ &error);
3814+
3815+ if (error != nullptr)
3816+ {
3817+ g_warning("Unable to get instances of job '%s': %s", job.c_str(), error->message);
3818+ g_error_free(error);
3819+ return {};
3820+ }
3821+
3822+ if (instance_tuple == nullptr)
3823+ {
3824+ return {};
3825+ }
3826+
3827+ GVariant* instance_list = g_variant_get_child_value(instance_tuple, 0);
3828+ g_variant_unref(instance_tuple);
3829+
3830+ GVariantIter instance_iter;
3831+ g_variant_iter_init(&instance_iter, instance_list);
3832+ const gchar* instance_path = nullptr;
3833+ std::list<std::string> instances;
3834+
3835+ while (g_variant_iter_loop(&instance_iter, "&o", &instance_path))
3836+ {
3837+ GVariant* props_tuple =
3838+ g_dbus_connection_call_sync(registry->impl->_dbus.get(), /* connection */
3839+ DBUS_SERVICE_UPSTART, /* service */
3840+ instance_path, /* object path */
3841+ "org.freedesktop.DBus.Properties", /* interface */
3842+ "GetAll", /* method */
3843+ g_variant_new("(s)", DBUS_INTERFACE_UPSTART_INSTANCE), /* params */
3844+ G_VARIANT_TYPE("(a{sv})"), /* return type */
3845+ G_DBUS_CALL_FLAGS_NONE, /* flags */
3846+ -1, /* timeout: default */
3847+ registry->impl->thread.getCancellable().get(), /* cancellable */
3848+ &error);
3849+
3850+ if (error != nullptr)
3851+ {
3852+ g_warning("Unable to name of instance '%s': %s", instance_path, error->message);
3853+ g_error_free(error);
3854+ error = nullptr;
3855+ continue;
3856+ }
3857+
3858+ GVariant* props_dict = g_variant_get_child_value(props_tuple, 0);
3859+
3860+ GVariant* namev = g_variant_lookup_value(props_dict, "name", G_VARIANT_TYPE_STRING);
3861+ if (namev != nullptr)
3862+ {
3863+ auto name = g_variant_get_string(namev, NULL);
3864+ g_debug("Adding instance for job '%s': %s", job.c_str(), name);
3865+ instances.push_back(name);
3866+ g_variant_unref(namev);
3867+ }
3868+
3869+ g_variant_unref(props_dict);
3870+ g_variant_unref(props_tuple);
3871+ }
3872+
3873+ g_variant_unref(instance_list);
3874+
3875+ return instances;
3876+ });
3877+}
3878+
3879+std::list<std::shared_ptr<Application>> Upstart::runningApps()
3880+{
3881+ std::list<std::string> instances;
3882+
3883+ /* Get all the legacy instances */
3884+ instances.splice(instances.begin(), upstartInstancesForJob("application-legacy"));
3885+ /* Get all the snap instances */
3886+ instances.splice(instances.begin(), upstartInstancesForJob("application-snap"));
3887+
3888+ /* Remove the instance ID */
3889+ std::transform(instances.begin(), instances.end(), instances.begin(), [](std::string& instancename) -> std::string {
3890+ static const std::regex instanceregex("^(.*)-[0-9]*$");
3891+ std::smatch match;
3892+ if (std::regex_match(instancename, match, instanceregex))
3893+ {
3894+ return match[1].str();
3895+ }
3896+ else
3897+ {
3898+ g_warning("Unable to match instance name: %s", instancename.c_str());
3899+ return {};
3900+ }
3901+ });
3902+
3903+ /* Deduplicate Set */
3904+ std::set<std::string> instanceset;
3905+ for (auto instance : instances)
3906+ {
3907+ if (!instance.empty())
3908+ instanceset.insert(instance);
3909+ }
3910+
3911+ /* Add in the click instances */
3912+ for (auto instance : upstartInstancesForJob("application-click"))
3913+ {
3914+ instanceset.insert(instance);
3915+ }
3916+
3917+ g_debug("Overall there are %d instances: %s", int(instanceset.size()),
3918+ std::accumulate(instanceset.begin(), instanceset.end(), std::string{},
3919+ [](const std::string& instr, std::string instance) {
3920+ return instr.empty() ? instance : instr + ", " + instance;
3921+ })
3922+ .c_str());
3923+
3924+ /* Convert to Applications */
3925+ auto registry = registry_.lock();
3926+ std::list<std::shared_ptr<Application>> apps;
3927+ for (auto instance : instanceset)
3928+ {
3929+ auto appid = AppID::find(registry, instance);
3930+ auto app = Application::create(appid, registry);
3931+ apps.push_back(app);
3932+ }
3933+
3934+ return apps;
3935+}
3936+
3937+} // namespace manager
3938+} // namespace jobs
3939+} // namespace app_launch
3940+} // namespace ubuntu
3941
3942=== added file 'libubuntu-app-launch/jobs-upstart.h'
3943--- libubuntu-app-launch/jobs-upstart.h 1970-01-01 00:00:00 +0000
3944+++ libubuntu-app-launch/jobs-upstart.h 2016-11-01 14:53:56 +0000
3945@@ -0,0 +1,75 @@
3946+/*
3947+ * Copyright © 2016 Canonical Ltd.
3948+ *
3949+ * This program is free software: you can redistribute it and/or modify it
3950+ * under the terms of the GNU General Public License version 3, as published
3951+ * by the Free Software Foundation.
3952+ *
3953+ * This program is distributed in the hope that it will be useful, but
3954+ * WITHOUT ANY WARRANTY; without even the implied warranties of
3955+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
3956+ * PURPOSE. See the GNU General Public License for more details.
3957+ *
3958+ * You should have received a copy of the GNU General Public License along
3959+ * with this program. If not, see <http://www.gnu.org/licenses/>.
3960+ *
3961+ * Authors:
3962+ * Ted Gould <ted.gould@canonical.com>
3963+ */
3964+
3965+#pragma once
3966+
3967+#include "jobs-base.h"
3968+#include <gio/gio.h>
3969+#include <map>
3970+
3971+namespace ubuntu
3972+{
3973+namespace app_launch
3974+{
3975+namespace jobs
3976+{
3977+namespace manager
3978+{
3979+
3980+class Upstart : public Base
3981+{
3982+public:
3983+ Upstart(std::shared_ptr<Registry> registry);
3984+ virtual ~Upstart();
3985+
3986+ virtual std::shared_ptr<Application::Instance> launch(
3987+ const AppID& appId,
3988+ const std::string& job,
3989+ const std::string& instance,
3990+ const std::vector<Application::URL>& urls,
3991+ launchMode mode,
3992+ std::function<std::list<std::pair<std::string, std::string>>(void)>& getenv) override;
3993+ virtual std::shared_ptr<Application::Instance> existing(const AppID& appId,
3994+ const std::string& job,
3995+ const std::string& instance,
3996+ const std::vector<Application::URL>& urls) override;
3997+
3998+ virtual std::list<std::shared_ptr<Application>> runningApps() override;
3999+
4000+ virtual std::vector<std::shared_ptr<instance::Base>> instances(const AppID& appID, const std::string& job) override;
4001+
4002+ std::vector<pid_t> pidsFromCgroup(const std::string& jobpath);
4003+
4004+ std::list<std::string> upstartInstancesForJob(const std::string& job);
4005+ std::string upstartJobPath(const std::string& job);
4006+
4007+private:
4008+ void initCGManager();
4009+
4010+ std::shared_ptr<GDBusConnection> cgManager_;
4011+
4012+ /** Getting the Upstart job path is relatively expensive in
4013+ that it requires a DBus call. Worth keeping a cache of. */
4014+ std::map<std::string, std::string> upstartJobPathCache_;
4015+};
4016+
4017+} // namespace manager
4018+} // namespace jobs
4019+} // namespace app_launch
4020+} // namespace ubuntu
4021
4022=== modified file 'libubuntu-app-launch/registry-impl.cpp'
4023--- libubuntu-app-launch/registry-impl.cpp 2016-09-23 22:30:51 +0000
4024+++ libubuntu-app-launch/registry-impl.cpp 2016-11-01 14:53:56 +0000
4025@@ -19,8 +19,6 @@
4026
4027 #include "registry-impl.h"
4028 #include "application-icon-finder.h"
4029-#include <cgmanager/cgmanager.h>
4030-#include <upstart.h>
4031
4032 namespace ubuntu
4033 {
4034@@ -34,7 +32,7 @@
4035 _clickDB.reset();
4036
4037 zgLog_.reset();
4038- cgManager_.reset();
4039+ jobs.reset();
4040
4041 if (_dbus)
4042 g_dbus_connection_flush_sync(_dbus.get(), nullptr, nullptr);
4043@@ -49,6 +47,14 @@
4044 return std::shared_ptr<GDBusConnection>(g_bus_get_sync(G_BUS_TYPE_SESSION, cancel.get(), nullptr),
4045 [](GDBusConnection* bus) { g_clear_object(&bus); });
4046 });
4047+
4048+ /* Determine where we're getting the helper from */
4049+ oomHelper_ = OOM_HELPER;
4050+ auto goomHelper = g_getenv("UBUNTU_APP_LAUNCH_OOM_HELPER");
4051+ if (goomHelper != nullptr)
4052+ {
4053+ oomHelper_ = goomHelper;
4054+ }
4055 }
4056
4057 void Registry::Impl::initClick()
4058@@ -145,7 +151,7 @@
4059 if (error != nullptr)
4060 {
4061 auto perror = std::shared_ptr<GError>(error, [](GError* error) { g_error_free(error); });
4062- g_critical("Error parsing manifest for package '%s': %s", package.c_str(), perror->message);
4063+ g_debug("Error parsing manifest for package '%s': %s", package.c_str(), perror->message);
4064 return std::shared_ptr<JsonObject>();
4065 }
4066
4067@@ -214,257 +220,6 @@
4068 });
4069 }
4070
4071-/** Initialize the CGManager connection, including a timeout to disconnect
4072- as CGManager doesn't free resources entirely well. So it's better if
4073- we connect and disconnect occationally */
4074-void Registry::Impl::initCGManager()
4075-{
4076- if (cgManager_)
4077- return;
4078-
4079- std::promise<std::shared_ptr<GDBusConnection>> promise;
4080- auto future = promise.get_future();
4081-
4082- thread.executeOnThread([this, &promise]() {
4083- bool use_session_bus = g_getenv("UBUNTU_APP_LAUNCH_CG_MANAGER_SESSION_BUS") != nullptr;
4084- if (use_session_bus)
4085- {
4086- /* For working dbusmock */
4087- g_debug("Connecting to CG Manager on session bus");
4088- promise.set_value(_dbus);
4089- return;
4090- }
4091-
4092- auto cancel =
4093- std::shared_ptr<GCancellable>(g_cancellable_new(), [](GCancellable* cancel) { g_clear_object(&cancel); });
4094-
4095- /* Ensure that we do not wait for more than a second */
4096- thread.timeoutSeconds(std::chrono::seconds{1}, [cancel]() { g_cancellable_cancel(cancel.get()); });
4097-
4098- g_dbus_connection_new_for_address(
4099- CGMANAGER_DBUS_PATH, /* cgmanager path */
4100- G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT, /* flags */
4101- nullptr, /* Auth Observer */
4102- cancel.get(), /* Cancellable */
4103- [](GObject* obj, GAsyncResult* res, gpointer data) -> void {
4104- GError* error = nullptr;
4105- auto promise = reinterpret_cast<std::promise<std::shared_ptr<GDBusConnection>>*>(data);
4106-
4107- auto gcon = g_dbus_connection_new_for_address_finish(res, &error);
4108- if (error != nullptr)
4109- {
4110- g_error_free(error);
4111- }
4112-
4113- auto con = std::shared_ptr<GDBusConnection>(gcon, [](GDBusConnection* con) { g_clear_object(&con); });
4114- promise->set_value(con);
4115- },
4116- &promise);
4117- });
4118-
4119- cgManager_ = future.get();
4120- thread.timeoutSeconds(std::chrono::seconds{10}, [this]() { cgManager_.reset(); });
4121-}
4122-
4123-/** Get a list of PIDs from a CGroup, uses the CGManager connection to list
4124- all of the PIDs. It is important to note that this is an IPC call, so it can
4125- by its nature, be racy. Once the message has been sent the group can change.
4126- You should take that into account in your usage of it. */
4127-std::vector<pid_t> Registry::Impl::pidsFromCgroup(const std::string& jobpath)
4128-{
4129- initCGManager();
4130- auto lmanager = cgManager_; /* Grab a local copy so we ensure it lasts through our lifetime */
4131-
4132- return thread.executeOnThread<std::vector<pid_t>>([&jobpath, lmanager]() -> std::vector<pid_t> {
4133- GError* error = nullptr;
4134- const gchar* name = g_getenv("UBUNTU_APP_LAUNCH_CG_MANAGER_NAME");
4135- std::string groupname;
4136- if (!jobpath.empty())
4137- {
4138- groupname = "upstart/" + jobpath;
4139- }
4140-
4141- g_debug("Looking for cg manager '%s' group '%s'", name, groupname.c_str());
4142-
4143- GVariant* vtpids = g_dbus_connection_call_sync(
4144- lmanager.get(), /* connection */
4145- name, /* bus name for direct connection is NULL */
4146- "/org/linuxcontainers/cgmanager", /* object */
4147- "org.linuxcontainers.cgmanager0_0", /* interface */
4148- "GetTasksRecursive", /* method */
4149- g_variant_new("(ss)", "freezer", groupname.empty() ? "" : groupname.c_str()), /* params */
4150- G_VARIANT_TYPE("(ai)"), /* output */
4151- G_DBUS_CALL_FLAGS_NONE, /* flags */
4152- -1, /* default timeout */
4153- nullptr, /* cancellable */
4154- &error); /* error */
4155-
4156- if (error != nullptr)
4157- {
4158- g_warning("Unable to get PID list from cgroup manager: %s", error->message);
4159- g_error_free(error);
4160- return {};
4161- }
4162-
4163- GVariant* vpids = g_variant_get_child_value(vtpids, 0);
4164- GVariantIter iter;
4165- g_variant_iter_init(&iter, vpids);
4166- gint32 pid;
4167- std::vector<pid_t> pids;
4168-
4169- while (g_variant_iter_loop(&iter, "i", &pid))
4170- {
4171- pids.push_back(pid);
4172- }
4173-
4174- g_variant_unref(vpids);
4175- g_variant_unref(vtpids);
4176-
4177- return pids;
4178- });
4179-}
4180-
4181-/** Looks to find the Upstart object path for a specific Upstart job. This first
4182- checks the cache, and otherwise does the lookup on DBus. */
4183-std::string Registry::Impl::upstartJobPath(const std::string& job)
4184-{
4185- try
4186- {
4187- return upstartJobPathCache_.at(job);
4188- }
4189- catch (std::out_of_range& e)
4190- {
4191- auto path = thread.executeOnThread<std::string>([this, &job]() -> std::string {
4192- GError* error = nullptr;
4193- GVariant* job_path_variant = g_dbus_connection_call_sync(_dbus.get(), /* connection */
4194- DBUS_SERVICE_UPSTART, /* service */
4195- DBUS_PATH_UPSTART, /* path */
4196- DBUS_INTERFACE_UPSTART, /* iface */
4197- "GetJobByName", /* method */
4198- g_variant_new("(s)", job.c_str()), /* params */
4199- G_VARIANT_TYPE("(o)"), /* return */
4200- G_DBUS_CALL_FLAGS_NONE, /* flags */
4201- -1, /* timeout: default */
4202- thread.getCancellable().get(), /* cancellable */
4203- &error); /* error */
4204-
4205- if (error != nullptr)
4206- {
4207- g_warning("Unable to find job '%s': %s", job.c_str(), error->message);
4208- g_error_free(error);
4209- return {};
4210- }
4211-
4212- gchar* job_path = nullptr;
4213- g_variant_get(job_path_variant, "(o)", &job_path);
4214- g_variant_unref(job_path_variant);
4215-
4216- if (job_path != nullptr)
4217- {
4218- std::string path(job_path);
4219- g_free(job_path);
4220- return path;
4221- }
4222- else
4223- {
4224- return {};
4225- }
4226- });
4227-
4228- upstartJobPathCache_[job] = path;
4229- return path;
4230- }
4231-}
4232-
4233-/** Queries Upstart to get all the instances of a given job. This
4234- can take a while as the number of dbus calls is n+1. It is
4235- rare that apps have many instances though. */
4236-std::list<std::string> Registry::Impl::upstartInstancesForJob(const std::string& job)
4237-{
4238- std::string jobpath = upstartJobPath(job);
4239- if (jobpath.empty())
4240- {
4241- return {};
4242- }
4243-
4244- return thread.executeOnThread<std::list<std::string>>([this, &job, &jobpath]() -> std::list<std::string> {
4245- GError* error = nullptr;
4246- GVariant* instance_tuple = g_dbus_connection_call_sync(_dbus.get(), /* connection */
4247- DBUS_SERVICE_UPSTART, /* service */
4248- jobpath.c_str(), /* object path */
4249- DBUS_INTERFACE_UPSTART_JOB, /* iface */
4250- "GetAllInstances", /* method */
4251- nullptr, /* params */
4252- G_VARIANT_TYPE("(ao)"), /* return type */
4253- G_DBUS_CALL_FLAGS_NONE, /* flags */
4254- -1, /* timeout: default */
4255- thread.getCancellable().get(), /* cancellable */
4256- &error);
4257-
4258- if (error != nullptr)
4259- {
4260- g_warning("Unable to get instances of job '%s': %s", job.c_str(), error->message);
4261- g_error_free(error);
4262- return {};
4263- }
4264-
4265- if (instance_tuple == nullptr)
4266- {
4267- return {};
4268- }
4269-
4270- GVariant* instance_list = g_variant_get_child_value(instance_tuple, 0);
4271- g_variant_unref(instance_tuple);
4272-
4273- GVariantIter instance_iter;
4274- g_variant_iter_init(&instance_iter, instance_list);
4275- const gchar* instance_path = nullptr;
4276- std::list<std::string> instances;
4277-
4278- while (g_variant_iter_loop(&instance_iter, "&o", &instance_path))
4279- {
4280- GVariant* props_tuple =
4281- g_dbus_connection_call_sync(_dbus.get(), /* connection */
4282- DBUS_SERVICE_UPSTART, /* service */
4283- instance_path, /* object path */
4284- "org.freedesktop.DBus.Properties", /* interface */
4285- "GetAll", /* method */
4286- g_variant_new("(s)", DBUS_INTERFACE_UPSTART_INSTANCE), /* params */
4287- G_VARIANT_TYPE("(a{sv})"), /* return type */
4288- G_DBUS_CALL_FLAGS_NONE, /* flags */
4289- -1, /* timeout: default */
4290- thread.getCancellable().get(), /* cancellable */
4291- &error);
4292-
4293- if (error != nullptr)
4294- {
4295- g_warning("Unable to name of instance '%s': %s", instance_path, error->message);
4296- g_error_free(error);
4297- error = nullptr;
4298- continue;
4299- }
4300-
4301- GVariant* props_dict = g_variant_get_child_value(props_tuple, 0);
4302-
4303- GVariant* namev = g_variant_lookup_value(props_dict, "name", G_VARIANT_TYPE_STRING);
4304- if (namev != nullptr)
4305- {
4306- auto name = g_variant_get_string(namev, NULL);
4307- g_debug("Adding instance for job '%s': %s", job.c_str(), name);
4308- instances.push_back(name);
4309- g_variant_unref(namev);
4310- }
4311-
4312- g_variant_unref(props_dict);
4313- g_variant_unref(props_tuple);
4314- }
4315-
4316- g_variant_unref(instance_list);
4317-
4318- return instances;
4319- });
4320-}
4321-
4322 /** Send an event to Zietgeist using the registry thread so that
4323 the callback comes back in the right place. */
4324 void Registry::Impl::zgSendEvent(AppID appid, const std::string& eventtype)
4325
4326=== modified file 'libubuntu-app-launch/registry-impl.h'
4327--- libubuntu-app-launch/registry-impl.h 2016-09-23 22:30:51 +0000
4328+++ libubuntu-app-launch/registry-impl.h 2016-11-01 14:53:56 +0000
4329@@ -18,6 +18,7 @@
4330 */
4331
4332 #include "glib-thread.h"
4333+#include "jobs-base.h"
4334 #include "registry.h"
4335 #include "snapd-info.h"
4336 #include <click.h>
4337@@ -69,15 +70,11 @@
4338 snapd::Info snapdInfo;
4339 #endif
4340
4341+ std::shared_ptr<jobs::manager::Base> jobs;
4342+
4343 std::shared_ptr<IconFinder> getIconFinder(std::string basePath);
4344
4345- void zgSendEvent(AppID appid, const std::string& eventtype);
4346-
4347- std::vector<pid_t> pidsFromCgroup(const std::string& jobpath);
4348-
4349- /* Upstart Jobs */
4350- std::list<std::string> upstartInstancesForJob(const std::string& job);
4351- std::string upstartJobPath(const std::string& job);
4352+ virtual void zgSendEvent(AppID appid, const std::string& eventtype);
4353
4354 static std::string printJson(std::shared_ptr<JsonObject> jsonobj);
4355 static std::string printJson(std::shared_ptr<JsonNode> jsonnode);
4356@@ -88,6 +85,11 @@
4357 static void watchingAppStarting(bool rWatching);
4358 static bool isWatchingAppStarting();
4359
4360+ const std::string oomHelper()
4361+ {
4362+ return oomHelper_;
4363+ }
4364+
4365 private:
4366 Registry* _registry;
4367 #if 0
4368@@ -101,15 +103,14 @@
4369
4370 std::shared_ptr<ZeitgeistLog> zgLog_;
4371
4372- std::shared_ptr<GDBusConnection> cgManager_;
4373-
4374- void initCGManager();
4375-
4376 std::unordered_map<std::string, std::shared_ptr<IconFinder>> _iconFinders;
4377
4378 /** Getting the Upstart job path is relatively expensive in
4379 that it requires a DBus call. Worth keeping a cache of. */
4380 std::map<std::string, std::string> upstartJobPathCache_;
4381+
4382+ /** Path to the OOM Helper */
4383+ std::string oomHelper_;
4384 };
4385
4386 } // namespace app_launch
4387
4388=== modified file 'libubuntu-app-launch/registry.cpp'
4389--- libubuntu-app-launch/registry.cpp 2016-08-26 17:33:34 +0000
4390+++ libubuntu-app-launch/registry.cpp 2016-11-01 14:53:56 +0000
4391@@ -47,61 +47,14 @@
4392 {
4393 }
4394
4395-std::list<std::shared_ptr<Application>> Registry::runningApps(std::shared_ptr<Registry> connection)
4396+std::list<std::shared_ptr<Application>> Registry::runningApps(std::shared_ptr<Registry> registry)
4397 {
4398- std::list<std::string> instances;
4399-
4400- /* Get all the legacy instances */
4401- instances.splice(instances.begin(), connection->impl->upstartInstancesForJob("application-legacy"));
4402- /* Get all the snap instances */
4403- instances.splice(instances.begin(), connection->impl->upstartInstancesForJob("application-snap"));
4404-
4405- /* Remove the instance ID */
4406- std::transform(instances.begin(), instances.end(), instances.begin(), [](std::string &instancename) -> std::string {
4407- static const std::regex instanceregex("^(.*)-[0-9]*$");
4408- std::smatch match;
4409- if (std::regex_match(instancename, match, instanceregex))
4410- {
4411- return match[1].str();
4412- }
4413- else
4414- {
4415- g_warning("Unable to match instance name: %s", instancename.c_str());
4416- return {};
4417- }
4418- });
4419-
4420- /* Deduplicate Set */
4421- std::set<std::string> instanceset;
4422- for (auto instance : instances)
4423- {
4424- if (!instance.empty())
4425- instanceset.insert(instance);
4426- }
4427-
4428- /* Add in the click instances */
4429- for (auto instance : connection->impl->upstartInstancesForJob("application-click"))
4430- {
4431- instanceset.insert(instance);
4432- }
4433-
4434- g_debug("Overall there are %d instances: %s", int(instanceset.size()),
4435- std::accumulate(instanceset.begin(), instanceset.end(), std::string{},
4436- [](const std::string &instr, std::string instance) {
4437- return instr.empty() ? instance : instr + ", " + instance;
4438- })
4439- .c_str());
4440-
4441- /* Convert to Applications */
4442- std::list<std::shared_ptr<Application>> apps;
4443- for (auto instance : instanceset)
4444- {
4445- auto appid = AppID::find(connection, instance);
4446- auto app = Application::create(appid, connection);
4447- apps.push_back(app);
4448- }
4449-
4450- return apps;
4451+ if (!registry->impl->jobs)
4452+ {
4453+ registry->impl->jobs = jobs::manager::Base::determineFactory(registry);
4454+ }
4455+
4456+ return registry->impl->jobs->runningApps();
4457 }
4458
4459 std::list<std::shared_ptr<Application>> Registry::installedApps(std::shared_ptr<Registry> connection)
4460
4461=== modified file 'libubuntu-app-launch/snapd-info.cpp'
4462--- libubuntu-app-launch/snapd-info.cpp 2016-10-03 23:54:08 +0000
4463+++ libubuntu-app-launch/snapd-info.cpp 2016-11-01 14:53:56 +0000
4464@@ -170,7 +170,7 @@
4465 }
4466 catch (std::runtime_error &e)
4467 {
4468- g_warning("Unable to get snap information for '%s': %s", package.value().c_str(), e.what());
4469+ g_debug("Unable to get snap information for '%s': %s", package.value().c_str(), e.what());
4470 return {};
4471 }
4472 }
4473
4474=== modified file 'libubuntu-app-launch/ubuntu-app-launch.cpp'
4475--- libubuntu-app-launch/ubuntu-app-launch.cpp 2016-09-14 21:58:39 +0000
4476+++ libubuntu-app-launch/ubuntu-app-launch.cpp 2016-11-01 14:53:56 +0000
4477@@ -1746,7 +1746,11 @@
4478 /* The exec value */
4479 gchar * envstr = NULL;
4480 if (demangler) {
4481- envstr = g_strdup_printf("APP_EXEC=%s %s", DEMANGLER_PATH, execline);
4482+ const gchar * demangler_path = g_getenv("UBUNTU_APP_LAUNCH_DEMANGLER");
4483+ if (demangler_path == nullptr) {
4484+ demangler_path = DEMANGLER_PATH;
4485+ }
4486+ envstr = g_strdup_printf("APP_EXEC=%s %s", demangler_path, execline);
4487 } else {
4488 envstr = g_strdup_printf("APP_EXEC=%s", execline);
4489 }
4490
4491=== modified file 'tests/CMakeLists.txt'
4492--- tests/CMakeLists.txt 2016-09-14 16:43:36 +0000
4493+++ tests/CMakeLists.txt 2016-11-01 14:53:56 +0000
4494@@ -12,23 +12,21 @@
4495
4496 include_directories(${GTEST_INCLUDE_DIR})
4497
4498-add_library (gtest STATIC
4499- ${GTEST_SOURCE_DIR}/gtest-all.cc
4500- ${GTEST_SOURCE_DIR}/gtest_main.cc)
4501+add_subdirectory("/usr/src/gmock" gmock)
4502
4503 # Helper test
4504
4505 add_executable (helper-test helper-test.cc)
4506 add_definitions ( -DCMAKE_SOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}" )
4507 add_definitions ( -DCMAKE_BINARY_DIR="${CMAKE_CURRENT_BINARY_DIR}" )
4508-target_link_libraries (helper-test helpers gtest ${GTEST_LIBS} ${DBUSTEST_LIBRARIES})
4509+target_link_libraries (helper-test helpers gtest_main ${GTEST_LIBS} ${DBUSTEST_LIBRARIES})
4510
4511 add_test (helper-test helper-test)
4512
4513 # Helper test
4514
4515 add_executable (helper-handshake-test helper-handshake-test.cc)
4516-target_link_libraries (helper-handshake-test helpers gtest ${GTEST_LIBS})
4517+target_link_libraries (helper-handshake-test helpers gtest_main ${GTEST_LIBS})
4518
4519 add_test (helper-handshake-test helper-handshake-test)
4520
4521@@ -45,13 +43,13 @@
4522 add_executable (libual-test
4523 libual-test.cc
4524 mir-mock.cpp)
4525-target_link_libraries (libual-test gtest ${GTEST_LIBS} ${LIBUPSTART_LIBRARIES} ${DBUSTEST_LIBRARIES} ubuntu-launcher)
4526+target_link_libraries (libual-test gtest_main ${GTEST_LIBS} ${LIBUPSTART_LIBRARIES} ${DBUSTEST_LIBRARIES} ubuntu-launcher)
4527
4528 add_executable (libual-cpp-test
4529 libual-cpp-test.cc
4530 ${CMAKE_SOURCE_DIR}/libubuntu-app-launch/glib-thread.cpp
4531 mir-mock.cpp)
4532-target_link_libraries (libual-cpp-test gtest ${GTEST_LIBS} ${LIBUPSTART_LIBRARIES} ${DBUSTEST_LIBRARIES} ubuntu-launcher)
4533+target_link_libraries (libual-cpp-test gtest_main ${GTEST_LIBS} ${LIBUPSTART_LIBRARIES} ${DBUSTEST_LIBRARIES} ubuntu-launcher)
4534
4535 add_executable (data-spew
4536 data-spew.c)
4537@@ -63,13 +61,21 @@
4538 add_test (NAME libual-test COMMAND libual-test)
4539 add_test (NAME libual-cpp-test COMMAND libual-cpp-test)
4540
4541+# Jobs Base Test
4542+
4543+add_executable (jobs-base-test
4544+ jobs-base-test.cpp)
4545+target_link_libraries (jobs-base-test gmock_main ${GTEST_LIBS} launcher-static)
4546+
4547+add_test(NAME jobs-base-test COMMAND jobs-base-test)
4548+
4549 # Snapd Info Test
4550
4551 if(CURL_FOUND)
4552 add_definitions ( -DSNAPD_TEST_SOCKET="/tmp/snapd-test-socket" )
4553 add_executable (snapd-info-test
4554 snapd-info-test.cpp)
4555-target_link_libraries (snapd-info-test gtest ${GTEST_LIBS} launcher-static)
4556+target_link_libraries (snapd-info-test gtest_main ${GTEST_LIBS} launcher-static)
4557 add_test (NAME snapd-info-test COMMAND snapd-info-test)
4558 endif()
4559
4560@@ -77,7 +83,7 @@
4561
4562 add_executable (list-apps
4563 list-apps.cpp)
4564-target_link_libraries (list-apps gtest ${GTEST_LIBS} launcher-static)
4565+target_link_libraries (list-apps gtest_main ${GTEST_LIBS} launcher-static)
4566 add_test (NAME list-apps COMMAND ${CMAKE_CURRENT_BINARY_DIR}/list-apps)
4567
4568 # Application Info Desktop
4569@@ -85,7 +91,7 @@
4570 add_executable (application-info-desktop-test
4571 application-info-desktop.cpp
4572 )
4573-target_link_libraries (application-info-desktop-test gtest ${GTEST_LIBS} launcher-static)
4574+target_link_libraries (application-info-desktop-test gtest_main ${GTEST_LIBS} launcher-static)
4575
4576 add_test (NAME application-info-desktop-test COMMAND application-info-desktop-test)
4577
4578@@ -97,7 +103,7 @@
4579
4580 #sources
4581 ${CMAKE_SOURCE_DIR}/libubuntu-app-launch/application-icon-finder.cpp)
4582-target_link_libraries (application-icon-finder-test gtest ${GTEST_LIBS} ubuntu-launcher)
4583+target_link_libraries (application-icon-finder-test gtest_main ${GTEST_LIBS} ubuntu-launcher)
4584
4585 add_test (NAME application-icon-finder-test COMMAND application-icon-finder-test)
4586
4587@@ -109,7 +115,7 @@
4588
4589 add_executable (failure-test
4590 failure-test.cc)
4591-target_link_libraries (failure-test gtest ${GTEST_LIBS} ubuntu-launcher)
4592+target_link_libraries (failure-test gtest_main ${GTEST_LIBS} ubuntu-launcher)
4593 add_test (failure-test failure-test)
4594
4595 # ZG Test
4596@@ -118,7 +124,7 @@
4597
4598 add_executable (zg-test
4599 zg-test.cc)
4600-target_link_libraries (zg-test gtest ${GTEST_LIBS} ${DBUSTEST_LIBRARIES} ${GIO2_LIBRARIES})
4601+target_link_libraries (zg-test gtest_main ${GTEST_LIBS} ${DBUSTEST_LIBRARIES} ${GIO2_LIBRARIES})
4602 add_test (zg-test zg-test)
4603
4604 # Exec Line Exec Test
4605@@ -130,7 +136,7 @@
4606
4607 add_executable (exec-util-test
4608 exec-util-test.cc)
4609-target_link_libraries (exec-util-test gtest ubuntu-launcher ${GTEST_LIBS} ${DBUSTEST_LIBRARIES} ${GIO2_LIBRARIES})
4610+target_link_libraries (exec-util-test gtest_main ubuntu-launcher ${GTEST_LIBS} ${DBUSTEST_LIBRARIES} ${GIO2_LIBRARIES})
4611 add_test (exec-util-test exec-util-test)
4612
4613 # CGroup Reap Test
4614@@ -139,7 +145,7 @@
4615
4616 add_executable (cgroup-reap-test
4617 cgroup-reap-test.cc)
4618-target_link_libraries (cgroup-reap-test gtest ${GTEST_LIBS} ${DBUSTEST_LIBRARIES} ${GIO2_LIBRARIES})
4619+target_link_libraries (cgroup-reap-test gtest_main ${GTEST_LIBS} ${DBUSTEST_LIBRARIES} ${GIO2_LIBRARIES})
4620 add_test (cgroup-reap-test cgroup-reap-test)
4621
4622 # Desktop Hook Test
4623@@ -161,6 +167,7 @@
4624 libual-cpp-test.cc
4625 list-apps.cpp
4626 eventually-fixture.h
4627+ jobs-base-test.cpp
4628 snapd-info-test.cpp
4629 snapd-mock.h
4630 zg-test.cc
4631
4632=== modified file 'tests/exec-util-test.cc'
4633--- tests/exec-util-test.cc 2016-09-15 17:13:04 +0000
4634+++ tests/exec-util-test.cc 2016-11-01 14:53:56 +0000
4635@@ -45,6 +45,7 @@
4636 g_setenv("XDG_DATA_HOME", CMAKE_SOURCE_DIR "/libertine-home", TRUE);
4637 g_setenv("UBUNTU_APP_LAUNCH_LIBERTINE_LAUNCH", "libertine-launch", TRUE);
4638 g_setenv("UBUNTU_APP_LAUNCH_SNAPD_SOCKET", "/this/should/not/exist", TRUE);
4639+ g_setenv("UBUNTU_APP_LAUNCH_SYSTEMD_PATH", "/this/should/not/exist", TRUE);
4640
4641 service = dbus_test_service_new(NULL);
4642
4643@@ -184,12 +185,15 @@
4644 EXPECT_STREQ("grep", value); }},
4645 {"APP_ID", [](const gchar * value) {
4646 EXPECT_STREQ("com.test.good_application_1.2.3", value); }},
4647+ {"APP_EXEC_POLICY", [](const gchar * value) {
4648+ EXPECT_STREQ("com.test.good_application_1.2.3", value); }},
4649 {"APP_LAUNCHER_PID", [](const gchar * value) {
4650 EXPECT_EQ(getpid(), atoi(value)); }},
4651 {"APP_DESKTOP_FILE_PATH", [](const gchar * value) {
4652 EXPECT_STREQ(APP_DIR "/application.desktop", value); }},
4653 {"APP_XMIR_ENABLE", [](const gchar * value) {
4654 EXPECT_STREQ("0", value); }},
4655+ {"QML2_IMPORT_PATH", nocheck},
4656 });
4657
4658 #undef APP_DIR
4659@@ -267,10 +271,13 @@
4660 {"APP_EXEC", nocheck},
4661 {"APP_ID", [](const gchar * value) {
4662 EXPECT_STREQ("com.test.mir_mir_1", value); }},
4663+ {"APP_EXEC_POLICY", [](const gchar * value) {
4664+ EXPECT_STREQ("com.test.mir_mir_1", value); }},
4665 {"APP_LAUNCHER_PID", nocheck},
4666 {"APP_DESKTOP_FILE_PATH", nocheck},
4667 {"APP_XMIR_ENABLE", [](const gchar * value) {
4668 EXPECT_STREQ("1", value); }},
4669+ {"QML2_IMPORT_PATH", nocheck},
4670 });
4671 }
4672
4673@@ -289,10 +296,13 @@
4674 {"APP_EXEC", nocheck},
4675 {"APP_ID", [](const gchar * value) {
4676 EXPECT_STREQ("com.test.mir_nomir_1", value); }},
4677+ {"APP_EXEC_POLICY", [](const gchar * value) {
4678+ EXPECT_STREQ("com.test.mir_nomir_1", value); }},
4679 {"APP_LAUNCHER_PID", nocheck},
4680 {"APP_DESKTOP_FILE_PATH", nocheck},
4681 {"APP_XMIR_ENABLE", [](const gchar * value) {
4682 EXPECT_STREQ("0", value); }},
4683+ {"QML2_IMPORT_PATH", nocheck},
4684 });
4685 }
4686
4687
4688=== added file 'tests/jobs-base-test.cpp'
4689--- tests/jobs-base-test.cpp 1970-01-01 00:00:00 +0000
4690+++ tests/jobs-base-test.cpp 2016-11-01 14:53:56 +0000
4691@@ -0,0 +1,364 @@
4692+/*
4693+ * Copyright © 2016 Canonical Ltd.
4694+ *
4695+ * This program is free software: you can redistribute it and/or modify it
4696+ * under the terms of the GNU General Public License version 3, as published
4697+ * by the Free Software Foundation.
4698+ *
4699+ * This program is distributed in the hope that it will be useful, but
4700+ * WITHOUT ANY WARRANTY; without even the implied warranties of
4701+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
4702+ * PURPOSE. See the GNU General Public License for more details.
4703+ *
4704+ * You should have received a copy of the GNU General Public License along
4705+ * with this program. If not, see <http://www.gnu.org/licenses/>.
4706+ *
4707+ * Authors:
4708+ * Ted Gould <ted.gould@canonical.com>
4709+ */
4710+
4711+#include "jobs-base.h"
4712+#include "appid.h"
4713+#include "registry-impl.h"
4714+#include "registry.h"
4715+
4716+#include "eventually-fixture.h"
4717+#include <gmock/gmock.h>
4718+#include <gtest/gtest.h>
4719+
4720+class RegistryImplMock : public ubuntu::app_launch::Registry::Impl
4721+{
4722+public:
4723+ RegistryImplMock(ubuntu::app_launch::Registry* reg)
4724+ : ubuntu::app_launch::Registry::Impl(reg)
4725+ {
4726+ }
4727+
4728+ MOCK_METHOD2(zgSendEvent, void(ubuntu::app_launch::AppID, const std::string& eventtype));
4729+};
4730+
4731+class RegistryMock : public ubuntu::app_launch::Registry
4732+{
4733+public:
4734+ RegistryMock()
4735+ {
4736+ impl = std::unique_ptr<RegistryImplMock>(new RegistryImplMock(this));
4737+ }
4738+};
4739+
4740+class instanceMock : public ubuntu::app_launch::jobs::instance::Base
4741+{
4742+public:
4743+ instanceMock(const ubuntu::app_launch::AppID& appId,
4744+ const std::string& job,
4745+ const std::string& instance,
4746+ const std::vector<ubuntu::app_launch::Application::URL>& urls,
4747+ const std::shared_ptr<ubuntu::app_launch::Registry>& registry)
4748+ : ubuntu::app_launch::jobs::instance::Base(appId, job, instance, urls, registry)
4749+ {
4750+ }
4751+
4752+ MOCK_METHOD0(primaryPid, pid_t());
4753+ MOCK_METHOD0(logPath, std::string());
4754+ MOCK_METHOD0(pids, std::vector<pid_t>());
4755+
4756+ MOCK_METHOD0(stop, void());
4757+};
4758+
4759+class SpewMaster
4760+{
4761+public:
4762+ SpewMaster()
4763+ : thread(
4764+ [this]() {
4765+ gint spewstdout = 0;
4766+ std::array<const gchar*, 2> spewline{SPEW_UTILITY, nullptr};
4767+ ASSERT_TRUE(g_spawn_async_with_pipes(NULL, /* directory */
4768+ (char**)spewline.data(), /* command line */
4769+ NULL, /* environment */
4770+ G_SPAWN_DEFAULT, /* flags */
4771+ NULL, /* child setup */
4772+ NULL, /* child setup */
4773+ &pid_, /* pid */
4774+ NULL, /* stdin */
4775+ &spewstdout, /* stdout */
4776+ NULL, /* stderr */
4777+ NULL)); /* error */
4778+
4779+ spewoutchan = g_io_channel_unix_new(spewstdout);
4780+ g_io_channel_set_flags(spewoutchan, G_IO_FLAG_NONBLOCK, NULL);
4781+
4782+ iosource = g_io_create_watch(spewoutchan, G_IO_IN);
4783+ g_source_set_callback(iosource, (GSourceFunc)datain, this, nullptr);
4784+ g_source_attach(iosource, g_main_context_get_thread_default());
4785+
4786+ /* Setup our OOM adjust file */
4787+ gchar* procdir = g_strdup_printf(CMAKE_BINARY_DIR "/jobs-base-proc/%d", pid_);
4788+ ASSERT_EQ(0, g_mkdir_with_parents(procdir, 0700));
4789+ oomadjfile = g_strdup_printf("%s/oom_score_adj", procdir);
4790+ g_free(procdir);
4791+ ASSERT_TRUE(g_file_set_contents(oomadjfile, "0", -1, NULL));
4792+ },
4793+ [this]() {
4794+ /* Clean up */
4795+ gchar* killstr = g_strdup_printf("kill -9 %d", pid_);
4796+ ASSERT_TRUE(g_spawn_command_line_sync(killstr, NULL, NULL, NULL, NULL));
4797+ g_free(killstr);
4798+
4799+ g_source_destroy(iosource);
4800+ g_io_channel_unref(spewoutchan);
4801+ g_clear_pointer(&oomadjfile, g_free);
4802+ })
4803+ {
4804+ datacnt_ = 0;
4805+ }
4806+
4807+ ~SpewMaster()
4808+ {
4809+ }
4810+
4811+ std::string oomScore()
4812+ {
4813+ gchar* oomvalue = nullptr;
4814+ g_file_get_contents(oomadjfile, &oomvalue, nullptr, nullptr);
4815+ if (oomvalue != nullptr)
4816+ {
4817+ return std::string(oomvalue);
4818+ }
4819+ else
4820+ {
4821+ return {};
4822+ }
4823+ }
4824+
4825+ GPid pid()
4826+ {
4827+ return pid_;
4828+ }
4829+
4830+ gsize dataCnt()
4831+ {
4832+ g_debug("Data Count for %d: %d", pid_, int(datacnt_));
4833+ return datacnt_;
4834+ }
4835+
4836+ void reset()
4837+ {
4838+ bool endofqueue = thread.executeOnThread<bool>([this]() {
4839+ while (G_IO_STATUS_AGAIN == g_io_channel_flush(spewoutchan, nullptr))
4840+ ;
4841+ return true; /* the main loop has processed */
4842+ });
4843+ g_debug("Reset %d", pid_);
4844+ if (endofqueue)
4845+ datacnt_ = 0;
4846+ else
4847+ g_warning("Unable to clear mainloop on reset");
4848+ }
4849+
4850+ std::atomic<gsize> datacnt_;
4851+
4852+private:
4853+ GPid pid_ = 0;
4854+ gchar* oomadjfile = nullptr;
4855+ GIOChannel* spewoutchan = nullptr;
4856+ GSource* iosource = nullptr;
4857+ GLib::ContextThread thread;
4858+
4859+ static gboolean datain(GIOChannel* source, GIOCondition cond, gpointer data)
4860+ {
4861+ auto spew = static_cast<SpewMaster*>(data);
4862+ gchar* str = NULL;
4863+ gsize len = 0;
4864+ GError* error = NULL;
4865+
4866+ g_io_channel_read_line(source, &str, &len, NULL, &error);
4867+ g_free(str);
4868+
4869+ if (error != NULL)
4870+ {
4871+ g_warning("Unable to read from channel: %s", error->message);
4872+ g_error_free(error);
4873+ }
4874+
4875+ spew->datacnt_ += len;
4876+
4877+ return TRUE;
4878+ }
4879+};
4880+
4881+class JobBaseTest : public EventuallyFixture
4882+{
4883+protected:
4884+ std::shared_ptr<RegistryMock> registry;
4885+
4886+ virtual void SetUp()
4887+ {
4888+ registry = std::make_shared<RegistryMock>();
4889+ }
4890+
4891+ virtual void TearDown()
4892+ {
4893+ registry.reset();
4894+ }
4895+
4896+ ubuntu::app_launch::AppID simpleAppID()
4897+ {
4898+ return {ubuntu::app_launch::AppID::Package::from_raw("package"),
4899+ ubuntu::app_launch::AppID::AppName::from_raw("appname"),
4900+ ubuntu::app_launch::AppID::Version::from_raw("version")};
4901+ }
4902+
4903+ std::shared_ptr<instanceMock> simpleInstance()
4904+ {
4905+ return std::make_shared<instanceMock>(simpleAppID(), "application-job", "1234567890",
4906+ std::vector<ubuntu::app_launch::Application::URL>{}, registry);
4907+ }
4908+};
4909+
4910+TEST_F(JobBaseTest, InitTest)
4911+{
4912+ auto instance = simpleInstance();
4913+
4914+ instance.reset();
4915+}
4916+
4917+TEST_F(JobBaseTest, isRunning)
4918+{
4919+ auto instance = simpleInstance();
4920+
4921+ EXPECT_CALL(*instance, primaryPid()).WillOnce(testing::Return(0));
4922+
4923+ EXPECT_FALSE(instance->isRunning());
4924+
4925+ EXPECT_CALL(*instance, primaryPid()).WillOnce(testing::Return(100));
4926+
4927+ EXPECT_TRUE(instance->isRunning());
4928+}
4929+
4930+TEST_F(JobBaseTest, pauseResume)
4931+{
4932+ g_setenv("UBUNTU_APP_LAUNCH_OOM_PROC_PATH", CMAKE_BINARY_DIR "/jobs-base-proc", TRUE);
4933+
4934+ /* Setup some spew */
4935+ SpewMaster spew;
4936+ std::vector<pid_t> pids{spew.pid()};
4937+
4938+ /* Build our instance */
4939+ auto instance = simpleInstance();
4940+ EXPECT_CALL(*instance, pids()).WillRepeatedly(testing::Return(pids));
4941+
4942+ /* Setup registry */
4943+ EXPECT_CALL(dynamic_cast<RegistryImplMock&>(*registry->impl), zgSendEvent(simpleAppID(), ZEITGEIST_ZG_LEAVE_EVENT))
4944+ .WillOnce(testing::Return());
4945+
4946+ /* Make sure it is running */
4947+ EXPECT_EVENTUALLY_NE(0, spew.datacnt_);
4948+
4949+ /*** Do Pause ***/
4950+ instance->pause();
4951+
4952+ spew.reset();
4953+ pause(100); // give spew a chance to send data if it is running
4954+
4955+ EXPECT_EQ(0, spew.dataCnt());
4956+
4957+ EXPECT_EQ(std::to_string(int(ubuntu::app_launch::oom::paused())), spew.oomScore());
4958+
4959+ /* Setup for Resume */
4960+ EXPECT_CALL(dynamic_cast<RegistryImplMock&>(*registry->impl), zgSendEvent(simpleAppID(), ZEITGEIST_ZG_ACCESS_EVENT))
4961+ .WillOnce(testing::Return());
4962+
4963+ spew.reset();
4964+ EXPECT_EQ(0, spew.dataCnt());
4965+
4966+ /*** Do Resume ***/
4967+ instance->resume();
4968+
4969+ EXPECT_EVENTUALLY_NE(0, spew.datacnt_);
4970+
4971+ EXPECT_EQ(std::to_string(int(ubuntu::app_launch::oom::focused())), spew.oomScore());
4972+}
4973+
4974+TEST_F(JobBaseTest, pauseResumeNone)
4975+{
4976+ std::vector<pid_t> pids{};
4977+
4978+ /* Build our instance */
4979+ auto instance = simpleInstance();
4980+ EXPECT_CALL(*instance, pids()).WillRepeatedly(testing::Return(pids));
4981+
4982+ /* Setup registry */
4983+ EXPECT_CALL(dynamic_cast<RegistryImplMock&>(*registry->impl), zgSendEvent(simpleAppID(), ZEITGEIST_ZG_LEAVE_EVENT))
4984+ .WillOnce(testing::Return());
4985+
4986+ /*** Do Pause ***/
4987+ instance->pause();
4988+
4989+ /* Setup for Resume */
4990+ EXPECT_CALL(dynamic_cast<RegistryImplMock&>(*registry->impl), zgSendEvent(simpleAppID(), ZEITGEIST_ZG_ACCESS_EVENT))
4991+ .WillOnce(testing::Return());
4992+
4993+ /*** Do Resume ***/
4994+ instance->resume();
4995+}
4996+
4997+TEST_F(JobBaseTest, pauseResumeMany)
4998+{
4999+ g_setenv("UBUNTU_APP_LAUNCH_OOM_PROC_PATH", CMAKE_BINARY_DIR "/jobs-base-proc", TRUE);
5000+
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches