Merge lp:~ted/ubuntu-app-launch/jobs-systemd into lp:ubuntu-app-launch

Proposed by Ted Gould
Status: Merged
Approved by: Ted Gould
Approved revision: 403
Merged at revision: 281
Proposed branch: lp:~ted/ubuntu-app-launch/jobs-systemd
Merge into: lp:ubuntu-app-launch
Prerequisite: lp:~ted/ubuntu-app-launch/jobs-tests
Diff against target: 3215 lines (+2555/-102)
31 files modified
debian/rules (+4/-1)
libubuntu-app-launch/CMakeLists.txt (+4/-0)
libubuntu-app-launch/application-impl-base.cpp (+26/-0)
libubuntu-app-launch/application-impl-base.h (+3/-0)
libubuntu-app-launch/application-impl-click.cpp (+6/-0)
libubuntu-app-launch/application-impl-legacy.cpp (+4/-17)
libubuntu-app-launch/application-impl-legacy.h (+0/-1)
libubuntu-app-launch/application-impl-libertine.cpp (+11/-8)
libubuntu-app-launch/application-impl-snap.cpp (+8/-4)
libubuntu-app-launch/application-info-desktop.cpp (+1/-0)
libubuntu-app-launch/application-info-desktop.h (+8/-0)
libubuntu-app-launch/helper.h (+10/-10)
libubuntu-app-launch/jobs-base.cpp (+47/-2)
libubuntu-app-launch/jobs-base.h (+7/-0)
libubuntu-app-launch/jobs-systemd.cpp (+1329/-0)
libubuntu-app-launch/jobs-systemd.h (+136/-0)
libubuntu-app-launch/jobs-upstart.cpp (+0/-25)
libubuntu-app-launch/registry-impl.cpp (+1/-1)
libubuntu-app-launch/snapd-info.cpp (+1/-1)
tests/CMakeLists.txt (+11/-0)
tests/eventually-fixture.h (+9/-0)
tests/exec-util-test.cc (+10/-0)
tests/failure-test.cc (+2/-0)
tests/jobs-base-test.cpp (+1/-22)
tests/jobs-systemd.cpp (+362/-0)
tests/libual-cpp-test.cc (+11/-10)
tests/libual-test.cc (+1/-0)
tests/registry-mock.h (+57/-0)
tests/spew-master.h (+2/-0)
tests/systemd-mock.h (+481/-0)
xmir-helper.c (+2/-0)
To merge this branch: bzr merge lp:~ted/ubuntu-app-launch/jobs-systemd
Reviewer Review Type Date Requested Status
unity-api-1-bot continuous-integration Needs Fixing
Charles Kerr (community) Approve
Review via email: mp+310590@code.launchpad.net

This proposal supersedes a proposal from 2016-10-26.

Commit message

SystemD backend added

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

FAILED: Continuous integration, rev:372
https://jenkins.canonical.com/unity-api-1/job/lp-ubuntu-app-launch-ci/178/
Executed test runs:
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build/1515/console
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-0-fetch/1522
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=xenial+overlay/1300
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=xenial+overlay/1300/artifact/output/*zip*/output.zip
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=zesty/1300/console
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=xenial+overlay/1300
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=xenial+overlay/1300/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=zesty/1300
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=zesty/1300/artifact/output/*zip*/output.zip
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=xenial+overlay/1300/console
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=zesty/1300
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=zesty/1300/artifact/output/*zip*/output.zip

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

review: Needs Fixing (continuous-integration)
373. By Ted Gould

Update to trunk

Revision history for this message
unity-api-1-bot (unity-api-1-bot) wrote :

FAILED: Continuous integration, rev:373
https://jenkins.canonical.com/unity-api-1/job/lp-ubuntu-app-launch-ci/183/
Executed test runs:
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build/1526/console
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-0-fetch/1533
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=xenial+overlay/1311
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=xenial+overlay/1311/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=zesty/1311
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=zesty/1311/artifact/output/*zip*/output.zip
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=xenial+overlay/1311/console
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=zesty/1311
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=zesty/1311/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=xenial+overlay/1311
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=xenial+overlay/1311/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=zesty/1311
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=zesty/1311/artifact/output/*zip*/output.zip

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

review: Needs Fixing (continuous-integration)
Revision history for this message
Charles Kerr (charlesk) wrote :

Comments inline.

As usual, mostly questions & minor. A few NF but nothing requiring major surgery.

review: Needs Fixing
374. By Ted Gould

Make getInstance a const method

375. By Ted Gould

Const getAllJobs()

376. By Ted Gould

Comment formatting, whatevs

377. By Ted Gould

Header reshuffle

378. By Ted Gould

Charles hates returns on void functions

379. By Ted Gould

We don't need no stinkin' std::string object

380. By Ted Gould

Remove some printouts when we cancel

381. By Ted Gould

Protect more against null GVariant pointers

382. By Ted Gould

Make the signal handlers safer

383. By Ted Gould

Make parseUnit and unitName const

384. By Ted Gould

Name lamba better

385. By Ted Gould

Use std::vector<> constructor instead of a loop

386. By Ted Gould

Be louder about not having an exec line

387. By Ted Gould

Getting rid of a TODO

388. By Ted Gould

Move declarations

389. By Ted Gould

Avoid calling getenv() twice

390. By Charles Kerr

Cleaner name finding

391. By Ted Gould

Make lists into real lists

392. By Ted Gould

Make sure we don't copy commands

393. By Ted Gould

Making sure we calculate the string once

394. By Ted Gould

Don't get all the jobs until we're sure we have a registry

395. By Ted Gould

Remove try/catch that isn't needed

396. By Ted Gould

Switching to static_cast<>

397. By Ted Gould

Fixing up the failure signals

398. By Charles Kerr

Clearer sorting

399. By Ted Gould

Overrides

Revision history for this message
Ted Gould (ted) :
Revision history for this message
unity-api-1-bot (unity-api-1-bot) wrote :

FAILED: Continuous integration, rev:399
https://jenkins.canonical.com/unity-api-1/job/lp-ubuntu-app-launch-ci/184/
Executed test runs:
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build/1528/console
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-0-fetch/1535
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=xenial+overlay/1313
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=xenial+overlay/1313/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=zesty/1313
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=zesty/1313/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=xenial+overlay/1313
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=xenial+overlay/1313/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=zesty/1313
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=zesty/1313/artifact/output/*zip*/output.zip
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=xenial+overlay/1313/console
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=zesty/1313
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=zesty/1313/artifact/output/*zip*/output.zip

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

review: Needs Fixing (continuous-integration)
400. By Ted Gould

Test the exec line and ensure it doesn't fail

Revision history for this message
unity-api-1-bot (unity-api-1-bot) wrote :

FAILED: Continuous integration, rev:400
https://jenkins.canonical.com/unity-api-1/job/lp-ubuntu-app-launch-ci/188/
Executed test runs:
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build/1532/console
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-0-fetch/1539
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=xenial+overlay/1317
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=xenial+overlay/1317/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=zesty/1317
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=zesty/1317/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=xenial+overlay/1317
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=xenial+overlay/1317/artifact/output/*zip*/output.zip
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=zesty/1317/console
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=xenial+overlay/1317/console
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=zesty/1317
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=zesty/1317/artifact/output/*zip*/output.zip

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

review: Needs Fixing (continuous-integration)
401. By Ted Gould

Don't crash free memory, works but is odd

402. By Ted Gould

Use the g_array functions to avoid some casts

Revision history for this message
unity-api-1-bot (unity-api-1-bot) wrote :

FAILED: Continuous integration, rev:402
https://jenkins.canonical.com/unity-api-1/job/lp-ubuntu-app-launch-ci/189/
Executed test runs:
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build/1533/console
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-0-fetch/1540
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=xenial+overlay/1318
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=xenial+overlay/1318/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=zesty/1318
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=zesty/1318/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=xenial+overlay/1318
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=xenial+overlay/1318/artifact/output/*zip*/output.zip
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=zesty/1318/console
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=xenial+overlay/1318
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=xenial+overlay/1318/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=zesty/1318
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=zesty/1318/artifact/output/*zip*/output.zip

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

review: Needs Fixing (continuous-integration)
Revision history for this message
Charles Kerr (charlesk) wrote :

I'm happy with these changes, feel free to top-approve once the Jenkins issue is resolved (or if it's a false issue)

review: Approve
403. By Ted Gould

Making sure everything is on the right bus

Revision history for this message
unity-api-1-bot (unity-api-1-bot) wrote :
review: Needs Fixing (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'debian/rules'
2--- debian/rules 2016-07-11 13:46:43 +0000
3+++ debian/rules 2017-02-01 20:34:54 +0000
4@@ -4,12 +4,15 @@
5 # Get full logs in tests
6 export G_MESSAGES_DEBUG=all
7
8+# Ensure tests fail with criticals
9+#export G_DEBUG=fatal_criticals
10+
11 # Uncomment this to turn on verbose mode.
12 #export DH_VERBOSE=1
13 export DPKG_GENSYMBOLS_CHECK_LEVEL=4
14
15 %:
16- dh $@ --with click,gir --parallel --fail-missing
17+ dh $@ --with click,gir --fail-missing
18
19 override_dh_click:
20 dh_click --name ubuntu-app-launch-desktop
21
22=== modified file 'libubuntu-app-launch/CMakeLists.txt'
23--- libubuntu-app-launch/CMakeLists.txt 2016-11-17 16:31:47 +0000
24+++ libubuntu-app-launch/CMakeLists.txt 2017-02-01 20:34:54 +0000
25@@ -17,6 +17,8 @@
26 set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility=hidden -Wpedantic")
27 add_definitions ( -DOOM_HELPER="${pkglibexecdir}/oom-adjust-setuid-helper" -DDEMANGLER_PATH="${pkglibexecdir}/socket-demangler" )
28 add_definitions ( -DLIBERTINE_LAUNCH="${CMAKE_INSTALL_FULL_BINDIR}/libertine-launch" )
29+add_definitions ( -DG_LOG_DOMAIN="ubuntu-app-launch" )
30+add_definitions ( -DUBUNTU_APP_LAUNCH_ARCH="${UBUNTU_APP_LAUNCH_ARCH}" )
31
32 set(LAUNCHER_HEADERS
33 ubuntu-app-launch.h
34@@ -54,6 +56,8 @@
35 glib-thread.cpp
36 jobs-base.h
37 jobs-base.cpp
38+jobs-systemd.h
39+jobs-systemd.cpp
40 jobs-upstart.h
41 jobs-upstart.cpp
42 )
43
44=== modified file 'libubuntu-app-launch/application-impl-base.cpp'
45--- libubuntu-app-launch/application-impl-base.cpp 2017-01-11 21:31:59 +0000
46+++ libubuntu-app-launch/application-impl-base.cpp 2017-02-01 20:34:54 +0000
47@@ -42,6 +42,12 @@
48 Base::Base(const std::shared_ptr<Registry>& registry)
49 : _registry(registry)
50 {
51+ g_debug("Application construction: %p", static_cast<void*>(this));
52+}
53+
54+Base::~Base()
55+{
56+ g_debug("Application deconstruction: %p", static_cast<void*>(this));
57 }
58
59 bool Base::hasInstances()
60@@ -95,6 +101,26 @@
61 return retval;
62 }
63
64+/** Generates an instance string based on the clock if we're a multi-instance
65+ application. */
66+std::string Base::getInstance(const std::shared_ptr<app_info::Desktop>& desktop) const
67+{
68+ if (!desktop)
69+ {
70+ g_warning("Invalid desktop file passed to getInstance");
71+ return {};
72+ }
73+
74+ if (desktop->singleInstance())
75+ {
76+ return {};
77+ }
78+ else
79+ {
80+ return std::to_string(g_get_real_time());
81+ }
82+}
83+
84 } // namespace app_impls
85 } // namespace app_launch
86 } // namespace ubuntu
87
88=== modified file 'libubuntu-app-launch/application-impl-base.h'
89--- libubuntu-app-launch/application-impl-base.h 2016-12-14 22:49:35 +0000
90+++ libubuntu-app-launch/application-impl-base.h 2017-02-01 20:34:54 +0000
91@@ -17,6 +17,7 @@
92 * Ted Gould <ted.gould@canonical.com>
93 */
94
95+#include "application-info-desktop.h"
96 #include "application.h"
97
98 extern "C" {
99@@ -40,9 +41,11 @@
100 {
101 public:
102 Base(const std::shared_ptr<Registry>& registry);
103+ virtual ~Base();
104
105 bool hasInstances() override;
106
107+ std::string getInstance(const std::shared_ptr<app_info::Desktop>& desktop) const;
108 virtual std::shared_ptr<Application::Instance> findInstance(const std::string& instanceid) = 0;
109
110 protected:
111
112=== modified file 'libubuntu-app-launch/application-impl-click.cpp'
113--- libubuntu-app-launch/application-impl-click.cpp 2016-12-14 22:49:35 +0000
114+++ libubuntu-app-launch/application-impl-click.cpp 2017-02-01 20:34:54 +0000
115@@ -51,6 +51,8 @@
116 std::tie(_keyfile, desktopPath_) = manifestAppDesktop(_manifest, appid.package, appid.appname, _clickDir);
117 if (!_keyfile)
118 throw std::runtime_error{"No keyfile found for click application: " + std::string(appid)};
119+
120+ g_debug("Application Click object for appid '%s'", std::string(appid).c_str());
121 }
122
123 AppID Click::appId()
124@@ -337,11 +339,15 @@
125 retval.emplace_back(std::make_pair("APP_DIR", _clickDir));
126 retval.emplace_back(std::make_pair("APP_DESKTOP_FILE_PATH", desktopPath_));
127
128+ retval.emplace_back(std::make_pair("QML2_IMPORT_PATH", _clickDir + "/lib/" + UBUNTU_APP_LAUNCH_ARCH + "/qml"));
129+
130 info();
131
132 retval.emplace_back(std::make_pair("APP_XMIR_ENABLE", _info->xMirEnable().value() ? "1" : "0"));
133 retval.emplace_back(std::make_pair("APP_EXEC", _info->execLine().value()));
134
135+ retval.emplace_back(std::make_pair("APP_EXEC_POLICY", std::string(appId())));
136+
137 return retval;
138 }
139
140
141=== modified file 'libubuntu-app-launch/application-impl-legacy.cpp'
142--- libubuntu-app-launch/application-impl-legacy.cpp 2016-12-14 22:49:35 +0000
143+++ libubuntu-app-launch/application-impl-legacy.cpp 2017-02-01 20:34:54 +0000
144@@ -74,6 +74,8 @@
145 {
146 throw std::runtime_error{"Looking like a legacy app, but should be a Snap: " + appname.value()};
147 }
148+
149+ g_debug("Application Legacy object for app '%s'", appname.value().c_str());
150 }
151
152 std::tuple<std::string, std::shared_ptr<GKeyFile>, std::string> keyfileForApp(const AppID::AppName& name)
153@@ -342,21 +344,6 @@
154 return retval;
155 }
156
157-/** Generates an instance string based on the clock if we're a multi-instance
158- application. */
159-std::string Legacy::getInstance()
160-{
161- auto single = g_key_file_get_boolean(_keyfile.get(), "Desktop Entry", "X-Ubuntu-Single-Instance", nullptr);
162- if (single)
163- {
164- return {};
165- }
166- else
167- {
168- return std::to_string(g_get_real_time());
169- }
170-}
171-
172 /** Create an UpstartInstance for this AppID using the UpstartInstance launch
173 function.
174
175@@ -364,7 +351,7 @@
176 */
177 std::shared_ptr<Application::Instance> Legacy::launch(const std::vector<Application::URL>& urls)
178 {
179- std::string instance = getInstance();
180+ auto instance = getInstance(appinfo_);
181 std::function<std::list<std::pair<std::string, std::string>>(void)> envfunc = [this, instance]() {
182 return launchEnv(instance);
183 };
184@@ -379,7 +366,7 @@
185 */
186 std::shared_ptr<Application::Instance> Legacy::launchTest(const std::vector<Application::URL>& urls)
187 {
188- std::string instance = getInstance();
189+ auto instance = getInstance(appinfo_);
190 std::function<std::list<std::pair<std::string, std::string>>(void)> envfunc = [this, instance]() {
191 return launchEnv(instance);
192 };
193
194=== modified file 'libubuntu-app-launch/application-impl-legacy.h'
195--- libubuntu-app-launch/application-impl-legacy.h 2016-12-14 22:49:35 +0000
196+++ libubuntu-app-launch/application-impl-legacy.h 2017-02-01 20:34:54 +0000
197@@ -88,7 +88,6 @@
198 std::regex instanceRegex_;
199
200 std::list<std::pair<std::string, std::string>> launchEnv(const std::string& instance);
201- std::string getInstance();
202 };
203
204 } // namespace app_impls
205
206=== modified file 'libubuntu-app-launch/application-impl-libertine.cpp'
207--- libubuntu-app-launch/application-impl-libertine.cpp 2017-01-06 17:53:21 +0000
208+++ libubuntu-app-launch/application-impl-libertine.cpp 2017-02-01 20:34:54 +0000
209@@ -65,6 +65,12 @@
210 if (!_keyfile)
211 throw std::runtime_error{"Unable to find a keyfile for application '" + appname.value() + "' in container '" +
212 container.value() + "'"};
213+
214+ appinfo_ = std::make_shared<app_info::Desktop>(_keyfile, _basedir, _container_path,
215+ app_info::DesktopFlags::XMIR_DEFAULT, _registry);
216+
217+ g_debug("Application Libertine object for container '%s' app '%s'", container.value().c_str(),
218+ appname.value().c_str());
219 }
220
221 std::shared_ptr<GKeyFile> Libertine::keyfileFromPath(const std::string& pathname)
222@@ -265,11 +271,6 @@
223
224 std::shared_ptr<Application::Info> Libertine::info()
225 {
226- if (!appinfo_)
227- {
228- appinfo_ = std::make_shared<app_info::Desktop>(_keyfile, _basedir, _container_path,
229- app_info::DesktopFlags::XMIR_DEFAULT, _registry);
230- }
231 return appinfo_;
232 }
233
234@@ -317,15 +318,17 @@
235
236 std::shared_ptr<Application::Instance> Libertine::launch(const std::vector<Application::URL>& urls)
237 {
238+ auto instance = getInstance(appinfo_);
239 std::function<std::list<std::pair<std::string, std::string>>(void)> envfunc = [this]() { return launchEnv(); };
240- return _registry->impl->jobs->launch(appId(), "application-legacy", {}, urls, jobs::manager::launchMode::STANDARD,
241- envfunc);
242+ return _registry->impl->jobs->launch(appId(), "application-legacy", instance, urls,
243+ jobs::manager::launchMode::STANDARD, envfunc);
244 }
245
246 std::shared_ptr<Application::Instance> Libertine::launchTest(const std::vector<Application::URL>& urls)
247 {
248+ auto instance = getInstance(appinfo_);
249 std::function<std::list<std::pair<std::string, std::string>>(void)> envfunc = [this]() { return launchEnv(); };
250- return _registry->impl->jobs->launch(appId(), "application-legacy", {}, urls, jobs::manager::launchMode::TEST,
251+ return _registry->impl->jobs->launch(appId(), "application-legacy", instance, urls, jobs::manager::launchMode::TEST,
252 envfunc);
253 }
254
255
256=== modified file 'libubuntu-app-launch/application-impl-snap.cpp'
257--- libubuntu-app-launch/application-impl-snap.cpp 2017-01-19 03:22:56 +0000
258+++ libubuntu-app-launch/application-impl-snap.cpp 2017-02-01 20:34:54 +0000
259@@ -226,6 +226,8 @@
260 }
261
262 info_ = std::make_shared<SnapInfo>(appid_, _registry, interface_, pkgInfo_->directory);
263+
264+ g_debug("Application Snap object for AppID '%s'", std::string(appid).c_str());
265 }
266
267 /** Uses the findInterface() function to find the interface if we don't
268@@ -259,7 +261,7 @@
269 }
270 catch (std::runtime_error& e)
271 {
272- g_warning("Unable to make Snap object for '%s': %s", std::string(id).c_str(), e.what());
273+ g_debug("Unable to make Snap object for '%s': %s", std::string(id).c_str(), e.what());
274 }
275 }
276 }
277@@ -466,9 +468,10 @@
278 */
279 std::shared_ptr<Application::Instance> Snap::launch(const std::vector<Application::URL>& urls)
280 {
281+ auto instance = getInstance(info_);
282 std::function<std::list<std::pair<std::string, std::string>>(void)> envfunc = [this]() { return launchEnv(); };
283- return _registry->impl->jobs->launch(appid_, "application-snap", {}, urls, jobs::manager::launchMode::STANDARD,
284- envfunc);
285+ return _registry->impl->jobs->launch(appid_, "application-snap", instance, urls,
286+ jobs::manager::launchMode::STANDARD, envfunc);
287 }
288
289 /** Create a new instance of this Snap with a testing environment
290@@ -478,8 +481,9 @@
291 */
292 std::shared_ptr<Application::Instance> Snap::launchTest(const std::vector<Application::URL>& urls)
293 {
294+ auto instance = getInstance(info_);
295 std::function<std::list<std::pair<std::string, std::string>>(void)> envfunc = [this]() { return launchEnv(); };
296- return _registry->impl->jobs->launch(appid_, "application-snap", {}, urls, jobs::manager::launchMode::TEST,
297+ return _registry->impl->jobs->launch(appid_, "application-snap", instance, urls, jobs::manager::launchMode::TEST,
298 envfunc);
299 }
300
301
302=== modified file 'libubuntu-app-launch/application-info-desktop.cpp'
303--- libubuntu-app-launch/application-info-desktop.cpp 2017-01-12 23:00:08 +0000
304+++ libubuntu-app-launch/application-info-desktop.cpp 2017-02-01 20:34:54 +0000
305@@ -396,6 +396,7 @@
306 , _xMirEnable(
307 boolFromKeyfile<XMirEnable>(keyfile, "X-Ubuntu-XMir-Enable", (flags & DesktopFlags::XMIR_DEFAULT).any()))
308 , _exec(stringFromKeyfile<Exec>(keyfile, "Exec"))
309+ , _singleInstance(boolFromKeyfile<SingleInstance>(keyfile, "X-Ubuntu-Single-Instance", false))
310 {
311 }
312
313
314=== modified file 'libubuntu-app-launch/application-info-desktop.h'
315--- libubuntu-app-launch/application-info-desktop.h 2016-09-14 16:38:42 +0000
316+++ libubuntu-app-launch/application-info-desktop.h 2017-02-01 20:34:54 +0000
317@@ -106,6 +106,13 @@
318 return _exec;
319 }
320
321+ struct SingleInstanceTag;
322+ typedef TypeTagger<SingleInstanceTag, bool> SingleInstance;
323+ virtual SingleInstance singleInstance()
324+ {
325+ return _singleInstance;
326+ }
327+
328 protected:
329 std::shared_ptr<GKeyFile> _keyfile;
330 std::string _basePath;
331@@ -125,6 +132,7 @@
332
333 XMirEnable _xMirEnable;
334 Exec _exec;
335+ SingleInstance _singleInstance;
336 };
337
338 } // namespace AppInfo
339
340=== modified file 'libubuntu-app-launch/helper.h'
341--- libubuntu-app-launch/helper.h 2017-01-24 04:21:06 +0000
342+++ libubuntu-app-launch/helper.h 2017-02-01 20:34:54 +0000
343@@ -62,11 +62,11 @@
344 */
345 class Helper
346 {
347-/*
348-protected:
349- Helper() = default;
350- virtual ~Helper() = default;
351- TODO: Next ABI break */
352+ /*
353+ protected:
354+ Helper() = default;
355+ virtual ~Helper() = default;
356+ TODO: Next ABI break */
357
358 public:
359 /** \private */
360@@ -93,11 +93,11 @@
361 /** Running instance of a a Helper */
362 class Instance
363 {
364-/*
365- protected:
366- Instance() = default;
367- virtual ~Instance() = default;
368- TODO: Next ABI break */
369+ /*
370+ protected:
371+ Instance() = default;
372+ virtual ~Instance() = default;
373+ TODO: Next ABI break */
374
375 public:
376 /** Check to see if this instance is running */
377
378=== modified file 'libubuntu-app-launch/jobs-base.cpp'
379--- libubuntu-app-launch/jobs-base.cpp 2017-01-11 22:28:11 +0000
380+++ libubuntu-app-launch/jobs-base.cpp 2017-02-01 20:34:54 +0000
381@@ -24,6 +24,7 @@
382
383 #include "application-impl-base.h"
384 #include "jobs-base.h"
385+#include "jobs-systemd.h"
386 #include "jobs-upstart.h"
387 #include "registry-impl.h"
388
389@@ -38,6 +39,7 @@
390
391 Base::Base(const std::shared_ptr<Registry>& registry)
392 : registry_(registry)
393+ , allJobs_{"application-click", "application-legacy", "application-snap"}
394 , dbus_(registry->impl->_dbus)
395 {
396 }
397@@ -61,7 +63,25 @@
398
399 std::shared_ptr<Base> Base::determineFactory(std::shared_ptr<Registry> registry)
400 {
401- return std::make_shared<jobs::manager::Upstart>(registry);
402+ /* Checking to see if we have a user bus, that is only started
403+ by systemd so we're in good shape if we have one. We're using
404+ the path instead of the RUNTIME variable because we want to work
405+ around the case of being relocated by the snappy environment */
406+ if (g_file_test(SystemD::userBusPath().c_str(), G_FILE_TEST_EXISTS))
407+ {
408+ g_debug("Building a systemd jobs manager");
409+ return std::make_shared<jobs::manager::SystemD>(registry);
410+ }
411+ else
412+ {
413+ g_debug("Building an Upstart jobs manager");
414+ return std::make_shared<jobs::manager::Upstart>(registry);
415+ }
416+}
417+
418+const std::set<std::string>& Base::getAllJobs() const
419+{
420+ return allJobs_;
421 }
422
423 /** Structure to track the data needed for upstart events. This cleans
424@@ -438,7 +458,9 @@
425 bool Base::hasPid(pid_t pid)
426 {
427 auto vpids = pids();
428- return std::find(vpids.begin(), vpids.end(), pid) != vpids.end();
429+ bool hasit = std::find(vpids.begin(), vpids.end(), pid) != vpids.end();
430+ g_debug("Checking for PID %d on AppID '%s' result: %s", pid, std::string(appId_).c_str(), hasit ? "YES" : "NO");
431+ return hasit;
432 }
433
434 /** Pauses this application by sending SIGSTOP to all the PIDs in the
435@@ -745,6 +767,29 @@
436 }
437 }
438
439+/** Reformat a C++ vector of URLs into a C GStrv of strings
440+
441+ \param urls Vector of URLs to make into C strings
442+*/
443+std::shared_ptr<gchar*> Base::urlsToStrv(const std::vector<Application::URL>& urls)
444+{
445+ if (urls.empty())
446+ {
447+ return {};
448+ }
449+
450+ auto array = g_array_new(TRUE, FALSE, sizeof(gchar*));
451+
452+ for (auto url : urls)
453+ {
454+ auto str = g_strdup(url.value().c_str());
455+ g_debug("Converting URL: %s", str);
456+ g_array_append_val(array, str);
457+ }
458+
459+ return std::shared_ptr<gchar*>((gchar**)g_array_free(array, FALSE), g_strfreev);
460+}
461+
462 } // namespace instance
463
464 } // namespace jobs
465
466=== modified file 'libubuntu-app-launch/jobs-base.h'
467--- libubuntu-app-launch/jobs-base.h 2017-01-11 22:28:11 +0000
468+++ libubuntu-app-launch/jobs-base.h 2017-02-01 20:34:54 +0000
469@@ -24,6 +24,7 @@
470
471 #include <core/signal.h>
472 #include <gio/gio.h>
473+#include <set>
474
475 namespace ubuntu
476 {
477@@ -76,6 +77,7 @@
478 static void oomValueToPid(pid_t pid, const oom::Score oomvalue);
479 static void oomValueToPidHelper(pid_t pid, const oom::Score oomvalue);
480 static std::string pidToOomPath(pid_t pid);
481+ static std::shared_ptr<gchar*> urlsToStrv(const std::vector<Application::URL>& urls);
482 };
483
484 } // namespace instance
485@@ -113,6 +115,8 @@
486
487 virtual std::vector<std::shared_ptr<instance::Base>> instances(const AppID& appID, const std::string& job) = 0;
488
489+ const std::set<std::string>& getAllJobs() const;
490+
491 static std::shared_ptr<Base> determineFactory(std::shared_ptr<Registry> registry);
492
493 /* Signals to apps */
494@@ -141,6 +145,9 @@
495 /** A link to the registry */
496 std::weak_ptr<Registry> registry_;
497
498+ /** A set of all the job names */
499+ std::set<std::string> allJobs_;
500+
501 /** The DBus connection we're connecting to */
502 std::shared_ptr<GDBusConnection> dbus_;
503
504
505=== added file 'libubuntu-app-launch/jobs-systemd.cpp'
506--- libubuntu-app-launch/jobs-systemd.cpp 1970-01-01 00:00:00 +0000
507+++ libubuntu-app-launch/jobs-systemd.cpp 2017-02-01 20:34:54 +0000
508@@ -0,0 +1,1329 @@
509+/*
510+ * Copyright © 2016 Canonical Ltd.
511+ *
512+ * This program is free software: you can redistribute it and/or modify it
513+ * under the terms of the GNU General Public License version 3, as published
514+ * by the Free Software Foundation.
515+ *
516+ * This program is distributed in the hope that it will be useful, but
517+ * WITHOUT ANY WARRANTY; without even the implied warranties of
518+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
519+ * PURPOSE. See the GNU General Public License for more details.
520+ *
521+ * You should have received a copy of the GNU General Public License along
522+ * with this program. If not, see <http://www.gnu.org/licenses/>.
523+ *
524+ * Authors:
525+ * Ted Gould <ted.gould@canonical.com>
526+ */
527+
528+#include "jobs-systemd.h"
529+#include "application-impl-base.h"
530+#include "helpers.h"
531+#include "registry-impl.h"
532+#include "second-exec-core.h"
533+
534+extern "C" {
535+#include "ubuntu-app-launch-trace.h"
536+}
537+
538+#include <gio/gio.h>
539+#include <sys/types.h>
540+#include <unistd.h>
541+
542+#include <algorithm>
543+#include <numeric>
544+#include <regex>
545+
546+namespace ubuntu
547+{
548+namespace app_launch
549+{
550+namespace jobs
551+{
552+namespace instance
553+{
554+
555+class SystemD : public instance::Base
556+{
557+ friend class manager::SystemD;
558+
559+public:
560+ explicit SystemD(const AppID& appId,
561+ const std::string& job,
562+ const std::string& instance,
563+ const std::vector<Application::URL>& urls,
564+ const std::shared_ptr<Registry>& registry);
565+ virtual ~SystemD()
566+ {
567+ g_debug("Destroying a SystemD for '%s' instance '%s'", std::string(appId_).c_str(), instance_.c_str());
568+ }
569+
570+ /* Query lifecycle */
571+ pid_t primaryPid() override;
572+ std::string logPath() override;
573+ std::vector<pid_t> pids() override;
574+
575+ /* Manage lifecycle */
576+ void stop() override;
577+
578+}; // class SystemD
579+
580+SystemD::SystemD(const AppID& appId,
581+ const std::string& job,
582+ const std::string& instance,
583+ const std::vector<Application::URL>& urls,
584+ const std::shared_ptr<Registry>& registry)
585+ : Base(appId, job, instance, urls, registry)
586+{
587+ g_debug("Creating a new SystemD for '%s' instance '%s'", std::string(appId).c_str(), instance.c_str());
588+}
589+
590+pid_t SystemD::primaryPid()
591+{
592+ auto manager = std::dynamic_pointer_cast<manager::SystemD>(registry_->impl->jobs);
593+ return manager->unitPrimaryPid(appId_, job_, instance_);
594+}
595+
596+std::string SystemD::logPath()
597+{
598+ /* NOTE: We can never get this for systemd */
599+ g_warning("Log paths aren't available for systemd");
600+ return {};
601+}
602+
603+std::vector<pid_t> SystemD::pids()
604+{
605+ auto manager = std::dynamic_pointer_cast<manager::SystemD>(registry_->impl->jobs);
606+ return manager->unitPids(appId_, job_, instance_);
607+}
608+
609+void SystemD::stop()
610+{
611+ auto manager = std::dynamic_pointer_cast<manager::SystemD>(registry_->impl->jobs);
612+ manager->stopUnit(appId_, job_, instance_);
613+}
614+
615+} // namespace instance
616+
617+namespace manager
618+{
619+
620+static const char* SYSTEMD_DBUS_ADDRESS{"org.freedesktop.systemd1"};
621+static const char* SYSTEMD_DBUS_IFACE_MANAGER{"org.freedesktop.systemd1.Manager"};
622+static const char* SYSTEMD_DBUS_PATH_MANAGER{"/org/freedesktop/systemd1"};
623+// static const char * SYSTEMD_DBUS_IFACE_UNIT{"org.freedesktop.systemd1.Unit"};
624+static const char* SYSTEMD_DBUS_IFACE_SERVICE{"org.freedesktop.systemd1.Service"};
625+
626+SystemD::SystemD(std::shared_ptr<Registry> registry)
627+ : Base(registry)
628+{
629+ auto gcgroup_root = getenv("UBUNTU_APP_LAUNCH_SYSTEMD_CGROUP_ROOT");
630+ if (gcgroup_root == nullptr)
631+ {
632+ auto cpath = g_build_filename("/sys", "fs", "cgroup", "systemd", nullptr);
633+ cgroup_root_ = cpath;
634+ g_free(cpath);
635+ }
636+ else
637+ {
638+ cgroup_root_ = gcgroup_root;
639+ }
640+
641+ auto cancel = registry->impl->thread.getCancellable();
642+ userbus_ = registry->impl->thread.executeOnThread<std::shared_ptr<GDBusConnection>>([this, cancel]() {
643+ GError* error = nullptr;
644+ auto bus = std::shared_ptr<GDBusConnection>(
645+ [&]() -> GDBusConnection* {
646+ if (g_file_test(SystemD::userBusPath().c_str(), G_FILE_TEST_EXISTS))
647+ {
648+ return g_dbus_connection_new_for_address_sync(
649+ ("unix:path=" + userBusPath()).c_str(), /* path to the user bus */
650+ (GDBusConnectionFlags)(
651+ G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT |
652+ G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION), /* It is a message bus */
653+ nullptr, /* observer */
654+ cancel.get(), /* cancellable from the thread */
655+ &error); /* error */
656+ }
657+ else
658+ {
659+ /* Fallback mostly for testing */
660+ g_debug("Using session bus for systemd user bus");
661+ return g_bus_get_sync(G_BUS_TYPE_SESSION, /* type */
662+ cancel.get(), /* thread cancellable */
663+ &error); /* error */
664+ }
665+ }(),
666+ [](GDBusConnection* bus) { g_clear_object(&bus); });
667+
668+ if (error != nullptr)
669+ {
670+ std::string message = std::string("Unable to connect to user bus: ") + error->message;
671+ g_error_free(error);
672+ throw std::runtime_error(message);
673+ }
674+
675+ /* If we don't subscribe, it doesn't send us signals :-( */
676+ g_dbus_connection_call(bus.get(), /* user bus */
677+ SYSTEMD_DBUS_ADDRESS, /* bus name */
678+ SYSTEMD_DBUS_PATH_MANAGER, /* path */
679+ SYSTEMD_DBUS_IFACE_MANAGER, /* interface */
680+ "Subscribe", /* method */
681+ nullptr, /* params */
682+ nullptr, /* ret type */
683+ G_DBUS_CALL_FLAGS_NONE, /* flags */
684+ -1, /* timeout */
685+ cancel.get(), /* cancellable */
686+ [](GObject* obj, GAsyncResult* res, gpointer user_data) {
687+ GError* error{nullptr};
688+ GVariant* callt = g_dbus_connection_call_finish(G_DBUS_CONNECTION(obj), res, &error);
689+
690+ if (error != nullptr)
691+ {
692+ if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
693+ {
694+ g_warning("Unable to subscribe to SystemD: %s", error->message);
695+ }
696+ g_error_free(error);
697+ return;
698+ }
699+
700+ g_clear_pointer(&callt, g_variant_unref);
701+ g_debug("Subscribed to Systemd");
702+ },
703+ nullptr);
704+
705+ /* Setup Unit add/remove signals */
706+ handle_unitNew = g_dbus_connection_signal_subscribe(
707+ bus.get(), /* bus */
708+ nullptr, /* sender */
709+ SYSTEMD_DBUS_IFACE_MANAGER, /* interface */
710+ "UnitNew", /* signal */
711+ SYSTEMD_DBUS_PATH_MANAGER, /* path */
712+ nullptr, /* arg0 */
713+ G_DBUS_SIGNAL_FLAGS_NONE,
714+ [](GDBusConnection*, const gchar*, const gchar*, const gchar*, const gchar*, GVariant* params,
715+ gpointer user_data) -> void {
716+ auto pthis = static_cast<SystemD*>(user_data);
717+
718+ if (!g_variant_check_format_string(params, "(so)", FALSE))
719+ {
720+ g_warning("Got 'UnitNew' signal with unknown parameter type: %s",
721+ g_variant_get_type_string(params));
722+ return;
723+ }
724+
725+ const gchar* unitname{nullptr};
726+ const gchar* unitpath{nullptr};
727+
728+ g_variant_get(params, "(&s&o)", &unitname, &unitpath);
729+
730+ if (unitname == nullptr || unitpath == nullptr)
731+ {
732+ g_warning("Got 'UnitNew' signal with funky params %p, %p", unitname, unitpath);
733+ return;
734+ }
735+
736+ try
737+ {
738+ pthis->parseUnit(unitname);
739+ }
740+ catch (std::runtime_error& e)
741+ {
742+ /* Not for UAL */
743+ g_debug("Unable to parse unit: %s", unitname);
744+ return;
745+ }
746+
747+ try
748+ {
749+ auto info = pthis->unitNew(unitname, unitpath, pthis->userbus_);
750+ pthis->emitSignal(pthis->sig_appStarted, info);
751+ }
752+ catch (std::runtime_error& e)
753+ {
754+ g_warning("%s", e.what());
755+ }
756+ }, /* callback */
757+ this, /* user data */
758+ nullptr); /* user data destroy */
759+
760+ handle_unitRemoved = g_dbus_connection_signal_subscribe(
761+ bus.get(), /* bus */
762+ nullptr, /* sender */
763+ SYSTEMD_DBUS_IFACE_MANAGER, /* interface */
764+ "UnitRemoved", /* signal */
765+ SYSTEMD_DBUS_PATH_MANAGER, /* path */
766+ nullptr, /* arg0 */
767+ G_DBUS_SIGNAL_FLAGS_NONE,
768+ [](GDBusConnection*, const gchar*, const gchar*, const gchar*, const gchar*, GVariant* params,
769+ gpointer user_data) -> void {
770+ auto pthis = static_cast<SystemD*>(user_data);
771+
772+ if (!g_variant_check_format_string(params, "(so)", FALSE))
773+ {
774+ g_warning("Got 'UnitRemoved' signal with unknown parameter type: %s",
775+ g_variant_get_type_string(params));
776+ return;
777+ }
778+
779+ const gchar* unitname{nullptr};
780+ const gchar* unitpath{nullptr};
781+
782+ g_variant_get(params, "(&s&o)", &unitname, &unitpath);
783+
784+ if (unitname == nullptr || unitpath == nullptr)
785+ {
786+ g_warning("Got 'UnitRemoved' signal with funky params %p, %p", unitname, unitpath);
787+ return;
788+ }
789+
790+ try
791+ {
792+ pthis->parseUnit(unitname);
793+ }
794+ catch (std::runtime_error& e)
795+ {
796+ /* Not for UAL */
797+ g_debug("Unable to parse unit: %s", unitname);
798+ return;
799+ }
800+
801+ pthis->unitRemoved(unitname, unitpath);
802+ }, /* callback */
803+ this, /* user data */
804+ nullptr); /* user data destroy */
805+
806+ getInitialUnits(bus, cancel);
807+
808+ return bus;
809+ });
810+}
811+
812+SystemD::~SystemD()
813+{
814+ auto unsub = [&](guint& handle) {
815+ if (handle != 0)
816+ {
817+ g_dbus_connection_signal_unsubscribe(userbus_.get(), handle);
818+ handle = 0;
819+ }
820+ };
821+
822+ unsub(handle_unitNew);
823+ unsub(handle_unitRemoved);
824+ unsub(handle_appFailed);
825+}
826+
827+void SystemD::getInitialUnits(const std::shared_ptr<GDBusConnection>& bus, const std::shared_ptr<GCancellable>& cancel)
828+{
829+ GError* error = nullptr;
830+
831+ auto callt = g_dbus_connection_call_sync(bus.get(), /* user bus */
832+ SYSTEMD_DBUS_ADDRESS, /* bus name */
833+ SYSTEMD_DBUS_PATH_MANAGER, /* path */
834+ SYSTEMD_DBUS_IFACE_MANAGER, /* interface */
835+ "ListUnits", /* method */
836+ nullptr, /* params */
837+ G_VARIANT_TYPE("(a(ssssssouso))"), /* ret type */
838+ G_DBUS_CALL_FLAGS_NONE, /* flags */
839+ -1, /* timeout */
840+ cancel.get(), /* cancellable */
841+ &error);
842+
843+ if (error != nullptr)
844+ {
845+ if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
846+ {
847+ g_warning("Unable to list SystemD units: %s", error->message);
848+ }
849+ g_error_free(error);
850+ return;
851+ }
852+
853+ GVariant* call = g_variant_get_child_value(callt, 0);
854+ g_variant_unref(callt);
855+
856+ const gchar* id;
857+ const gchar* description;
858+ const gchar* loadState;
859+ const gchar* activeState;
860+ const gchar* subState;
861+ const gchar* following;
862+ const gchar* path;
863+ guint32 jobId;
864+ const gchar* jobType;
865+ const gchar* jobPath;
866+ auto iter = g_variant_iter_new(call);
867+ while (g_variant_iter_loop(iter, "(&s&s&s&s&s&s&ou&s&o)", &id, &description, &loadState, &activeState, &subState,
868+ &following, &path, &jobId, &jobType, &jobPath))
869+ {
870+ g_debug("List Units: %s", id);
871+ try
872+ {
873+ unitNew(id, jobPath, bus);
874+ }
875+ catch (std::runtime_error& e)
876+ {
877+ g_debug("%s", e.what());
878+ }
879+ }
880+
881+ g_variant_iter_free(iter);
882+ g_variant_unref(call);
883+}
884+
885+std::string SystemD::findEnv(const std::string& value, std::list<std::pair<std::string, std::string>>& env)
886+{
887+ std::string retval;
888+ auto entry = std::find_if(env.begin(), env.end(),
889+ [&value](std::pair<std::string, std::string>& entry) { return entry.first == value; });
890+
891+ if (entry != env.end())
892+ {
893+ retval = entry->second;
894+ }
895+
896+ return retval;
897+}
898+
899+void SystemD::removeEnv(const std::string& value, std::list<std::pair<std::string, std::string>>& env)
900+{
901+ auto entry = std::find_if(env.begin(), env.end(),
902+ [&value](std::pair<std::string, std::string>& entry) { return entry.first == value; });
903+
904+ if (entry != env.end())
905+ {
906+ env.erase(entry);
907+ }
908+}
909+
910+int SystemD::envSize(std::list<std::pair<std::string, std::string>>& env)
911+{
912+ int len = std::string{"Environment="}.length();
913+
914+ for (const auto& entry : env)
915+ {
916+ len += 3; /* two quotes, one space */
917+ len += entry.first.length();
918+ len += entry.second.length();
919+ }
920+
921+ len -= 1; /* We account for a space each time but the first doesn't have */
922+
923+ return len;
924+}
925+
926+std::vector<std::string> SystemD::parseExec(std::list<std::pair<std::string, std::string>>& env)
927+{
928+ auto exec = findEnv("APP_EXEC", env);
929+ if (exec.empty())
930+ {
931+ g_warning("Application exec line is empty?!?!?");
932+ return {};
933+ }
934+ auto uris = findEnv("APP_URIS", env);
935+
936+ g_debug("Exec line: %s", exec.c_str());
937+ g_debug("App URLS: %s", uris.c_str());
938+
939+ auto execarray = desktop_exec_parse(exec.c_str(), uris.c_str());
940+
941+ std::vector<std::string> retval;
942+ for (unsigned int i = 0; i < execarray->len; i++)
943+ {
944+ auto cstr = g_array_index(execarray, gchar*, i);
945+ if (cstr != nullptr)
946+ {
947+ retval.emplace_back(cstr);
948+ }
949+ }
950+
951+ /* This seems to work better than array_free(), I can't figure out why */
952+ auto strv = (gchar**)g_array_free(execarray, FALSE);
953+ g_strfreev(strv);
954+
955+ if (retval.empty())
956+ {
957+ g_warning("After parsing 'APP_EXEC=%s' we ended up with no tokens", exec.c_str());
958+ }
959+
960+ /* See if we need the xmir helper */
961+ if (findEnv("APP_XMIR_ENABLE", env) == "1" && getenv("DISPLAY") == nullptr)
962+ {
963+ retval.emplace(retval.begin(), findEnv("APP_ID", env));
964+ retval.emplace(retval.begin(), XMIR_HELPER);
965+ }
966+
967+ /* See if we're doing apparmor by hand */
968+ auto appexecpolicy = findEnv("APP_EXEC_POLICY", env);
969+ if (!appexecpolicy.empty() && appexecpolicy != "unconfined")
970+ {
971+ retval.emplace(retval.begin(), appexecpolicy);
972+ retval.emplace(retval.begin(), "-p");
973+ retval.emplace(retval.begin(), "aa-exec");
974+ }
975+
976+ return retval;
977+}
978+
979+/** Small helper that we can new/delete to work better with C stuff */
980+struct StartCHelper
981+{
982+ std::shared_ptr<instance::SystemD> ptr;
983+ std::shared_ptr<GDBusConnection> bus;
984+};
985+
986+void SystemD::application_start_cb(GObject* obj, GAsyncResult* res, gpointer user_data)
987+{
988+ auto data = static_cast<StartCHelper*>(user_data);
989+
990+ tracepoint(ubuntu_app_launch, libual_start_message_callback, std::string(data->ptr->appId_).c_str());
991+
992+ g_debug("Started Message Callback: %s", std::string(data->ptr->appId_).c_str());
993+
994+ GError* error{nullptr};
995+ GVariant* result{nullptr};
996+
997+ result = g_dbus_connection_call_finish(G_DBUS_CONNECTION(obj), res, &error);
998+
999+ /* We don't care about the result but we need to make sure we don't
1000+ have a leak. */
1001+ g_clear_pointer(&result, g_variant_unref);
1002+
1003+ if (error != nullptr)
1004+ {
1005+ if (g_dbus_error_is_remote_error(error))
1006+ {
1007+ gchar* remote_error = g_dbus_error_get_remote_error(error);
1008+ g_debug("Remote error: %s", remote_error);
1009+ if (g_strcmp0(remote_error, "org.freedesktop.systemd1.UnitExists") == 0)
1010+ {
1011+ auto urls = instance::SystemD::urlsToStrv(data->ptr->urls_);
1012+ second_exec(data->bus.get(), /* DBus */
1013+ data->ptr->registry_->impl->thread.getCancellable().get(), /* cancellable */
1014+ data->ptr->primaryPid(), /* primary pid */
1015+ std::string(data->ptr->appId_).c_str(), /* appid */
1016+ data->ptr->instance_.c_str(), /* instance */
1017+ urls.get()); /* urls */
1018+ }
1019+
1020+ g_free(remote_error);
1021+ }
1022+ else
1023+ {
1024+ if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
1025+ {
1026+ g_warning("Unable to emit event to start application: %s", error->message);
1027+ }
1028+ }
1029+ g_error_free(error);
1030+ }
1031+
1032+ delete data;
1033+}
1034+
1035+void SystemD::copyEnv(const std::string& envname, std::list<std::pair<std::string, std::string>>& env)
1036+{
1037+ if (!findEnv(envname, env).empty())
1038+ {
1039+ g_debug("Already a value set for '%s' ignoring", envname.c_str());
1040+ return;
1041+ }
1042+
1043+ auto cvalue = getenv(envname.c_str());
1044+ g_debug("Copying Environment: %s", envname.c_str());
1045+ if (cvalue != nullptr)
1046+ {
1047+ std::string value{cvalue};
1048+ env.emplace_back(std::make_pair(envname, value));
1049+ }
1050+ else
1051+ {
1052+ g_debug("Unable to copy environment '%s'", envname.c_str());
1053+ }
1054+}
1055+
1056+void SystemD::copyEnvByPrefix(const std::string& prefix, std::list<std::pair<std::string, std::string>>& env)
1057+{
1058+ for (unsigned int i = 0; environ[i] != nullptr; i++)
1059+ {
1060+ if (g_str_has_prefix(environ[i], prefix.c_str()))
1061+ {
1062+ std::string envname = environ[i];
1063+ envname.erase(envname.find('='));
1064+ copyEnv(envname, env);
1065+ }
1066+ }
1067+}
1068+
1069+std::shared_ptr<Application::Instance> SystemD::launch(
1070+ const AppID& appId,
1071+ const std::string& job,
1072+ const std::string& instance,
1073+ const std::vector<Application::URL>& urls,
1074+ launchMode mode,
1075+ std::function<std::list<std::pair<std::string, std::string>>(void)>& getenv)
1076+{
1077+ if (appId.empty())
1078+ return {};
1079+
1080+ auto registry = registry_.lock();
1081+ return registry->impl->thread.executeOnThread<std::shared_ptr<instance::SystemD>>(
1082+ [&]() -> std::shared_ptr<instance::SystemD> {
1083+ auto manager = std::dynamic_pointer_cast<manager::SystemD>(registry->impl->jobs);
1084+ std::string appIdStr{appId};
1085+ g_debug("Initializing params for an new instance::SystemD for: %s", appIdStr.c_str());
1086+
1087+ tracepoint(ubuntu_app_launch, libual_start, appIdStr.c_str());
1088+
1089+ int timeout = 1;
1090+ if (ubuntu::app_launch::Registry::Impl::isWatchingAppStarting())
1091+ {
1092+ timeout = 0;
1093+ }
1094+
1095+ auto handshake = starting_handshake_start(appIdStr.c_str(), instance.c_str(), timeout);
1096+ if (handshake == nullptr)
1097+ {
1098+ g_warning("Unable to setup starting handshake");
1099+ }
1100+
1101+ /* Figure out the unit name for the job */
1102+ auto unitname = unitName(SystemD::UnitInfo{appIdStr, job, instance});
1103+
1104+ /* Build up our environment */
1105+ auto env = getenv();
1106+
1107+ env.emplace_back(std::make_pair("APP_ID", appIdStr)); /* Application ID */
1108+ env.emplace_back(std::make_pair("APP_LAUNCHER_PID", std::to_string(getpid()))); /* Who we are, for bugs */
1109+
1110+ copyEnv("DISPLAY", env);
1111+ for (const auto prefix : {"DBUS_", "MIR_", "QT_", "UBUNTU_", "UNITY_", "XDG_"})
1112+ {
1113+ copyEnvByPrefix(prefix, env);
1114+ }
1115+
1116+ if (!urls.empty())
1117+ {
1118+ auto accumfunc = [](const std::string& prev, Application::URL thisurl) -> std::string {
1119+ gchar* gescaped = g_shell_quote(thisurl.value().c_str());
1120+ std::string escaped;
1121+ if (gescaped != nullptr)
1122+ {
1123+ escaped = gescaped;
1124+ g_free(gescaped);
1125+ }
1126+ else
1127+ {
1128+ g_warning("Unable to escape URL: %s", thisurl.value().c_str());
1129+ return prev;
1130+ }
1131+
1132+ if (prev.empty())
1133+ {
1134+ return escaped;
1135+ }
1136+ else
1137+ {
1138+ return prev + " " + escaped;
1139+ }
1140+ };
1141+ auto urlstring = std::accumulate(urls.begin(), urls.end(), std::string{}, accumfunc);
1142+ env.emplace_back(std::make_pair("APP_URIS", urlstring));
1143+ }
1144+
1145+ if (mode == launchMode::TEST)
1146+ {
1147+ env.emplace_back(std::make_pair("QT_LOAD_TESTABILITY", "1"));
1148+ }
1149+
1150+ /* Convert to GVariant */
1151+ GVariantBuilder builder;
1152+ g_variant_builder_init(&builder, G_VARIANT_TYPE_TUPLE);
1153+
1154+ g_variant_builder_add_value(&builder, g_variant_new_string(unitname.c_str()));
1155+ g_variant_builder_add_value(&builder, g_variant_new_string("replace")); // Job mode
1156+
1157+ /* Parameter Array */
1158+ g_variant_builder_open(&builder, G_VARIANT_TYPE_ARRAY);
1159+
1160+ /* ExecStart */
1161+ auto commands = parseExec(env);
1162+ if (!commands.empty())
1163+ {
1164+ g_variant_builder_open(&builder, G_VARIANT_TYPE_TUPLE);
1165+ g_variant_builder_add_value(&builder, g_variant_new_string("ExecStart"));
1166+ g_variant_builder_open(&builder, G_VARIANT_TYPE_VARIANT);
1167+ g_variant_builder_open(&builder, G_VARIANT_TYPE_ARRAY);
1168+
1169+ g_variant_builder_open(&builder, G_VARIANT_TYPE_TUPLE);
1170+
1171+ gchar* pathexec = g_find_program_in_path(commands[0].c_str());
1172+ if (pathexec != nullptr)
1173+ {
1174+ g_variant_builder_add_value(&builder, g_variant_new_take_string(pathexec));
1175+ }
1176+ else
1177+ {
1178+ g_debug("Unable to find '%s' in PATH=%s", commands[0].c_str(), g_getenv("PATH"));
1179+ g_variant_builder_add_value(&builder, g_variant_new_string(commands[0].c_str()));
1180+ }
1181+
1182+ g_variant_builder_open(&builder, G_VARIANT_TYPE_ARRAY);
1183+ for (const auto& param : commands)
1184+ {
1185+ g_variant_builder_add_value(&builder, g_variant_new_string(param.c_str()));
1186+ }
1187+ g_variant_builder_close(&builder);
1188+
1189+ g_variant_builder_add_value(&builder, g_variant_new_boolean(FALSE));
1190+
1191+ g_variant_builder_close(&builder);
1192+ g_variant_builder_close(&builder);
1193+ g_variant_builder_close(&builder);
1194+ g_variant_builder_close(&builder);
1195+ }
1196+
1197+ /* RemainAfterExit */
1198+ g_variant_builder_open(&builder, G_VARIANT_TYPE_TUPLE);
1199+ g_variant_builder_add_value(&builder, g_variant_new_string("RemainAfterExit"));
1200+ g_variant_builder_open(&builder, G_VARIANT_TYPE_VARIANT);
1201+ g_variant_builder_add_value(&builder, g_variant_new_boolean(FALSE));
1202+ g_variant_builder_close(&builder);
1203+ g_variant_builder_close(&builder);
1204+
1205+ /* Type */
1206+ g_variant_builder_open(&builder, G_VARIANT_TYPE_TUPLE);
1207+ g_variant_builder_add_value(&builder, g_variant_new_string("Type"));
1208+ g_variant_builder_open(&builder, G_VARIANT_TYPE_VARIANT);
1209+ g_variant_builder_add_value(&builder, g_variant_new_string("oneshot"));
1210+ g_variant_builder_close(&builder);
1211+ g_variant_builder_close(&builder);
1212+
1213+ /* Working Directory */
1214+ if (!findEnv("APP_DIR", env).empty())
1215+ {
1216+ g_variant_builder_open(&builder, G_VARIANT_TYPE_TUPLE);
1217+ g_variant_builder_add_value(&builder, g_variant_new_string("WorkingDirectory"));
1218+ g_variant_builder_open(&builder, G_VARIANT_TYPE_VARIANT);
1219+ g_variant_builder_add_value(&builder, g_variant_new_string(findEnv("APP_DIR", env).c_str()));
1220+ g_variant_builder_close(&builder);
1221+ g_variant_builder_close(&builder);
1222+ }
1223+
1224+ /* Clean up env before shipping it */
1225+ for (const auto rmenv :
1226+ {"APP_XMIR_ENABLE", "APP_DIR", "APP_URIS", "APP_EXEC", "APP_EXEC_POLICY", "APP_LAUNCHER_PID",
1227+ "INSTANCE_ID", "MIR_SERVER_PLATFORM_PATH", "MIR_SERVER_PROMPT_FILE", "MIR_SERVER_HOST_SOCKET",
1228+ "UBUNTU_APP_LAUNCH_DEMANGLER", "UBUNTU_APP_LAUNCH_OOM_HELPER", "UBUNTU_APP_LAUNCH_LEGACY_ROOT",
1229+ "UBUNTU_APP_LAUNCH_XMIR_HELPER"})
1230+ {
1231+ removeEnv(rmenv, env);
1232+ }
1233+
1234+ g_debug("Environment length: %d", envSize(env));
1235+
1236+ /* Environment */
1237+ g_variant_builder_open(&builder, G_VARIANT_TYPE_TUPLE);
1238+ g_variant_builder_add_value(&builder, g_variant_new_string("Environment"));
1239+ g_variant_builder_open(&builder, G_VARIANT_TYPE_VARIANT);
1240+ g_variant_builder_open(&builder, G_VARIANT_TYPE_ARRAY);
1241+ for (const auto& envvar : env)
1242+ {
1243+ if (!envvar.first.empty() && !envvar.second.empty())
1244+ {
1245+ g_variant_builder_add_value(&builder, g_variant_new_take_string(g_strdup_printf(
1246+ "%s=%s", envvar.first.c_str(), envvar.second.c_str())));
1247+ // g_debug("Setting environment: %s=%s", envvar.first.c_str(), envvar.second.c_str());
1248+ }
1249+ }
1250+
1251+ g_variant_builder_close(&builder);
1252+ g_variant_builder_close(&builder);
1253+ g_variant_builder_close(&builder);
1254+
1255+ /* Parameter Array */
1256+ g_variant_builder_close(&builder);
1257+
1258+ /* Dependent Units (none) */
1259+ g_variant_builder_add_value(&builder, g_variant_new_array(G_VARIANT_TYPE("(sa(sv))"), nullptr, 0));
1260+
1261+ auto retval = std::make_shared<instance::SystemD>(appId, job, instance, urls, registry);
1262+ auto chelper = new StartCHelper{};
1263+ chelper->ptr = retval;
1264+ chelper->bus = registry->impl->_dbus;
1265+
1266+ tracepoint(ubuntu_app_launch, handshake_wait, appIdStr.c_str());
1267+ starting_handshake_wait(handshake);
1268+ tracepoint(ubuntu_app_launch, handshake_complete, appIdStr.c_str());
1269+
1270+ /* Call the job start function */
1271+ g_debug("Asking systemd to start task for: %s", appIdStr.c_str());
1272+ g_dbus_connection_call(manager->userbus_.get(), /* bus */
1273+ SYSTEMD_DBUS_ADDRESS, /* service name */
1274+ SYSTEMD_DBUS_PATH_MANAGER, /* Path */
1275+ SYSTEMD_DBUS_IFACE_MANAGER, /* interface */
1276+ "StartTransientUnit", /* method */
1277+ g_variant_builder_end(&builder), /* params */
1278+ G_VARIANT_TYPE("(o)"), /* return */
1279+ G_DBUS_CALL_FLAGS_NONE, /* flags */
1280+ -1, /* default timeout */
1281+ registry->impl->thread.getCancellable().get(), /* cancellable */
1282+ application_start_cb, /* callback */
1283+ chelper /* object */
1284+ );
1285+
1286+ tracepoint(ubuntu_app_launch, libual_start_message_sent, appIdStr.c_str());
1287+
1288+ return retval;
1289+ });
1290+}
1291+
1292+std::shared_ptr<Application::Instance> SystemD::existing(const AppID& appId,
1293+ const std::string& job,
1294+ const std::string& instance,
1295+ const std::vector<Application::URL>& urls)
1296+{
1297+ return std::make_shared<instance::SystemD>(appId, job, instance, urls, registry_.lock());
1298+}
1299+
1300+std::vector<std::shared_ptr<instance::Base>> SystemD::instances(const AppID& appID, const std::string& job)
1301+{
1302+ std::vector<std::shared_ptr<instance::Base>> instances;
1303+ std::vector<Application::URL> urls;
1304+ auto registry = registry_.lock();
1305+
1306+ if (!registry)
1307+ {
1308+ g_warning("Unable to list instances without a registry");
1309+ return {};
1310+ }
1311+
1312+ std::string sappid{appID};
1313+ for (const auto& unit : unitPaths)
1314+ {
1315+ const SystemD::UnitInfo& unitinfo = unit.first;
1316+
1317+ if (job != unitinfo.job)
1318+ {
1319+ continue;
1320+ }
1321+
1322+ if (sappid != unitinfo.appid)
1323+ {
1324+ continue;
1325+ }
1326+
1327+ instances.emplace_back(std::make_shared<instance::SystemD>(appID, job, unitinfo.inst, urls, registry));
1328+ }
1329+
1330+ g_debug("Found %d instances for AppID '%s'", int(instances.size()), std::string(appID).c_str());
1331+
1332+ return instances;
1333+}
1334+
1335+std::list<std::shared_ptr<Application>> SystemD::runningApps()
1336+{
1337+ auto registry = registry_.lock();
1338+
1339+ if (!registry)
1340+ {
1341+ g_warning("Unable to list instances without a registry");
1342+ return {};
1343+ }
1344+
1345+ auto allJobs = getAllJobs();
1346+ std::set<std::string> appids;
1347+
1348+ for (const auto& unit : unitPaths)
1349+ {
1350+ const SystemD::UnitInfo& unitinfo = unit.first;
1351+
1352+ if (allJobs.find(unitinfo.job) == allJobs.end())
1353+ {
1354+ continue;
1355+ }
1356+
1357+ appids.insert(unitinfo.appid);
1358+ }
1359+
1360+ std::list<std::shared_ptr<Application>> apps;
1361+ for (const auto& appid : appids)
1362+ {
1363+ auto id = AppID::find(registry, appid);
1364+ if (id.empty())
1365+ {
1366+ g_debug("Unable to handle AppID: %s", appid.c_str());
1367+ continue;
1368+ }
1369+
1370+ apps.emplace_back(Application::create(id, registry));
1371+ }
1372+
1373+ return apps;
1374+}
1375+
1376+std::string SystemD::userBusPath()
1377+{
1378+ auto cpath = getenv("UBUNTU_APP_LAUNCH_SYSTEMD_PATH");
1379+ if (cpath != nullptr)
1380+ {
1381+ return cpath;
1382+ }
1383+ return std::string{"/run/user/"} + std::to_string(getuid()) + std::string{"/bus"};
1384+}
1385+
1386+/* TODO: Application job names */
1387+const std::regex unitNaming{
1388+ "^ubuntu\\-app\\-launch\\-(application\\-(?:click|legacy|snap))\\-(.*)\\-([0-9]*)\\.service$"};
1389+
1390+SystemD::UnitInfo SystemD::parseUnit(const std::string& unit) const
1391+{
1392+ std::smatch match;
1393+ if (!std::regex_match(unit, match, unitNaming))
1394+ {
1395+ throw std::runtime_error{"Unable to parse unit name: " + unit};
1396+ }
1397+
1398+ return {match[2].str(), match[1].str(), match[3].str()};
1399+}
1400+
1401+std::string SystemD::unitName(const SystemD::UnitInfo& info) const
1402+{
1403+ return std::string{"ubuntu-app-launch-"} + info.job + "-" + info.appid + "-" + info.inst + ".service";
1404+}
1405+
1406+std::string SystemD::unitPath(const SystemD::UnitInfo& info)
1407+{
1408+ auto data = unitPaths[info];
1409+
1410+ if (!data)
1411+ {
1412+ return {};
1413+ }
1414+
1415+ auto registry = registry_.lock();
1416+
1417+ if (!registry)
1418+ {
1419+ g_warning("Unable to get registry to determine path");
1420+ return {};
1421+ }
1422+
1423+ /* Execute on the thread so that we're sure that we're not in
1424+ a dbus call to get the value. No racey for you! */
1425+ return registry->impl->thread.executeOnThread<std::string>([&data]() { return data->unitpath; });
1426+}
1427+
1428+SystemD::UnitInfo SystemD::unitNew(const std::string& name,
1429+ const std::string& path,
1430+ const std::shared_ptr<GDBusConnection>& bus)
1431+{
1432+ if (path == "/")
1433+ {
1434+ throw std::runtime_error{"Job path for unit is '/' so likely failed"};
1435+ }
1436+
1437+ g_debug("New Unit: %s", name.c_str());
1438+
1439+ auto info = parseUnit(name);
1440+
1441+ auto data = std::make_shared<UnitData>();
1442+ data->jobpath = path;
1443+
1444+ /* We already have this one, continue on */
1445+ if (!unitPaths.insert(std::make_pair(info, data)).second)
1446+ {
1447+ throw std::runtime_error{"Duplicate unit, not really new"};
1448+ }
1449+
1450+ /* We need to get the path, we're blocking everyone else on
1451+ this call if they try to get the path. But we're just locking
1452+ up the UAL thread so it should be a big deal. But if someone
1453+ comes an asking at this point we'll think that we have the
1454+ app, but not yet its path */
1455+ GError* error{nullptr};
1456+ auto reg = registry_.lock();
1457+
1458+ if (!reg)
1459+ {
1460+ g_warning("Unable to get SystemD unit path for '%s': Registry out of scope", name.c_str());
1461+ throw std::runtime_error{"Unable to get SystemD unit path for '" + name + "': Registry out of scope"};
1462+ }
1463+
1464+ GVariant* call = g_dbus_connection_call_sync(bus.get(), /* user bus */
1465+ SYSTEMD_DBUS_ADDRESS, /* bus name */
1466+ SYSTEMD_DBUS_PATH_MANAGER, /* path */
1467+ SYSTEMD_DBUS_IFACE_MANAGER, /* interface */
1468+ "GetUnit", /* method */
1469+ g_variant_new("(s)", name.c_str()), /* params */
1470+ G_VARIANT_TYPE("(o)"), /* ret type */
1471+ G_DBUS_CALL_FLAGS_NONE, /* flags */
1472+ -1, /* timeout */
1473+ reg->impl->thread.getCancellable().get(), /* cancellable */
1474+ &error);
1475+
1476+ if (error != nullptr)
1477+ {
1478+ std::string message = "Unable to get SystemD unit path for '" + name + "': " + error->message;
1479+ g_error_free(error);
1480+ throw std::runtime_error{message};
1481+ }
1482+
1483+ /* Parse variant */
1484+ gchar* gpath = nullptr;
1485+ g_variant_get(call, "(o)", &gpath);
1486+ if (gpath != nullptr)
1487+ {
1488+ data->unitpath = gpath;
1489+ }
1490+
1491+ g_clear_pointer(&call, g_variant_unref);
1492+
1493+ return info;
1494+}
1495+
1496+void SystemD::unitRemoved(const std::string& name, const std::string& path)
1497+{
1498+ UnitInfo info = parseUnit(name);
1499+
1500+ auto it = unitPaths.find(info);
1501+ if (it != unitPaths.end())
1502+ {
1503+ unitPaths.erase(it);
1504+ emitSignal(sig_appStopped, info);
1505+ }
1506+}
1507+
1508+void SystemD::emitSignal(
1509+ core::Signal<const std::shared_ptr<Application>&, const std::shared_ptr<Application::Instance>&>& sig,
1510+ UnitInfo& info)
1511+{
1512+ auto reg = registry_.lock();
1513+ if (!reg)
1514+ {
1515+ g_warning("Unable to emit systemd signal, invalid registry");
1516+ return;
1517+ }
1518+
1519+ auto appid = AppID::find(reg, info.appid);
1520+ auto app = Application::create(appid, reg);
1521+ auto inst = std::dynamic_pointer_cast<app_impls::Base>(app)->findInstance(info.inst);
1522+
1523+ sig(app, inst);
1524+}
1525+
1526+pid_t SystemD::unitPrimaryPid(const AppID& appId, const std::string& job, const std::string& instance)
1527+{
1528+ auto registry = registry_.lock();
1529+
1530+ if (!registry)
1531+ {
1532+ g_warning("Unable to get registry to determine primary PID");
1533+ return 0;
1534+ }
1535+
1536+ auto unitinfo = SystemD::UnitInfo{appId, job, instance};
1537+ auto unitname = unitName(unitinfo);
1538+ auto unitpath = unitPath(unitinfo);
1539+
1540+ if (unitpath.empty())
1541+ {
1542+ return 0;
1543+ }
1544+
1545+ return registry->impl->thread.executeOnThread<pid_t>([this, registry, unitname, unitpath]() {
1546+ GError* error{nullptr};
1547+ GVariant* call =
1548+ g_dbus_connection_call_sync(userbus_.get(), /* user bus */
1549+ SYSTEMD_DBUS_ADDRESS, /* bus name */
1550+ unitpath.c_str(), /* path */
1551+ "org.freedesktop.DBus.Properties", /* interface */
1552+ "Get", /* method */
1553+ g_variant_new("(ss)", SYSTEMD_DBUS_IFACE_SERVICE, "MainPID"), /* params */
1554+ G_VARIANT_TYPE("(v)"), /* ret type */
1555+ G_DBUS_CALL_FLAGS_NONE, /* flags */
1556+ -1, /* timeout */
1557+ registry->impl->thread.getCancellable().get(), /* cancellable */
1558+ &error);
1559+
1560+ if (error != nullptr)
1561+ {
1562+ auto message =
1563+ std::string{"Unable to get SystemD PID for '"} + unitname + std::string{"': "} + error->message;
1564+ g_error_free(error);
1565+ throw std::runtime_error(message);
1566+ }
1567+
1568+ /* Parse variant */
1569+ GVariant* vpid{nullptr};
1570+ g_variant_get(call, "(v)", &vpid);
1571+ g_clear_pointer(&call, g_variant_unref);
1572+
1573+ pid_t pid;
1574+ pid = g_variant_get_uint32(vpid);
1575+ g_clear_pointer(&vpid, g_variant_unref);
1576+
1577+ return pid;
1578+ });
1579+}
1580+
1581+std::vector<pid_t> SystemD::unitPids(const AppID& appId, const std::string& job, const std::string& instance)
1582+{
1583+ auto registry = registry_.lock();
1584+
1585+ if (!registry)
1586+ {
1587+ g_warning("Unable to get registry to determine primary PID");
1588+ return {};
1589+ }
1590+
1591+ auto unitinfo = SystemD::UnitInfo{appId, job, instance};
1592+ auto unitname = unitName(unitinfo);
1593+ auto unitpath = unitPath(unitinfo);
1594+
1595+ if (unitpath.empty())
1596+ {
1597+ return {};
1598+ }
1599+
1600+ auto cgrouppath = registry->impl->thread.executeOnThread<std::string>([this, registry, unitname, unitpath]() {
1601+ GError* error{nullptr};
1602+ GVariant* call =
1603+ g_dbus_connection_call_sync(userbus_.get(), /* user bus */
1604+ SYSTEMD_DBUS_ADDRESS, /* bus name */
1605+ unitpath.c_str(), /* path */
1606+ "org.freedesktop.DBus.Properties", /* interface */
1607+ "Get", /* method */
1608+ g_variant_new("(ss)", SYSTEMD_DBUS_IFACE_SERVICE, "ControlGroup"), /* params */
1609+ G_VARIANT_TYPE("(v)"), /* ret type */
1610+ G_DBUS_CALL_FLAGS_NONE, /* flags */
1611+ -1, /* timeout */
1612+ registry->impl->thread.getCancellable().get(), /* cancellable */
1613+ &error);
1614+
1615+ if (error != nullptr)
1616+ {
1617+ auto message = std::string{"Unable to get SystemD Control Group for '"} + unitname + std::string{"': "} +
1618+ error->message;
1619+ g_error_free(error);
1620+ throw std::runtime_error(message);
1621+ }
1622+
1623+ /* Parse variant */
1624+ GVariant* vstring = nullptr;
1625+ g_variant_get(call, "(v)", &vstring);
1626+ g_clear_pointer(&call, g_variant_unref);
1627+
1628+ if (vstring == nullptr)
1629+ {
1630+ return std::string{};
1631+ }
1632+
1633+ std::string group;
1634+ auto ggroup = g_variant_get_string(vstring, nullptr);
1635+ if (ggroup != nullptr)
1636+ {
1637+ group = ggroup;
1638+ }
1639+ g_variant_unref(vstring);
1640+
1641+ return group;
1642+ });
1643+
1644+ gchar* fullpath = g_build_filename(cgroup_root_.c_str(), cgrouppath.c_str(), "tasks", nullptr);
1645+ gchar* pidstr = nullptr;
1646+ GError* error = nullptr;
1647+
1648+ g_debug("Getting PIDs from %s", fullpath);
1649+ g_file_get_contents(fullpath, &pidstr, nullptr, &error);
1650+ g_free(fullpath);
1651+
1652+ if (error != nullptr)
1653+ {
1654+ g_warning("Unable to read cgroup PID list: %s", error->message);
1655+ g_error_free(error);
1656+ return {};
1657+ }
1658+
1659+ gchar** pidlines = g_strsplit(pidstr, "\n", -1);
1660+ g_free(pidstr);
1661+ std::vector<pid_t> pids;
1662+
1663+ for (auto i = 0; pidlines[i] != nullptr; i++)
1664+ {
1665+ const gchar* pidline = pidlines[i];
1666+ if (pidline[0] != '\n')
1667+ {
1668+ auto pid = std::atoi(pidline);
1669+ if (pid != 0)
1670+ {
1671+ pids.emplace_back(pid);
1672+ }
1673+ }
1674+ }
1675+
1676+ g_strfreev(pidlines);
1677+
1678+ return pids;
1679+}
1680+
1681+void SystemD::stopUnit(const AppID& appId, const std::string& job, const std::string& instance)
1682+{
1683+ auto registry = registry_.lock();
1684+ auto unitname = unitName(SystemD::UnitInfo{appId, job, instance});
1685+
1686+ registry->impl->thread.executeOnThread<bool>([this, registry, unitname] {
1687+ GError* error{nullptr};
1688+ GVariant* call = g_dbus_connection_call_sync(
1689+ userbus_.get(), /* user bus */
1690+ SYSTEMD_DBUS_ADDRESS, /* bus name */
1691+ SYSTEMD_DBUS_PATH_MANAGER, /* path */
1692+ SYSTEMD_DBUS_IFACE_MANAGER, /* interface */
1693+ "StopUnit", /* method */
1694+ g_variant_new(
1695+ "(ss)", /* params */
1696+ unitname.c_str(), /* param: specify unit */
1697+ "replace-irreversibly"), /* param: replace the current job but don't allow us to be replaced */
1698+ G_VARIANT_TYPE("(o)"), /* ret type */
1699+ G_DBUS_CALL_FLAGS_NONE, /* flags */
1700+ -1, /* timeout */
1701+ registry->impl->thread.getCancellable().get(), /* cancellable */
1702+ &error);
1703+
1704+ if (error != nullptr)
1705+ {
1706+ auto message =
1707+ std::string{"Unable to get SystemD to stop '"} + unitname + std::string{"': "} + error->message;
1708+ g_error_free(error);
1709+ throw std::runtime_error(message);
1710+ }
1711+
1712+ g_clear_pointer(&call, g_variant_unref);
1713+
1714+ return true;
1715+ });
1716+}
1717+
1718+core::Signal<const std::shared_ptr<Application>&, const std::shared_ptr<Application::Instance>&>& SystemD::appStarted()
1719+{
1720+ /* For systemd we're automatically listening to the UnitNew signal
1721+ and emitting on the object */
1722+ return sig_appStarted;
1723+}
1724+
1725+core::Signal<const std::shared_ptr<Application>&, const std::shared_ptr<Application::Instance>&>& SystemD::appStopped()
1726+{
1727+ /* For systemd we're automatically listening to the UnitRemoved signal
1728+ and emitting on the object */
1729+ return sig_appStopped;
1730+}
1731+
1732+struct FailedData
1733+{
1734+ std::weak_ptr<Registry> registry;
1735+};
1736+
1737+core::Signal<const std::shared_ptr<Application>&, const std::shared_ptr<Application::Instance>&, Registry::FailureType>&
1738+ SystemD::appFailed()
1739+{
1740+ std::call_once(flag_appFailed, [this]() {
1741+ auto reg = registry_.lock();
1742+
1743+ reg->impl->thread.executeOnThread<bool>([this, reg]() {
1744+ auto data = new FailedData{reg};
1745+
1746+ handle_appFailed = g_dbus_connection_signal_subscribe(
1747+ userbus_.get(), /* bus */
1748+ SYSTEMD_DBUS_ADDRESS, /* sender */
1749+ "org.freedesktop.DBus.Properties", /* interface */
1750+ "PropertiesChanged", /* signal */
1751+ nullptr, /* path */
1752+ SYSTEMD_DBUS_IFACE_SERVICE, /* arg0 */
1753+ G_DBUS_SIGNAL_FLAGS_NONE,
1754+ [](GDBusConnection*, const gchar*, const gchar* path, const gchar*, const gchar*, GVariant* params,
1755+ gpointer user_data) -> void {
1756+ auto data = static_cast<FailedData*>(user_data);
1757+ auto reg = data->registry.lock();
1758+
1759+ if (!reg)
1760+ {
1761+ g_warning("Registry object invalid!");
1762+ return;
1763+ }
1764+
1765+ auto manager = std::dynamic_pointer_cast<SystemD>(reg->impl->jobs);
1766+
1767+ /* Check to see if this is a path we care about */
1768+ bool pathfound{false};
1769+ UnitInfo unitinfo;
1770+ for (const auto& unit : manager->unitPaths)
1771+ {
1772+ if (unit.second->unitpath == path)
1773+ {
1774+ pathfound = true;
1775+ unitinfo = unit.first;
1776+ break;
1777+ }
1778+ }
1779+ if (!pathfound)
1780+ {
1781+ return;
1782+ }
1783+
1784+ /* Now see if it is a property we care about */
1785+ auto vdict = g_variant_get_child_value(params, 1);
1786+ GVariantDict dict;
1787+ g_variant_dict_init(&dict, vdict);
1788+ g_clear_pointer(&vdict, g_variant_unref);
1789+
1790+ if (g_variant_dict_contains(&dict, "Result") == FALSE)
1791+ {
1792+ /* We don't care about anything else */
1793+ g_variant_dict_clear(&dict);
1794+ return;
1795+ }
1796+
1797+ /* Check to see if it just was successful */
1798+ const gchar* value{nullptr};
1799+ g_variant_dict_lookup(&dict, "Result", "&s", &value);
1800+
1801+ if (g_strcmp0(value, "success") == 0)
1802+ {
1803+ g_variant_dict_clear(&dict);
1804+ return;
1805+ }
1806+ g_variant_dict_clear(&dict);
1807+
1808+ /* Oh, we might want to do something now */
1809+ auto reason{Registry::FailureType::CRASH};
1810+ if (g_strcmp0(value, "exit-code") == 0)
1811+ {
1812+ reason = Registry::FailureType::START_FAILURE;
1813+ }
1814+
1815+ auto appid = AppID::find(reg, unitinfo.appid);
1816+ auto app = Application::create(appid, reg);
1817+ auto inst = std::dynamic_pointer_cast<app_impls::Base>(app)->findInstance(unitinfo.inst);
1818+
1819+ manager->sig_appFailed(app, inst, reason);
1820+ }, /* callback */
1821+ data, /* user data */
1822+ [](gpointer user_data) {
1823+ auto data = static_cast<FailedData*>(user_data);
1824+ delete data;
1825+ }); /* user data destroy */
1826+
1827+ return true;
1828+ });
1829+ });
1830+
1831+ return sig_appFailed;
1832+}
1833+
1834+} // namespace manager
1835+} // namespace jobs
1836+} // namespace app_launch
1837+} // namespace ubuntu
1838
1839=== added file 'libubuntu-app-launch/jobs-systemd.h'
1840--- libubuntu-app-launch/jobs-systemd.h 1970-01-01 00:00:00 +0000
1841+++ libubuntu-app-launch/jobs-systemd.h 2017-02-01 20:34:54 +0000
1842@@ -0,0 +1,136 @@
1843+/*
1844+ * Copyright © 2016 Canonical Ltd.
1845+ *
1846+ * This program is free software: you can redistribute it and/or modify it
1847+ * under the terms of the GNU General Public License version 3, as published
1848+ * by the Free Software Foundation.
1849+ *
1850+ * This program is distributed in the hope that it will be useful, but
1851+ * WITHOUT ANY WARRANTY; without even the implied warranties of
1852+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1853+ * PURPOSE. See the GNU General Public License for more details.
1854+ *
1855+ * You should have received a copy of the GNU General Public License along
1856+ * with this program. If not, see <http://www.gnu.org/licenses/>.
1857+ *
1858+ * Authors:
1859+ * Ted Gould <ted.gould@canonical.com>
1860+ */
1861+
1862+#pragma once
1863+
1864+#include "jobs-base.h"
1865+#include <chrono>
1866+#include <future>
1867+#include <gio/gio.h>
1868+#include <map>
1869+#include <mutex>
1870+
1871+namespace ubuntu
1872+{
1873+namespace app_launch
1874+{
1875+namespace jobs
1876+{
1877+namespace manager
1878+{
1879+
1880+class SystemD : public Base
1881+{
1882+public:
1883+ SystemD(std::shared_ptr<Registry> registry);
1884+ virtual ~SystemD();
1885+
1886+ virtual std::shared_ptr<Application::Instance> launch(
1887+ const AppID& appId,
1888+ const std::string& job,
1889+ const std::string& instance,
1890+ const std::vector<Application::URL>& urls,
1891+ launchMode mode,
1892+ std::function<std::list<std::pair<std::string, std::string>>(void)>& getenv) override;
1893+ virtual std::shared_ptr<Application::Instance> existing(const AppID& appId,
1894+ const std::string& job,
1895+ const std::string& instance,
1896+ const std::vector<Application::URL>& urls) override;
1897+
1898+ virtual std::list<std::shared_ptr<Application>> runningApps() override;
1899+
1900+ virtual std::vector<std::shared_ptr<instance::Base>> instances(const AppID& appID, const std::string& job) override;
1901+
1902+ virtual core::Signal<const std::shared_ptr<Application>&, const std::shared_ptr<Application::Instance>&>&
1903+ appStarted() override;
1904+ virtual core::Signal<const std::shared_ptr<Application>&, const std::shared_ptr<Application::Instance>&>&
1905+ appStopped() override;
1906+ virtual core::Signal<const std::shared_ptr<Application>&,
1907+ const std::shared_ptr<Application::Instance>&,
1908+ Registry::FailureType>&
1909+ appFailed() override;
1910+
1911+ static std::string userBusPath();
1912+
1913+ pid_t unitPrimaryPid(const AppID& appId, const std::string& job, const std::string& instance);
1914+ std::vector<pid_t> unitPids(const AppID& appId, const std::string& job, const std::string& instance);
1915+ void stopUnit(const AppID& appId, const std::string& job, const std::string& instance);
1916+
1917+private:
1918+ std::string cgroup_root_;
1919+ std::shared_ptr<GDBusConnection> userbus_;
1920+
1921+ guint handle_unitNew{0}; /**< GDBus signal watcher handle for the unit new signal */
1922+ guint handle_unitRemoved{0}; /**< GDBus signal watcher handle for the unit removed signal */
1923+ guint handle_appFailed{0}; /**< GDBus signal watcher handle for app failed signal */
1924+
1925+ std::once_flag
1926+ flag_appFailed; /**< Variable to track to see if signal handlers are installed for application failed */
1927+
1928+ struct UnitInfo
1929+ {
1930+ std::string appid;
1931+ std::string job;
1932+ std::string inst;
1933+
1934+ bool operator<(const UnitInfo& b) const
1935+ {
1936+ if (job != b.job)
1937+ return job < b.job;
1938+
1939+ if (appid != b.appid)
1940+ return appid < b.appid;
1941+
1942+ return inst < b.inst;
1943+ }
1944+ };
1945+
1946+ void getInitialUnits(const std::shared_ptr<GDBusConnection>& bus, const std::shared_ptr<GCancellable>& cancel);
1947+
1948+ struct UnitData
1949+ {
1950+ std::string jobpath;
1951+ std::string unitpath;
1952+ };
1953+
1954+ std::map<UnitInfo, std::shared_ptr<UnitData>> unitPaths;
1955+ UnitInfo parseUnit(const std::string& unit) const;
1956+ std::string unitName(const UnitInfo& info) const;
1957+ std::string unitPath(const UnitInfo& info);
1958+
1959+ UnitInfo unitNew(const std::string& name, const std::string& path, const std::shared_ptr<GDBusConnection>& bus);
1960+ void unitRemoved(const std::string& name, const std::string& path);
1961+ void emitSignal(
1962+ core::Signal<const std::shared_ptr<Application>&, const std::shared_ptr<Application::Instance>&>& sig,
1963+ UnitInfo& info);
1964+
1965+ static std::string findEnv(const std::string& value, std::list<std::pair<std::string, std::string>>& env);
1966+ static void removeEnv(const std::string& value, std::list<std::pair<std::string, std::string>>& env);
1967+ static void copyEnv(const std::string& envname, std::list<std::pair<std::string, std::string>>& env);
1968+ static void copyEnvByPrefix(const std::string& prefix, std::list<std::pair<std::string, std::string>>& env);
1969+ static int envSize(std::list<std::pair<std::string, std::string>>& env);
1970+
1971+ static std::vector<std::string> parseExec(std::list<std::pair<std::string, std::string>>& env);
1972+ static void application_start_cb(GObject* obj, GAsyncResult* res, gpointer user_data);
1973+};
1974+
1975+} // namespace manager
1976+} // namespace jobs
1977+} // namespace app_launch
1978+} // namespace ubuntu
1979
1980=== modified file 'libubuntu-app-launch/jobs-upstart.cpp'
1981--- libubuntu-app-launch/jobs-upstart.cpp 2017-01-11 22:28:11 +0000
1982+++ libubuntu-app-launch/jobs-upstart.cpp 2017-02-01 20:34:54 +0000
1983@@ -84,8 +84,6 @@
1984 const AppID& appid,
1985 const std::string& jobpath,
1986 std::function<void(pid_t)> eachPid);
1987-
1988- static std::shared_ptr<gchar*> urlsToStrv(const std::vector<Application::URL>& urls);
1989 };
1990
1991 /** Uses Upstart to get the primary PID of the instance using Upstart's
1992@@ -317,29 +315,6 @@
1993 g_debug("Creating a new Upstart for '%s' instance '%s'", std::string(appId).c_str(), instance.c_str());
1994 }
1995
1996-/** Reformat a C++ vector of URLs into a C GStrv of strings
1997-
1998- \param urls Vector of URLs to make into C strings
1999-*/
2000-std::shared_ptr<gchar*> Upstart::urlsToStrv(const std::vector<Application::URL>& urls)
2001-{
2002- if (urls.empty())
2003- {
2004- return {};
2005- }
2006-
2007- auto array = g_array_new(TRUE, FALSE, sizeof(gchar*));
2008-
2009- for (auto url : urls)
2010- {
2011- auto str = g_strdup(url.value().c_str());
2012- g_debug("Converting URL: %s", str);
2013- g_array_append_val(array, str);
2014- }
2015-
2016- return std::shared_ptr<gchar*>((gchar**)g_array_free(array, FALSE), g_strfreev);
2017-}
2018-
2019 /** Small helper that we can new/delete to work better with C stuff */
2020 struct StartCHelper
2021 {
2022
2023=== modified file 'libubuntu-app-launch/registry-impl.cpp'
2024--- libubuntu-app-launch/registry-impl.cpp 2017-01-11 22:16:14 +0000
2025+++ libubuntu-app-launch/registry-impl.cpp 2017-02-01 20:34:54 +0000
2026@@ -132,7 +132,7 @@
2027 if (error != nullptr)
2028 {
2029 auto perror = std::shared_ptr<GError>(error, [](GError* error) { g_error_free(error); });
2030- g_critical("Error parsing manifest for package '%s': %s", package.c_str(), perror->message);
2031+ g_debug("Error parsing manifest for package '%s': %s", package.c_str(), perror->message);
2032 return std::shared_ptr<JsonObject>();
2033 }
2034
2035
2036=== modified file 'libubuntu-app-launch/snapd-info.cpp'
2037--- libubuntu-app-launch/snapd-info.cpp 2016-11-09 22:24:10 +0000
2038+++ libubuntu-app-launch/snapd-info.cpp 2017-02-01 20:34:54 +0000
2039@@ -170,7 +170,7 @@
2040 }
2041 catch (std::runtime_error &e)
2042 {
2043- g_warning("Unable to get snap information for '%s': %s", package.value().c_str(), e.what());
2044+ g_debug("Unable to get snap information for '%s': %s", package.value().c_str(), e.what());
2045 return {};
2046 }
2047 }
2048
2049=== modified file 'tests/CMakeLists.txt'
2050--- tests/CMakeLists.txt 2017-01-11 23:13:23 +0000
2051+++ tests/CMakeLists.txt 2017-02-01 20:34:54 +0000
2052@@ -67,6 +67,14 @@
2053
2054 add_test(NAME jobs-base-test COMMAND jobs-base-test)
2055
2056+# Jobs Systemd Test
2057+
2058+add_executable (jobs-systemd
2059+ jobs-systemd.cpp)
2060+target_link_libraries (jobs-systemd ${GMOCK_LIBRARIES} launcher-static ${DBUSTEST_LIBRARIES})
2061+
2062+add_test(NAME jobs-systemd COMMAND jobs-systemd)
2063+
2064 # Snapd Info Test
2065
2066 if(CURL_FOUND)
2067@@ -167,8 +175,11 @@
2068 list-apps.cpp
2069 eventually-fixture.h
2070 jobs-base-test.cpp
2071+ jobs-systemd.cpp
2072+ registry-mock.h
2073 snapd-info-test.cpp
2074 snapd-mock.h
2075 spew-master.h
2076+ systemd-mock.h
2077 zg-test.cc
2078 )
2079
2080=== modified file 'tests/eventually-fixture.h'
2081--- tests/eventually-fixture.h 2017-01-11 23:13:23 +0000
2082+++ tests/eventually-fixture.h 2017-02-01 20:34:54 +0000
2083@@ -27,6 +27,7 @@
2084 {
2085 protected:
2086 std::chrono::milliseconds _eventuallyTime = std::chrono::minutes{1};
2087+ std::once_flag checkEventuallyEnv_;
2088
2089 static gboolean timeout_cb(gpointer user_data)
2090 {
2091@@ -45,6 +46,14 @@
2092
2093 testing::AssertionResult eventuallyLoop(std::function<testing::AssertionResult(void)> &testfunc)
2094 {
2095+ std::call_once(checkEventuallyEnv_, [this]() {
2096+ auto eventuallyenv = getenv("EVENTUALLY_TIMEOUT");
2097+ if (eventuallyenv != nullptr)
2098+ {
2099+ _eventuallyTime = std::chrono::seconds{std::atoi(eventuallyenv)};
2100+ }
2101+ });
2102+
2103 auto loop = std::shared_ptr<GMainLoop>(g_main_loop_new(nullptr, FALSE),
2104 [](GMainLoop *loop) { g_clear_pointer(&loop, g_main_loop_unref); });
2105
2106
2107=== modified file 'tests/exec-util-test.cc'
2108--- tests/exec-util-test.cc 2017-01-19 03:23:54 +0000
2109+++ tests/exec-util-test.cc 2017-02-01 20:34:54 +0000
2110@@ -50,6 +50,7 @@
2111 g_setenv("XDG_DATA_HOME", CMAKE_SOURCE_DIR "/libertine-home", TRUE);
2112 g_setenv("UBUNTU_APP_LAUNCH_LIBERTINE_LAUNCH", "libertine-launch", TRUE);
2113 g_setenv("UBUNTU_APP_LAUNCH_SNAPD_SOCKET", "/this/should/not/exist", TRUE);
2114+ g_setenv("UBUNTU_APP_LAUNCH_SYSTEMD_PATH", "/this/should/not/exist", TRUE);
2115
2116 service = dbus_test_service_new(NULL);
2117
2118@@ -184,12 +185,15 @@
2119 EXPECT_STREQ("grep", value); }},
2120 {"APP_ID", [](const gchar * value) {
2121 EXPECT_STREQ("com.test.good_application_1.2.3", value); }},
2122+ {"APP_EXEC_POLICY", [](const gchar * value) {
2123+ EXPECT_STREQ("com.test.good_application_1.2.3", value); }},
2124 {"APP_LAUNCHER_PID", [](const gchar * value) {
2125 EXPECT_EQ(getpid(), atoi(value)); }},
2126 {"APP_DESKTOP_FILE_PATH", [](const gchar * value) {
2127 EXPECT_STREQ(APP_DIR "/application.desktop", value); }},
2128 {"APP_XMIR_ENABLE", [](const gchar * value) {
2129 EXPECT_STREQ("0", value); }},
2130+ {"QML2_IMPORT_PATH", nocheck},
2131 });
2132
2133 #undef APP_DIR
2134@@ -267,10 +271,13 @@
2135 {"APP_EXEC", nocheck},
2136 {"APP_ID", [](const gchar * value) {
2137 EXPECT_STREQ("com.test.mir_mir_1", value); }},
2138+ {"APP_EXEC_POLICY", [](const gchar * value) {
2139+ EXPECT_STREQ("com.test.mir_mir_1", value); }},
2140 {"APP_LAUNCHER_PID", nocheck},
2141 {"APP_DESKTOP_FILE_PATH", nocheck},
2142 {"APP_XMIR_ENABLE", [](const gchar * value) {
2143 EXPECT_STREQ("1", value); }},
2144+ {"QML2_IMPORT_PATH", nocheck},
2145 });
2146 }
2147
2148@@ -289,10 +296,13 @@
2149 {"APP_EXEC", nocheck},
2150 {"APP_ID", [](const gchar * value) {
2151 EXPECT_STREQ("com.test.mir_nomir_1", value); }},
2152+ {"APP_EXEC_POLICY", [](const gchar * value) {
2153+ EXPECT_STREQ("com.test.mir_nomir_1", value); }},
2154 {"APP_LAUNCHER_PID", nocheck},
2155 {"APP_DESKTOP_FILE_PATH", nocheck},
2156 {"APP_XMIR_ENABLE", [](const gchar * value) {
2157 EXPECT_STREQ("0", value); }},
2158+ {"QML2_IMPORT_PATH", nocheck},
2159 });
2160 }
2161
2162
2163=== modified file 'tests/failure-test.cc'
2164--- tests/failure-test.cc 2017-01-11 05:48:33 +0000
2165+++ tests/failure-test.cc 2017-02-01 20:34:54 +0000
2166@@ -45,6 +45,8 @@
2167 g_setenv("XDG_CACHE_HOME", CMAKE_SOURCE_DIR "/libertine-data", TRUE);
2168 g_setenv("XDG_DATA_HOME", CMAKE_SOURCE_DIR "/libertine-home", TRUE);
2169
2170+ g_setenv("UBUNTU_APP_LAUNCH_SYSTEMD_PATH", "/this/should/not/exist", TRUE);
2171+
2172 testbus = g_test_dbus_new(G_TEST_DBUS_NONE);
2173 g_test_dbus_up(testbus);
2174
2175
2176=== modified file 'tests/jobs-base-test.cpp'
2177--- tests/jobs-base-test.cpp 2017-01-12 17:31:50 +0000
2178+++ tests/jobs-base-test.cpp 2017-02-01 20:34:54 +0000
2179@@ -19,35 +19,14 @@
2180
2181 #include "appid.h"
2182 #include "jobs-base.h"
2183-#include "registry-impl.h"
2184-#include "registry.h"
2185
2186 #include "eventually-fixture.h"
2187+#include "registry-mock.h"
2188 #include "spew-master.h"
2189 #include <gmock/gmock.h>
2190 #include <gtest/gtest.h>
2191 #include <libdbustest/dbus-test.h>
2192
2193-class RegistryImplMock : public ubuntu::app_launch::Registry::Impl
2194-{
2195-public:
2196- RegistryImplMock(ubuntu::app_launch::Registry* reg)
2197- : ubuntu::app_launch::Registry::Impl(reg)
2198- {
2199- }
2200-
2201- MOCK_METHOD2(zgSendEvent, void(ubuntu::app_launch::AppID, const std::string& eventtype));
2202-};
2203-
2204-class RegistryMock : public ubuntu::app_launch::Registry
2205-{
2206-public:
2207- RegistryMock()
2208- {
2209- impl = std::unique_ptr<RegistryImplMock>(new RegistryImplMock(this));
2210- }
2211-};
2212-
2213 class instanceMock : public ubuntu::app_launch::jobs::instance::Base
2214 {
2215 public:
2216
2217=== added file 'tests/jobs-systemd.cpp'
2218--- tests/jobs-systemd.cpp 1970-01-01 00:00:00 +0000
2219+++ tests/jobs-systemd.cpp 2017-02-01 20:34:54 +0000
2220@@ -0,0 +1,362 @@
2221+/*
2222+ * Copyright © 2017 Canonical Ltd.
2223+ *
2224+ * This program is free software: you can redistribute it and/or modify it
2225+ * under the terms of the GNU General Public License version 3, as published
2226+ * by the Free Software Foundation.
2227+ *
2228+ * This program is distributed in the hope that it will be useful, but
2229+ * WITHOUT ANY WARRANTY; without even the implied warranties of
2230+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
2231+ * PURPOSE. See the GNU General Public License for more details.
2232+ *
2233+ * You should have received a copy of the GNU General Public License along
2234+ * with this program. If not, see <http://www.gnu.org/licenses/>.
2235+ *
2236+ * Authors:
2237+ * Ted Gould <ted.gould@canonical.com>
2238+ */
2239+
2240+#include "jobs-systemd.h"
2241+
2242+#include "eventually-fixture.h"
2243+#include "registry-mock.h"
2244+#include "systemd-mock.h"
2245+
2246+#define CGROUP_DIR (CMAKE_BINARY_DIR "/systemd-cgroups")
2247+
2248+class JobsSystemd : public EventuallyFixture
2249+{
2250+protected:
2251+ std::shared_ptr<DbusTestService> service;
2252+ std::shared_ptr<RegistryMock> registry;
2253+ std::shared_ptr<SystemdMock> systemd;
2254+ GDBusConnection *bus = nullptr;
2255+
2256+ virtual void SetUp() override
2257+ {
2258+ /* Get the applications dir */
2259+ g_setenv("XDG_DATA_DIRS", CMAKE_SOURCE_DIR, TRUE);
2260+
2261+ /* Setting the cgroup temp directory */
2262+ g_setenv("UBUNTU_APP_LAUNCH_SYSTEMD_CGROUP_ROOT", CGROUP_DIR, TRUE);
2263+
2264+ /* Force over to session bus */
2265+ g_setenv("UBUNTU_APP_LAUNCH_SYSTEMD_PATH", "/this/should/not/exist", TRUE);
2266+
2267+ service = std::shared_ptr<DbusTestService>(dbus_test_service_new(nullptr),
2268+ [](DbusTestService *service) { g_clear_object(&service); });
2269+
2270+ systemd = std::make_shared<SystemdMock>(
2271+ std::list<SystemdMock::Instance>{
2272+ {defaultJobName(), std::string{multipleAppID()}, "1234567890", 11, {12, 13, 11}},
2273+ {defaultJobName(), std::string{multipleAppID()}, "0987654321", 10, {10}},
2274+ {defaultJobName(), std::string{singleAppID()}, {}, 5, {1, 2, 3, 4, 5}}},
2275+ CGROUP_DIR);
2276+ dbus_test_service_add_task(service.get(), *systemd);
2277+
2278+ dbus_test_service_start_tasks(service.get());
2279+ registry = std::make_shared<RegistryMock>();
2280+
2281+ bus = g_bus_get_sync(G_BUS_TYPE_SESSION, nullptr, nullptr);
2282+ g_dbus_connection_set_exit_on_close(bus, FALSE);
2283+ g_object_add_weak_pointer(G_OBJECT(bus), (gpointer *)&bus);
2284+ }
2285+
2286+ virtual void TearDown() override
2287+ {
2288+ systemd.reset();
2289+ registry.reset();
2290+ service.reset();
2291+
2292+ g_object_unref(bus);
2293+ ASSERT_EVENTUALLY_EQ(nullptr, bus);
2294+ }
2295+
2296+ std::string defaultJobName()
2297+ {
2298+ return "application-legacy";
2299+ }
2300+
2301+ ubuntu::app_launch::AppID singleAppID()
2302+ {
2303+ return {ubuntu::app_launch::AppID::Package::from_raw({}),
2304+ ubuntu::app_launch::AppID::AppName::from_raw("single"),
2305+ ubuntu::app_launch::AppID::Version::from_raw({})};
2306+ }
2307+
2308+ ubuntu::app_launch::AppID multipleAppID()
2309+ {
2310+ return {ubuntu::app_launch::AppID::Package::from_raw({}),
2311+ ubuntu::app_launch::AppID::AppName::from_raw("multiple"),
2312+ ubuntu::app_launch::AppID::Version::from_raw({})};
2313+ }
2314+};
2315+
2316+/* Make sure we can build an object and destroy it */
2317+TEST_F(JobsSystemd, Init)
2318+{
2319+ registry->impl->jobs = std::make_shared<ubuntu::app_launch::jobs::manager::SystemD>(registry);
2320+}
2321+
2322+/* Make sure we make the initial call to get signals and an initial list */
2323+TEST_F(JobsSystemd, Startup)
2324+{
2325+ registry->impl->jobs = std::make_shared<ubuntu::app_launch::jobs::manager::SystemD>(registry);
2326+
2327+ EXPECT_EVENTUALLY_FUNC_EQ(true, std::function<bool()>([this]() { return systemd->subscribeCallsCnt() > 0; }));
2328+ EXPECT_EVENTUALLY_FUNC_EQ(true, std::function<bool()>([this]() -> bool { return systemd->listCallsCnt() > 0; }));
2329+}
2330+
2331+std::function<bool(const std::shared_ptr<ubuntu::app_launch::Application> &app)> findAppID(
2332+ const ubuntu::app_launch::AppID &appid)
2333+{
2334+ return [appid](const std::shared_ptr<ubuntu::app_launch::Application> &app) { return appid == app->appId(); };
2335+}
2336+
2337+/* Get the running apps and check out their instances */
2338+TEST_F(JobsSystemd, RunningApps)
2339+{
2340+ auto manager = std::make_shared<ubuntu::app_launch::jobs::manager::SystemD>(registry);
2341+ registry->impl->jobs = manager;
2342+
2343+ auto apps = manager->runningApps();
2344+ ASSERT_FALSE(apps.empty());
2345+ EXPECT_EQ(2u, apps.size());
2346+
2347+ auto single = *std::find_if(apps.begin(), apps.end(), findAppID(singleAppID()));
2348+ EXPECT_TRUE(bool(single));
2349+
2350+ auto multiple = *std::find_if(apps.begin(), apps.end(), findAppID(multipleAppID()));
2351+ EXPECT_TRUE(bool(multiple));
2352+
2353+ auto sinstances = single->instances();
2354+
2355+ ASSERT_FALSE(sinstances.empty());
2356+ EXPECT_EQ(1u, sinstances.size());
2357+
2358+ auto minstances = multiple->instances();
2359+
2360+ ASSERT_FALSE(minstances.empty());
2361+ EXPECT_EQ(2u, minstances.size());
2362+}
2363+
2364+/* Check to make sure we're getting the user bus path correctly */
2365+TEST_F(JobsSystemd, UserBusPath)
2366+{
2367+ auto manager = std::make_shared<ubuntu::app_launch::jobs::manager::SystemD>(registry);
2368+ registry->impl->jobs = manager;
2369+
2370+ EXPECT_EQ(std::string{"/this/should/not/exist"}, manager->userBusPath());
2371+
2372+ unsetenv("UBUNTU_APP_LAUNCH_SYSTEMD_PATH");
2373+ EXPECT_EQ(std::string{"/run/user/"} + std::to_string(getuid()) + std::string{"/bus"}, manager->userBusPath());
2374+}
2375+
2376+/* PID Tools */
2377+TEST_F(JobsSystemd, PidTools)
2378+{
2379+ auto manager = std::make_shared<ubuntu::app_launch::jobs::manager::SystemD>(registry);
2380+ registry->impl->jobs = manager;
2381+
2382+ EXPECT_EQ(5, manager->unitPrimaryPid(singleAppID(), defaultJobName(), {}));
2383+ std::vector<pid_t> pidlist{1, 2, 3, 4, 5};
2384+ EXPECT_EQ(pidlist, manager->unitPids(singleAppID(), defaultJobName(), {}));
2385+}
2386+
2387+/* PID Instance */
2388+TEST_F(JobsSystemd, PidInstance)
2389+{
2390+ auto manager = std::make_shared<ubuntu::app_launch::jobs::manager::SystemD>(registry);
2391+ registry->impl->jobs = manager;
2392+
2393+ auto inst = manager->existing(singleAppID(), defaultJobName(), {}, {});
2394+ EXPECT_TRUE(bool(inst));
2395+
2396+ EXPECT_EQ(5, inst->primaryPid());
2397+ std::vector<pid_t> pidlist{1, 2, 3, 4, 5};
2398+ EXPECT_EQ(pidlist, inst->pids());
2399+}
2400+
2401+/* Stopping a Job */
2402+TEST_F(JobsSystemd, StopUnit)
2403+{
2404+ auto manager = std::make_shared<ubuntu::app_launch::jobs::manager::SystemD>(registry);
2405+ registry->impl->jobs = manager;
2406+
2407+ manager->stopUnit(singleAppID(), defaultJobName(), {});
2408+
2409+ std::list<std::string> stopcalls;
2410+ EXPECT_EVENTUALLY_FUNC_LT(0u, std::function<unsigned int()>([&]() {
2411+ stopcalls = systemd->stopCalls();
2412+ return stopcalls.size();
2413+ }));
2414+
2415+ EXPECT_EQ(SystemdMock::instanceName({defaultJobName(), std::string{singleAppID()}, {}, 1, {}}), *stopcalls.begin());
2416+
2417+ systemd->managerClear();
2418+ stopcalls.clear();
2419+
2420+ manager->stopUnit(multipleAppID(), defaultJobName(), "1234567890");
2421+
2422+ EXPECT_EVENTUALLY_FUNC_LT(0u, std::function<unsigned int()>([&]() {
2423+ stopcalls = systemd->stopCalls();
2424+ return stopcalls.size();
2425+ }));
2426+
2427+ EXPECT_EQ(SystemdMock::instanceName({defaultJobName(), std::string{multipleAppID()}, "1234567890", 1, {}}),
2428+ *stopcalls.begin());
2429+}
2430+
2431+/* Stop Instance */
2432+TEST_F(JobsSystemd, StopInstance)
2433+{
2434+ auto manager = std::make_shared<ubuntu::app_launch::jobs::manager::SystemD>(registry);
2435+ registry->impl->jobs = manager;
2436+
2437+ auto inst = manager->existing(singleAppID(), defaultJobName(), {}, {});
2438+ EXPECT_TRUE(bool(inst));
2439+
2440+ inst->stop();
2441+
2442+ std::list<std::string> stopcalls;
2443+ EXPECT_EVENTUALLY_FUNC_LT(0u, std::function<unsigned int()>([&]() {
2444+ stopcalls = systemd->stopCalls();
2445+ return stopcalls.size();
2446+ }));
2447+
2448+ EXPECT_EQ(SystemdMock::instanceName({defaultJobName(), std::string{singleAppID()}, {}, 1, {}}), *stopcalls.begin());
2449+}
2450+
2451+/* Starting a new job */
2452+TEST_F(JobsSystemd, LaunchJob)
2453+{
2454+ auto manager = std::make_shared<ubuntu::app_launch::jobs::manager::SystemD>(registry);
2455+ registry->impl->jobs = manager;
2456+
2457+ bool gotenv{false};
2458+ std::function<std::list<std::pair<std::string, std::string>>()> getenvfunc =
2459+ [&]() -> std::list<std::pair<std::string, std::string>> {
2460+ gotenv = true;
2461+ return {{"APP_EXEC", "sh"}};
2462+ };
2463+
2464+ auto inst = manager->launch(multipleAppID(), defaultJobName(), "123", {},
2465+ ubuntu::app_launch::jobs::manager::launchMode::STANDARD, getenvfunc);
2466+
2467+ EXPECT_TRUE(bool(inst));
2468+ EXPECT_TRUE(gotenv);
2469+
2470+ /* Check to see that we got called */
2471+ std::list<SystemdMock::TransientUnit> units;
2472+ EXPECT_EVENTUALLY_FUNC_LT(0u, std::function<unsigned int()>([&]() {
2473+ units = systemd->unitCalls();
2474+ return units.size();
2475+ }));
2476+
2477+ /* Make sure it was the right one */
2478+ EXPECT_EQ(SystemdMock::instanceName({defaultJobName(), std::string{multipleAppID()}, "123", 1, {}}),
2479+ units.begin()->name);
2480+
2481+ /* Check some standard environment variables */
2482+ EXPECT_NE(units.begin()->environment.end(),
2483+ units.begin()->environment.find(std::string{"APP_ID="} + std::string(multipleAppID())));
2484+ EXPECT_NE(
2485+ units.begin()->environment.end(),
2486+ units.begin()->environment.find(std::string{"DBUS_SESSION_BUS_ADDRESS="} + getenv("DBUS_SESSION_BUS_ADDRESS")));
2487+
2488+ /* Ensure the exec is correct */
2489+ EXPECT_EQ("/bin/sh", units.begin()->execpath);
2490+
2491+ /* Try an entirely custom variable */
2492+ systemd->managerClear();
2493+ units.clear();
2494+
2495+ std::function<std::list<std::pair<std::string, std::string>>()> arbitraryenvfunc =
2496+ [&]() -> std::list<std::pair<std::string, std::string>> {
2497+ return {{"ARBITRARY_KEY", "EVEN_MORE_ARBITRARY_VALUE"}};
2498+ };
2499+
2500+ manager->launch(multipleAppID(), defaultJobName(), "123", {},
2501+ ubuntu::app_launch::jobs::manager::launchMode::STANDARD, arbitraryenvfunc);
2502+
2503+ EXPECT_EVENTUALLY_FUNC_LT(0u, std::function<unsigned int()>([&]() {
2504+ units = systemd->unitCalls();
2505+ return units.size();
2506+ }));
2507+
2508+ EXPECT_NE(units.begin()->environment.end(),
2509+ units.begin()->environment.find("ARBITRARY_KEY=EVEN_MORE_ARBITRARY_VALUE"));
2510+}
2511+
2512+TEST_F(JobsSystemd, SignalNew)
2513+{
2514+ auto manager = std::make_shared<ubuntu::app_launch::jobs::manager::SystemD>(registry);
2515+ registry->impl->jobs = manager;
2516+
2517+ std::promise<ubuntu::app_launch::AppID> newunit;
2518+ manager->appStarted().connect([&](const std::shared_ptr<ubuntu::app_launch::Application> &app,
2519+ const std::shared_ptr<ubuntu::app_launch::Application::Instance> &inst) {
2520+ try
2521+ {
2522+ if (!app)
2523+ {
2524+ throw std::runtime_error("Invalid Application");
2525+ }
2526+
2527+ if (!inst)
2528+ {
2529+ throw std::runtime_error("Invalid Instance");
2530+ }
2531+
2532+ newunit.set_value(app->appId());
2533+ }
2534+ catch (...)
2535+ {
2536+ newunit.set_exception(std::current_exception());
2537+ }
2538+ });
2539+
2540+ systemd->managerEmitNew(SystemdMock::instanceName(
2541+
2542+ {defaultJobName(), std::string{multipleAppID()}, "1234", 1, {}}),
2543+ "/foo");
2544+
2545+ EXPECT_EQ(multipleAppID(), newunit.get_future().get());
2546+}
2547+
2548+TEST_F(JobsSystemd, SignalRemove)
2549+{
2550+ auto manager = std::make_shared<ubuntu::app_launch::jobs::manager::SystemD>(registry);
2551+ registry->impl->jobs = manager;
2552+
2553+ std::promise<ubuntu::app_launch::AppID> removeunit;
2554+ manager->appStopped().connect([&](const std::shared_ptr<ubuntu::app_launch::Application> &app,
2555+ const std::shared_ptr<ubuntu::app_launch::Application::Instance> &inst) {
2556+ try
2557+ {
2558+ if (!app)
2559+ {
2560+ throw std::runtime_error("Invalid Application");
2561+ }
2562+
2563+ if (!inst)
2564+ {
2565+ throw std::runtime_error("Invalid Instance");
2566+ }
2567+
2568+ removeunit.set_value(app->appId());
2569+ }
2570+ catch (...)
2571+ {
2572+ removeunit.set_exception(std::current_exception());
2573+ }
2574+ });
2575+
2576+ systemd->managerEmitRemoved(SystemdMock::instanceName(
2577+
2578+ {defaultJobName(), std::string{multipleAppID()}, "1234567890", 1, {}}),
2579+ "/foo");
2580+
2581+ EXPECT_EQ(multipleAppID(), removeunit.get_future().get());
2582+}
2583
2584=== modified file 'tests/libual-cpp-test.cc'
2585--- tests/libual-cpp-test.cc 2017-01-19 03:24:42 +0000
2586+++ tests/libual-cpp-test.cc 2017-02-01 20:34:54 +0000
2587@@ -157,6 +157,7 @@
2588 g_setenv("UBUNTU_APP_LAUNCH_DISABLE_SNAPD_TIMEOUT", "You betcha!", TRUE);
2589 g_unlink(LOCAL_SNAPD_TEST_SOCKET);
2590 #endif
2591+ g_setenv("UBUNTU_APP_LAUNCH_SYSTEMD_PATH", "/this/should/not/exist", TRUE);
2592
2593 service = dbus_test_service_new(NULL);
2594
2595@@ -518,12 +519,12 @@
2596 /* Snapd mock data */
2597 static std::pair<std::string, std::string> interfaces{
2598 "GET /v2/interfaces HTTP/1.1\r\nHost: snapd\r\nAccept: */*\r\n\r\n",
2599- SnapdMock::httpJsonResponse(
2600- SnapdMock::snapdOkay(SnapdMock::interfacesJson({{"unity8", "unity8-package", {"foo", "single", "xmir", "noxmir"}}})))};
2601+ SnapdMock::httpJsonResponse(SnapdMock::snapdOkay(
2602+ SnapdMock::interfacesJson({{"unity8", "unity8-package", {"foo", "single", "xmir", "noxmir"}}})))};
2603 static std::pair<std::string, std::string> u8Package{
2604 "GET /v2/snaps/unity8-package HTTP/1.1\r\nHost: snapd\r\nAccept: */*\r\n\r\n",
2605- SnapdMock::httpJsonResponse(SnapdMock::snapdOkay(
2606- SnapdMock::packageJson("unity8-package", "active", "app", "1.2.3.4", "x123", {"foo", "single", "xmir", "noxmir"})))};
2607+ SnapdMock::httpJsonResponse(SnapdMock::snapdOkay(SnapdMock::packageJson(
2608+ "unity8-package", "active", "app", "1.2.3.4", "x123", {"foo", "single", "xmir", "noxmir"})))};
2609
2610 TEST_F(LibUAL, ApplicationIdSnap)
2611 {
2612@@ -550,12 +551,12 @@
2613 {
2614 /* Queries come in threes, apparently */
2615 SnapdMock snapd{LOCAL_SNAPD_TEST_SOCKET,
2616- {
2617- u8Package, interfaces, u8Package,
2618- u8Package, interfaces, u8Package,
2619- u8Package, interfaces, u8Package,
2620- u8Package, interfaces, u8Package,
2621- }};
2622+ {
2623+ u8Package, interfaces, u8Package, /* App 1 */
2624+ u8Package, interfaces, u8Package, /* App 2 */
2625+ u8Package, interfaces, u8Package, /* App 3 */
2626+ u8Package, interfaces, u8Package, /* App 4 */
2627+ }};
2628 registry = std::make_shared<ubuntu::app_launch::Registry>();
2629
2630 std::string snapRoot{SNAP_BASEDIR};
2631
2632=== modified file 'tests/libual-test.cc'
2633--- tests/libual-test.cc 2017-01-11 23:22:52 +0000
2634+++ tests/libual-test.cc 2017-02-01 20:34:54 +0000
2635@@ -90,6 +90,7 @@
2636 g_setenv("XDG_DATA_HOME", CMAKE_SOURCE_DIR "/libertine-home", TRUE);
2637
2638 g_setenv("UBUNTU_APP_LAUNCH_SNAPD_SOCKET", "/this/should/not/exist", TRUE);
2639+ g_setenv("UBUNTU_APP_LAUNCH_SYSTEMD_PATH", "/this/should/not/exist", TRUE);
2640
2641 service = dbus_test_service_new(NULL);
2642
2643
2644=== added file 'tests/registry-mock.h'
2645--- tests/registry-mock.h 1970-01-01 00:00:00 +0000
2646+++ tests/registry-mock.h 2017-02-01 20:34:54 +0000
2647@@ -0,0 +1,57 @@
2648+/*
2649+ * Copyright © 2017 Canonical Ltd.
2650+ *
2651+ * This program is free software: you can redistribute it and/or modify it
2652+ * under the terms of the GNU General Public License version 3, as published
2653+ * by the Free Software Foundation.
2654+ *
2655+ * This program is distributed in the hope that it will be useful, but
2656+ * WITHOUT ANY WARRANTY; without even the implied warranties of
2657+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
2658+ * PURPOSE. See the GNU General Public License for more details.
2659+ *
2660+ * You should have received a copy of the GNU General Public License along
2661+ * with this program. If not, see <http://www.gnu.org/licenses/>.
2662+ *
2663+ * Authors:
2664+ * Ted Gould <ted.gould@canonical.com>
2665+ */
2666+
2667+#pragma once
2668+
2669+#include "registry-impl.h"
2670+#include "registry.h"
2671+
2672+#include <gmock/gmock.h>
2673+
2674+class RegistryImplMock : public ubuntu::app_launch::Registry::Impl
2675+{
2676+public:
2677+ RegistryImplMock(ubuntu::app_launch::Registry* reg)
2678+ : ubuntu::app_launch::Registry::Impl(reg)
2679+ {
2680+ g_debug("Registry Mock Implementation Created");
2681+ }
2682+
2683+ ~RegistryImplMock()
2684+ {
2685+ g_debug("Registry Mock Implementation taken down");
2686+ }
2687+
2688+ MOCK_METHOD2(zgSendEvent, void(ubuntu::app_launch::AppID, const std::string& eventtype));
2689+};
2690+
2691+class RegistryMock : public ubuntu::app_launch::Registry
2692+{
2693+public:
2694+ RegistryMock()
2695+ {
2696+ g_debug("Registry Mock Created");
2697+ impl = std::unique_ptr<RegistryImplMock>(new RegistryImplMock(this));
2698+ }
2699+
2700+ ~RegistryMock()
2701+ {
2702+ g_debug("Registry Mock taken down");
2703+ }
2704+};
2705
2706=== modified file 'tests/spew-master.h'
2707--- tests/spew-master.h 2016-12-16 17:23:55 +0000
2708+++ tests/spew-master.h 2017-02-01 20:34:54 +0000
2709@@ -19,6 +19,8 @@
2710
2711 #pragma once
2712
2713+#include "glib-thread.h"
2714+
2715 class SpewMaster
2716 {
2717 public:
2718
2719=== added file 'tests/systemd-mock.h'
2720--- tests/systemd-mock.h 1970-01-01 00:00:00 +0000
2721+++ tests/systemd-mock.h 2017-02-01 20:34:54 +0000
2722@@ -0,0 +1,481 @@
2723+/*
2724+ * Copyright © 2017 Canonical Ltd.
2725+ *
2726+ * This program is free software; you can redistribute it and/or modify
2727+ * it under the terms of the GNU General Public License as published by
2728+ * the Free Software Foundation; version 3.
2729+ *
2730+ * This program is distributed in the hope that it will be useful,
2731+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2732+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2733+ * GNU General Public License for more details.
2734+ *
2735+ * You should have received a copy of the GNU General Public License
2736+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2737+ *
2738+ * Authors:
2739+ * Ted Gould <ted@canonical.com>
2740+ */
2741+
2742+#include <algorithm>
2743+#include <future>
2744+#include <list>
2745+#include <map>
2746+#include <memory>
2747+#include <numeric>
2748+#include <type_traits>
2749+
2750+#include <gio/gio.h>
2751+
2752+#include <libdbustest/dbus-test.h>
2753+
2754+#include "glib-thread.h"
2755+
2756+class SystemdMock
2757+{
2758+public:
2759+ struct Instance
2760+ {
2761+ std::string job;
2762+ std::string appid;
2763+ std::string instanceid;
2764+ pid_t primaryPid;
2765+ std::vector<pid_t> pids;
2766+ };
2767+
2768+private:
2769+ DbusTestDbusMock* mock = nullptr;
2770+ DbusTestDbusMockObject* managerobj = nullptr;
2771+ GLib::ContextThread thread;
2772+ std::list<std::pair<Instance, DbusTestDbusMockObject*>> insts;
2773+
2774+ void throwError(GError* error)
2775+ {
2776+ if (error == nullptr)
2777+ {
2778+ return;
2779+ }
2780+
2781+ auto message = std::string{"Error in systemd mock: "} + error->message;
2782+ g_error_free(error);
2783+ throw std::runtime_error{message};
2784+ }
2785+
2786+public:
2787+ SystemdMock(const std::list<Instance>& instances, const std::string& controlGroupPath)
2788+ {
2789+ GError* error = nullptr;
2790+ mock = dbus_test_dbus_mock_new("org.freedesktop.systemd1");
2791+ dbus_test_task_set_bus(DBUS_TEST_TASK(mock), DBUS_TEST_SERVICE_BUS_SESSION);
2792+ dbus_test_task_set_name(DBUS_TEST_TASK(mock), "systemd");
2793+
2794+ managerobj = dbus_test_dbus_mock_get_object(mock, "/org/freedesktop/systemd1",
2795+ "org.freedesktop.systemd1.Manager", nullptr);
2796+
2797+ dbus_test_dbus_mock_object_add_method(mock, managerobj, "Subscribe", nullptr, nullptr, "", nullptr);
2798+ dbus_test_dbus_mock_object_add_method(
2799+ mock, managerobj, "ListUnits", nullptr, G_VARIANT_TYPE("(a(ssssssouso))"), /* ret type */
2800+ ("ret = [ " + std::accumulate(instances.begin(), instances.end(), std::string{},
2801+ [](const std::string accum, const Instance& inst) {
2802+ std::string retval = accum;
2803+
2804+ if (!retval.empty())
2805+ {
2806+ retval += ", ";
2807+ }
2808+
2809+ retval += std::string{"("} + /* start tuple */
2810+ "'" + instanceName(inst) + "', " + /* id */
2811+ "'unused', " + /* description */
2812+ "'unused', " + /* load state */
2813+ "'unused', " + /* active state */
2814+ "'unused', " + /* substate */
2815+ "'unused', " + /* following */
2816+ "'/unused', " + /* path */
2817+ "5, " + /* jobId */
2818+ "'unused', " + /* jobType */
2819+ "'" + instancePath(inst) + "'" + /* jobPath */
2820+ ")"; /* finish tuple */
2821+
2822+ return retval;
2823+ }) +
2824+ "]")
2825+ .c_str(),
2826+ &error);
2827+ throwError(error);
2828+
2829+ dbus_test_dbus_mock_object_add_method(
2830+ mock, managerobj, "GetUnit", G_VARIANT_TYPE_STRING, G_VARIANT_TYPE_OBJECT_PATH, /* ret type */
2831+ ("ret = '/'\n" + std::accumulate(instances.begin(), instances.end(), std::string{},
2832+ [](const std::string accum, const Instance& inst) {
2833+ std::string retval = accum;
2834+
2835+ retval += "if args[0] == '" + instanceName(inst) + "':\n";
2836+ retval += "\tret = '" + instancePath(inst) + "'\n";
2837+
2838+ return retval;
2839+ }))
2840+ .c_str(),
2841+ &error);
2842+ throwError(error);
2843+
2844+ dbus_test_dbus_mock_object_add_method(
2845+ mock, managerobj, "StopUnit", G_VARIANT_TYPE("(ss)"), G_VARIANT_TYPE_OBJECT_PATH, /* ret type */
2846+ std::accumulate(instances.begin(), instances.end(), std::string{},
2847+ [](const std::string accum, const Instance& inst) {
2848+ std::string retval = accum;
2849+
2850+ retval += "if args[0] == '" + instanceName(inst) + "':\n";
2851+ retval += "\tret = '" + instancePath(inst) + "'\n";
2852+
2853+ return retval;
2854+ })
2855+ .c_str(),
2856+ &error);
2857+ throwError(error);
2858+
2859+ dbus_test_dbus_mock_object_add_method(mock, managerobj, "StartTransientUnit",
2860+ G_VARIANT_TYPE("(ssa(sv)a(sa(sv)))"),
2861+ G_VARIANT_TYPE_OBJECT_PATH, /* ret type */
2862+ "ret = '/'", &error);
2863+ throwError(error);
2864+
2865+ for (auto& instance : instances)
2866+ {
2867+ auto obj = dbus_test_dbus_mock_get_object(mock, instancePath(instance).c_str(),
2868+ "org.freedesktop.systemd1.Service", &error);
2869+ throwError(error);
2870+ dbus_test_dbus_mock_object_add_property(mock, obj, "MainPID", G_VARIANT_TYPE_UINT32,
2871+ g_variant_new_uint32(instance.primaryPid), &error);
2872+ throwError(error);
2873+
2874+ /* Control Group */
2875+ auto dir = g_build_filename(controlGroupPath.c_str(), instancePath(instance).c_str(), nullptr);
2876+ auto tasks = g_build_filename(dir, "tasks", nullptr);
2877+
2878+ g_mkdir_with_parents(dir, 0777);
2879+
2880+ g_file_set_contents(tasks, std::accumulate(instance.pids.begin(), instance.pids.end(), std::string{},
2881+ [](const std::string& accum, pid_t pid) {
2882+ if (accum.empty())
2883+ {
2884+ return std::to_string(pid);
2885+ }
2886+ else
2887+ {
2888+ return accum + "\n" + std::to_string(pid);
2889+ }
2890+ })
2891+ .c_str(),
2892+ -1, &error);
2893+ throwError(error);
2894+
2895+ g_free(tasks);
2896+ g_free(dir);
2897+
2898+ dbus_test_dbus_mock_object_add_property(mock, obj, "ControlGroup", G_VARIANT_TYPE_STRING,
2899+ g_variant_new_string(instancePath(instance).c_str()), nullptr);
2900+
2901+ insts.emplace_back(std::make_pair(instance, obj));
2902+ }
2903+ }
2904+
2905+ ~SystemdMock()
2906+ {
2907+ g_debug("Destroying the Systemd Mock");
2908+ g_clear_object(&mock);
2909+ }
2910+
2911+ static std::string dbusSafe(const std::string& input)
2912+ {
2913+ std::string output = input;
2914+ std::transform(output.begin(), output.end(), output.begin(), [](char in) {
2915+ if (std::isalpha(in) || std::isdigit(in))
2916+ {
2917+ return in;
2918+ }
2919+ else
2920+ {
2921+ return '_';
2922+ }
2923+
2924+ });
2925+ return output;
2926+ }
2927+
2928+ static std::string instancePath(const Instance& inst)
2929+ {
2930+ std::string retval = std::string{"/"} + dbusSafe(inst.job) + "/" + dbusSafe(inst.appid);
2931+
2932+ if (!inst.instanceid.empty())
2933+ {
2934+ retval += "/" + dbusSafe(inst.instanceid);
2935+ }
2936+
2937+ return retval;
2938+ }
2939+
2940+ static std::string instanceName(const Instance& inst)
2941+ {
2942+ return std::string{"ubuntu-app-launch-"} + inst.job + "-" + inst.appid + "-" + inst.instanceid + ".service";
2943+ }
2944+
2945+ operator std::shared_ptr<DbusTestTask>()
2946+ {
2947+ std::shared_ptr<DbusTestTask> retval(DBUS_TEST_TASK(g_object_ref(mock)),
2948+ [](DbusTestTask* task) { g_clear_object(&task); });
2949+ return retval;
2950+ }
2951+
2952+ operator DbusTestTask*()
2953+ {
2954+ return DBUS_TEST_TASK(mock);
2955+ }
2956+
2957+ operator DbusTestDbusMock*()
2958+ {
2959+ return mock;
2960+ }
2961+
2962+ unsigned int subscribeCallsCnt()
2963+ {
2964+ guint len = 0;
2965+ GError* error = nullptr;
2966+
2967+ dbus_test_dbus_mock_object_get_method_calls(mock, /* mock */
2968+ managerobj, /* manager */
2969+ "Subscribe", /* function */
2970+ &len, /* number */
2971+ &error /* error */
2972+ );
2973+
2974+ if (error != nullptr)
2975+ {
2976+ g_warning("Unable to get 'Subscribe' calls from systemd mock: %s", error->message);
2977+ g_error_free(error);
2978+ throw std::runtime_error{"Mock disfunctional"};
2979+ }
2980+
2981+ return len;
2982+ }
2983+
2984+ unsigned int listCallsCnt()
2985+ {
2986+ guint len = 0;
2987+ GError* error = nullptr;
2988+
2989+ dbus_test_dbus_mock_object_get_method_calls(mock, /* mock */
2990+ managerobj, /* manager */
2991+ "ListUnits", /* function */
2992+ &len, /* number */
2993+ &error /* error */
2994+ );
2995+
2996+ if (error != nullptr)
2997+ {
2998+ g_warning("Unable to get 'Subscribe' calls from systemd mock: %s", error->message);
2999+ g_error_free(error);
3000+ throw std::runtime_error{"Mock disfunctional"};
3001+ }
3002+
3003+ return len;
3004+ }
3005+
3006+ std::list<std::string> stopCalls()
3007+ {
3008+ guint len = 0;
3009+ GError* error = nullptr;
3010+
3011+ auto calls = dbus_test_dbus_mock_object_get_method_calls(mock, /* mock */
3012+ managerobj, /* manager */
3013+ "StopUnit", /* function */
3014+ &len, /* number */
3015+ &error /* error */
3016+ );
3017+
3018+ if (error != nullptr)
3019+ {
3020+ g_warning("Unable to get 'StopUnit' calls from systemd mock: %s", error->message);
3021+ g_error_free(error);
3022+ throw std::runtime_error{"Mock disfunctional"};
3023+ }
3024+
3025+ std::list<std::string> retval;
3026+
3027+ for (unsigned int i = 0; i < len; i++)
3028+ {
3029+ auto& call = calls[i];
3030+ gchar* name = nullptr;
3031+ gchar* inst = nullptr;
3032+
3033+ g_variant_get(call.params, "(&s&s)", &name, &inst);
3034+
3035+ if (name == nullptr)
3036+ {
3037+ g_warning("Invalid 'name' on 'StopUnit' call");
3038+ continue;
3039+ }
3040+
3041+ retval.emplace_back(name);
3042+ }
3043+
3044+ return retval;
3045+ }
3046+
3047+ struct TransientUnit
3048+ {
3049+ std::string name;
3050+ std::set<std::string> environment;
3051+ std::string execpath;
3052+ std::list<std::string> execline;
3053+ };
3054+
3055+ std::list<TransientUnit> unitCalls()
3056+ {
3057+ guint len = 0;
3058+ GError* error = nullptr;
3059+
3060+ auto calls = dbus_test_dbus_mock_object_get_method_calls(mock, /* mock */
3061+ managerobj, /* manager */
3062+ "StartTransientUnit", /* function */
3063+ &len, /* number */
3064+ &error /* error */
3065+ );
3066+
3067+ if (error != nullptr)
3068+ {
3069+ g_warning("Unable to get 'StartTransientUnit' calls from systemd mock: %s", error->message);
3070+ g_error_free(error);
3071+ throw std::runtime_error{"Mock disfunctional"};
3072+ }
3073+
3074+ std::list<TransientUnit> retval;
3075+
3076+ for (unsigned int i = 0; i < len; i++)
3077+ {
3078+ auto& call = calls[i];
3079+ gchar* name = nullptr;
3080+
3081+ g_variant_get_child(call.params, 0, "&s", &name);
3082+
3083+ if (name == nullptr)
3084+ {
3085+ g_warning("Invalid 'name' on 'StartTransientUnit' call");
3086+ continue;
3087+ }
3088+
3089+ TransientUnit unit;
3090+ unit.name = name;
3091+
3092+ auto paramarray = g_variant_get_child_value(call.params, 2);
3093+ gchar* ckey;
3094+ GVariant* var;
3095+ GVariantIter iter;
3096+ g_variant_iter_init(&iter, paramarray);
3097+ while (g_variant_iter_loop(&iter, "(sv)", &ckey, &var))
3098+ {
3099+ g_debug("Looking at parameter: %s", ckey);
3100+ std::string key{ckey};
3101+
3102+ if (key == "Environment")
3103+ {
3104+ GVariantIter array;
3105+ gchar* envvar;
3106+ g_variant_iter_init(&array, var);
3107+
3108+ while (g_variant_iter_loop(&array, "&s", &envvar))
3109+ {
3110+ unit.environment.emplace(envvar);
3111+ }
3112+ }
3113+ else if (key == "ExecStart")
3114+ {
3115+ /* a(sasb) */
3116+ if (g_variant_n_children(var) > 1)
3117+ {
3118+ g_warning("'ExecStart' has more than one entry, only processing the first");
3119+ }
3120+
3121+ auto tuple = g_variant_get_child_value(var, 0);
3122+
3123+ const gchar* cpath = nullptr;
3124+ g_variant_get_child(tuple, 0, "&s", &cpath);
3125+
3126+ if (cpath != nullptr)
3127+ {
3128+ unit.execpath = cpath;
3129+ }
3130+ else
3131+ {
3132+ g_warning("'ExecStart[0][0]' isn't a string?");
3133+ }
3134+
3135+ auto vexecarray = g_variant_get_child_value(tuple, 1);
3136+ GVariantIter execarray;
3137+ g_variant_iter_init(&execarray, vexecarray);
3138+ const gchar* execentry;
3139+
3140+ while (g_variant_iter_loop(&execarray, "&s", &execentry))
3141+ {
3142+ unit.execline.emplace_back(execentry);
3143+ }
3144+
3145+ g_clear_pointer(&vexecarray, g_variant_unref);
3146+ g_clear_pointer(&tuple, g_variant_unref);
3147+ }
3148+ }
3149+ g_variant_unref(paramarray);
3150+
3151+ retval.emplace_back(unit);
3152+ }
3153+
3154+ return retval;
3155+ }
3156+
3157+ void managerClear()
3158+ {
3159+ GError* error = nullptr;
3160+
3161+ dbus_test_dbus_mock_object_clear_method_calls(mock, /* mock */
3162+ managerobj, /* manager */
3163+ &error /* error */
3164+ );
3165+
3166+ if (error != nullptr)
3167+ {
3168+ g_warning("Unable to clear manager calls: %s", error->message);
3169+ g_error_free(error);
3170+ throw std::runtime_error{"Mock disfunctional"};
3171+ }
3172+ }
3173+
3174+ void managerEmitNew(const std::string& name, const std::string& path)
3175+ {
3176+ GError* error = nullptr;
3177+
3178+ dbus_test_dbus_mock_object_emit_signal(mock, managerobj, "UnitNew", G_VARIANT_TYPE("(so)"),
3179+ g_variant_new("(so)", name.c_str(), path.c_str()), &error);
3180+
3181+ if (error != nullptr)
3182+ {
3183+ g_warning("Unable to emit 'UnitNew': %s", error->message);
3184+ g_error_free(error);
3185+ throw std::runtime_error{"Mock disfunctional"};
3186+ }
3187+ }
3188+
3189+ void managerEmitRemoved(const std::string& name, const std::string& path)
3190+ {
3191+ GError* error = nullptr;
3192+
3193+ dbus_test_dbus_mock_object_emit_signal(mock, managerobj, "UnitRemoved", G_VARIANT_TYPE("(so)"),
3194+ g_variant_new("(so)", name.c_str(), path.c_str()), &error);
3195+
3196+ if (error != nullptr)
3197+ {
3198+ g_warning("Unable to emit 'UnitRemoved': %s", error->message);
3199+ g_error_free(error);
3200+ throw std::runtime_error{"Mock disfunctional"};
3201+ }
3202+ }
3203+};
3204
3205=== modified file 'xmir-helper.c'
3206--- xmir-helper.c 2015-08-11 02:41:08 +0000
3207+++ xmir-helper.c 2017-02-01 20:34:54 +0000
3208@@ -86,6 +86,8 @@
3209 NULL
3210 };
3211
3212+ printf("Executing XMir on PID: %d", getpid());
3213+
3214 return execv(xmir, xmirexec);
3215 }
3216

Subscribers

People subscribed via source and target branches