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

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

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

Commit message

Make UAL relocatable for Snappy installs

Description of the change

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

Also added docs for all the envvars that we use.

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

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

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

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

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

LGTM

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

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

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

review: Needs Fixing
257. By Ted Gould

Merging the jobs-systemd branch

258. By Ted Gould

Update to latest systemd branch

259. By Ted Gould

Pulling through updates

260. By Ted Gould

Adding an environment variable for XMir Helper

261. By Ted Gould

Pulling through trunk

262. By Ted Gould

Restructure exec line building so we can add more

263. By Ted Gould

Adding extra environment in the snap world

264. By Ted Gould

Make sure we get the other snap environment variables

265. By Ted Gould

Switch to calling legacy-exec

266. By Ted Gould

Starting utilities to put together an environment setup

267. By Ted Gould

Better exit codes

268. By Ted Gould

Fleshing out our utilities

269. By Ted Gould

Get the binaries into a deb

270. By Ted Gould

Make the command line reference the outside util

271. By Ted Gould

Call the snappy-xmir util unconfined

272. By Ted Gould

Handle write() returns

273. By Ted Gould

Disable for a moment

274. By Ted Gould

Remove the snap setting libertine launch

275. By Ted Gould

Fix command name

276. By Ted Gould

Make sure we sleep forever

277. By Ted Gould

Get negative errors as well

278. By Ted Gould

Make sure the FD stays open

279. By Ted Gould

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

280. By Ted Gould

Some basic test scripts

281. By Ted Gould

Switch to an abstract socket

282. By Ted Gould

Make sure we don't overrun sun_path

283. By Ted Gould

Clear out the MIR_* variables

284. By Ted Gould

Getting the snappy-xmir tests into the test suite

285. By Ted Gould

Check for MIR_ variables

286. By Ted Gould

Making sure we clean up the envvars utils

287. By Ted Gould

Increase output and debugging info in tests

288. By Ted Gould

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

289. By Ted Gould

Cleanup magic numbers

290. By Ted Gould

Explicitly set the DBus address as well

291. By Ted Gould

Make sure to pad with zeros

292. By Ted Gould

Test for long AppIDs

293. By Ted Gould

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

294. By Ted Gould

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

295. By Ted Gould

Fix formatting

296. By Ted Gould

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

297. By Ted Gould

Use a build variable to get the path

298. By Ted Gould

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

299. By Ted Gould

Allow the legacy-exec util to be overridden

300. By Ted Gould

Merge error

301. By Ted Gould

More magic numbers brought down to Earth

302. By Ted Gould

Bigger libs warning. Like really people, read this.

303. By Ted Gould

Fix the libertine-launch case for non-snaps

304. By Ted Gould

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

305. By Ted Gould

Removing more variables

306. By Ted Gould

Upstream changes

307. By Ted Gould

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

308. By Ted Gould

Merge upstream updates

309. By Ted Gould

Update to trunk

310. By Ted Gould

Update to current jobs-systemd

311. By Ted Gould

Grab QT_ and XDG_ for legacy apps on deb installs

312. By Ted Gould

Switching functions

313. By Ted Gould

Merge Trunk

314. By Ted Gould

Merge changes to jobs-systemd

315. By Ted Gould

Put the wrong environment var into the documentation

316. By Ted Gould

Getting these variables figured out

317. By Ted Gould

Fix docs to include default path for LEGACY_EXEC

318. By Ted Gould

Cache snap env var

319. By Ted Gould

Make sure we only use references in loops

320. By Ted Gould

Don't store if we have the variable

321. By Ted Gould

Return const pointer

322. By Ted Gould

Handle larger possible pids

323. By Ted Gould

Make sure we're gettin' data

324. By Ted Gould

Move debug outside of the print loop

325. By Ted Gould

A read failure is a helper failure

326. By Ted Gould

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

327. By Ted Gould

Special case legacy apps on Unity8 from debs

328. By Ted Gould

Put on the dunce cap and sit in the corner

Unmerged revisions

328. By Ted Gould

Put on the dunce cap and sit in the corner

327. By Ted Gould

Special case legacy apps on Unity8 from debs

326. By Ted Gould

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

325. By Ted Gould

A read failure is a helper failure

324. By Ted Gould

Move debug outside of the print loop

323. By Ted Gould

Make sure we're gettin' data

322. By Ted Gould

Handle larger possible pids

321. By Ted Gould

Return const pointer

320. By Ted Gould

Don't store if we have the variable

319. By Ted Gould

Make sure we only use references in loops

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'docs/index.rst'
--- docs/index.rst 2016-08-04 14:04:40 +0000
+++ docs/index.rst 2016-11-01 14:53:56 +0000
@@ -18,6 +18,47 @@
1818
19.. _Upstart: http://upstart.ubuntu.com/19.. _Upstart: http://upstart.ubuntu.com/
2020
21
22Environment Variables
23=====================
24
25There are a few environment variables that can effect the behavior of UAL while
26it is running.
27
28UBUNTU_APP_LAUNCH_CG_MANAGER_NAME
29 The DBus name that CG Manager registers under if it is on the session bus.
30
31UBUNTU_APP_LAUNCH_CG_MANAGER_SESSION_BUS
32 Tell UAL to look on the session bus for CG Manager.
33
34UBUNTU_APP_LAUNCH_DEMANGLER
35 Path to the UAL demangler tool that will get the Mir FD for trusted prompt session.
36
37UBUNTU_APP_LAUNCH_DISABLE_SNAPD_TIMEOUT
38 Wait as long as Snapd wants to return data instead of erroring after 100ms.
39
40UBUNTU_APP_LAUNCH_LEGACY_ROOT
41 Set the path that represents the root for legacy applications.
42
43UBUNTU_APP_LAUNCH_LIBERTINE_LAUNCH
44 Path to the libertine launch utility for setting up libertine containers and XMir based legacy apps.
45
46UBUNTU_APP_LAUNCH_LINK_FARM
47 Path to the link farm that is created by Click of all the installed Click applications.
48
49UBUNTU_APP_LAUNCH_OOM_HELPER
50 Path to the setuid helper that configures OOM values on application processes that we otherwise couldn't, mostly this is for Oxide.
51
52UBUNTU_APP_LAUNCH_OOM_PROC_PATH
53 Path to look for the files to set OOM values, defaults to /proc.
54
55UBUNTU_APP_LAUNCH_SNAP_BASEDIR
56 The place where snaps are installed in the system, /snap is the default.
57
58UBUNTU_APP_LAUNCH_SNAPD_SOCKET
59 Path to the snapd socket.
60
61
21API Documentation62API Documentation
22=================63=================
2364
@@ -146,6 +187,46 @@
146 :private-members:187 :private-members:
147 :undoc-members:188 :undoc-members:
148189
190Jobs Manager Base
191-----------------
192
193.. doxygenclass:: ubuntu::app_launch::jobs::manager::Base
194 :project: libubuntu-app-launch
195 :members:
196 :protected-members:
197 :private-members:
198 :undoc-members:
199
200Jobs Instance Base
201------------------
202
203.. doxygenclass:: ubuntu::app_launch::jobs::instance::Base
204 :project: libubuntu-app-launch
205 :members:
206 :protected-members:
207 :private-members:
208 :undoc-members:
209
210Jobs Manager Upstart
211--------------------
212
213.. doxygenclass:: ubuntu::app_launch::jobs::manager::Upstart
214 :project: libubuntu-app-launch
215 :members:
216 :protected-members:
217 :private-members:
218 :undoc-members:
219
220Jobs Instance Upstart
221---------------------
222
223.. doxygenclass:: ubuntu::app_launch::jobs::instance::Upstart
224 :project: libubuntu-app-launch
225 :members:
226 :protected-members:
227 :private-members:
228 :undoc-members:
229
149Registry Implementation230Registry Implementation
150-----------------------231-----------------------
151232
@@ -176,16 +257,6 @@
176 :private-members:257 :private-members:
177 :undoc-members:258 :undoc-members:
178259
179Upstart Instance
180----------------
181
182.. doxygenclass:: ubuntu::app_launch::app_impls::UpstartInstance
183 :project: libubuntu-app-launch
184 :members:
185 :protected-members:
186 :private-members:
187 :undoc-members:
188
189Quality260Quality
190=======261=======
191262
192263
=== modified file 'libubuntu-app-launch/CMakeLists.txt'
--- libubuntu-app-launch/CMakeLists.txt 2016-08-26 17:33:34 +0000
+++ libubuntu-app-launch/CMakeLists.txt 2016-11-01 14:53:56 +0000
@@ -17,6 +17,8 @@
17set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility=hidden -Wpedantic")17set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility=hidden -Wpedantic")
18add_definitions ( -DOOM_HELPER="${pkglibexecdir}/oom-adjust-setuid-helper" -DDEMANGLER_PATH="${pkglibexecdir}/socket-demangler" )18add_definitions ( -DOOM_HELPER="${pkglibexecdir}/oom-adjust-setuid-helper" -DDEMANGLER_PATH="${pkglibexecdir}/socket-demangler" )
19add_definitions ( -DLIBERTINE_LAUNCH="${CMAKE_INSTALL_FULL_BINDIR}/libertine-launch" )19add_definitions ( -DLIBERTINE_LAUNCH="${CMAKE_INSTALL_FULL_BINDIR}/libertine-launch" )
20add_definitions ( -DG_LOG_DOMAIN="ubuntu-app-launch" )
21add_definitions ( -DUBUNTU_APP_LAUNCH_ARCH="${UBUNTU_APP_LAUNCH_ARCH}" )
2022
21set(LAUNCHER_HEADERS23set(LAUNCHER_HEADERS
22ubuntu-app-launch.h24ubuntu-app-launch.h
@@ -52,6 +54,12 @@
52helper-impl-click.cpp54helper-impl-click.cpp
53glib-thread.h55glib-thread.h
54glib-thread.cpp56glib-thread.cpp
57jobs-base.h
58jobs-base.cpp
59jobs-systemd.h
60jobs-systemd.cpp
61jobs-upstart.h
62jobs-upstart.cpp
55)63)
5664
57set(LAUNCHER_SOURCES65set(LAUNCHER_SOURCES
5866
=== modified file 'libubuntu-app-launch/application-impl-base.cpp'
--- libubuntu-app-launch/application-impl-base.cpp 2016-10-03 23:54:20 +0000
+++ libubuntu-app-launch/application-impl-base.cpp 2016-11-01 14:53:56 +0000
@@ -23,8 +23,6 @@
23#include <map>23#include <map>
24#include <numeric>24#include <numeric>
2525
26#include <upstart.h>
27
28#include "application-impl-base.h"26#include "application-impl-base.h"
29#include "helpers.h"27#include "helpers.h"
30#include "registry-impl.h"28#include "registry-impl.h"
@@ -97,768 +95,24 @@
97 return retval;95 return retval;
98}96}
9997
100/** Checks to see if we have a primary PID for the instance */98/** Generates an instance string based on the clock if we're a multi-instance
101bool UpstartInstance::isRunning()99 application. */
102{100std::string Base::getInstance(const std::shared_ptr<app_info::Desktop>& desktop)
103 return primaryPid() != 0;101{
104}102 if (!desktop)
105103 {
106/** Uses Upstart to get the primary PID of the instance using Upstart's104 g_warning("Invalid desktop file passed to getInstance");
107 DBus interface */105 return {};
108pid_t UpstartInstance::primaryPid()106 }
109{107
110 auto jobpath = registry_->impl->upstartJobPath(job_);108 if (desktop->singleInstance())
111 if (jobpath.empty())109 {
112 {110 return {};
113 g_debug("Unable to get a valid job path");111 }
114 return 0;
115 }
116
117 return registry_->impl->thread.executeOnThread<pid_t>([this, &jobpath]() -> pid_t {
118 GError* error = nullptr;
119
120 std::string instancename = std::string(appId_);
121 if (job_ != "application-click")
122 {
123 instancename += "-" + instance_;
124 }
125
126 g_debug("Getting instance by name: %s", instance_.c_str());
127 GVariant* vinstance_path =
128 g_dbus_connection_call_sync(registry_->impl->_dbus.get(), /* connection */
129 DBUS_SERVICE_UPSTART, /* service */
130 jobpath.c_str(), /* object path */
131 DBUS_INTERFACE_UPSTART_JOB, /* iface */
132 "GetInstanceByName", /* method */
133 g_variant_new("(s)", instancename.c_str()), /* params */
134 G_VARIANT_TYPE("(o)"), /* return type */
135 G_DBUS_CALL_FLAGS_NONE, /* flags */
136 -1, /* timeout: default */
137 registry_->impl->thread.getCancellable().get(), /* cancellable */
138 &error);
139
140 if (error != nullptr)
141 {
142 g_warning("Unable to get instance '%s' of job '%s': %s", instance_.c_str(), job_.c_str(), error->message);
143 g_error_free(error);
144 return 0;
145 }
146
147 /* Jump rope to make this into a C++ type */
148 std::string instance_path;
149 gchar* cinstance_path = nullptr;
150 g_variant_get(vinstance_path, "(o)", &cinstance_path);
151 g_variant_unref(vinstance_path);
152 if (cinstance_path != nullptr)
153 {
154 instance_path = cinstance_path;
155 g_free(cinstance_path);
156 }
157
158 if (instance_path.empty())
159 {
160 g_debug("No instance object for instance name: %s", instance_.c_str());
161 return 0;
162 }
163
164 GVariant* props_tuple =
165 g_dbus_connection_call_sync(registry_->impl->_dbus.get(), /* connection */
166 DBUS_SERVICE_UPSTART, /* service */
167 instance_path.c_str(), /* object path */
168 "org.freedesktop.DBus.Properties", /* interface */
169 "GetAll", /* method */
170 g_variant_new("(s)", DBUS_INTERFACE_UPSTART_INSTANCE), /* params */
171 G_VARIANT_TYPE("(a{sv})"), /* return type */
172 G_DBUS_CALL_FLAGS_NONE, /* flags */
173 -1, /* timeout: default */
174 registry_->impl->thread.getCancellable().get(), /* cancellable */
175 &error);
176
177 if (error != nullptr)
178 {
179 g_warning("Unable to name of properties '%s': %s", instance_path.c_str(), error->message);
180 g_error_free(error);
181 error = nullptr;
182 return 0;
183 }
184
185 GVariant* props_dict = g_variant_get_child_value(props_tuple, 0);
186
187 pid_t retval = 0;
188 GVariant* processes = g_variant_lookup_value(props_dict, "processes", G_VARIANT_TYPE("a(si)"));
189 if (processes != nullptr && g_variant_n_children(processes) > 0)
190 {
191
192 GVariant* first_entry = g_variant_get_child_value(processes, 0);
193 GVariant* pidv = g_variant_get_child_value(first_entry, 1);
194
195 retval = g_variant_get_int32(pidv);
196
197 g_variant_unref(pidv);
198 g_variant_unref(first_entry);
199 }
200 else
201 {
202 g_debug("Unable to get 'processes' from properties of instance at path: %s", instance_path.c_str());
203 }
204
205 g_variant_unref(props_dict);
206
207 return retval;
208 });
209}
210
211/** Generate the full name of the Upstart job for the job, the
212 instance and how all those fit together.
213
214 Handles the special case of application-click which isn't designed
215 to have multi-instance apps.
216*/
217std::string UpstartInstance::upstartJobPath()
218{
219 std::string path = job_ + "-" + std::string(appId_);
220 if (job_ != "application-click")
221 {
222 path += "-";
223 }
224 if (!instance_.empty())
225 {
226 path += instance_;
227 }
228
229 return path;
230}
231
232/** Looks at the PIDs in the instance cgroup and checks to see if @pid
233 is in the set.
234
235 @param pid PID to look for
236*/
237bool UpstartInstance::hasPid(pid_t pid)
238{
239 for (auto testpid : registry_->impl->pidsFromCgroup(upstartJobPath()))
240 if (pid == testpid)
241 return true;
242 return false;
243}
244
245/** Gets the path to the log file for this instance */
246std::string UpstartInstance::logPath()
247{
248 std::string logfile = upstartJobPath() + ".log";
249
250 gchar* cpath = g_build_filename(g_get_user_cache_dir(), "upstart", logfile.c_str(), nullptr);
251 std::string path(cpath);
252 g_free(cpath);
253
254 return path;
255}
256
257/** Returns all the PIDs that are in the cgroup for this application */
258std::vector<pid_t> UpstartInstance::pids()
259{
260 auto pids = registry_->impl->pidsFromCgroup(upstartJobPath());
261 g_debug("Got %d PIDs for AppID '%s'", int(pids.size()), std::string(appId_).c_str());
262 return pids;
263}
264
265/** Pauses this application by sending SIGSTOP to all the PIDs in the
266 cgroup and tells Zeitgeist that we've left the application. */
267void UpstartInstance::pause()
268{
269 g_debug("Pausing application: %s", std::string(appId_).c_str());
270 registry_->impl->zgSendEvent(appId_, ZEITGEIST_ZG_LEAVE_EVENT);
271
272 auto pids = forAllPids([this](pid_t pid) {
273 auto oomval = oom::paused();
274 g_debug("Pausing PID: %d (%d)", pid, int(oomval));
275 signalToPid(pid, SIGSTOP);
276 oomValueToPid(pid, oomval);
277 });
278
279 pidListToDbus(pids, "ApplicationPaused");
280}
281
282/** Resumes this application by sending SIGCONT to all the PIDs in the
283 cgroup and tells Zeitgeist that we're accessing the application. */
284void UpstartInstance::resume()
285{
286 g_debug("Resuming application: %s", std::string(appId_).c_str());
287 registry_->impl->zgSendEvent(appId_, ZEITGEIST_ZG_ACCESS_EVENT);
288
289 auto pids = forAllPids([this](pid_t pid) {
290 auto oomval = oom::focused();
291 g_debug("Resuming PID: %d (%d)", pid, int(oomval));
292 signalToPid(pid, SIGCONT);
293 oomValueToPid(pid, oomval);
294 });
295
296 pidListToDbus(pids, "ApplicationResumed");
297}
298
299/** Stops this instance by asking Upstart to stop it. Upstart will then
300 send a SIGTERM and five seconds later start killing things. */
301void UpstartInstance::stop()
302{
303 if (!registry_->impl->thread.executeOnThread<bool>([this]() {
304
305 g_debug("Stopping job %s app_id %s instance_id %s", job_.c_str(), std::string(appId_).c_str(),
306 instance_.c_str());
307
308 auto jobpath = registry_->impl->upstartJobPath(job_);
309 if (jobpath.empty())
310 {
311 throw new std::runtime_error("Unable to get job path for Upstart job '" + job_ + "'");
312 }
313
314 GVariantBuilder builder;
315 g_variant_builder_init(&builder, G_VARIANT_TYPE_TUPLE);
316 g_variant_builder_open(&builder, G_VARIANT_TYPE_ARRAY);
317
318 g_variant_builder_add_value(
319 &builder, g_variant_new_take_string(g_strdup_printf("APP_ID=%s", std::string(appId_).c_str())));
320
321 if (!instance_.empty())
322 {
323 g_variant_builder_add_value(
324 &builder, g_variant_new_take_string(g_strdup_printf("INSTANCE_ID=%s", instance_.c_str())));
325 }
326
327 g_variant_builder_close(&builder);
328 g_variant_builder_add_value(&builder, g_variant_new_boolean(FALSE)); /* wait */
329
330 GError* error = nullptr;
331 GVariant* stop_variant =
332 g_dbus_connection_call_sync(registry_->impl->_dbus.get(), /* Dbus */
333 DBUS_SERVICE_UPSTART, /* Upstart name */
334 jobpath.c_str(), /* path */
335 DBUS_INTERFACE_UPSTART_JOB, /* interface */
336 "Stop", /* method */
337 g_variant_builder_end(&builder), /* params */
338 nullptr, /* return */
339 G_DBUS_CALL_FLAGS_NONE, /* flags */
340 -1, /* timeout: default */
341 registry_->impl->thread.getCancellable().get(), /* cancellable */
342 &error); /* error (hopefully not) */
343
344 g_clear_pointer(&stop_variant, g_variant_unref);
345
346 if (error != nullptr)
347 {
348 g_warning("Unable to stop job %s app_id %s instance_id %s: %s", job_.c_str(),
349 std::string(appId_).c_str(), instance_.c_str(), error->message);
350 g_error_free(error);
351 return false;
352 }
353
354 return true;
355 }))
356 {
357 g_warning("Unable to stop Upstart instance");
358 }
359}
360
361/** Sets the OOM adjustment by getting the list of PIDs and writing
362 the value to each of their files in proc
363
364 \param score OOM Score to set
365*/
366void UpstartInstance::setOomAdjustment(const oom::Score score)
367{
368 forAllPids([this, &score](pid_t pid) { oomValueToPid(pid, score); });
369}
370
371/** Figures out the path to the primary PID of the application and
372 then reads its OOM adjustment file. */
373const oom::Score UpstartInstance::getOomAdjustment()
374{
375 auto pid = primaryPid();
376 if (pid == 0)
377 {
378 throw std::runtime_error("No PID for application: " + std::string(appId_));
379 }
380
381 auto path = pidToOomPath(pid);
382 GError* error = nullptr;
383 gchar* content = nullptr;
384
385 g_file_get_contents(path.c_str(), /* path */
386 &content, /* data */
387 nullptr, /* size */
388 &error); /* error */
389
390 if (error != nullptr)
391 {
392 auto serror = std::shared_ptr<GError>(error, g_error_free);
393 throw std::runtime_error("Unable to access OOM value for '" + std::string(appId_) + "' primary PID '" +
394 std::to_string(pid) + "' because: " + serror->message);
395 }
396
397 auto score = static_cast<oom::Score>(std::atoi(content));
398 g_free(content);
399 return score;
400}
401
402/** Go through the list of PIDs calling a function and handling
403 the issue with getting PIDs being a racey condition.
404
405 \param eachPid Function to run on each PID
406*/
407std::vector<pid_t> UpstartInstance::forAllPids(std::function<void(pid_t)> eachPid)
408{
409 std::set<pid_t> seenPids;
410 bool added = true;
411
412 while (added)
413 {
414 added = false;
415 auto pidlist = pids();
416 for (auto pid : pidlist)
417 {
418 if (seenPids.insert(pid).second)
419 {
420 eachPid(pid);
421 added = true;
422 }
423 }
424 }
425
426 return std::vector<pid_t>(seenPids.begin(), seenPids.end());
427}
428
429/** Sends a signal to a PID with a warning if we can't send it.
430 We could throw an exception, but we can't handle it usefully anyway
431
432 \param pid PID to send the signal to
433 \param signal signal to send
434*/
435void UpstartInstance::signalToPid(pid_t pid, int signal)
436{
437 if (-1 == kill(pid, signal))
438 {
439 /* While that didn't work, we still want to try as many as we can */
440 g_warning("Unable to send signal %d to pid %d", signal, pid);
441 }
442}
443
444/** Get the path to the PID's OOM adjust path, with allowing for an
445 override for testing using the environment variable
446 UBUNTU_APP_LAUNCH_OOM_PROC_PATH
447
448 \param pid PID to build path for
449*/
450std::string UpstartInstance::pidToOomPath(pid_t pid)
451{
452 static std::string procpath;
453 if (G_UNLIKELY(procpath.empty()))
454 {
455 /* Set by the test suite, probably not anyone else */
456 auto envvar = g_getenv("UBUNTU_APP_LAUNCH_OOM_PROC_PATH");
457 if (G_LIKELY(envvar == nullptr))
458 procpath = "/proc";
459 else
460 procpath = envvar;
461 }
462
463 gchar* gpath = g_build_filename(procpath.c_str(), std::to_string(pid).c_str(), "oom_score_adj", nullptr);
464 std::string path = gpath;
465 g_free(gpath);
466 return path;
467}
468
469/** Writes an OOM value to proc, assuming we have a string
470 in the outer loop
471
472 \param pid PID to change the OOM value of
473 \param oomvalue OOM value to set
474*/
475void UpstartInstance::oomValueToPid(pid_t pid, const oom::Score oomvalue)
476{
477 auto oomstr = std::to_string(static_cast<std::int32_t>(oomvalue));
478 auto path = pidToOomPath(pid);
479 FILE* adj = fopen(path.c_str(), "w");
480 int openerr = errno;
481
482 if (adj == nullptr)
483 {
484 switch (openerr)
485 {
486 case ENOENT:
487 /* ENOENT happens a fair amount because of races, so it's not
488 worth printing a warning about */
489 return;
490 case EACCES:
491 {
492 /* We can get this error when trying to set the OOM value on
493 Oxide renderers because they're started by the sandbox and
494 don't have their adjustment value available for us to write.
495 We have a helper to deal with this, but it's kinda expensive
496 so we only use it when we have to. */
497 oomValueToPidHelper(pid, oomvalue);
498 return;
499 }
500 default:
501 g_warning("Unable to set OOM value for '%d' to '%s': %s", int(pid), oomstr.c_str(),
502 std::strerror(openerr));
503 return;
504 }
505 }
506
507 size_t writesize = fwrite(oomstr.c_str(), 1, oomstr.size(), adj);
508 int writeerr = errno;
509 fclose(adj);
510
511 if (writesize == oomstr.size())
512 return;
513
514 if (writeerr != 0)
515 g_warning("Unable to set OOM value for '%d' to '%s': %s", int(pid), oomstr.c_str(), strerror(writeerr));
516 else112 else
517 /* No error, but yet, wrong size. Not sure, what could cause this. */113 {
518 g_debug("Unable to set OOM value for '%d' to '%s': Wrote %d bytes", int(pid), oomstr.c_str(), int(writesize));114 return std::to_string(g_get_real_time());
519}115 }
520
521/** Use a setuid root helper for setting the oom value of
522 Chromium instances
523
524 \param pid PID to change the OOM value of
525 \param oomvalue OOM value to set
526*/
527void UpstartInstance::oomValueToPidHelper(pid_t pid, const oom::Score oomvalue)
528{
529 GError* error = nullptr;
530 std::string oomstr = std::to_string(static_cast<std::int32_t>(oomvalue));
531 std::string pidstr = std::to_string(pid);
532 std::array<const char*, 4> args = {OOM_HELPER, pidstr.c_str(), oomstr.c_str(), nullptr};
533
534 g_debug("Excuting OOM Helper (pid: %d, score: %d): %s", int(pid), int(oomvalue),
535 std::accumulate(args.begin(), args.end(), std::string{},
536 [](const std::string& instr, const char* output) -> std::string {
537 if (instr.empty())
538 {
539 return output;
540 }
541 else if (output != nullptr)
542 {
543 return instr + " " + std::string(output);
544 }
545 else
546 {
547 return instr;
548 }
549 })
550 .c_str());
551
552 g_spawn_async(nullptr, /* working dir */
553 (char**)(args.data()), /* args */
554 nullptr, /* env */
555 G_SPAWN_DEFAULT, /* flags */
556 nullptr, /* child setup */
557 nullptr, /* child setup userdata*/
558 nullptr, /* pid */
559 &error); /* error */
560
561 if (error != nullptr)
562 {
563 g_warning("Unable to launch OOM helper '" OOM_HELPER "' on PID '%d': %s", pid, error->message);
564 g_error_free(error);
565 return;
566 }
567}
568
569/** Send a signal that we've change the application. Do this on the
570 registry thread in an idle so that we don't block anyone.
571
572 \param pids List of PIDs to turn into variants to send
573 \param signal Name of the DBus signal to send
574*/
575void UpstartInstance::pidListToDbus(const std::vector<pid_t>& pids, const std::string& signal)
576{
577 auto registry = registry_;
578 auto lappid = appId_;
579
580 registry_->impl->thread.executeOnThread([registry, lappid, pids, signal] {
581 auto vpids = std::shared_ptr<GVariant>(
582 [pids]() {
583 GVariant* pidarray = nullptr;
584
585 if (pids.empty())
586 {
587 pidarray = g_variant_new_array(G_VARIANT_TYPE_UINT64, nullptr, 0);
588 g_variant_ref_sink(pidarray);
589 return pidarray;
590 }
591
592 GVariantBuilder builder;
593 g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY);
594 for (auto pid : pids)
595 {
596 g_variant_builder_add_value(&builder, g_variant_new_uint64(pid));
597 }
598
599 pidarray = g_variant_builder_end(&builder);
600 g_variant_ref_sink(pidarray);
601 return pidarray;
602 }(),
603 [](GVariant* var) { g_variant_unref(var); });
604
605 GVariantBuilder params;
606 g_variant_builder_init(&params, G_VARIANT_TYPE_TUPLE);
607 g_variant_builder_add_value(&params, g_variant_new_string(std::string(lappid).c_str()));
608 g_variant_builder_add_value(&params, vpids.get());
609
610 GError* error = nullptr;
611 g_dbus_connection_emit_signal(registry->impl->_dbus.get(), /* bus */
612 nullptr, /* destination */
613 "/", /* path */
614 "com.canonical.UbuntuAppLaunch", /* interface */
615 signal.c_str(), /* signal */
616 g_variant_builder_end(&params), /* params, the same */
617 &error); /* error */
618
619 if (error != nullptr)
620 {
621 g_warning("Unable to emit signal '%s' for appid '%s': %s", signal.c_str(), std::string(lappid).c_str(),
622 error->message);
623 g_error_free(error);
624 }
625 else
626 {
627 g_debug("Emmitted '%s' to DBus", signal.c_str());
628 }
629 });
630}
631
632/** Create a new Upstart Instance object that can track the job and
633 get information about it.
634
635 \param appId Application ID
636 \param job Upstart job name
637 \param instance Upstart instance name
638 \param urls URLs sent to the application (only on launch today)
639 \param registry Registry of persistent connections to use
640*/
641UpstartInstance::UpstartInstance(const AppID& appId,
642 const std::string& job,
643 const std::string& instance,
644 const std::vector<Application::URL>& urls,
645 const std::shared_ptr<Registry>& registry)
646 : appId_(appId)
647 , job_(job)
648 , instance_(instance)
649 , urls_(urls)
650 , registry_(registry)
651{
652 g_debug("Creating a new UpstartInstance for '%s' instance '%s'", std::string(appId_).c_str(), instance.c_str());
653}
654
655/** Reformat a C++ vector of URLs into a C GStrv of strings
656
657 \param urls Vector of URLs to make into C strings
658*/
659std::shared_ptr<gchar*> UpstartInstance::urlsToStrv(const std::vector<Application::URL>& urls)
660{
661 if (urls.empty())
662 {
663 return {};
664 }
665
666 auto array = g_array_new(TRUE, FALSE, sizeof(gchar*));
667
668 for (auto url : urls)
669 {
670 auto str = g_strdup(url.value().c_str());
671 g_debug("Converting URL: %s", str);
672 g_array_append_val(array, str);
673 }
674
675 return std::shared_ptr<gchar*>((gchar**)g_array_free(array, FALSE), g_strfreev);
676}
677
678/** Small helper that we can new/delete to work better with C stuff */
679struct StartCHelper
680{
681 std::shared_ptr<UpstartInstance> ptr;
682};
683
684/** Callback from starting an application. It checks to see whether the
685 app is already running. If it is already running then we need to send
686 the URLs to it via DBus.
687
688 \param obj The GDBusConnection object
689 \param res Async result object
690 \param user_data A pointer to a StartCHelper structure
691*/
692void UpstartInstance::application_start_cb(GObject* obj, GAsyncResult* res, gpointer user_data)
693{
694 auto data = static_cast<StartCHelper*>(user_data);
695 GError* error{nullptr};
696 GVariant* result{nullptr};
697
698 tracepoint(ubuntu_app_launch, libual_start_message_callback, std::string(data->ptr->appId_).c_str());
699
700 g_debug("Started Message Callback: %s", std::string(data->ptr->appId_).c_str());
701
702 result = g_dbus_connection_call_finish(G_DBUS_CONNECTION(obj), res, &error);
703
704 g_clear_pointer(&result, g_variant_unref);
705
706 if (error != nullptr)
707 {
708 if (g_dbus_error_is_remote_error(error))
709 {
710 gchar* remote_error = g_dbus_error_get_remote_error(error);
711 g_debug("Remote error: %s", remote_error);
712 if (g_strcmp0(remote_error, "com.ubuntu.Upstart0_6.Error.AlreadyStarted") == 0)
713 {
714 auto urls = urlsToStrv(data->ptr->urls_);
715 second_exec(data->ptr->registry_->impl->_dbus.get(), /* DBus */
716 data->ptr->registry_->impl->thread.getCancellable().get(), /* cancellable */
717 data->ptr->primaryPid(), /* primary pid */
718 std::string(data->ptr->appId_).c_str(), /* appid */
719 urls.get()); /* urls */
720 }
721
722 g_free(remote_error);
723 }
724 else
725 {
726 g_warning("Unable to emit event to start application: %s", error->message);
727 }
728 g_error_free(error);
729 }
730
731 delete data;
732}
733
734/** Launch an application and create a new UpstartInstance object to track
735 its progress.
736
737 \param appId Application ID
738 \param job Upstart job name
739 \param instance Upstart instance name
740 \param urls URLs sent to the application (only on launch today)
741 \param registry Registry of persistent connections to use
742 \param mode Whether or not to setup the environment for testing
743 \param getenv A function to get additional environment variable when appropriate
744*/
745std::shared_ptr<UpstartInstance> UpstartInstance::launch(
746 const AppID& appId,
747 const std::string& job,
748 const std::string& instance,
749 const std::vector<Application::URL>& urls,
750 const std::shared_ptr<Registry>& registry,
751 launchMode mode,
752 std::function<std::list<std::pair<std::string, std::string>>(void)>& getenv)
753{
754 if (appId.empty())
755 return {};
756
757 return registry->impl->thread.executeOnThread<std::shared_ptr<UpstartInstance>>(
758 [&]() -> std::shared_ptr<UpstartInstance> {
759 std::string appIdStr{appId};
760 g_debug("Initializing params for an new UpstartInstance for: %s", appIdStr.c_str());
761
762 tracepoint(ubuntu_app_launch, libual_start, appIdStr.c_str());
763
764 int timeout = 1;
765 if (ubuntu::app_launch::Registry::Impl::isWatchingAppStarting())
766 {
767 timeout = 0;
768 }
769
770 auto handshake = starting_handshake_start(appIdStr.c_str(), timeout);
771 if (handshake == nullptr)
772 {
773 g_warning("Unable to setup starting handshake");
774 }
775
776 /* Figure out the DBus path for the job */
777 auto jobpath = registry->impl->upstartJobPath(job);
778
779 /* Build up our environment */
780 auto env = getenv();
781
782 env.emplace_back(std::make_pair("APP_ID", appIdStr)); /* Application ID */
783 env.emplace_back(std::make_pair("APP_LAUNCHER_PID", std::to_string(getpid()))); /* Who we are, for bugs */
784
785 if (!urls.empty())
786 {
787 auto accumfunc = [](const std::string& prev, Application::URL thisurl) -> std::string {
788 gchar* gescaped = g_shell_quote(thisurl.value().c_str());
789 std::string escaped;
790 if (gescaped != nullptr)
791 {
792 escaped = gescaped;
793 g_free(gescaped);
794 }
795 else
796 {
797 g_warning("Unable to escape URL: %s", thisurl.value().c_str());
798 return prev;
799 }
800
801 if (prev.empty())
802 {
803 return escaped;
804 }
805 else
806 {
807 return prev + " " + escaped;
808 }
809 };
810 auto urlstring = std::accumulate(urls.begin(), urls.end(), std::string{}, accumfunc);
811 env.emplace_back(std::make_pair("APP_URIS", urlstring));
812 }
813
814 if (mode == launchMode::TEST)
815 {
816 env.emplace_back(std::make_pair("QT_LOAD_TESTABILITY", "1"));
817 }
818
819 /* Convert to GVariant */
820 GVariantBuilder builder;
821 g_variant_builder_init(&builder, G_VARIANT_TYPE_TUPLE);
822
823 g_variant_builder_open(&builder, G_VARIANT_TYPE_ARRAY);
824
825 for (const auto& envvar : env)
826 {
827 g_variant_builder_add_value(&builder, g_variant_new_take_string(g_strdup_printf(
828 "%s=%s", envvar.first.c_str(), envvar.second.c_str())));
829 }
830
831 g_variant_builder_close(&builder);
832 g_variant_builder_add_value(&builder, g_variant_new_boolean(TRUE));
833
834 auto retval = std::make_shared<UpstartInstance>(appId, job, instance, urls, registry);
835 auto chelper = new StartCHelper{};
836 chelper->ptr = retval;
837
838 tracepoint(ubuntu_app_launch, handshake_wait, appIdStr.c_str());
839 starting_handshake_wait(handshake);
840 tracepoint(ubuntu_app_launch, handshake_complete, appIdStr.c_str());
841
842 /* Call the job start function */
843 g_debug("Asking Upstart to start task for: %s", appIdStr.c_str());
844 g_dbus_connection_call(registry->impl->_dbus.get(), /* bus */
845 DBUS_SERVICE_UPSTART, /* service name */
846 jobpath.c_str(), /* Path */
847 DBUS_INTERFACE_UPSTART_JOB, /* interface */
848 "Start", /* method */
849 g_variant_builder_end(&builder), /* params */
850 nullptr, /* return */
851 G_DBUS_CALL_FLAGS_NONE, /* flags */
852 -1, /* default timeout */
853 registry->impl->thread.getCancellable().get(), /* cancellable */
854 application_start_cb, /* callback */
855 chelper /* object */
856 );
857
858 tracepoint(ubuntu_app_launch, libual_start_message_sent, appIdStr.c_str());
859
860 return retval;
861 });
862}116}
863117
864} // namespace app_impls118} // namespace app_impls
865119
=== modified file 'libubuntu-app-launch/application-impl-base.h'
--- libubuntu-app-launch/application-impl-base.h 2016-09-23 22:30:51 +0000
+++ libubuntu-app-launch/application-impl-base.h 2016-11-01 14:53:56 +0000
@@ -17,6 +17,7 @@
17 * Ted Gould <ted.gould@canonical.com>17 * Ted Gould <ted.gould@canonical.com>
18 */18 */
1919
20#include "application-info-desktop.h"
20#include "application.h"21#include "application.h"
2122
22extern "C" {23extern "C" {
@@ -43,6 +44,8 @@
4344
44 bool hasInstances() override;45 bool hasInstances() override;
4546
47 std::string getInstance(const std::shared_ptr<app_info::Desktop>& desktop);
48
46protected:49protected:
47 /** Pointer to the registry so we can ask it for things */50 /** Pointer to the registry so we can ask it for things */
48 std::shared_ptr<Registry> _registry;51 std::shared_ptr<Registry> _registry;
@@ -51,75 +54,6 @@
51 const std::string& pkgdir);54 const std::string& pkgdir);
52};55};
5356
54/** An object that represents an instance of a job on Upstart. This
55 then implements everything needed by the instance interface. Most
56 applications tie into this today and use it as the backend for
57 their instances. */
58class UpstartInstance : public Application::Instance
59{
60public:
61 explicit UpstartInstance(const AppID& appId,
62 const std::string& job,
63 const std::string& instance,
64 const std::vector<Application::URL>& urls,
65 const std::shared_ptr<Registry>& registry);
66
67 /* Query lifecycle */
68 bool isRunning() override;
69 pid_t primaryPid() override;
70 bool hasPid(pid_t pid) override;
71 std::string logPath() override;
72 std::vector<pid_t> pids() override;
73
74 /* Manage lifecycle */
75 void pause() override;
76 void resume() override;
77 void stop() override;
78
79 /* OOM Functions */
80 void setOomAdjustment(const oom::Score score) override;
81 const oom::Score getOomAdjustment() override;
82
83 /** Flag for whether we should include the testing environment variables */
84 enum class launchMode
85 {
86 STANDARD, /**< Standard variable set */
87 TEST /**< Include testing environment vars */
88 };
89 static std::shared_ptr<UpstartInstance> launch(
90 const AppID& appId,
91 const std::string& job,
92 const std::string& instance,
93 const std::vector<Application::URL>& urls,
94 const std::shared_ptr<Registry>& registry,
95 launchMode mode,
96 std::function<std::list<std::pair<std::string, std::string>>(void)>& getenv);
97
98private:
99 /** Application ID */
100 const AppID appId_;
101 /** Upstart job name */
102 const std::string job_;
103 /** Instance ID environment value, empty if none */
104 const std::string instance_;
105 /** The URLs that this was launched for. Only valid on launched jobs, we
106 should look at perhaps changing that. */
107 std::vector<Application::URL> urls_;
108 /** A link to the registry we're using for connections */
109 std::shared_ptr<Registry> registry_;
110
111 std::vector<pid_t> forAllPids(std::function<void(pid_t)> eachPid);
112 void signalToPid(pid_t pid, int signal);
113 std::string pidToOomPath(pid_t pid);
114 void oomValueToPid(pid_t pid, const oom::Score oomvalue);
115 void oomValueToPidHelper(pid_t pid, const oom::Score oomvalue);
116 void pidListToDbus(const std::vector<pid_t>& pids, const std::string& signal);
117 std::string upstartJobPath();
118
119 static std::shared_ptr<gchar*> urlsToStrv(const std::vector<Application::URL>& urls);
120 static void application_start_cb(GObject* obj, GAsyncResult* res, gpointer user_data);
121};
122
123} // namespace app_impls57} // namespace app_impls
124} // namespace app_launch58} // namespace app_launch
125} // namespace ubuntu59} // namespace ubuntu
12660
=== modified file 'libubuntu-app-launch/application-impl-click.cpp'
--- libubuntu-app-launch/application-impl-click.cpp 2016-10-03 23:54:20 +0000
+++ libubuntu-app-launch/application-impl-click.cpp 2016-11-01 14:53:56 +0000
@@ -51,6 +51,8 @@
51 std::tie(_keyfile, desktopPath_) = manifestAppDesktop(_manifest, appid.package, appid.appname, _clickDir);51 std::tie(_keyfile, desktopPath_) = manifestAppDesktop(_manifest, appid.package, appid.appname, _clickDir);
52 if (!_keyfile)52 if (!_keyfile)
53 throw std::runtime_error{"No keyfile found for click application: " + std::string(appid)};53 throw std::runtime_error{"No keyfile found for click application: " + std::string(appid)};
54
55 g_debug("Application Click object for appid '%s'", std::string(appid).c_str());
54}56}
5557
56AppID Click::appId()58AppID Click::appId()
@@ -323,21 +325,8 @@
323325
324std::vector<std::shared_ptr<Application::Instance>> Click::instances()326std::vector<std::shared_ptr<Application::Instance>> Click::instances()
325{327{
326 std::vector<std::shared_ptr<Instance>> vect;328 auto vbase = _registry->impl->jobs->instances(appId(), "application-click");
327 std::string sappid = appId();329 return std::vector<std::shared_ptr<Application::Instance>>(vbase.begin(), vbase.end());
328
329 for (auto instancename : _registry->impl->upstartInstancesForJob("application-click"))
330 {
331 /* There an be only one, but we want to make sure it is
332 there or return an empty vector */
333 if (sappid == instancename)
334 {
335 vect.emplace_back(std::make_shared<UpstartInstance>(appId(), "application-click", std::string{},
336 std::vector<Application::URL>{}, _registry));
337 break;
338 }
339 }
340 return vect;
341}330}
342331
343/** Grabs all the environment variables for the application to332/** Grabs all the environment variables for the application to
@@ -350,26 +339,30 @@
350 retval.emplace_back(std::make_pair("APP_DIR", _clickDir));339 retval.emplace_back(std::make_pair("APP_DIR", _clickDir));
351 retval.emplace_back(std::make_pair("APP_DESKTOP_FILE_PATH", desktopPath_));340 retval.emplace_back(std::make_pair("APP_DESKTOP_FILE_PATH", desktopPath_));
352341
342 retval.emplace_back(std::make_pair("QML2_IMPORT_PATH", _clickDir + "/lib/" + UBUNTU_APP_LAUNCH_ARCH + "/qml"));
343
353 info();344 info();
354345
355 retval.emplace_back(std::make_pair("APP_XMIR_ENABLE", _info->xMirEnable().value() ? "1" : "0"));346 retval.emplace_back(std::make_pair("APP_XMIR_ENABLE", _info->xMirEnable().value() ? "1" : "0"));
356 retval.emplace_back(std::make_pair("APP_EXEC", _info->execLine().value()));347 retval.emplace_back(std::make_pair("APP_EXEC", _info->execLine().value()));
357348
349 retval.emplace_back(std::make_pair("APP_EXEC_POLICY", std::string(appId())));
350
358 return retval;351 return retval;
359}352}
360353
361std::shared_ptr<Application::Instance> Click::launch(const std::vector<Application::URL>& urls)354std::shared_ptr<Application::Instance> Click::launch(const std::vector<Application::URL>& urls)
362{355{
363 std::function<std::list<std::pair<std::string, std::string>>(void)> envfunc = [this]() { return launchEnv(); };356 std::function<std::list<std::pair<std::string, std::string>>(void)> envfunc = [this]() { return launchEnv(); };
364 return UpstartInstance::launch(appId(), "application-click", {}, urls, _registry,357 return _registry->impl->jobs->launch(appId(), "application-click", {}, urls, jobs::manager::launchMode::STANDARD,
365 UpstartInstance::launchMode::STANDARD, envfunc);358 envfunc);
366}359}
367360
368std::shared_ptr<Application::Instance> Click::launchTest(const std::vector<Application::URL>& urls)361std::shared_ptr<Application::Instance> Click::launchTest(const std::vector<Application::URL>& urls)
369{362{
370 std::function<std::list<std::pair<std::string, std::string>>(void)> envfunc = [this]() { return launchEnv(); };363 std::function<std::list<std::pair<std::string, std::string>>(void)> envfunc = [this]() { return launchEnv(); };
371 return UpstartInstance::launch(appId(), "application-click", {}, urls, _registry, UpstartInstance::launchMode::TEST,364 return _registry->impl->jobs->launch(appId(), "application-click", {}, urls, jobs::manager::launchMode::TEST,
372 envfunc);365 envfunc);
373}366}
374367
375} // namespace app_impls368} // namespace app_impls
376369
=== modified file 'libubuntu-app-launch/application-impl-legacy.cpp'
--- libubuntu-app-launch/application-impl-legacy.cpp 2016-10-03 23:54:20 +0000
+++ libubuntu-app-launch/application-impl-legacy.cpp 2016-11-01 14:53:56 +0000
@@ -33,9 +33,6 @@
33/** Path that snapd puts desktop files, we don't want to read those directly33/** Path that snapd puts desktop files, we don't want to read those directly
34 in the Legacy backend. We want to use the snap backend. */34 in the Legacy backend. We want to use the snap backend. */
35const std::string snappyDesktopPath{"/var/lib/snapd"};35const std::string snappyDesktopPath{"/var/lib/snapd"};
36/** Special characters that could be an application name that
37 would activate in a regex */
38const static std::regex regexCharacters("([\\.\\-])");
3936
40/***********************************37/***********************************
41 Prototypes38 Prototypes
@@ -78,13 +75,7 @@
78 throw std::runtime_error{"Looking like a legacy app, but should be a Snap: " + appname.value()};75 throw std::runtime_error{"Looking like a legacy app, but should be a Snap: " + appname.value()};
79 }76 }
8077
81 /* Build a regex that'll match instances of the applications which78 g_debug("Application Legacy object for app '%s'", appname.value().c_str());
82 roughly looks like: $(appid)-2345345
83
84 It is important to filter out the special characters that are in
85 the appid.
86 */
87 instanceRegex_ = std::regex("^(?:" + std::regex_replace(_appname.value(), regexCharacters, "\\$&") + ")\\-(\\d*)$");
88}79}
8980
90std::tuple<std::string, std::shared_ptr<GKeyFile>, std::string> keyfileForApp(const AppID::AppName& name)81std::tuple<std::string, std::shared_ptr<GKeyFile>, std::string> keyfileForApp(const AppID::AppName& name)
@@ -291,23 +282,8 @@
291282
292std::vector<std::shared_ptr<Application::Instance>> Legacy::instances()283std::vector<std::shared_ptr<Application::Instance>> Legacy::instances()
293{284{
294 std::vector<std::shared_ptr<Instance>> vect;285 auto vbase = _registry->impl->jobs->instances(appId(), "application-legacy");
295 auto startsWith = std::string(appId()) + "-";286 return std::vector<std::shared_ptr<Application::Instance>>(vbase.begin(), vbase.end());
296
297 for (auto instance : _registry->impl->upstartInstancesForJob("application-legacy"))
298 {
299 std::smatch instanceMatch;
300 g_debug("Looking at legacy instance: %s", instance.c_str());
301 if (std::regex_match(instance, instanceMatch, instanceRegex_))
302 {
303 vect.emplace_back(std::make_shared<UpstartInstance>(appId(), "application-legacy", instanceMatch[1].str(),
304 std::vector<Application::URL>{}, _registry));
305 }
306 }
307
308 g_debug("Legacy app '%s' has %d instances", std::string(appId()).c_str(), int(vect.size()));
309
310 return vect;
311}287}
312288
313/** Grabs all the environment for a legacy app. Mostly this consists of289/** Grabs all the environment for a legacy app. Mostly this consists of
@@ -368,21 +344,6 @@
368 return retval;344 return retval;
369}345}
370346
371/** Generates an instance string based on the clock if we're a multi-instance
372 application. */
373std::string Legacy::getInstance()
374{
375 auto single = g_key_file_get_boolean(_keyfile.get(), "Desktop Entry", "X-Ubuntu-Single-Instance", nullptr);
376 if (single)
377 {
378 return {};
379 }
380 else
381 {
382 return std::to_string(g_get_real_time());
383 }
384}
385
386/** Create an UpstartInstance for this AppID using the UpstartInstance launch347/** Create an UpstartInstance for this AppID using the UpstartInstance launch
387 function.348 function.
388349
@@ -390,12 +351,12 @@
390*/351*/
391std::shared_ptr<Application::Instance> Legacy::launch(const std::vector<Application::URL>& urls)352std::shared_ptr<Application::Instance> Legacy::launch(const std::vector<Application::URL>& urls)
392{353{
393 std::string instance = getInstance();354 auto instance = getInstance(appinfo_);
394 std::function<std::list<std::pair<std::string, std::string>>(void)> envfunc = [this, instance]() {355 std::function<std::list<std::pair<std::string, std::string>>(void)> envfunc = [this, instance]() {
395 return launchEnv(instance);356 return launchEnv(instance);
396 };357 };
397 return UpstartInstance::launch(appId(), "application-legacy", instance, urls, _registry,358 return _registry->impl->jobs->launch(appId(), "application-legacy", instance, urls,
398 UpstartInstance::launchMode::STANDARD, envfunc);359 jobs::manager::launchMode::STANDARD, envfunc);
399}360}
400361
401/** Create an UpstartInstance for this AppID using the UpstartInstance launch362/** Create an UpstartInstance for this AppID using the UpstartInstance launch
@@ -405,12 +366,12 @@
405*/366*/
406std::shared_ptr<Application::Instance> Legacy::launchTest(const std::vector<Application::URL>& urls)367std::shared_ptr<Application::Instance> Legacy::launchTest(const std::vector<Application::URL>& urls)
407{368{
408 std::string instance = getInstance();369 auto instance = getInstance(appinfo_);
409 std::function<std::list<std::pair<std::string, std::string>>(void)> envfunc = [this, instance]() {370 std::function<std::list<std::pair<std::string, std::string>>(void)> envfunc = [this, instance]() {
410 return launchEnv(instance);371 return launchEnv(instance);
411 };372 };
412 return UpstartInstance::launch(appId(), "application-legacy", instance, urls, _registry,373 return _registry->impl->jobs->launch(appId(), "application-legacy", instance, urls, jobs::manager::launchMode::TEST,
413 UpstartInstance::launchMode::TEST, envfunc);374 envfunc);
414}375}
415376
416} // namespace app_impls377} // namespace app_impls
417378
=== modified file 'libubuntu-app-launch/application-impl-legacy.h'
--- libubuntu-app-launch/application-impl-legacy.h 2016-09-23 21:54:33 +0000
+++ libubuntu-app-launch/application-impl-legacy.h 2016-11-01 14:53:56 +0000
@@ -86,7 +86,6 @@
86 std::regex instanceRegex_;86 std::regex instanceRegex_;
8787
88 std::list<std::pair<std::string, std::string>> launchEnv(const std::string& instance);88 std::list<std::pair<std::string, std::string>> launchEnv(const std::string& instance);
89 std::string getInstance();
90};89};
9190
92} // namespace app_impls91} // namespace app_impls
9392
=== modified file 'libubuntu-app-launch/application-impl-libertine.cpp'
--- libubuntu-app-launch/application-impl-libertine.cpp 2016-10-03 23:54:20 +0000
+++ libubuntu-app-launch/application-impl-libertine.cpp 2016-11-01 14:53:56 +0000
@@ -65,6 +65,12 @@
65 if (!_keyfile)65 if (!_keyfile)
66 throw std::runtime_error{"Unable to find a keyfile for application '" + appname.value() + "' in container '" +66 throw std::runtime_error{"Unable to find a keyfile for application '" + appname.value() + "' in container '" +
67 container.value() + "'"};67 container.value() + "'"};
68
69 appinfo_ = std::make_shared<app_info::Desktop>(_keyfile, _basedir, _container_path,
70 app_info::DesktopFlags::XMIR_DEFAULT, _registry);
71
72 g_debug("Application Libertine object for container '%s' app '%s'", container.value().c_str(),
73 appname.value().c_str());
68}74}
6975
70std::shared_ptr<GKeyFile> Libertine::keyfileFromPath(const std::string& pathname)76std::shared_ptr<GKeyFile> Libertine::keyfileFromPath(const std::string& pathname)
@@ -255,8 +261,7 @@
255 }261 }
256 catch (std::runtime_error& e)262 catch (std::runtime_error& e)
257 {263 {
258 g_debug("Unable to create application for libertine appname '%s': %s",264 g_debug("Unable to create application for libertine appname '%s': %s", apps.get()[j], e.what());
259 apps.get()[j], e.what());
260 }265 }
261 }266 }
262 }267 }
@@ -266,27 +271,13 @@
266271
267std::shared_ptr<Application::Info> Libertine::info()272std::shared_ptr<Application::Info> Libertine::info()
268{273{
269 if (!appinfo_)
270 {
271 appinfo_ = std::make_shared<app_info::Desktop>(_keyfile, _basedir, _container_path,
272 app_info::DesktopFlags::XMIR_DEFAULT, _registry);
273 }
274 return appinfo_;274 return appinfo_;
275}275}
276276
277std::vector<std::shared_ptr<Application::Instance>> Libertine::instances()277std::vector<std::shared_ptr<Application::Instance>> Libertine::instances()
278{278{
279 std::vector<std::shared_ptr<Instance>> vect;279 auto vbase = _registry->impl->jobs->instances(appId(), "application-legacy");
280 std::string sappid = appId();280 return std::vector<std::shared_ptr<Application::Instance>>(vbase.begin(), vbase.end());
281
282 for (auto instancename : _registry->impl->upstartInstancesForJob("application-legacy"))
283 {
284 if (std::equal(sappid.begin(), sappid.end(), instancename.begin()))
285 vect.emplace_back(std::make_shared<UpstartInstance>(appId(), "application-legacy", std::string{},
286 std::vector<Application::URL>{}, _registry));
287 }
288
289 return vect;
290}281}
291282
292/** Grabs all the environment variables for the application to283/** Grabs all the environment variables for the application to
@@ -327,16 +318,18 @@
327318
328std::shared_ptr<Application::Instance> Libertine::launch(const std::vector<Application::URL>& urls)319std::shared_ptr<Application::Instance> Libertine::launch(const std::vector<Application::URL>& urls)
329{320{
321 auto instance = getInstance(appinfo_);
330 std::function<std::list<std::pair<std::string, std::string>>(void)> envfunc = [this]() { return launchEnv(); };322 std::function<std::list<std::pair<std::string, std::string>>(void)> envfunc = [this]() { return launchEnv(); };
331 return UpstartInstance::launch(appId(), "application-legacy", {}, urls, _registry,323 return _registry->impl->jobs->launch(appId(), "application-legacy", instance, urls,
332 UpstartInstance::launchMode::STANDARD, envfunc);324 jobs::manager::launchMode::STANDARD, envfunc);
333}325}
334326
335std::shared_ptr<Application::Instance> Libertine::launchTest(const std::vector<Application::URL>& urls)327std::shared_ptr<Application::Instance> Libertine::launchTest(const std::vector<Application::URL>& urls)
336{328{
329 auto instance = getInstance(appinfo_);
337 std::function<std::list<std::pair<std::string, std::string>>(void)> envfunc = [this]() { return launchEnv(); };330 std::function<std::list<std::pair<std::string, std::string>>(void)> envfunc = [this]() { return launchEnv(); };
338 return UpstartInstance::launch(appId(), "application-legacy", {}, urls, _registry,331 return _registry->impl->jobs->launch(appId(), "application-legacy", instance, urls, jobs::manager::launchMode::TEST,
339 UpstartInstance::launchMode::TEST, envfunc);332 envfunc);
340}333}
341334
342} // namespace app_impls335} // namespace app_impls
343336
=== modified file 'libubuntu-app-launch/application-impl-snap.cpp'
--- libubuntu-app-launch/application-impl-snap.cpp 2016-10-03 23:54:20 +0000
+++ libubuntu-app-launch/application-impl-snap.cpp 2016-11-01 14:53:56 +0000
@@ -196,6 +196,8 @@
196 }196 }
197197
198 info_ = std::make_shared<SnapInfo>(appid_, _registry, interface_, pkgInfo_->directory);198 info_ = std::make_shared<SnapInfo>(appid_, _registry, interface_, pkgInfo_->directory);
199
200 g_debug("Application Snap object for AppID '%s'", std::string(appid).c_str());
199}201}
200202
201/** Uses the findInterface() function to find the interface if we don't203/** Uses the findInterface() function to find the interface if we don't
@@ -229,7 +231,7 @@
229 }231 }
230 catch (std::runtime_error& e)232 catch (std::runtime_error& e)
231 {233 {
232 g_warning("Unable to make Snap object for '%s': %s", std::string(id).c_str(), e.what());234 g_debug("Unable to make Snap object for '%s': %s", std::string(id).c_str(), e.what());
233 }235 }
234 }236 }
235 }237 }
@@ -396,19 +398,8 @@
396/** Get all of the instances of this snap package that are running */398/** Get all of the instances of this snap package that are running */
397std::vector<std::shared_ptr<Application::Instance>> Snap::instances()399std::vector<std::shared_ptr<Application::Instance>> Snap::instances()
398{400{
399 std::vector<std::shared_ptr<Instance>> vect;401 auto vbase = _registry->impl->jobs->instances(appId(), "application-snap");
400 auto startsWith = std::string(appid_) + "-";402 return std::vector<std::shared_ptr<Application::Instance>>(vbase.begin(), vbase.end());
401
402 for (const auto& instance : _registry->impl->upstartInstancesForJob("application-snap"))
403 {
404 if (std::equal(startsWith.begin(), startsWith.end(), instance.begin()))
405 {
406 vect.emplace_back(std::make_shared<UpstartInstance>(appid_, "application-snap", std::string{},
407 std::vector<Application::URL>{}, _registry));
408 }
409 }
410
411 return vect;
412}403}
413404
414/** Return the launch environment for this snap. That includes whether405/** Return the launch environment for this snap. That includes whether
@@ -447,9 +438,10 @@
447*/438*/
448std::shared_ptr<Application::Instance> Snap::launch(const std::vector<Application::URL>& urls)439std::shared_ptr<Application::Instance> Snap::launch(const std::vector<Application::URL>& urls)
449{440{
441 auto instance = getInstance(info_);
450 std::function<std::list<std::pair<std::string, std::string>>(void)> envfunc = [this]() { return launchEnv(); };442 std::function<std::list<std::pair<std::string, std::string>>(void)> envfunc = [this]() { return launchEnv(); };
451 return UpstartInstance::launch(appid_, "application-snap", {}, urls, _registry,443 return _registry->impl->jobs->launch(appid_, "application-snap", instance, urls,
452 UpstartInstance::launchMode::STANDARD, envfunc);444 jobs::manager::launchMode::STANDARD, envfunc);
453}445}
454446
455/** Create a new instance of this Snap with a testing environment447/** Create a new instance of this Snap with a testing environment
@@ -459,9 +451,10 @@
459*/451*/
460std::shared_ptr<Application::Instance> Snap::launchTest(const std::vector<Application::URL>& urls)452std::shared_ptr<Application::Instance> Snap::launchTest(const std::vector<Application::URL>& urls)
461{453{
454 auto instance = getInstance(info_);
462 std::function<std::list<std::pair<std::string, std::string>>(void)> envfunc = [this]() { return launchEnv(); };455 std::function<std::list<std::pair<std::string, std::string>>(void)> envfunc = [this]() { return launchEnv(); };
463 return UpstartInstance::launch(appid_, "application-snap", {}, urls, _registry, UpstartInstance::launchMode::TEST,456 return _registry->impl->jobs->launch(appid_, "application-snap", instance, urls, jobs::manager::launchMode::TEST,
464 envfunc);457 envfunc);
465}458}
466459
467} // namespace app_impls460} // namespace app_impls
468461
=== modified file 'libubuntu-app-launch/application-info-desktop.cpp'
--- libubuntu-app-launch/application-info-desktop.cpp 2016-09-14 16:42:20 +0000
+++ libubuntu-app-launch/application-info-desktop.cpp 2016-11-01 14:53:56 +0000
@@ -253,7 +253,7 @@
253 if (stringlistFromKeyfileContains(keyfile, "NotShowIn", xdg_current_desktop, false) ||253 if (stringlistFromKeyfileContains(keyfile, "NotShowIn", xdg_current_desktop, false) ||
254 !stringlistFromKeyfileContains(keyfile, "OnlyShowIn", xdg_current_desktop, true))254 !stringlistFromKeyfileContains(keyfile, "OnlyShowIn", xdg_current_desktop, true))
255 {255 {
256 g_warning("Application is not shown in Unity");256 g_debug("Application is not shown in current desktop: %s", xdg_current_desktop);
257 // Exception removed for OTA11 as a temporary fix257 // Exception removed for OTA11 as a temporary fix
258 // throw std::runtime_error("Application is not shown in Unity");258 // throw std::runtime_error("Application is not shown in Unity");
259 }259 }
@@ -352,6 +352,7 @@
352 , _xMirEnable(352 , _xMirEnable(
353 boolFromKeyfile<XMirEnable>(keyfile, "X-Ubuntu-XMir-Enable", (flags & DesktopFlags::XMIR_DEFAULT).any()))353 boolFromKeyfile<XMirEnable>(keyfile, "X-Ubuntu-XMir-Enable", (flags & DesktopFlags::XMIR_DEFAULT).any()))
354 , _exec(stringFromKeyfile<Exec>(keyfile, "Exec"))354 , _exec(stringFromKeyfile<Exec>(keyfile, "Exec"))
355 , _singleInstance(boolFromKeyfile<SingleInstance>(keyfile, "X-Ubuntu-Single-Instance", false))
355{356{
356}357}
357358
358359
=== modified file 'libubuntu-app-launch/application-info-desktop.h'
--- libubuntu-app-launch/application-info-desktop.h 2016-09-14 16:38:42 +0000
+++ libubuntu-app-launch/application-info-desktop.h 2016-11-01 14:53:56 +0000
@@ -106,6 +106,13 @@
106 return _exec;106 return _exec;
107 }107 }
108108
109 struct SingleInstanceTag;
110 typedef TypeTagger<SingleInstanceTag, bool> SingleInstance;
111 virtual SingleInstance singleInstance()
112 {
113 return _singleInstance;
114 }
115
109protected:116protected:
110 std::shared_ptr<GKeyFile> _keyfile;117 std::shared_ptr<GKeyFile> _keyfile;
111 std::string _basePath;118 std::string _basePath;
@@ -125,6 +132,7 @@
125132
126 XMirEnable _xMirEnable;133 XMirEnable _xMirEnable;
127 Exec _exec;134 Exec _exec;
135 SingleInstance _singleInstance;
128};136};
129137
130} // namespace AppInfo138} // namespace AppInfo
131139
=== modified file 'libubuntu-app-launch/application.cpp'
--- libubuntu-app-launch/application.cpp 2016-08-26 17:33:34 +0000
+++ libubuntu-app-launch/application.cpp 2016-11-01 14:53:56 +0000
@@ -28,6 +28,8 @@
28#include "application-impl-snap.h"28#include "application-impl-snap.h"
29#endif29#endif
30#include "application.h"30#include "application.h"
31#include "jobs-base.h"
32#include "registry-impl.h"
31#include "registry.h"33#include "registry.h"
3234
33#include <functional>35#include <functional>
@@ -46,6 +48,11 @@
46 throw std::runtime_error("AppID is empty");48 throw std::runtime_error("AppID is empty");
47 }49 }
4850
51 if (!registry->impl->jobs)
52 {
53 registry->impl->jobs = jobs::manager::Base::determineFactory(registry);
54 }
55
49 if (app_impls::Click::hasAppId(appid, registry))56 if (app_impls::Click::hasAppId(appid, registry))
50 {57 {
51 return std::make_shared<app_impls::Click>(appid, registry);58 return std::make_shared<app_impls::Click>(appid, registry);
5259
=== added file 'libubuntu-app-launch/jobs-base.cpp'
--- libubuntu-app-launch/jobs-base.cpp 1970-01-01 00:00:00 +0000
+++ libubuntu-app-launch/jobs-base.cpp 2016-11-01 14:53:56 +0000
@@ -0,0 +1,434 @@
1/*
2 * Copyright © 2016 Canonical Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License version 3, as published
6 * by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranties of
10 * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11 * PURPOSE. See the GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License along
14 * with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authors:
17 * Ted Gould <ted.gould@canonical.com>
18 */
19
20#include <algorithm>
21#include <cerrno>
22#include <cstring>
23
24#include "jobs-base.h"
25#include "jobs-systemd.h"
26#include "jobs-upstart.h"
27#include "registry-impl.h"
28
29namespace ubuntu
30{
31namespace app_launch
32{
33namespace jobs
34{
35namespace manager
36{
37
38Base::Base(const std::shared_ptr<Registry>& registry)
39 : registry_(registry)
40 , allJobs_{"application-click", "application-legacy", "application-snap"}
41{
42}
43
44std::shared_ptr<Base> Base::determineFactory(std::shared_ptr<Registry> registry)
45{
46 /* Checking to see if we have a user bus, that is only started
47 by systemd so we're in good shape if we have one. We're using
48 the path instead of the RUNTIME variable because we want to work
49 around the case of being relocated by the snappy environment */
50 if (g_file_test(SystemD::userBusPath().c_str(), G_FILE_TEST_EXISTS))
51 {
52 return std::make_shared<jobs::manager::SystemD>(registry);
53 }
54 else
55 {
56 return std::make_shared<jobs::manager::Upstart>(registry);
57 }
58}
59
60const std::set<std::string>& Base::getAllJobs()
61{
62 return allJobs_;
63}
64
65} // namespace manager
66
67namespace instance
68{
69
70Base::Base(const AppID& appId,
71 const std::string& job,
72 const std::string& instance,
73 const std::vector<Application::URL>& urls,
74 const std::shared_ptr<Registry>& registry)
75 : appId_(appId)
76 , job_(job)
77 , instance_(instance)
78 , urls_(urls)
79 , registry_(registry)
80{
81}
82
83/** Checks to see if we have a primary PID for the instance */
84bool Base::isRunning()
85{
86 return primaryPid() != 0;
87}
88
89/** Looks at the PIDs in the instance cgroup and checks to see if @pid
90 is in the set.
91
92 @param pid PID to look for
93*/
94bool Base::hasPid(pid_t pid)
95{
96 auto vpids = pids();
97 bool hasit = std::find(vpids.begin(), vpids.end(), pid) != vpids.end();
98 g_debug("Checking for PID %d on AppID '%s' result: %s", pid, std::string(appId_).c_str(), hasit ? "YES" : "NO");
99 return hasit;
100}
101
102/** Pauses this application by sending SIGSTOP to all the PIDs in the
103 cgroup and tells Zeitgeist that we've left the application. */
104void Base::pause()
105{
106 g_debug("Pausing application: %s", std::string(appId_).c_str());
107 registry_->impl->zgSendEvent(appId_, ZEITGEIST_ZG_LEAVE_EVENT);
108
109 auto pids = forAllPids([this](pid_t pid) {
110 auto oomval = oom::paused();
111 g_debug("Pausing PID: %d (%d)", pid, int(oomval));
112 signalToPid(pid, SIGSTOP);
113 oomValueToPid(pid, oomval);
114 });
115
116 pidListToDbus(pids, "ApplicationPaused");
117}
118
119/** Resumes this application by sending SIGCONT to all the PIDs in the
120 cgroup and tells Zeitgeist that we're accessing the application. */
121void Base::resume()
122{
123 g_debug("Resuming application: %s", std::string(appId_).c_str());
124 registry_->impl->zgSendEvent(appId_, ZEITGEIST_ZG_ACCESS_EVENT);
125
126 auto pids = forAllPids([this](pid_t pid) {
127 auto oomval = oom::focused();
128 g_debug("Resuming PID: %d (%d)", pid, int(oomval));
129 signalToPid(pid, SIGCONT);
130 oomValueToPid(pid, oomval);
131 });
132
133 pidListToDbus(pids, "ApplicationResumed");
134}
135
136/** Go through the list of PIDs calling a function and handling
137 the issue with getting PIDs being a racey condition.
138
139 \param eachPid Function to run on each PID
140*/
141std::vector<pid_t> Base::forAllPids(std::function<void(pid_t)> eachPid)
142{
143 std::set<pid_t> seenPids;
144 bool added = true;
145
146 while (added)
147 {
148 added = false;
149 auto pidlist = pids();
150 for (auto pid : pidlist)
151 {
152 if (seenPids.insert(pid).second)
153 {
154 eachPid(pid);
155 added = true;
156 }
157 }
158 }
159
160 return std::vector<pid_t>(seenPids.begin(), seenPids.end());
161}
162
163/** Send a signal that we've change the application. Do this on the
164 registry thread in an idle so that we don't block anyone.
165
166 \param pids List of PIDs to turn into variants to send
167 \param signal Name of the DBus signal to send
168*/
169void Base::pidListToDbus(const std::vector<pid_t>& pids, const std::string& signal)
170{
171 auto registry = registry_;
172 auto lappid = appId_;
173
174 registry_->impl->thread.executeOnThread([registry, lappid, pids, signal] {
175 auto vpids = std::shared_ptr<GVariant>(
176 [pids]() {
177 GVariant* pidarray = nullptr;
178
179 if (pids.empty())
180 {
181 pidarray = g_variant_new_array(G_VARIANT_TYPE_UINT64, nullptr, 0);
182 g_variant_ref_sink(pidarray);
183 return pidarray;
184 }
185
186 GVariantBuilder builder;
187 g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY);
188 for (auto pid : pids)
189 {
190 g_variant_builder_add_value(&builder, g_variant_new_uint64(pid));
191 }
192
193 pidarray = g_variant_builder_end(&builder);
194 g_variant_ref_sink(pidarray);
195 return pidarray;
196 }(),
197 [](GVariant* var) { g_variant_unref(var); });
198
199 GVariantBuilder params;
200 g_variant_builder_init(&params, G_VARIANT_TYPE_TUPLE);
201 g_variant_builder_add_value(&params, g_variant_new_string(std::string(lappid).c_str()));
202 g_variant_builder_add_value(&params, vpids.get());
203
204 GError* error = nullptr;
205 g_dbus_connection_emit_signal(registry->impl->_dbus.get(), /* bus */
206 nullptr, /* destination */
207 "/", /* path */
208 "com.canonical.UbuntuAppLaunch", /* interface */
209 signal.c_str(), /* signal */
210 g_variant_builder_end(&params), /* params, the same */
211 &error); /* error */
212
213 if (error != nullptr)
214 {
215 g_warning("Unable to emit signal '%s' for appid '%s': %s", signal.c_str(), std::string(lappid).c_str(),
216 error->message);
217 g_error_free(error);
218 }
219 else
220 {
221 g_debug("Emmitted '%s' to DBus", signal.c_str());
222 }
223 });
224}
225
226/** Sets the OOM adjustment by getting the list of PIDs and writing
227 the value to each of their files in proc
228
229 \param score OOM Score to set
230*/
231void Base::setOomAdjustment(const oom::Score score)
232{
233 forAllPids([this, &score](pid_t pid) { oomValueToPid(pid, score); });
234}
235
236/** Figures out the path to the primary PID of the application and
237 then reads its OOM adjustment file. */
238const oom::Score Base::getOomAdjustment()
239{
240 auto pid = primaryPid();
241 if (pid == 0)
242 {
243 throw std::runtime_error("No PID for application: " + std::string(appId_));
244 }
245
246 auto path = pidToOomPath(pid);
247 GError* error = nullptr;
248 gchar* content = nullptr;
249
250 g_file_get_contents(path.c_str(), /* path */
251 &content, /* data */
252 nullptr, /* size */
253 &error); /* error */
254
255 if (error != nullptr)
256 {
257 auto serror = std::shared_ptr<GError>(error, g_error_free);
258 throw std::runtime_error("Unable to access OOM value for '" + std::string(appId_) + "' primary PID '" +
259 std::to_string(pid) + "' because: " + serror->message);
260 }
261
262 auto score = static_cast<oom::Score>(std::atoi(content));
263 g_free(content);
264 return score;
265}
266
267/** Sends a signal to a PID with a warning if we can't send it.
268 We could throw an exception, but we can't handle it usefully anyway
269
270 \param pid PID to send the signal to
271 \param signal signal to send
272*/
273void Base::signalToPid(pid_t pid, int signal)
274{
275 if (-1 == kill(pid, signal))
276 {
277 /* While that didn't work, we still want to try as many as we can */
278 g_warning("Unable to send signal %d to pid %d", signal, pid);
279 }
280}
281
282/** Get the path to the PID's OOM adjust path, with allowing for an
283 override for testing using the environment variable
284 UBUNTU_APP_LAUNCH_OOM_PROC_PATH
285
286 \param pid PID to build path for
287*/
288std::string Base::pidToOomPath(pid_t pid)
289{
290 static std::string procpath;
291 if (G_UNLIKELY(procpath.empty()))
292 {
293 /* Set by the test suite, probably not anyone else */
294 auto envvar = g_getenv("UBUNTU_APP_LAUNCH_OOM_PROC_PATH");
295 if (G_LIKELY(envvar == nullptr))
296 procpath = "/proc";
297 else
298 procpath = envvar;
299 }
300
301 gchar* gpath = g_build_filename(procpath.c_str(), std::to_string(pid).c_str(), "oom_score_adj", nullptr);
302 std::string path = gpath;
303 g_free(gpath);
304 return path;
305}
306
307/** Writes an OOM value to proc, assuming we have a string
308 in the outer loop
309
310 \param pid PID to change the OOM value of
311 \param oomvalue OOM value to set
312*/
313void Base::oomValueToPid(pid_t pid, const oom::Score oomvalue)
314{
315 auto oomstr = std::to_string(static_cast<std::int32_t>(oomvalue));
316 auto path = pidToOomPath(pid);
317 FILE* adj = fopen(path.c_str(), "w");
318 int openerr = errno;
319
320 if (adj == nullptr)
321 {
322 switch (openerr)
323 {
324 case ENOENT:
325 /* ENOENT happens a fair amount because of races, so it's not
326 worth printing a warning about */
327 return;
328 case EACCES:
329 {
330 /* We can get this error when trying to set the OOM value on
331 Oxide renderers because they're started by the sandbox and
332 don't have their adjustment value available for us to write.
333 We have a helper to deal with this, but it's kinda expensive
334 so we only use it when we have to. */
335 oomValueToPidHelper(pid, oomvalue);
336 return;
337 }
338 default:
339 g_warning("Unable to set OOM value for '%d' to '%s': %s", int(pid), oomstr.c_str(),
340 std::strerror(openerr));
341 return;
342 }
343 }
344
345 size_t writesize = fwrite(oomstr.c_str(), 1, oomstr.size(), adj);
346 int writeerr = errno;
347 fclose(adj);
348
349 if (writesize == oomstr.size())
350 return;
351
352 if (writeerr != 0)
353 g_warning("Unable to set OOM value for '%d' to '%s': %s", int(pid), oomstr.c_str(), strerror(writeerr));
354 else
355 /* No error, but yet, wrong size. Not sure, what could cause this. */
356 g_debug("Unable to set OOM value for '%d' to '%s': Wrote %d bytes", int(pid), oomstr.c_str(), int(writesize));
357}
358
359/** Use a setuid root helper for setting the oom value of
360 Chromium instances
361
362 \param pid PID to change the OOM value of
363 \param oomvalue OOM value to set
364*/
365void Base::oomValueToPidHelper(pid_t pid, const oom::Score oomvalue)
366{
367 GError* error = nullptr;
368 std::string oomstr = std::to_string(static_cast<std::int32_t>(oomvalue));
369 std::string pidstr = std::to_string(pid);
370 std::array<const char*, 4> args = {OOM_HELPER, pidstr.c_str(), oomstr.c_str(), nullptr};
371
372 g_debug("Excuting OOM Helper (pid: %d, score: %d): %s", int(pid), int(oomvalue),
373 std::accumulate(args.begin(), args.end(), std::string{},
374 [](const std::string& instr, const char* output) -> std::string {
375 if (instr.empty())
376 {
377 return output;
378 }
379 else if (output != nullptr)
380 {
381 return instr + " " + std::string(output);
382 }
383 else
384 {
385 return instr;
386 }
387 })
388 .c_str());
389
390 g_spawn_async(nullptr, /* working dir */
391 (char**)(args.data()), /* args */
392 nullptr, /* env */
393 G_SPAWN_DEFAULT, /* flags */
394 nullptr, /* child setup */
395 nullptr, /* child setup userdata*/
396 nullptr, /* pid */
397 &error); /* error */
398
399 if (error != nullptr)
400 {
401 g_warning("Unable to launch OOM helper '" OOM_HELPER "' on PID '%d': %s", pid, error->message);
402 g_error_free(error);
403 return;
404 }
405}
406
407/** Reformat a C++ vector of URLs into a C GStrv of strings
408
409 \param urls Vector of URLs to make into C strings
410*/
411std::shared_ptr<gchar*> Base::urlsToStrv(const std::vector<Application::URL>& urls)
412{
413 if (urls.empty())
414 {
415 return {};
416 }
417
418 auto array = g_array_new(TRUE, FALSE, sizeof(gchar*));
419
420 for (auto url : urls)
421 {
422 auto str = g_strdup(url.value().c_str());
423 g_debug("Converting URL: %s", str);
424 g_array_append_val(array, str);
425 }
426
427 return std::shared_ptr<gchar*>((gchar**)g_array_free(array, FALSE), g_strfreev);
428}
429
430} // namespace instance
431
432} // namespace jobs
433} // namespace app_launch
434} // namespace ubuntu
0435
=== added file 'libubuntu-app-launch/jobs-base.h'
--- libubuntu-app-launch/jobs-base.h 1970-01-01 00:00:00 +0000
+++ libubuntu-app-launch/jobs-base.h 2016-11-01 14:53:56 +0000
@@ -0,0 +1,124 @@
1/*
2 * Copyright © 2016 Canonical Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License version 3, as published
6 * by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranties of
10 * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11 * PURPOSE. See the GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License along
14 * with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authors:
17 * Ted Gould <ted.gould@canonical.com>
18 */
19
20#pragma once
21#include "application.h"
22#include <glib.h>
23#include <set>
24
25namespace ubuntu
26{
27namespace app_launch
28{
29namespace jobs
30{
31namespace instance
32{
33
34class Base : public Application::Instance
35{
36public:
37 Base(const AppID& appId,
38 const std::string& job,
39 const std::string& instance,
40 const std::vector<Application::URL>& urls,
41 const std::shared_ptr<Registry>& registry);
42 virtual ~Base() = default;
43
44 bool isRunning() override;
45 bool hasPid(pid_t pid) override;
46 void pause() override;
47 void resume() override;
48
49 /* OOM Functions */
50 void setOomAdjustment(const oom::Score score) override;
51 const oom::Score getOomAdjustment() override;
52
53protected:
54 /** Application ID */
55 const AppID appId_;
56 /** Upstart job name */
57 const std::string job_;
58 /** Instance ID environment value, empty if none */
59 const std::string instance_;
60 /** The URLs that this was launched for. Only valid on launched jobs, we
61 should look at perhaps changing that. */
62 std::vector<Application::URL> urls_;
63 /** A link to the registry we're using for connections */
64 std::shared_ptr<Registry> registry_;
65
66 static std::shared_ptr<gchar*> urlsToStrv(const std::vector<Application::URL>& urls);
67
68private:
69 std::vector<pid_t> forAllPids(std::function<void(pid_t)> eachPid);
70 void signalToPid(pid_t pid, int signal);
71 std::string pidToOomPath(pid_t pid);
72 void oomValueToPid(pid_t pid, const oom::Score oomvalue);
73 void oomValueToPidHelper(pid_t pid, const oom::Score oomvalue);
74 void pidListToDbus(const std::vector<pid_t>& pids, const std::string& signal);
75};
76
77} // namespace instance
78
79namespace manager
80{
81
82/** Flag for whether we should include the testing environment variables */
83enum class launchMode
84{
85 STANDARD, /**< Standard variable set */
86 TEST /**< Include testing environment vars */
87};
88
89class Base
90{
91public:
92 Base(const std::shared_ptr<Registry>& registry);
93 virtual ~Base() = default;
94
95 virtual std::shared_ptr<Application::Instance> launch(
96 const AppID& appId,
97 const std::string& job,
98 const std::string& instance,
99 const std::vector<Application::URL>& urls,
100 launchMode mode,
101 std::function<std::list<std::pair<std::string, std::string>>(void)>& getenv) = 0;
102
103 virtual std::shared_ptr<Application::Instance> existing(const AppID& appId,
104 const std::string& job,
105 const std::string& instance,
106 const std::vector<Application::URL>& urls) = 0;
107
108 virtual std::list<std::shared_ptr<Application>> runningApps() = 0;
109
110 virtual std::vector<std::shared_ptr<instance::Base>> instances(const AppID& appID, const std::string& job) = 0;
111
112 const std::set<std::string>& getAllJobs();
113
114 static std::shared_ptr<Base> determineFactory(std::shared_ptr<Registry> registry);
115
116protected:
117 std::weak_ptr<Registry> registry_;
118 std::set<std::string> allJobs_;
119};
120
121} // namespace manager
122} // namespace jobs
123} // namespace app_launch
124} // namespace ubuntu
0125
=== added file 'libubuntu-app-launch/jobs-systemd.cpp'
--- libubuntu-app-launch/jobs-systemd.cpp 1970-01-01 00:00:00 +0000
+++ libubuntu-app-launch/jobs-systemd.cpp 2016-11-01 14:53:56 +0000
@@ -0,0 +1,917 @@
1/*
2 * Copyright © 2016 Canonical Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License version 3, as published
6 * by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranties of
10 * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11 * PURPOSE. See the GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License along
14 * with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authors:
17 * Ted Gould <ted.gould@canonical.com>
18 */
19
20#include <algorithm>
21#include <gio/gio.h>
22#include <numeric>
23#include <regex>
24#include <sys/types.h>
25#include <unistd.h>
26
27#include "helpers.h"
28#include "jobs-systemd.h"
29#include "registry-impl.h"
30#include "second-exec-core.h"
31
32extern "C" {
33#include "ubuntu-app-launch-trace.h"
34}
35
36namespace ubuntu
37{
38namespace app_launch
39{
40namespace jobs
41{
42namespace instance
43{
44
45class SystemD : public Base
46{
47 friend class manager::SystemD;
48
49public:
50 explicit SystemD(const AppID& appId,
51 const std::string& job,
52 const std::string& instance,
53 const std::vector<Application::URL>& urls,
54 const std::shared_ptr<Registry>& registry);
55
56 /* Query lifecycle */
57 pid_t primaryPid() override;
58 std::string logPath() override;
59 std::vector<pid_t> pids() override;
60
61 /* Manage lifecycle */
62 void stop() override;
63
64}; // class SystemD
65
66SystemD::SystemD(const AppID& appId,
67 const std::string& job,
68 const std::string& instance,
69 const std::vector<Application::URL>& urls,
70 const std::shared_ptr<Registry>& registry)
71 : Base(appId, job, instance, urls, registry)
72{
73 g_debug("Creating a new SystemD for '%s' instance '%s'", std::string(appId).c_str(), instance.c_str());
74}
75
76pid_t SystemD::primaryPid()
77{
78 auto manager = std::dynamic_pointer_cast<manager::SystemD>(registry_->impl->jobs);
79 return manager->unitPrimaryPid(appId_, job_, instance_);
80}
81
82std::string SystemD::logPath()
83{
84 /* NOTE: We can never get this for systemd */
85 return {};
86}
87
88std::vector<pid_t> SystemD::pids()
89{
90 auto manager = std::dynamic_pointer_cast<manager::SystemD>(registry_->impl->jobs);
91 return manager->unitPids(appId_, job_, instance_);
92}
93
94void SystemD::stop()
95{
96 auto manager = std::dynamic_pointer_cast<manager::SystemD>(registry_->impl->jobs);
97 return manager->stopUnit(appId_, job_, instance_);
98}
99
100} // namespace instance
101
102namespace manager
103{
104
105static const std::string SYSTEMD_DBUS_ADDRESS{"org.freedesktop.systemd1"};
106static const std::string SYSTEMD_DBUS_IFACE_MANAGER{"org.freedesktop.systemd1.Manager"};
107static const std::string SYSTEMD_DBUS_PATH_MANAGER{"/org/freedesktop/systemd1"};
108static const std::string SYSTEMD_DBUS_IFACE_UNIT{"org.freedesktop.systemd1.Unit"};
109static const std::string SYSTEMD_DBUS_IFACE_SERVICE{"org.freedesktop.systemd1.Service"};
110
111SystemD::SystemD(std::shared_ptr<Registry> registry)
112 : Base(registry)
113{
114 auto cancel = registry->impl->thread.getCancellable();
115 userbus_ = registry->impl->thread.executeOnThread<std::shared_ptr<GDBusConnection>>([cancel]() {
116 GError* error = nullptr;
117 auto bus = std::shared_ptr<GDBusConnection>(
118 g_dbus_connection_new_for_address_sync(
119 ("unix:path=" + userBusPath()).c_str(), /* path to the user bus */
120 (GDBusConnectionFlags)(G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT |
121 G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION), /* It is a message bus */
122 nullptr, /* observer */
123 cancel.get(), /* cancellable from the thread */
124 &error), /* error */
125 [](GDBusConnection* bus) { g_clear_object(&bus); });
126
127 if (error != nullptr)
128 {
129 std::string message = std::string("Unable to connect to user bus: ") + error->message;
130 g_error_free(error);
131 throw std::runtime_error(message);
132 }
133
134 return bus;
135 });
136}
137
138SystemD::~SystemD()
139{
140}
141
142std::string SystemD::findEnv(const std::string& value, std::list<std::pair<std::string, std::string>>& env)
143{
144 std::string retval;
145 auto entry = std::find_if(env.begin(), env.end(),
146 [&value](std::pair<std::string, std::string>& entry) { return entry.first == value; });
147
148 if (entry != env.end())
149 {
150 retval = entry->second;
151 }
152
153 return retval;
154}
155
156std::vector<std::string> SystemD::parseExec(std::list<std::pair<std::string, std::string>>& env)
157{
158 auto exec = findEnv("APP_EXEC", env);
159 if (exec.empty())
160 {
161 g_debug("Application exec line is empty?!?!?");
162 return {};
163 }
164 auto uris = findEnv("APP_URIS", env);
165
166 auto execarray = desktop_exec_parse(exec.c_str(), uris.c_str());
167
168 std::vector<std::string> retval;
169 retval.reserve(execarray->len);
170 for (unsigned int i = 0; i < execarray->len; i++)
171 {
172 retval.emplace_back(g_array_index(execarray, gchar*, i));
173 }
174
175 g_array_set_clear_func(execarray, g_free);
176 g_array_free(execarray, FALSE); /* TODO: Not TRUE? */
177
178 /* See if we need the xmir helper */
179 if (findEnv("APP_XMIR_ENABLE", env) == "1" && getenv("DISPLAY") == nullptr)
180 {
181 retval.emplace(retval.begin(), findEnv("APP_ID", env));
182 retval.emplace(retval.begin(), XMIR_HELPER);
183 }
184
185 /* See if we're doing apparmor by hand */
186 auto appexecpolicy = findEnv("APP_EXEC_POLICY", env);
187 if (!appexecpolicy.empty() && appexecpolicy != "unconfined")
188 {
189 retval.emplace(retval.begin(), appexecpolicy);
190 retval.emplace(retval.begin(), "-p");
191 retval.emplace(retval.begin(), "aa-exec");
192 }
193
194 return retval;
195}
196
197/** Small helper that we can new/delete to work better with C stuff */
198struct StartCHelper
199{
200 std::shared_ptr<instance::SystemD> ptr;
201 std::shared_ptr<GDBusConnection> bus;
202};
203
204void SystemD::application_start_cb(GObject* obj, GAsyncResult* res, gpointer user_data)
205{
206 auto data = static_cast<StartCHelper*>(user_data);
207 GError* error{nullptr};
208 GVariant* result{nullptr};
209
210 tracepoint(ubuntu_app_launch, libual_start_message_callback, std::string(data->ptr->appId_).c_str());
211
212 g_debug("Started Message Callback: %s", std::string(data->ptr->appId_).c_str());
213
214 result = g_dbus_connection_call_finish(G_DBUS_CONNECTION(obj), res, &error);
215
216 g_clear_pointer(&result, g_variant_unref);
217
218 if (error != nullptr)
219 {
220 if (g_dbus_error_is_remote_error(error))
221 {
222 gchar* remote_error = g_dbus_error_get_remote_error(error);
223 g_debug("Remote error: %s", remote_error);
224 if (g_strcmp0(remote_error, "org.freedesktop.systemd1.UnitExists") == 0)
225 {
226 auto urls = instance::SystemD::urlsToStrv(data->ptr->urls_);
227 second_exec(data->bus.get(), /* DBus */
228 data->ptr->registry_->impl->thread.getCancellable().get(), /* cancellable */
229 data->ptr->primaryPid(), /* primary pid */
230 std::string(data->ptr->appId_).c_str(), /* appid */
231 urls.get()); /* urls */
232 }
233
234 g_free(remote_error);
235 }
236 else
237 {
238 g_warning("Unable to emit event to start application: %s", error->message);
239 }
240 g_error_free(error);
241 }
242
243 delete data;
244}
245
246void SystemD::copyEnv(const std::string& envname, std::list<std::pair<std::string, std::string>>& env)
247{
248 if (!findEnv(envname, env).empty())
249 {
250 g_debug("Already a value set for '%s' ignoring", envname.c_str());
251 return;
252 }
253
254 auto cvalue = getenv(envname.c_str());
255 g_debug("Copying Environment: %s", envname.c_str());
256 if (cvalue != nullptr)
257 {
258 std::string value = getenv(envname.c_str());
259 env.emplace_back(std::make_pair(envname, value));
260 }
261 else
262 {
263 g_debug("Unable to copy environment '%s'", envname.c_str());
264 }
265}
266
267void SystemD::copyEnvByPrefix(const std::string& prefix, std::list<std::pair<std::string, std::string>>& env)
268{
269 for (unsigned int i = 0; environ[i] != nullptr; i++)
270 {
271 if (g_str_has_prefix(environ[i], prefix.c_str()))
272 {
273 std::string envfull = environ[i];
274 std::string envname;
275 bool seenequal = false;
276 std::remove_copy_if(envfull.begin(), envfull.end(), std::back_inserter(envname),
277 [&seenequal](const char c) {
278 if (c == '=')
279 {
280 seenequal = true;
281 }
282 return seenequal;
283 });
284 copyEnv(envname, env);
285 }
286 }
287}
288
289std::shared_ptr<Application::Instance> SystemD::launch(
290 const AppID& appId,
291 const std::string& job,
292 const std::string& instance,
293 const std::vector<Application::URL>& urls,
294 launchMode mode,
295 std::function<std::list<std::pair<std::string, std::string>>(void)>& getenv)
296{
297 if (appId.empty())
298 return {};
299
300 auto registry = registry_.lock();
301 return registry->impl->thread.executeOnThread<std::shared_ptr<instance::SystemD>>(
302 [&]() -> std::shared_ptr<instance::SystemD> {
303 auto manager = std::dynamic_pointer_cast<manager::SystemD>(registry->impl->jobs);
304 std::string appIdStr{appId};
305 g_debug("Initializing params for an new instance::SystemD for: %s", appIdStr.c_str());
306
307 tracepoint(ubuntu_app_launch, libual_start, appIdStr.c_str());
308
309 int timeout = 1;
310 if (ubuntu::app_launch::Registry::Impl::isWatchingAppStarting())
311 {
312 timeout = 0;
313 }
314
315 auto handshake = starting_handshake_start(appIdStr.c_str(), timeout);
316 if (handshake == nullptr)
317 {
318 g_warning("Unable to setup starting handshake");
319 }
320
321 /* Figure out the unit name for the job */
322 auto unitname = unitName(SystemD::UnitInfo{appIdStr, job, instance});
323
324 /* Build up our environment */
325 auto env = getenv();
326
327 env.emplace_back(std::make_pair("APP_ID", appIdStr)); /* Application ID */
328 env.emplace_back(std::make_pair("APP_LAUNCHER_PID", std::to_string(getpid()))); /* Who we are, for bugs */
329
330 copyEnv("DISPLAY", env);
331 copyEnvByPrefix("DBUS_", env);
332 copyEnvByPrefix("MIR_", env);
333 copyEnvByPrefix("QT_", env);
334 copyEnvByPrefix("UBUNTU_", env);
335 copyEnvByPrefix("UNITY_", env);
336 copyEnvByPrefix("XDG_", env);
337
338 if (!urls.empty())
339 {
340 auto accumfunc = [](const std::string& prev, Application::URL thisurl) -> std::string {
341 gchar* gescaped = g_shell_quote(thisurl.value().c_str());
342 std::string escaped;
343 if (gescaped != nullptr)
344 {
345 escaped = gescaped;
346 g_free(gescaped);
347 }
348 else
349 {
350 g_warning("Unable to escape URL: %s", thisurl.value().c_str());
351 return prev;
352 }
353
354 if (prev.empty())
355 {
356 return escaped;
357 }
358 else
359 {
360 return prev + " " + escaped;
361 }
362 };
363 auto urlstring = std::accumulate(urls.begin(), urls.end(), std::string{}, accumfunc);
364 env.emplace_back(std::make_pair("APP_URIS", urlstring));
365 }
366
367 if (mode == launchMode::TEST)
368 {
369 env.emplace_back(std::make_pair("QT_LOAD_TESTABILITY", "1"));
370 }
371
372 /* Convert to GVariant */
373 GVariantBuilder builder;
374 g_variant_builder_init(&builder, G_VARIANT_TYPE_TUPLE);
375
376 g_variant_builder_add_value(&builder, g_variant_new_string(unitname.c_str()));
377 g_variant_builder_add_value(&builder, g_variant_new_string("replace")); // Job mode
378
379 /* Parameter Array */
380 g_variant_builder_open(&builder, G_VARIANT_TYPE_ARRAY);
381
382 /* Environment */
383 g_variant_builder_open(&builder, G_VARIANT_TYPE_TUPLE);
384 g_variant_builder_add_value(&builder, g_variant_new_string("Environment"));
385 g_variant_builder_open(&builder, G_VARIANT_TYPE_VARIANT);
386 g_variant_builder_open(&builder, G_VARIANT_TYPE_ARRAY);
387 for (const auto& envvar : env)
388 {
389 if (!envvar.first.empty() && !envvar.second.empty())
390 {
391 g_variant_builder_add_value(&builder, g_variant_new_take_string(g_strdup_printf(
392 "%s=%s", envvar.first.c_str(), envvar.second.c_str())));
393 // g_debug("Setting environment: %s=%s", envvar.first.c_str(), envvar.second.c_str());
394 }
395 }
396
397 g_variant_builder_close(&builder);
398 g_variant_builder_close(&builder);
399 g_variant_builder_close(&builder);
400
401 /* ExecStart */
402 auto commands = parseExec(env);
403 gchar* pathexec{nullptr};
404 if (!commands.empty() && ((pathexec = g_find_program_in_path(commands[0].c_str())) != nullptr))
405 {
406 g_variant_builder_open(&builder, G_VARIANT_TYPE_TUPLE);
407 g_variant_builder_add_value(&builder, g_variant_new_string("ExecStart"));
408 g_variant_builder_open(&builder, G_VARIANT_TYPE_VARIANT);
409 g_variant_builder_open(&builder, G_VARIANT_TYPE_ARRAY);
410
411 g_variant_builder_open(&builder, G_VARIANT_TYPE_TUPLE);
412 g_variant_builder_add_value(&builder, g_variant_new_take_string(pathexec));
413
414 g_variant_builder_open(&builder, G_VARIANT_TYPE_ARRAY);
415 for (auto param : commands)
416 {
417 g_variant_builder_add_value(&builder, g_variant_new_string(param.c_str()));
418 }
419 g_variant_builder_close(&builder);
420
421 g_variant_builder_add_value(&builder, g_variant_new_boolean(FALSE));
422
423 g_variant_builder_close(&builder);
424 g_variant_builder_close(&builder);
425 g_variant_builder_close(&builder);
426 g_variant_builder_close(&builder);
427 }
428
429 /* RemainAfterExit */
430 g_variant_builder_open(&builder, G_VARIANT_TYPE_TUPLE);
431 g_variant_builder_add_value(&builder, g_variant_new_string("RemainAfterExit"));
432 g_variant_builder_open(&builder, G_VARIANT_TYPE_VARIANT);
433 g_variant_builder_add_value(&builder, g_variant_new_boolean(FALSE));
434 g_variant_builder_close(&builder);
435 g_variant_builder_close(&builder);
436
437 /* Type */
438 g_variant_builder_open(&builder, G_VARIANT_TYPE_TUPLE);
439 g_variant_builder_add_value(&builder, g_variant_new_string("Type"));
440 g_variant_builder_open(&builder, G_VARIANT_TYPE_VARIANT);
441 g_variant_builder_add_value(&builder, g_variant_new_string("oneshot"));
442 g_variant_builder_close(&builder);
443 g_variant_builder_close(&builder);
444
445 /* Working Directory */
446 if (!findEnv("APP_DIR", env).empty())
447 {
448 g_variant_builder_open(&builder, G_VARIANT_TYPE_TUPLE);
449 g_variant_builder_add_value(&builder, g_variant_new_string("WorkingDirectory"));
450 g_variant_builder_open(&builder, G_VARIANT_TYPE_VARIANT);
451 g_variant_builder_add_value(&builder, g_variant_new_string(findEnv("APP_DIR", env).c_str()));
452 g_variant_builder_close(&builder);
453 g_variant_builder_close(&builder);
454 }
455
456 /* Parameter Array */
457 g_variant_builder_close(&builder);
458
459 /* Dependent Units (none) */
460 g_variant_builder_add_value(&builder, g_variant_new_array(G_VARIANT_TYPE("(sa(sv))"), nullptr, 0));
461
462 auto retval = std::make_shared<instance::SystemD>(appId, job, instance, urls, registry);
463 auto chelper = new StartCHelper{};
464 chelper->ptr = retval;
465 chelper->bus = manager->userbus_;
466
467 tracepoint(ubuntu_app_launch, handshake_wait, appIdStr.c_str());
468 starting_handshake_wait(handshake);
469 tracepoint(ubuntu_app_launch, handshake_complete, appIdStr.c_str());
470
471 /* Call the job start function */
472 g_debug("Asking systemd to start task for: %s", appIdStr.c_str());
473 g_dbus_connection_call(manager->userbus_.get(), /* bus */
474 SYSTEMD_DBUS_ADDRESS.c_str(), /* service name */
475 SYSTEMD_DBUS_PATH_MANAGER.c_str(), /* Path */
476 SYSTEMD_DBUS_IFACE_MANAGER.c_str(), /* interface */
477 "StartTransientUnit", /* method */
478 g_variant_builder_end(&builder), /* params */
479 G_VARIANT_TYPE("(o)"), /* return */
480 G_DBUS_CALL_FLAGS_NONE, /* flags */
481 -1, /* default timeout */
482 registry->impl->thread.getCancellable().get(), /* cancellable */
483 application_start_cb, /* callback */
484 chelper /* object */
485 );
486
487 tracepoint(ubuntu_app_launch, libual_start_message_sent, appIdStr.c_str());
488
489 return retval;
490 });
491}
492
493std::shared_ptr<Application::Instance> SystemD::existing(const AppID& appId,
494 const std::string& job,
495 const std::string& instance,
496 const std::vector<Application::URL>& urls)
497{
498 return std::make_shared<instance::SystemD>(appId, job, instance, urls, registry_.lock());
499}
500
501std::vector<std::shared_ptr<instance::Base>> SystemD::instances(const AppID& appID, const std::string& job)
502{
503 std::vector<std::shared_ptr<instance::Base>> instances;
504 auto registry = registry_.lock();
505 std::vector<Application::URL> urls;
506
507 for (const auto& unit : listUnits())
508 {
509 SystemD::UnitInfo unitinfo;
510
511 try
512 {
513 unitinfo = parseUnit(unit.id);
514 }
515 catch (std::runtime_error& e)
516 {
517 continue;
518 }
519
520 if (job != unitinfo.job)
521 {
522 continue;
523 }
524
525 if (std::string(appID) != unitinfo.appid)
526 {
527 continue;
528 }
529
530 instances.emplace_back(std::make_shared<instance::SystemD>(appID, job, unitinfo.inst, urls, registry));
531 }
532
533 g_debug("Found %d instances for AppID '%s'", int(instances.size()), std::string(appID).c_str());
534
535 return instances;
536}
537
538std::list<std::shared_ptr<Application>> SystemD::runningApps()
539{
540 auto allJobs = getAllJobs();
541 auto registry = registry_.lock();
542 std::set<std::string> appids;
543
544 for (const auto& unit : listUnits())
545 {
546 SystemD::UnitInfo unitinfo;
547
548 try
549 {
550 unitinfo = parseUnit(unit.id);
551 }
552 catch (std::runtime_error& e)
553 {
554 continue;
555 }
556
557 if (allJobs.find(unitinfo.job) == allJobs.end())
558 {
559 continue;
560 }
561
562 appids.insert(unitinfo.appid);
563 }
564
565 std::list<std::shared_ptr<Application>> apps;
566 for (const auto& appid : appids)
567 {
568 auto id = AppID::find(appid);
569 if (id.empty())
570 {
571 g_debug("Unable to handle AppID: %s", appid.c_str());
572 continue;
573 }
574
575 apps.emplace_back(Application::create(id, registry));
576 }
577
578 return apps;
579}
580
581std::string SystemD::userBusPath()
582{
583 auto cpath = getenv("UBUNTU_APP_LAUNCH_SYSTEMD_PATH");
584 if (cpath != nullptr)
585 {
586 return cpath;
587 }
588 return std::string{"/run/user/"} + std::to_string(getuid()) + std::string{"/bus"};
589}
590
591std::list<SystemD::UnitEntry> SystemD::listUnits()
592{
593 auto registry = registry_.lock();
594 return registry->impl->thread.executeOnThread<std::list<SystemD::UnitEntry>>([this, registry]() {
595 GError* error{nullptr};
596 std::list<SystemD::UnitEntry> ret;
597
598 GVariant* callt = g_dbus_connection_call_sync(userbus_.get(), /* user bus */
599 SYSTEMD_DBUS_ADDRESS.c_str(), /* bus name */
600 SYSTEMD_DBUS_PATH_MANAGER.c_str(), /* path */
601 SYSTEMD_DBUS_IFACE_MANAGER.c_str(), /* interface */
602 "ListUnits", /* method */
603 nullptr, /* params */
604 G_VARIANT_TYPE("(a(ssssssouso))"), /* ret type */
605 G_DBUS_CALL_FLAGS_NONE, /* flags */
606 -1, /* timeout */
607 registry->impl->thread.getCancellable().get(), /* cancellable */
608 &error);
609
610 if (error != nullptr)
611 {
612 auto message = std::string{"Unable to list SystemD units: "} + error->message;
613 g_error_free(error);
614 throw std::runtime_error(message);
615 }
616
617 GVariant* call = g_variant_get_child_value(callt, 0);
618 g_variant_unref(callt);
619
620 const gchar* id;
621 const gchar* description;
622 const gchar* loadState;
623 const gchar* activeState;
624 const gchar* subState;
625 const gchar* following;
626 const gchar* path;
627 guint32 jobId;
628 const gchar* jobType;
629 const gchar* jobPath;
630 auto iter = g_variant_iter_new(call);
631 while (g_variant_iter_loop(iter, "(&s&s&s&s&s&s&ou&s&o)", &id, &description, &loadState, &activeState,
632 &subState, &following, &path, &jobId, &jobType, &jobPath))
633 {
634 ret.emplace_back(SystemD::UnitEntry{id, description, loadState, activeState, subState, following, path,
635 jobId, jobType, jobPath});
636 }
637
638 g_variant_iter_free(iter);
639 g_variant_unref(call);
640
641 return ret;
642 });
643}
644
645/* TODO: Application job names */
646const std::regex unitNaming{
647 "^ubuntu\\-app\\-launch\\-(application\\-(?:click|legacy|snap))\\-(.*)\\-([0-9]*)\\.service$"};
648
649SystemD::UnitInfo SystemD::parseUnit(const std::string& unit)
650{
651 std::smatch match;
652 if (!std::regex_match(unit, match, unitNaming))
653 {
654 throw std::runtime_error{"Unable to parse unit name: " + unit};
655 }
656
657 return {match[2].str(), match[1].str(), match[3].str()};
658}
659
660std::string SystemD::unitName(const SystemD::UnitInfo& info)
661{
662 return std::string{"ubuntu-app-launch-"} + info.job + "-" + info.appid + "-" + info.inst + ".service";
663}
664
665/** Function that uses and maintains the cache of the paths for units
666 on the systemd dbus connection. If we already have the entry in the
667 cache we just return the path and this function is fast. If not we have
668 to ask systemd for it and that can take a bit longer.
669
670 After getting the data we throw a small background task in to clean
671 up the cache if it has more than 50 entries. We delete those who
672 haven't be used for an hour.
673*/
674std::string SystemD::unitPath(const std::string& unitName)
675{
676 auto registry = registry_.lock();
677 std::string retval;
678
679 if (true)
680 {
681 /* Create a context for the gaurd */
682 std::lock_guard<std::mutex> guard(unitPathsMutex_);
683 auto iter = std::find_if(unitPaths_.begin(), unitPaths_.end(),
684 [&unitName](const SystemD::UnitPath& entry) { return entry.unitName == unitName; });
685
686 if (iter != unitPaths_.end())
687 {
688 retval = iter->unitPath;
689 iter->timeStamp = std::chrono::system_clock::now();
690 }
691 }
692
693 if (retval.empty())
694 {
695 retval = registry->impl->thread.executeOnThread<std::string>([this, registry, unitName]() {
696 std::string path;
697 GError* error{nullptr};
698 GVariant* call =
699 g_dbus_connection_call_sync(userbus_.get(), /* user bus */
700 SYSTEMD_DBUS_ADDRESS.c_str(), /* bus name */
701 SYSTEMD_DBUS_PATH_MANAGER.c_str(), /* path */
702 SYSTEMD_DBUS_IFACE_MANAGER.c_str(), /* interface */
703 "GetUnit", /* method */
704 g_variant_new("(s)", unitName.c_str()), /* params */
705 G_VARIANT_TYPE("(o)"), /* ret type */
706 G_DBUS_CALL_FLAGS_NONE, /* flags */
707 -1, /* timeout */
708 registry->impl->thread.getCancellable().get(), /* cancellable */
709 &error);
710
711 if (error != nullptr)
712 {
713 auto message = std::string{"Unable to get SystemD unit path for '"} + unitName + std::string{"': "} +
714 error->message;
715 g_error_free(error);
716 throw std::runtime_error(message);
717 }
718
719 /* Parse variant */
720 gchar* gpath = nullptr;
721 g_variant_get(call, "(o)", &gpath);
722 if (gpath != nullptr)
723 {
724 std::lock_guard<std::mutex> guard(unitPathsMutex_);
725 path = gpath;
726 unitPaths_.emplace_back(SystemD::UnitPath{unitName, path, std::chrono::system_clock::now()});
727 }
728
729 g_variant_unref(call);
730
731 return path;
732 });
733 }
734
735 /* Queue a possible cleanup */
736 if (unitPaths_.size() > 50)
737 {
738 /* TODO: We should look at UnitRemoved as well */
739 /* TODO: Add to cache on UnitNew */
740 registry->impl->thread.executeOnThread([this] {
741 std::lock_guard<std::mutex> guard(unitPathsMutex_);
742 std::remove_if(unitPaths_.begin(), unitPaths_.end(), [](const SystemD::UnitPath& entry) -> bool {
743 auto age = std::chrono::system_clock::now() - entry.timeStamp;
744 return age > std::chrono::hours{1};
745 });
746 });
747 }
748
749 return retval;
750}
751
752pid_t SystemD::unitPrimaryPid(const AppID& appId, const std::string& job, const std::string& instance)
753{
754 auto registry = registry_.lock();
755 auto unitname = unitName(SystemD::UnitInfo{appId, job, instance});
756 auto unitpath = unitPath(unitname);
757
758 return registry->impl->thread.executeOnThread<pid_t>([this, registry, unitname, unitpath]() {
759 GError* error{nullptr};
760 GVariant* call = g_dbus_connection_call_sync(
761 userbus_.get(), /* user bus */
762 SYSTEMD_DBUS_ADDRESS.c_str(), /* bus name */
763 unitpath.c_str(), /* path */
764 "org.freedesktop.DBus.Properties", /* interface */
765 "Get", /* method */
766 g_variant_new("(ss)", SYSTEMD_DBUS_IFACE_SERVICE.c_str(), "MainPID"), /* params */
767 G_VARIANT_TYPE("(v)"), /* ret type */
768 G_DBUS_CALL_FLAGS_NONE, /* flags */
769 -1, /* timeout */
770 registry->impl->thread.getCancellable().get(), /* cancellable */
771 &error);
772
773 if (error != nullptr)
774 {
775 auto message =
776 std::string{"Unable to get SystemD PID for '"} + unitname + std::string{"': "} + error->message;
777 g_error_free(error);
778 throw std::runtime_error(message);
779 }
780
781 /* Parse variant */
782 GVariant* vpid{nullptr};
783 g_variant_get(call, "(v)", &vpid);
784 g_variant_unref(call);
785
786 pid_t pid;
787 pid = g_variant_get_uint32(vpid);
788 g_variant_unref(vpid);
789
790 return pid;
791 });
792}
793
794std::vector<pid_t> SystemD::unitPids(const AppID& appId, const std::string& job, const std::string& instance)
795{
796 auto registry = registry_.lock();
797 auto unitname = unitName(SystemD::UnitInfo{appId, job, instance});
798 auto unitpath = unitPath(unitname);
799
800 auto cgrouppath = registry->impl->thread.executeOnThread<std::string>([this, registry, unitname, unitpath]() {
801 GError* error{nullptr};
802 GVariant* call = g_dbus_connection_call_sync(
803 userbus_.get(), /* user bus */
804 SYSTEMD_DBUS_ADDRESS.c_str(), /* bus name */
805 unitpath.c_str(), /* path */
806 "org.freedesktop.DBus.Properties", /* interface */
807 "Get", /* method */
808 g_variant_new("(ss)", SYSTEMD_DBUS_IFACE_SERVICE.c_str(), "ControlGroup"), /* params */
809 G_VARIANT_TYPE("(v)"), /* ret type */
810 G_DBUS_CALL_FLAGS_NONE, /* flags */
811 -1, /* timeout */
812 registry->impl->thread.getCancellable().get(), /* cancellable */
813 &error);
814
815 if (error != nullptr)
816 {
817 auto message = std::string{"Unable to get SystemD Control Group for '"} + unitname + std::string{"': "} +
818 error->message;
819 g_error_free(error);
820 throw std::runtime_error(message);
821 }
822
823 /* Parse variant */
824 GVariant* vstring = nullptr;
825 g_variant_get(call, "(v)", &vstring);
826 g_variant_unref(call);
827
828 if (vstring == nullptr)
829 {
830 return std::string{};
831 }
832
833 std::string group;
834 auto ggroup = g_variant_get_string(vstring, nullptr);
835 if (ggroup != nullptr)
836 {
837 group = ggroup;
838 }
839 g_variant_unref(vstring);
840
841 return group;
842 });
843
844 gchar* fullpath = g_build_filename("/sys", "fs", "cgroup", "systemd", cgrouppath.c_str(), "tasks", nullptr);
845 gchar* pidstr = nullptr;
846 GError* error = nullptr;
847
848 g_debug("Getting PIDs from %s", fullpath);
849 g_file_get_contents(fullpath, &pidstr, nullptr, &error);
850 g_free(fullpath);
851
852 if (error != nullptr)
853 {
854 auto message = std::string{"Unable to read cgroup PID list: "} + error->message;
855 g_error_free(error);
856 throw std::runtime_error(message);
857 }
858
859 gchar** pidlines = g_strsplit(pidstr, "\n", -1);
860 g_free(pidstr);
861 std::vector<pid_t> pids;
862
863 for (auto i = 0; pidlines[i] != nullptr; i++)
864 {
865 const gchar* pidline = pidlines[i];
866 if (pidline[0] != '\n')
867 {
868 auto pid = std::atoi(pidline);
869 if (pid != 0)
870 {
871 pids.emplace_back(pid);
872 }
873 }
874 }
875
876 g_strfreev(pidlines);
877
878 return pids;
879}
880
881void SystemD::stopUnit(const AppID& appId, const std::string& job, const std::string& instance)
882{
883 auto registry = registry_.lock();
884 auto unitname = unitName(SystemD::UnitInfo{appId, job, instance});
885
886 registry->impl->thread.executeOnThread<bool>([this, registry, unitname] {
887 GError* error{nullptr};
888 GVariant* call = g_dbus_connection_call_sync(userbus_.get(), /* user bus */
889 SYSTEMD_DBUS_ADDRESS.c_str(), /* bus name */
890 SYSTEMD_DBUS_PATH_MANAGER.c_str(), /* path */
891 SYSTEMD_DBUS_IFACE_MANAGER.c_str(), /* interface */
892 "StopUnit", /* method */
893 g_variant_new("(ss)", unitname.c_str(), "fail"), /* params */
894 G_VARIANT_TYPE("(o)"), /* ret type */
895 G_DBUS_CALL_FLAGS_NONE, /* flags */
896 -1, /* timeout */
897 registry->impl->thread.getCancellable().get(), /* cancellable */
898 &error);
899
900 if (error != nullptr)
901 {
902 auto message =
903 std::string{"Unable to get SystemD to stop '"} + unitname + std::string{"': "} + error->message;
904 g_error_free(error);
905 throw std::runtime_error(message);
906 }
907
908 g_variant_unref(call);
909
910 return true;
911 });
912}
913
914} // namespace manager
915} // namespace jobs
916} // namespace app_launch
917} // namespace ubuntu
0918
=== added file 'libubuntu-app-launch/jobs-systemd.h'
--- libubuntu-app-launch/jobs-systemd.h 1970-01-01 00:00:00 +0000
+++ libubuntu-app-launch/jobs-systemd.h 2016-11-01 14:53:56 +0000
@@ -0,0 +1,114 @@
1/*
2 * Copyright © 2016 Canonical Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License version 3, as published
6 * by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranties of
10 * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11 * PURPOSE. See the GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License along
14 * with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authors:
17 * Ted Gould <ted.gould@canonical.com>
18 */
19
20#pragma once
21
22#include "jobs-base.h"
23#include <chrono>
24#include <gio/gio.h>
25#include <map>
26#include <mutex>
27
28namespace ubuntu
29{
30namespace app_launch
31{
32namespace jobs
33{
34namespace manager
35{
36
37class SystemD : public Base
38{
39public:
40 SystemD(std::shared_ptr<Registry> registry);
41 virtual ~SystemD();
42
43 virtual std::shared_ptr<Application::Instance> launch(
44 const AppID& appId,
45 const std::string& job,
46 const std::string& instance,
47 const std::vector<Application::URL>& urls,
48 launchMode mode,
49 std::function<std::list<std::pair<std::string, std::string>>(void)>& getenv) override;
50 virtual std::shared_ptr<Application::Instance> existing(const AppID& appId,
51 const std::string& job,
52 const std::string& instance,
53 const std::vector<Application::URL>& urls) override;
54
55 virtual std::list<std::shared_ptr<Application>> runningApps() override;
56
57 virtual std::vector<std::shared_ptr<instance::Base>> instances(const AppID& appID, const std::string& job) override;
58
59 static std::string userBusPath();
60
61 pid_t unitPrimaryPid(const AppID& appId, const std::string& job, const std::string& instance);
62 std::vector<pid_t> unitPids(const AppID& appId, const std::string& job, const std::string& instance);
63 void stopUnit(const AppID& appId, const std::string& job, const std::string& instance);
64
65private:
66 std::shared_ptr<GDBusConnection> userbus_;
67
68 /* ssssssouso */
69 struct UnitEntry
70 {
71 std::string id;
72 std::string description;
73 std::string loadState;
74 std::string activeState;
75 std::string subState;
76 std::string following;
77 std::string path;
78 std::uint32_t jobId;
79 std::string jobType;
80 std::string jobPath;
81 };
82 std::list<UnitEntry> listUnits();
83
84 struct UnitInfo
85 {
86 std::string appid;
87 std::string job;
88 std::string inst;
89 };
90 UnitInfo parseUnit(const std::string& unit);
91 std::string unitName(const UnitInfo& info);
92
93 struct UnitPath
94 {
95 std::string unitName;
96 std::string unitPath;
97 std::chrono::time_point<std::chrono::system_clock> timeStamp;
98 };
99 std::list<UnitPath> unitPaths_;
100 std::mutex unitPathsMutex_;
101 std::string unitPath(const std::string& unitName);
102
103 static std::string findEnv(const std::string& value, std::list<std::pair<std::string, std::string>>& env);
104 static void copyEnv(const std::string& envname, std::list<std::pair<std::string, std::string>>& env);
105 static void copyEnvByPrefix(const std::string& prefix, std::list<std::pair<std::string, std::string>>& env);
106
107 static std::vector<std::string> parseExec(std::list<std::pair<std::string, std::string>>& env);
108 static void application_start_cb(GObject* obj, GAsyncResult* res, gpointer user_data);
109};
110
111} // namespace manager
112} // namespace jobs
113} // namespace app_launch
114} // namespace ubuntu
0115
=== added file 'libubuntu-app-launch/jobs-upstart.cpp'
--- libubuntu-app-launch/jobs-upstart.cpp 1970-01-01 00:00:00 +0000
+++ libubuntu-app-launch/jobs-upstart.cpp 2016-11-01 14:53:56 +0000
@@ -0,0 +1,874 @@
1/*
2 * Copyright © 2016 Canonical Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License version 3, as published
6 * by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranties of
10 * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11 * PURPOSE. See the GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License along
14 * with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authors:
17 * Ted Gould <ted.gould@canonical.com>
18 */
19
20#include <algorithm>
21#include <cerrno>
22#include <cstring>
23#include <map>
24#include <numeric>
25#include <regex>
26
27#include <cgmanager/cgmanager.h>
28#include <upstart.h>
29
30#include "helpers.h"
31#include "registry-impl.h"
32#include "second-exec-core.h"
33
34extern "C" {
35#include "ubuntu-app-launch-trace.h"
36}
37
38#include "jobs-upstart.h"
39
40namespace ubuntu
41{
42namespace app_launch
43{
44namespace jobs
45{
46namespace instance
47{
48
49/** An object that represents an instance of a job on Upstart. This
50 then implements everything needed by the instance interface. Most
51 applications tie into this today and use it as the backend for
52 their instances. */
53class Upstart : public Base
54{
55public:
56 explicit Upstart(const AppID& appId,
57 const std::string& job,
58 const std::string& instance,
59 const std::vector<Application::URL>& urls,
60 const std::shared_ptr<Registry>& registry);
61
62 /* Query lifecycle */
63 pid_t primaryPid() override;
64 std::string logPath() override;
65 std::vector<pid_t> pids() override;
66
67 /* Manage lifecycle */
68 void stop() override;
69
70 /* C Callback */
71 static void application_start_cb(GObject* obj, GAsyncResult* res, gpointer user_data);
72
73private:
74 std::string upstartJobPath(const std::string& job);
75 std::string upstartName();
76};
77
78/** Uses Upstart to get the primary PID of the instance using Upstart's
79 DBus interface */
80pid_t Upstart::primaryPid()
81{
82 auto jobpath = upstartJobPath(job_);
83 if (jobpath.empty())
84 {
85 g_debug("Unable to get a valid job path");
86 return 0;
87 }
88
89 return registry_->impl->thread.executeOnThread<pid_t>([this, &jobpath]() -> pid_t {
90 GError* error = nullptr;
91
92 std::string instancename = std::string(appId_);
93 if (job_ != "application-click")
94 {
95 instancename += "-" + instance_;
96 }
97
98 g_debug("Getting instance by name: %s", instance_.c_str());
99 GVariant* vinstance_path =
100 g_dbus_connection_call_sync(registry_->impl->_dbus.get(), /* connection */
101 DBUS_SERVICE_UPSTART, /* service */
102 jobpath.c_str(), /* object path */
103 DBUS_INTERFACE_UPSTART_JOB, /* iface */
104 "GetInstanceByName", /* method */
105 g_variant_new("(s)", instancename.c_str()), /* params */
106 G_VARIANT_TYPE("(o)"), /* return type */
107 G_DBUS_CALL_FLAGS_NONE, /* flags */
108 -1, /* timeout: default */
109 registry_->impl->thread.getCancellable().get(), /* cancellable */
110 &error);
111
112 if (error != nullptr)
113 {
114 g_warning("Unable to get instance '%s' of job '%s': %s", instance_.c_str(), job_.c_str(), error->message);
115 g_error_free(error);
116 return 0;
117 }
118
119 /* Jump rope to make this into a C++ type */
120 std::string instance_path;
121 gchar* cinstance_path = nullptr;
122 g_variant_get(vinstance_path, "(o)", &cinstance_path);
123 g_variant_unref(vinstance_path);
124 if (cinstance_path != nullptr)
125 {
126 instance_path = cinstance_path;
127 g_free(cinstance_path);
128 }
129
130 if (instance_path.empty())
131 {
132 g_debug("No instance object for instance name: %s", instance_.c_str());
133 return 0;
134 }
135
136 GVariant* props_tuple =
137 g_dbus_connection_call_sync(registry_->impl->_dbus.get(), /* connection */
138 DBUS_SERVICE_UPSTART, /* service */
139 instance_path.c_str(), /* object path */
140 "org.freedesktop.DBus.Properties", /* interface */
141 "GetAll", /* method */
142 g_variant_new("(s)", DBUS_INTERFACE_UPSTART_INSTANCE), /* params */
143 G_VARIANT_TYPE("(a{sv})"), /* return type */
144 G_DBUS_CALL_FLAGS_NONE, /* flags */
145 -1, /* timeout: default */
146 registry_->impl->thread.getCancellable().get(), /* cancellable */
147 &error);
148
149 if (error != nullptr)
150 {
151 g_warning("Unable to name of properties '%s': %s", instance_path.c_str(), error->message);
152 g_error_free(error);
153 error = nullptr;
154 return 0;
155 }
156
157 GVariant* props_dict = g_variant_get_child_value(props_tuple, 0);
158
159 pid_t retval = 0;
160 GVariant* processes = g_variant_lookup_value(props_dict, "processes", G_VARIANT_TYPE("a(si)"));
161 if (processes != nullptr && g_variant_n_children(processes) > 0)
162 {
163
164 GVariant* first_entry = g_variant_get_child_value(processes, 0);
165 GVariant* pidv = g_variant_get_child_value(first_entry, 1);
166
167 retval = g_variant_get_int32(pidv);
168
169 g_variant_unref(pidv);
170 g_variant_unref(first_entry);
171 }
172 else
173 {
174 g_debug("Unable to get 'processes' from properties of instance at path: %s", instance_path.c_str());
175 }
176
177 g_variant_unref(props_dict);
178
179 return retval;
180 });
181}
182
183/** Generate the full name of the Upstart job for the job, the
184 instance and how all those fit together.
185
186 Handles the special case of application-click which isn't designed
187 to have multi-instance apps.
188*/
189std::string Upstart::upstartName()
190{
191 std::string path = job_ + "-" + std::string(appId_);
192 if (job_ != "application-click")
193 {
194 path += "-";
195 }
196 if (!instance_.empty())
197 {
198 path += instance_;
199 }
200
201 return path;
202}
203
204/** Gets the path to the log file for this instance */
205std::string Upstart::logPath()
206{
207 std::string logfile = upstartName() + ".log";
208
209 gchar* cpath = g_build_filename(g_get_user_cache_dir(), "upstart", logfile.c_str(), nullptr);
210 std::string path(cpath);
211 g_free(cpath);
212
213 return path;
214}
215
216/** Returns all the PIDs that are in the cgroup for this application */
217std::vector<pid_t> Upstart::pids()
218{
219 auto manager = std::dynamic_pointer_cast<manager::Upstart>(registry_->impl->jobs);
220 auto pids = manager->pidsFromCgroup(upstartName());
221 g_debug("Got %d PIDs for AppID '%s'", int(pids.size()), std::string(appId_).c_str());
222 return pids;
223}
224
225/** Stops this instance by asking Upstart to stop it. Upstart will then
226 send a SIGTERM and five seconds later start killing things. */
227void Upstart::stop()
228{
229 if (!registry_->impl->thread.executeOnThread<bool>([this]() {
230 auto manager = std::dynamic_pointer_cast<manager::Upstart>(registry_->impl->jobs);
231
232 g_debug("Stopping job %s app_id %s instance_id %s", job_.c_str(), std::string(appId_).c_str(),
233 instance_.c_str());
234
235 auto jobpath = manager->upstartJobPath(job_);
236 if (jobpath.empty())
237 {
238 throw new std::runtime_error("Unable to get job path for Upstart job '" + job_ + "'");
239 }
240
241 GVariantBuilder builder;
242 g_variant_builder_init(&builder, G_VARIANT_TYPE_TUPLE);
243 g_variant_builder_open(&builder, G_VARIANT_TYPE_ARRAY);
244
245 g_variant_builder_add_value(
246 &builder, g_variant_new_take_string(g_strdup_printf("APP_ID=%s", std::string(appId_).c_str())));
247
248 if (!instance_.empty())
249 {
250 g_variant_builder_add_value(
251 &builder, g_variant_new_take_string(g_strdup_printf("INSTANCE_ID=%s", instance_.c_str())));
252 }
253
254 g_variant_builder_close(&builder);
255 g_variant_builder_add_value(&builder, g_variant_new_boolean(FALSE)); /* wait */
256
257 GError* error = nullptr;
258 GVariant* stop_variant =
259 g_dbus_connection_call_sync(registry_->impl->_dbus.get(), /* Dbus */
260 DBUS_SERVICE_UPSTART, /* Upstart name */
261 jobpath.c_str(), /* path */
262 DBUS_INTERFACE_UPSTART_JOB, /* interface */
263 "Stop", /* method */
264 g_variant_builder_end(&builder), /* params */
265 nullptr, /* return */
266 G_DBUS_CALL_FLAGS_NONE, /* flags */
267 -1, /* timeout: default */
268 registry_->impl->thread.getCancellable().get(), /* cancellable */
269 &error); /* error (hopefully not) */
270
271 g_clear_pointer(&stop_variant, g_variant_unref);
272
273 if (error != nullptr)
274 {
275 g_warning("Unable to stop job %s app_id %s instance_id %s: %s", job_.c_str(),
276 std::string(appId_).c_str(), instance_.c_str(), error->message);
277 g_error_free(error);
278 return false;
279 }
280
281 return true;
282 }))
283 {
284 g_warning("Unable to stop Upstart instance");
285 }
286}
287
288/** Create a new Upstart Instance object that can track the job and
289 get information about it.
290
291 \param appId Application ID
292 \param job Upstart job name
293 \param instance Upstart instance name
294 \param urls URLs sent to the application (only on launch today)
295 \param registry Registry of persistent connections to use
296*/
297Upstart::Upstart(const AppID& appId,
298 const std::string& job,
299 const std::string& instance,
300 const std::vector<Application::URL>& urls,
301 const std::shared_ptr<Registry>& registry)
302 : Base(appId, job, instance, urls, registry)
303{
304 g_debug("Creating a new Upstart for '%s' instance '%s'", std::string(appId).c_str(), instance.c_str());
305}
306
307/** Small helper that we can new/delete to work better with C stuff */
308struct StartCHelper
309{
310 std::shared_ptr<Upstart> ptr;
311};
312
313/** Callback from starting an application. It checks to see whether the
314 app is already running. If it is already running then we need to send
315 the URLs to it via DBus.
316
317 \param obj The GDBusConnection object
318 \param res Async result object
319 \param user_data A pointer to a StartCHelper structure
320*/
321void Upstart::application_start_cb(GObject* obj, GAsyncResult* res, gpointer user_data)
322{
323 auto data = static_cast<StartCHelper*>(user_data);
324 GError* error{nullptr};
325 GVariant* result{nullptr};
326
327 tracepoint(ubuntu_app_launch, libual_start_message_callback, std::string(data->ptr->appId_).c_str());
328
329 g_debug("Started Message Callback: %s", std::string(data->ptr->appId_).c_str());
330
331 result = g_dbus_connection_call_finish(G_DBUS_CONNECTION(obj), res, &error);
332
333 g_clear_pointer(&result, g_variant_unref);
334
335 if (error != nullptr)
336 {
337 if (g_dbus_error_is_remote_error(error))
338 {
339 gchar* remote_error = g_dbus_error_get_remote_error(error);
340 g_debug("Remote error: %s", remote_error);
341 if (g_strcmp0(remote_error, "com.ubuntu.Upstart0_6.Error.AlreadyStarted") == 0)
342 {
343 auto urls = urlsToStrv(data->ptr->urls_);
344 second_exec(data->ptr->registry_->impl->_dbus.get(), /* DBus */
345 data->ptr->registry_->impl->thread.getCancellable().get(), /* cancellable */
346 data->ptr->primaryPid(), /* primary pid */
347 std::string(data->ptr->appId_).c_str(), /* appid */
348 urls.get()); /* urls */
349 }
350
351 g_free(remote_error);
352 }
353 else
354 {
355 g_warning("Unable to emit event to start application: %s", error->message);
356 }
357 g_error_free(error);
358 }
359
360 delete data;
361}
362
363std::string Upstart::upstartJobPath(const std::string& job)
364{
365 auto manager = std::dynamic_pointer_cast<manager::Upstart>(registry_->impl->jobs);
366 return manager->upstartJobPath(job);
367}
368
369} // namespace instances
370
371namespace manager
372{
373
374Upstart::Upstart(std::shared_ptr<Registry> registry)
375 : Base(registry)
376{
377}
378
379Upstart::~Upstart()
380{
381}
382
383/** Launch an application and create a new Upstart instance object to track
384 its progress.
385
386 \param appId Application ID
387 \param job Upstart job name
388 \param instance Upstart instance name
389 \param urls URLs sent to the application (only on launch today)
390 \param mode Whether or not to setup the environment for testing
391 \param getenv A function to get additional environment variable when appropriate
392*/
393std::shared_ptr<Application::Instance> Upstart::launch(
394 const AppID& appId,
395 const std::string& job,
396 const std::string& instance,
397 const std::vector<Application::URL>& urls,
398 launchMode mode,
399 std::function<std::list<std::pair<std::string, std::string>>(void)>& getenv)
400{
401 if (appId.empty())
402 return {};
403
404 auto registry = registry_.lock();
405 return registry->impl->thread.executeOnThread<std::shared_ptr<instance::Upstart>>(
406 [&]() -> std::shared_ptr<instance::Upstart> {
407 auto manager = std::dynamic_pointer_cast<manager::Upstart>(registry->impl->jobs);
408 std::string appIdStr{appId};
409 g_debug("Initializing params for an new instance::Upstart for: %s", appIdStr.c_str());
410
411 tracepoint(ubuntu_app_launch, libual_start, appIdStr.c_str());
412
413 int timeout = 1;
414 if (ubuntu::app_launch::Registry::Impl::isWatchingAppStarting())
415 {
416 timeout = 0;
417 }
418
419 auto handshake = starting_handshake_start(appIdStr.c_str(), timeout);
420 if (handshake == nullptr)
421 {
422 g_warning("Unable to setup starting handshake");
423 }
424
425 /* Figure out the DBus path for the job */
426 auto jobpath = manager->upstartJobPath(job);
427
428 /* Build up our environment */
429 auto env = getenv();
430
431 env.emplace_back(std::make_pair("APP_ID", appIdStr)); /* Application ID */
432 env.emplace_back(std::make_pair("APP_LAUNCHER_PID", std::to_string(getpid()))); /* Who we are, for bugs */
433
434 if (!urls.empty())
435 {
436 auto accumfunc = [](const std::string& prev, Application::URL thisurl) -> std::string {
437 gchar* gescaped = g_shell_quote(thisurl.value().c_str());
438 std::string escaped;
439 if (gescaped != nullptr)
440 {
441 escaped = gescaped;
442 g_free(gescaped);
443 }
444 else
445 {
446 g_warning("Unable to escape URL: %s", thisurl.value().c_str());
447 return prev;
448 }
449
450 if (prev.empty())
451 {
452 return escaped;
453 }
454 else
455 {
456 return prev + " " + escaped;
457 }
458 };
459 auto urlstring = std::accumulate(urls.begin(), urls.end(), std::string{}, accumfunc);
460 env.emplace_back(std::make_pair("APP_URIS", urlstring));
461 }
462
463 if (mode == launchMode::TEST)
464 {
465 env.emplace_back(std::make_pair("QT_LOAD_TESTABILITY", "1"));
466 }
467
468 /* Convert to GVariant */
469 GVariantBuilder builder;
470 g_variant_builder_init(&builder, G_VARIANT_TYPE_TUPLE);
471
472 g_variant_builder_open(&builder, G_VARIANT_TYPE_ARRAY);
473
474 for (const auto& envvar : env)
475 {
476 g_variant_builder_add_value(&builder, g_variant_new_take_string(g_strdup_printf(
477 "%s=%s", envvar.first.c_str(), envvar.second.c_str())));
478 }
479
480 g_variant_builder_close(&builder);
481 g_variant_builder_add_value(&builder, g_variant_new_boolean(TRUE));
482
483 auto retval = std::make_shared<instance::Upstart>(appId, job, instance, urls, registry);
484 auto chelper = new instance::StartCHelper{};
485 chelper->ptr = retval;
486
487 tracepoint(ubuntu_app_launch, handshake_wait, appIdStr.c_str());
488 starting_handshake_wait(handshake);
489 tracepoint(ubuntu_app_launch, handshake_complete, appIdStr.c_str());
490
491 /* Call the job start function */
492 g_debug("Asking Upstart to start task for: %s", appIdStr.c_str());
493 g_dbus_connection_call(registry->impl->_dbus.get(), /* bus */
494 DBUS_SERVICE_UPSTART, /* service name */
495 jobpath.c_str(), /* Path */
496 DBUS_INTERFACE_UPSTART_JOB, /* interface */
497 "Start", /* method */
498 g_variant_builder_end(&builder), /* params */
499 nullptr, /* return */
500 G_DBUS_CALL_FLAGS_NONE, /* flags */
501 -1, /* default timeout */
502 registry->impl->thread.getCancellable().get(), /* cancellable */
503 instance::Upstart::application_start_cb, /* callback */
504 chelper /* object */
505 );
506
507 tracepoint(ubuntu_app_launch, libual_start_message_sent, appIdStr.c_str());
508
509 return retval;
510 });
511}
512
513/** Special characters that could be an application name that
514 would activate in a regex */
515const static std::regex regexCharacters("([\\.\\-])");
516
517std::shared_ptr<Application::Instance> Upstart::existing(const AppID& appId,
518 const std::string& job,
519 const std::string& instance,
520 const std::vector<Application::URL>& urls)
521{
522 return std::make_shared<instance::Upstart>(appId, job, instance, urls, registry_.lock());
523}
524
525std::vector<std::shared_ptr<instance::Base>> Upstart::instances(const AppID& appID, const std::string& job)
526{
527 std::vector<std::shared_ptr<instance::Base>> vect;
528 auto startsWith = std::string(appID);
529 if (job != "application-click")
530 {
531 startsWith += "-";
532 }
533
534 auto regexstr =
535 std::string{"^(?:"} + std::regex_replace(startsWith, regexCharacters, "\\$&") + std::string{")(\\d*)$"};
536 auto instanceRegex = std::regex(regexstr);
537
538 for (auto instance : upstartInstancesForJob(job))
539 {
540 std::smatch instanceMatch;
541 g_debug("Looking at job '%s' instance: %s", job.c_str(), instance.c_str());
542 if (std::regex_match(instance, instanceMatch, instanceRegex))
543 {
544 auto app = existing(appID, job, instanceMatch[1].str(), {});
545 vect.emplace_back(std::dynamic_pointer_cast<instance::Base>(app));
546 }
547 }
548
549 g_debug("App '%s' has %d instances", std::string(appID).c_str(), int(vect.size()));
550
551 return vect;
552}
553
554/** Initialize the CGManager connection, including a timeout to disconnect
555 as CGManager doesn't free resources entirely well. So it's better if
556 we connect and disconnect occationally */
557void Upstart::initCGManager()
558{
559 if (cgManager_)
560 return;
561
562 std::promise<std::shared_ptr<GDBusConnection>> promise;
563 auto future = promise.get_future();
564 auto registry = registry_.lock();
565
566 registry->impl->thread.executeOnThread([this, &promise, &registry]() {
567 bool use_session_bus = g_getenv("UBUNTU_APP_LAUNCH_CG_MANAGER_SESSION_BUS") != nullptr;
568 if (use_session_bus)
569 {
570 /* For working dbusmock */
571 g_debug("Connecting to CG Manager on session bus");
572 promise.set_value(registry->impl->_dbus);
573 return;
574 }
575
576 auto cancel =
577 std::shared_ptr<GCancellable>(g_cancellable_new(), [](GCancellable* cancel) { g_clear_object(&cancel); });
578
579 /* Ensure that we do not wait for more than a second */
580 registry->impl->thread.timeoutSeconds(std::chrono::seconds{1},
581 [cancel]() { g_cancellable_cancel(cancel.get()); });
582
583 g_dbus_connection_new_for_address(
584 CGMANAGER_DBUS_PATH, /* cgmanager path */
585 G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT, /* flags */
586 nullptr, /* Auth Observer */
587 cancel.get(), /* Cancellable */
588 [](GObject* obj, GAsyncResult* res, gpointer data) -> void {
589 GError* error = nullptr;
590 auto promise = reinterpret_cast<std::promise<std::shared_ptr<GDBusConnection>>*>(data);
591
592 auto gcon = g_dbus_connection_new_for_address_finish(res, &error);
593 if (error != nullptr)
594 {
595 g_error_free(error);
596 }
597
598 auto con = std::shared_ptr<GDBusConnection>(gcon, [](GDBusConnection* con) { g_clear_object(&con); });
599 promise->set_value(con);
600 },
601 &promise);
602 });
603
604 cgManager_ = future.get();
605 registry->impl->thread.timeoutSeconds(std::chrono::seconds{10}, [this]() { cgManager_.reset(); });
606}
607
608/** Get a list of PIDs from a CGroup, uses the CGManager connection to list
609 all of the PIDs. It is important to note that this is an IPC call, so it can
610 by its nature, be racy. Once the message has been sent the group can change.
611 You should take that into account in your usage of it. */
612std::vector<pid_t> Upstart::pidsFromCgroup(const std::string& jobpath)
613{
614 initCGManager();
615 auto lmanager = cgManager_; /* Grab a local copy so we ensure it lasts through our lifetime */
616 auto registry = registry_.lock();
617
618 return registry->impl->thread.executeOnThread<std::vector<pid_t>>([&jobpath, lmanager]() -> std::vector<pid_t> {
619 GError* error = nullptr;
620 const gchar* name = g_getenv("UBUNTU_APP_LAUNCH_CG_MANAGER_NAME");
621 std::string groupname;
622 if (!jobpath.empty())
623 {
624 groupname = "upstart/" + jobpath;
625 }
626
627 g_debug("Looking for cg manager '%s' group '%s'", name, groupname.c_str());
628
629 GVariant* vtpids = g_dbus_connection_call_sync(
630 lmanager.get(), /* connection */
631 name, /* bus name for direct connection is NULL */
632 "/org/linuxcontainers/cgmanager", /* object */
633 "org.linuxcontainers.cgmanager0_0", /* interface */
634 "GetTasksRecursive", /* method */
635 g_variant_new("(ss)", "freezer", groupname.empty() ? "" : groupname.c_str()), /* params */
636 G_VARIANT_TYPE("(ai)"), /* output */
637 G_DBUS_CALL_FLAGS_NONE, /* flags */
638 -1, /* default timeout */
639 nullptr, /* cancellable */
640 &error); /* error */
641
642 if (error != nullptr)
643 {
644 g_warning("Unable to get PID list from cgroup manager: %s", error->message);
645 g_error_free(error);
646 return {};
647 }
648
649 GVariant* vpids = g_variant_get_child_value(vtpids, 0);
650 GVariantIter iter;
651 g_variant_iter_init(&iter, vpids);
652 gint32 pid;
653 std::vector<pid_t> pids;
654
655 while (g_variant_iter_loop(&iter, "i", &pid))
656 {
657 pids.push_back(pid);
658 }
659
660 g_variant_unref(vpids);
661 g_variant_unref(vtpids);
662
663 return pids;
664 });
665}
666
667/** Looks to find the Upstart object path for a specific Upstart job. This first
668 checks the cache, and otherwise does the lookup on DBus. */
669std::string Upstart::upstartJobPath(const std::string& job)
670{
671 try
672 {
673 return upstartJobPathCache_.at(job);
674 }
675 catch (std::out_of_range& e)
676 {
677 auto registry = registry_.lock();
678 auto path = registry->impl->thread.executeOnThread<std::string>([this, &job, &registry]() -> std::string {
679 GError* error = nullptr;
680 GVariant* job_path_variant =
681 g_dbus_connection_call_sync(registry->impl->_dbus.get(), /* connection */
682 DBUS_SERVICE_UPSTART, /* service */
683 DBUS_PATH_UPSTART, /* path */
684 DBUS_INTERFACE_UPSTART, /* iface */
685 "GetJobByName", /* method */
686 g_variant_new("(s)", job.c_str()), /* params */
687 G_VARIANT_TYPE("(o)"), /* return */
688 G_DBUS_CALL_FLAGS_NONE, /* flags */
689 -1, /* timeout: default */
690 registry->impl->thread.getCancellable().get(), /* cancellable */
691 &error); /* error */
692
693 if (error != nullptr)
694 {
695 g_warning("Unable to find job '%s': %s", job.c_str(), error->message);
696 g_error_free(error);
697 return {};
698 }
699
700 gchar* job_path = nullptr;
701 g_variant_get(job_path_variant, "(o)", &job_path);
702 g_variant_unref(job_path_variant);
703
704 if (job_path != nullptr)
705 {
706 std::string path(job_path);
707 g_free(job_path);
708 return path;
709 }
710 else
711 {
712 return {};
713 }
714 });
715
716 upstartJobPathCache_[job] = path;
717 return path;
718 }
719}
720
721/** Queries Upstart to get all the instances of a given job. This
722 can take a while as the number of dbus calls is n+1. It is
723 rare that apps have many instances though. */
724std::list<std::string> Upstart::upstartInstancesForJob(const std::string& job)
725{
726 std::string jobpath = upstartJobPath(job);
727 if (jobpath.empty())
728 {
729 return {};
730 }
731
732 auto registry = registry_.lock();
733 return registry->impl->thread.executeOnThread<std::list<std::string>>(
734 [this, &job, &jobpath, &registry]() -> std::list<std::string> {
735 GError* error = nullptr;
736 GVariant* instance_tuple =
737 g_dbus_connection_call_sync(registry->impl->_dbus.get(), /* connection */
738 DBUS_SERVICE_UPSTART, /* service */
739 jobpath.c_str(), /* object path */
740 DBUS_INTERFACE_UPSTART_JOB, /* iface */
741 "GetAllInstances", /* method */
742 nullptr, /* params */
743 G_VARIANT_TYPE("(ao)"), /* return type */
744 G_DBUS_CALL_FLAGS_NONE, /* flags */
745 -1, /* timeout: default */
746 registry->impl->thread.getCancellable().get(), /* cancellable */
747 &error);
748
749 if (error != nullptr)
750 {
751 g_warning("Unable to get instances of job '%s': %s", job.c_str(), error->message);
752 g_error_free(error);
753 return {};
754 }
755
756 if (instance_tuple == nullptr)
757 {
758 return {};
759 }
760
761 GVariant* instance_list = g_variant_get_child_value(instance_tuple, 0);
762 g_variant_unref(instance_tuple);
763
764 GVariantIter instance_iter;
765 g_variant_iter_init(&instance_iter, instance_list);
766 const gchar* instance_path = nullptr;
767 std::list<std::string> instances;
768
769 while (g_variant_iter_loop(&instance_iter, "&o", &instance_path))
770 {
771 GVariant* props_tuple =
772 g_dbus_connection_call_sync(registry->impl->_dbus.get(), /* connection */
773 DBUS_SERVICE_UPSTART, /* service */
774 instance_path, /* object path */
775 "org.freedesktop.DBus.Properties", /* interface */
776 "GetAll", /* method */
777 g_variant_new("(s)", DBUS_INTERFACE_UPSTART_INSTANCE), /* params */
778 G_VARIANT_TYPE("(a{sv})"), /* return type */
779 G_DBUS_CALL_FLAGS_NONE, /* flags */
780 -1, /* timeout: default */
781 registry->impl->thread.getCancellable().get(), /* cancellable */
782 &error);
783
784 if (error != nullptr)
785 {
786 g_warning("Unable to name of instance '%s': %s", instance_path, error->message);
787 g_error_free(error);
788 error = nullptr;
789 continue;
790 }
791
792 GVariant* props_dict = g_variant_get_child_value(props_tuple, 0);
793
794 GVariant* namev = g_variant_lookup_value(props_dict, "name", G_VARIANT_TYPE_STRING);
795 if (namev != nullptr)
796 {
797 auto name = g_variant_get_string(namev, NULL);
798 g_debug("Adding instance for job '%s': %s", job.c_str(), name);
799 instances.push_back(name);
800 g_variant_unref(namev);
801 }
802
803 g_variant_unref(props_dict);
804 g_variant_unref(props_tuple);
805 }
806
807 g_variant_unref(instance_list);
808
809 return instances;
810 });
811}
812
813std::list<std::shared_ptr<Application>> Upstart::runningApps()
814{
815 std::list<std::string> instances;
816
817 /* Get all the legacy instances */
818 instances.splice(instances.begin(), upstartInstancesForJob("application-legacy"));
819 /* Get all the snap instances */
820 instances.splice(instances.begin(), upstartInstancesForJob("application-snap"));
821
822 /* Remove the instance ID */
823 std::transform(instances.begin(), instances.end(), instances.begin(), [](std::string& instancename) -> std::string {
824 static const std::regex instanceregex("^(.*)-[0-9]*$");
825 std::smatch match;
826 if (std::regex_match(instancename, match, instanceregex))
827 {
828 return match[1].str();
829 }
830 else
831 {
832 g_warning("Unable to match instance name: %s", instancename.c_str());
833 return {};
834 }
835 });
836
837 /* Deduplicate Set */
838 std::set<std::string> instanceset;
839 for (auto instance : instances)
840 {
841 if (!instance.empty())
842 instanceset.insert(instance);
843 }
844
845 /* Add in the click instances */
846 for (auto instance : upstartInstancesForJob("application-click"))
847 {
848 instanceset.insert(instance);
849 }
850
851 g_debug("Overall there are %d instances: %s", int(instanceset.size()),
852 std::accumulate(instanceset.begin(), instanceset.end(), std::string{},
853 [](const std::string& instr, std::string instance) {
854 return instr.empty() ? instance : instr + ", " + instance;
855 })
856 .c_str());
857
858 /* Convert to Applications */
859 auto registry = registry_.lock();
860 std::list<std::shared_ptr<Application>> apps;
861 for (auto instance : instanceset)
862 {
863 auto appid = AppID::find(registry, instance);
864 auto app = Application::create(appid, registry);
865 apps.push_back(app);
866 }
867
868 return apps;
869}
870
871} // namespace manager
872} // namespace jobs
873} // namespace app_launch
874} // namespace ubuntu
0875
=== added file 'libubuntu-app-launch/jobs-upstart.h'
--- libubuntu-app-launch/jobs-upstart.h 1970-01-01 00:00:00 +0000
+++ libubuntu-app-launch/jobs-upstart.h 2016-11-01 14:53:56 +0000
@@ -0,0 +1,75 @@
1/*
2 * Copyright © 2016 Canonical Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License version 3, as published
6 * by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranties of
10 * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11 * PURPOSE. See the GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License along
14 * with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authors:
17 * Ted Gould <ted.gould@canonical.com>
18 */
19
20#pragma once
21
22#include "jobs-base.h"
23#include <gio/gio.h>
24#include <map>
25
26namespace ubuntu
27{
28namespace app_launch
29{
30namespace jobs
31{
32namespace manager
33{
34
35class Upstart : public Base
36{
37public:
38 Upstart(std::shared_ptr<Registry> registry);
39 virtual ~Upstart();
40
41 virtual std::shared_ptr<Application::Instance> launch(
42 const AppID& appId,
43 const std::string& job,
44 const std::string& instance,
45 const std::vector<Application::URL>& urls,
46 launchMode mode,
47 std::function<std::list<std::pair<std::string, std::string>>(void)>& getenv) override;
48 virtual std::shared_ptr<Application::Instance> existing(const AppID& appId,
49 const std::string& job,
50 const std::string& instance,
51 const std::vector<Application::URL>& urls) override;
52
53 virtual std::list<std::shared_ptr<Application>> runningApps() override;
54
55 virtual std::vector<std::shared_ptr<instance::Base>> instances(const AppID& appID, const std::string& job) override;
56
57 std::vector<pid_t> pidsFromCgroup(const std::string& jobpath);
58
59 std::list<std::string> upstartInstancesForJob(const std::string& job);
60 std::string upstartJobPath(const std::string& job);
61
62private:
63 void initCGManager();
64
65 std::shared_ptr<GDBusConnection> cgManager_;
66
67 /** Getting the Upstart job path is relatively expensive in
68 that it requires a DBus call. Worth keeping a cache of. */
69 std::map<std::string, std::string> upstartJobPathCache_;
70};
71
72} // namespace manager
73} // namespace jobs
74} // namespace app_launch
75} // namespace ubuntu
076
=== modified file 'libubuntu-app-launch/registry-impl.cpp'
--- libubuntu-app-launch/registry-impl.cpp 2016-09-23 22:30:51 +0000
+++ libubuntu-app-launch/registry-impl.cpp 2016-11-01 14:53:56 +0000
@@ -19,8 +19,6 @@
1919
20#include "registry-impl.h"20#include "registry-impl.h"
21#include "application-icon-finder.h"21#include "application-icon-finder.h"
22#include <cgmanager/cgmanager.h>
23#include <upstart.h>
2422
25namespace ubuntu23namespace ubuntu
26{24{
@@ -34,7 +32,7 @@
34 _clickDB.reset();32 _clickDB.reset();
3533
36 zgLog_.reset();34 zgLog_.reset();
37 cgManager_.reset();35 jobs.reset();
3836
39 if (_dbus)37 if (_dbus)
40 g_dbus_connection_flush_sync(_dbus.get(), nullptr, nullptr);38 g_dbus_connection_flush_sync(_dbus.get(), nullptr, nullptr);
@@ -49,6 +47,14 @@
49 return std::shared_ptr<GDBusConnection>(g_bus_get_sync(G_BUS_TYPE_SESSION, cancel.get(), nullptr),47 return std::shared_ptr<GDBusConnection>(g_bus_get_sync(G_BUS_TYPE_SESSION, cancel.get(), nullptr),
50 [](GDBusConnection* bus) { g_clear_object(&bus); });48 [](GDBusConnection* bus) { g_clear_object(&bus); });
51 });49 });
50
51 /* Determine where we're getting the helper from */
52 oomHelper_ = OOM_HELPER;
53 auto goomHelper = g_getenv("UBUNTU_APP_LAUNCH_OOM_HELPER");
54 if (goomHelper != nullptr)
55 {
56 oomHelper_ = goomHelper;
57 }
52}58}
5359
54void Registry::Impl::initClick()60void Registry::Impl::initClick()
@@ -145,7 +151,7 @@
145 if (error != nullptr)151 if (error != nullptr)
146 {152 {
147 auto perror = std::shared_ptr<GError>(error, [](GError* error) { g_error_free(error); });153 auto perror = std::shared_ptr<GError>(error, [](GError* error) { g_error_free(error); });
148 g_critical("Error parsing manifest for package '%s': %s", package.c_str(), perror->message);154 g_debug("Error parsing manifest for package '%s': %s", package.c_str(), perror->message);
149 return std::shared_ptr<JsonObject>();155 return std::shared_ptr<JsonObject>();
150 }156 }
151157
@@ -214,257 +220,6 @@
214 });220 });
215}221}
216222
217/** Initialize the CGManager connection, including a timeout to disconnect
218 as CGManager doesn't free resources entirely well. So it's better if
219 we connect and disconnect occationally */
220void Registry::Impl::initCGManager()
221{
222 if (cgManager_)
223 return;
224
225 std::promise<std::shared_ptr<GDBusConnection>> promise;
226 auto future = promise.get_future();
227
228 thread.executeOnThread([this, &promise]() {
229 bool use_session_bus = g_getenv("UBUNTU_APP_LAUNCH_CG_MANAGER_SESSION_BUS") != nullptr;
230 if (use_session_bus)
231 {
232 /* For working dbusmock */
233 g_debug("Connecting to CG Manager on session bus");
234 promise.set_value(_dbus);
235 return;
236 }
237
238 auto cancel =
239 std::shared_ptr<GCancellable>(g_cancellable_new(), [](GCancellable* cancel) { g_clear_object(&cancel); });
240
241 /* Ensure that we do not wait for more than a second */
242 thread.timeoutSeconds(std::chrono::seconds{1}, [cancel]() { g_cancellable_cancel(cancel.get()); });
243
244 g_dbus_connection_new_for_address(
245 CGMANAGER_DBUS_PATH, /* cgmanager path */
246 G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT, /* flags */
247 nullptr, /* Auth Observer */
248 cancel.get(), /* Cancellable */
249 [](GObject* obj, GAsyncResult* res, gpointer data) -> void {
250 GError* error = nullptr;
251 auto promise = reinterpret_cast<std::promise<std::shared_ptr<GDBusConnection>>*>(data);
252
253 auto gcon = g_dbus_connection_new_for_address_finish(res, &error);
254 if (error != nullptr)
255 {
256 g_error_free(error);
257 }
258
259 auto con = std::shared_ptr<GDBusConnection>(gcon, [](GDBusConnection* con) { g_clear_object(&con); });
260 promise->set_value(con);
261 },
262 &promise);
263 });
264
265 cgManager_ = future.get();
266 thread.timeoutSeconds(std::chrono::seconds{10}, [this]() { cgManager_.reset(); });
267}
268
269/** Get a list of PIDs from a CGroup, uses the CGManager connection to list
270 all of the PIDs. It is important to note that this is an IPC call, so it can
271 by its nature, be racy. Once the message has been sent the group can change.
272 You should take that into account in your usage of it. */
273std::vector<pid_t> Registry::Impl::pidsFromCgroup(const std::string& jobpath)
274{
275 initCGManager();
276 auto lmanager = cgManager_; /* Grab a local copy so we ensure it lasts through our lifetime */
277
278 return thread.executeOnThread<std::vector<pid_t>>([&jobpath, lmanager]() -> std::vector<pid_t> {
279 GError* error = nullptr;
280 const gchar* name = g_getenv("UBUNTU_APP_LAUNCH_CG_MANAGER_NAME");
281 std::string groupname;
282 if (!jobpath.empty())
283 {
284 groupname = "upstart/" + jobpath;
285 }
286
287 g_debug("Looking for cg manager '%s' group '%s'", name, groupname.c_str());
288
289 GVariant* vtpids = g_dbus_connection_call_sync(
290 lmanager.get(), /* connection */
291 name, /* bus name for direct connection is NULL */
292 "/org/linuxcontainers/cgmanager", /* object */
293 "org.linuxcontainers.cgmanager0_0", /* interface */
294 "GetTasksRecursive", /* method */
295 g_variant_new("(ss)", "freezer", groupname.empty() ? "" : groupname.c_str()), /* params */
296 G_VARIANT_TYPE("(ai)"), /* output */
297 G_DBUS_CALL_FLAGS_NONE, /* flags */
298 -1, /* default timeout */
299 nullptr, /* cancellable */
300 &error); /* error */
301
302 if (error != nullptr)
303 {
304 g_warning("Unable to get PID list from cgroup manager: %s", error->message);
305 g_error_free(error);
306 return {};
307 }
308
309 GVariant* vpids = g_variant_get_child_value(vtpids, 0);
310 GVariantIter iter;
311 g_variant_iter_init(&iter, vpids);
312 gint32 pid;
313 std::vector<pid_t> pids;
314
315 while (g_variant_iter_loop(&iter, "i", &pid))
316 {
317 pids.push_back(pid);
318 }
319
320 g_variant_unref(vpids);
321 g_variant_unref(vtpids);
322
323 return pids;
324 });
325}
326
327/** Looks to find the Upstart object path for a specific Upstart job. This first
328 checks the cache, and otherwise does the lookup on DBus. */
329std::string Registry::Impl::upstartJobPath(const std::string& job)
330{
331 try
332 {
333 return upstartJobPathCache_.at(job);
334 }
335 catch (std::out_of_range& e)
336 {
337 auto path = thread.executeOnThread<std::string>([this, &job]() -> std::string {
338 GError* error = nullptr;
339 GVariant* job_path_variant = g_dbus_connection_call_sync(_dbus.get(), /* connection */
340 DBUS_SERVICE_UPSTART, /* service */
341 DBUS_PATH_UPSTART, /* path */
342 DBUS_INTERFACE_UPSTART, /* iface */
343 "GetJobByName", /* method */
344 g_variant_new("(s)", job.c_str()), /* params */
345 G_VARIANT_TYPE("(o)"), /* return */
346 G_DBUS_CALL_FLAGS_NONE, /* flags */
347 -1, /* timeout: default */
348 thread.getCancellable().get(), /* cancellable */
349 &error); /* error */
350
351 if (error != nullptr)
352 {
353 g_warning("Unable to find job '%s': %s", job.c_str(), error->message);
354 g_error_free(error);
355 return {};
356 }
357
358 gchar* job_path = nullptr;
359 g_variant_get(job_path_variant, "(o)", &job_path);
360 g_variant_unref(job_path_variant);
361
362 if (job_path != nullptr)
363 {
364 std::string path(job_path);
365 g_free(job_path);
366 return path;
367 }
368 else
369 {
370 return {};
371 }
372 });
373
374 upstartJobPathCache_[job] = path;
375 return path;
376 }
377}
378
379/** Queries Upstart to get all the instances of a given job. This
380 can take a while as the number of dbus calls is n+1. It is
381 rare that apps have many instances though. */
382std::list<std::string> Registry::Impl::upstartInstancesForJob(const std::string& job)
383{
384 std::string jobpath = upstartJobPath(job);
385 if (jobpath.empty())
386 {
387 return {};
388 }
389
390 return thread.executeOnThread<std::list<std::string>>([this, &job, &jobpath]() -> std::list<std::string> {
391 GError* error = nullptr;
392 GVariant* instance_tuple = g_dbus_connection_call_sync(_dbus.get(), /* connection */
393 DBUS_SERVICE_UPSTART, /* service */
394 jobpath.c_str(), /* object path */
395 DBUS_INTERFACE_UPSTART_JOB, /* iface */
396 "GetAllInstances", /* method */
397 nullptr, /* params */
398 G_VARIANT_TYPE("(ao)"), /* return type */
399 G_DBUS_CALL_FLAGS_NONE, /* flags */
400 -1, /* timeout: default */
401 thread.getCancellable().get(), /* cancellable */
402 &error);
403
404 if (error != nullptr)
405 {
406 g_warning("Unable to get instances of job '%s': %s", job.c_str(), error->message);
407 g_error_free(error);
408 return {};
409 }
410
411 if (instance_tuple == nullptr)
412 {
413 return {};
414 }
415
416 GVariant* instance_list = g_variant_get_child_value(instance_tuple, 0);
417 g_variant_unref(instance_tuple);
418
419 GVariantIter instance_iter;
420 g_variant_iter_init(&instance_iter, instance_list);
421 const gchar* instance_path = nullptr;
422 std::list<std::string> instances;
423
424 while (g_variant_iter_loop(&instance_iter, "&o", &instance_path))
425 {
426 GVariant* props_tuple =
427 g_dbus_connection_call_sync(_dbus.get(), /* connection */
428 DBUS_SERVICE_UPSTART, /* service */
429 instance_path, /* object path */
430 "org.freedesktop.DBus.Properties", /* interface */
431 "GetAll", /* method */
432 g_variant_new("(s)", DBUS_INTERFACE_UPSTART_INSTANCE), /* params */
433 G_VARIANT_TYPE("(a{sv})"), /* return type */
434 G_DBUS_CALL_FLAGS_NONE, /* flags */
435 -1, /* timeout: default */
436 thread.getCancellable().get(), /* cancellable */
437 &error);
438
439 if (error != nullptr)
440 {
441 g_warning("Unable to name of instance '%s': %s", instance_path, error->message);
442 g_error_free(error);
443 error = nullptr;
444 continue;
445 }
446
447 GVariant* props_dict = g_variant_get_child_value(props_tuple, 0);
448
449 GVariant* namev = g_variant_lookup_value(props_dict, "name", G_VARIANT_TYPE_STRING);
450 if (namev != nullptr)
451 {
452 auto name = g_variant_get_string(namev, NULL);
453 g_debug("Adding instance for job '%s': %s", job.c_str(), name);
454 instances.push_back(name);
455 g_variant_unref(namev);
456 }
457
458 g_variant_unref(props_dict);
459 g_variant_unref(props_tuple);
460 }
461
462 g_variant_unref(instance_list);
463
464 return instances;
465 });
466}
467
468/** Send an event to Zietgeist using the registry thread so that223/** Send an event to Zietgeist using the registry thread so that
469 the callback comes back in the right place. */224 the callback comes back in the right place. */
470void Registry::Impl::zgSendEvent(AppID appid, const std::string& eventtype)225void Registry::Impl::zgSendEvent(AppID appid, const std::string& eventtype)
471226
=== modified file 'libubuntu-app-launch/registry-impl.h'
--- libubuntu-app-launch/registry-impl.h 2016-09-23 22:30:51 +0000
+++ libubuntu-app-launch/registry-impl.h 2016-11-01 14:53:56 +0000
@@ -18,6 +18,7 @@
18 */18 */
1919
20#include "glib-thread.h"20#include "glib-thread.h"
21#include "jobs-base.h"
21#include "registry.h"22#include "registry.h"
22#include "snapd-info.h"23#include "snapd-info.h"
23#include <click.h>24#include <click.h>
@@ -69,15 +70,11 @@
69 snapd::Info snapdInfo;70 snapd::Info snapdInfo;
70#endif71#endif
7172
73 std::shared_ptr<jobs::manager::Base> jobs;
74
72 std::shared_ptr<IconFinder> getIconFinder(std::string basePath);75 std::shared_ptr<IconFinder> getIconFinder(std::string basePath);
7376
74 void zgSendEvent(AppID appid, const std::string& eventtype);77 virtual void zgSendEvent(AppID appid, const std::string& eventtype);
75
76 std::vector<pid_t> pidsFromCgroup(const std::string& jobpath);
77
78 /* Upstart Jobs */
79 std::list<std::string> upstartInstancesForJob(const std::string& job);
80 std::string upstartJobPath(const std::string& job);
8178
82 static std::string printJson(std::shared_ptr<JsonObject> jsonobj);79 static std::string printJson(std::shared_ptr<JsonObject> jsonobj);
83 static std::string printJson(std::shared_ptr<JsonNode> jsonnode);80 static std::string printJson(std::shared_ptr<JsonNode> jsonnode);
@@ -88,6 +85,11 @@
88 static void watchingAppStarting(bool rWatching);85 static void watchingAppStarting(bool rWatching);
89 static bool isWatchingAppStarting();86 static bool isWatchingAppStarting();
9087
88 const std::string oomHelper()
89 {
90 return oomHelper_;
91 }
92
91private:93private:
92 Registry* _registry;94 Registry* _registry;
93#if 095#if 0
@@ -101,15 +103,14 @@
101103
102 std::shared_ptr<ZeitgeistLog> zgLog_;104 std::shared_ptr<ZeitgeistLog> zgLog_;
103105
104 std::shared_ptr<GDBusConnection> cgManager_;
105
106 void initCGManager();
107
108 std::unordered_map<std::string, std::shared_ptr<IconFinder>> _iconFinders;106 std::unordered_map<std::string, std::shared_ptr<IconFinder>> _iconFinders;
109107
110 /** Getting the Upstart job path is relatively expensive in108 /** Getting the Upstart job path is relatively expensive in
111 that it requires a DBus call. Worth keeping a cache of. */109 that it requires a DBus call. Worth keeping a cache of. */
112 std::map<std::string, std::string> upstartJobPathCache_;110 std::map<std::string, std::string> upstartJobPathCache_;
111
112 /** Path to the OOM Helper */
113 std::string oomHelper_;
113};114};
114115
115} // namespace app_launch116} // namespace app_launch
116117
=== modified file 'libubuntu-app-launch/registry.cpp'
--- libubuntu-app-launch/registry.cpp 2016-08-26 17:33:34 +0000
+++ libubuntu-app-launch/registry.cpp 2016-11-01 14:53:56 +0000
@@ -47,61 +47,14 @@
47{47{
48}48}
4949
50std::list<std::shared_ptr<Application>> Registry::runningApps(std::shared_ptr<Registry> connection)50std::list<std::shared_ptr<Application>> Registry::runningApps(std::shared_ptr<Registry> registry)
51{51{
52 std::list<std::string> instances;52 if (!registry->impl->jobs)
5353 {
54 /* Get all the legacy instances */54 registry->impl->jobs = jobs::manager::Base::determineFactory(registry);
55 instances.splice(instances.begin(), connection->impl->upstartInstancesForJob("application-legacy"));55 }
56 /* Get all the snap instances */56
57 instances.splice(instances.begin(), connection->impl->upstartInstancesForJob("application-snap"));57 return registry->impl->jobs->runningApps();
58
59 /* Remove the instance ID */
60 std::transform(instances.begin(), instances.end(), instances.begin(), [](std::string &instancename) -> std::string {
61 static const std::regex instanceregex("^(.*)-[0-9]*$");
62 std::smatch match;
63 if (std::regex_match(instancename, match, instanceregex))
64 {
65 return match[1].str();
66 }
67 else
68 {
69 g_warning("Unable to match instance name: %s", instancename.c_str());
70 return {};
71 }
72 });
73
74 /* Deduplicate Set */
75 std::set<std::string> instanceset;
76 for (auto instance : instances)
77 {
78 if (!instance.empty())
79 instanceset.insert(instance);
80 }
81
82 /* Add in the click instances */
83 for (auto instance : connection->impl->upstartInstancesForJob("application-click"))
84 {
85 instanceset.insert(instance);
86 }
87
88 g_debug("Overall there are %d instances: %s", int(instanceset.size()),
89 std::accumulate(instanceset.begin(), instanceset.end(), std::string{},
90 [](const std::string &instr, std::string instance) {
91 return instr.empty() ? instance : instr + ", " + instance;
92 })
93 .c_str());
94
95 /* Convert to Applications */
96 std::list<std::shared_ptr<Application>> apps;
97 for (auto instance : instanceset)
98 {
99 auto appid = AppID::find(connection, instance);
100 auto app = Application::create(appid, connection);
101 apps.push_back(app);
102 }
103
104 return apps;
105}58}
10659
107std::list<std::shared_ptr<Application>> Registry::installedApps(std::shared_ptr<Registry> connection)60std::list<std::shared_ptr<Application>> Registry::installedApps(std::shared_ptr<Registry> connection)
10861
=== modified file 'libubuntu-app-launch/snapd-info.cpp'
--- libubuntu-app-launch/snapd-info.cpp 2016-10-03 23:54:08 +0000
+++ libubuntu-app-launch/snapd-info.cpp 2016-11-01 14:53:56 +0000
@@ -170,7 +170,7 @@
170 }170 }
171 catch (std::runtime_error &e)171 catch (std::runtime_error &e)
172 {172 {
173 g_warning("Unable to get snap information for '%s': %s", package.value().c_str(), e.what());173 g_debug("Unable to get snap information for '%s': %s", package.value().c_str(), e.what());
174 return {};174 return {};
175 }175 }
176}176}
177177
=== modified file 'libubuntu-app-launch/ubuntu-app-launch.cpp'
--- libubuntu-app-launch/ubuntu-app-launch.cpp 2016-09-14 21:58:39 +0000
+++ libubuntu-app-launch/ubuntu-app-launch.cpp 2016-11-01 14:53:56 +0000
@@ -1746,7 +1746,11 @@
1746 /* The exec value */1746 /* The exec value */
1747 gchar * envstr = NULL;1747 gchar * envstr = NULL;
1748 if (demangler) {1748 if (demangler) {
1749 envstr = g_strdup_printf("APP_EXEC=%s %s", DEMANGLER_PATH, execline);1749 const gchar * demangler_path = g_getenv("UBUNTU_APP_LAUNCH_DEMANGLER");
1750 if (demangler_path == nullptr) {
1751 demangler_path = DEMANGLER_PATH;
1752 }
1753 envstr = g_strdup_printf("APP_EXEC=%s %s", demangler_path, execline);
1750 } else {1754 } else {
1751 envstr = g_strdup_printf("APP_EXEC=%s", execline);1755 envstr = g_strdup_printf("APP_EXEC=%s", execline);
1752 }1756 }
17531757
=== modified file 'tests/CMakeLists.txt'
--- tests/CMakeLists.txt 2016-09-14 16:43:36 +0000
+++ tests/CMakeLists.txt 2016-11-01 14:53:56 +0000
@@ -12,23 +12,21 @@
1212
13include_directories(${GTEST_INCLUDE_DIR})13include_directories(${GTEST_INCLUDE_DIR})
1414
15add_library (gtest STATIC15add_subdirectory("/usr/src/gmock" gmock)
16 ${GTEST_SOURCE_DIR}/gtest-all.cc
17 ${GTEST_SOURCE_DIR}/gtest_main.cc)
1816
19# Helper test17# Helper test
2018
21add_executable (helper-test helper-test.cc)19add_executable (helper-test helper-test.cc)
22add_definitions ( -DCMAKE_SOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}" )20add_definitions ( -DCMAKE_SOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}" )
23add_definitions ( -DCMAKE_BINARY_DIR="${CMAKE_CURRENT_BINARY_DIR}" )21add_definitions ( -DCMAKE_BINARY_DIR="${CMAKE_CURRENT_BINARY_DIR}" )
24target_link_libraries (helper-test helpers gtest ${GTEST_LIBS} ${DBUSTEST_LIBRARIES})22target_link_libraries (helper-test helpers gtest_main ${GTEST_LIBS} ${DBUSTEST_LIBRARIES})
2523
26add_test (helper-test helper-test)24add_test (helper-test helper-test)
2725
28# Helper test26# Helper test
2927
30add_executable (helper-handshake-test helper-handshake-test.cc)28add_executable (helper-handshake-test helper-handshake-test.cc)
31target_link_libraries (helper-handshake-test helpers gtest ${GTEST_LIBS})29target_link_libraries (helper-handshake-test helpers gtest_main ${GTEST_LIBS})
3230
33add_test (helper-handshake-test helper-handshake-test)31add_test (helper-handshake-test helper-handshake-test)
3432
@@ -45,13 +43,13 @@
45add_executable (libual-test43add_executable (libual-test
46 libual-test.cc44 libual-test.cc
47 mir-mock.cpp)45 mir-mock.cpp)
48target_link_libraries (libual-test gtest ${GTEST_LIBS} ${LIBUPSTART_LIBRARIES} ${DBUSTEST_LIBRARIES} ubuntu-launcher)46target_link_libraries (libual-test gtest_main ${GTEST_LIBS} ${LIBUPSTART_LIBRARIES} ${DBUSTEST_LIBRARIES} ubuntu-launcher)
4947
50add_executable (libual-cpp-test48add_executable (libual-cpp-test
51 libual-cpp-test.cc49 libual-cpp-test.cc
52 ${CMAKE_SOURCE_DIR}/libubuntu-app-launch/glib-thread.cpp50 ${CMAKE_SOURCE_DIR}/libubuntu-app-launch/glib-thread.cpp
53 mir-mock.cpp)51 mir-mock.cpp)
54target_link_libraries (libual-cpp-test gtest ${GTEST_LIBS} ${LIBUPSTART_LIBRARIES} ${DBUSTEST_LIBRARIES} ubuntu-launcher)52target_link_libraries (libual-cpp-test gtest_main ${GTEST_LIBS} ${LIBUPSTART_LIBRARIES} ${DBUSTEST_LIBRARIES} ubuntu-launcher)
5553
56add_executable (data-spew54add_executable (data-spew
57 data-spew.c)55 data-spew.c)
@@ -63,13 +61,21 @@
63add_test (NAME libual-test COMMAND libual-test)61add_test (NAME libual-test COMMAND libual-test)
64add_test (NAME libual-cpp-test COMMAND libual-cpp-test)62add_test (NAME libual-cpp-test COMMAND libual-cpp-test)
6563
64# Jobs Base Test
65
66add_executable (jobs-base-test
67 jobs-base-test.cpp)
68target_link_libraries (jobs-base-test gmock_main ${GTEST_LIBS} launcher-static)
69
70add_test(NAME jobs-base-test COMMAND jobs-base-test)
71
66# Snapd Info Test72# Snapd Info Test
6773
68if(CURL_FOUND)74if(CURL_FOUND)
69add_definitions ( -DSNAPD_TEST_SOCKET="/tmp/snapd-test-socket" )75add_definitions ( -DSNAPD_TEST_SOCKET="/tmp/snapd-test-socket" )
70add_executable (snapd-info-test76add_executable (snapd-info-test
71 snapd-info-test.cpp)77 snapd-info-test.cpp)
72target_link_libraries (snapd-info-test gtest ${GTEST_LIBS} launcher-static)78target_link_libraries (snapd-info-test gtest_main ${GTEST_LIBS} launcher-static)
73add_test (NAME snapd-info-test COMMAND snapd-info-test)79add_test (NAME snapd-info-test COMMAND snapd-info-test)
74endif()80endif()
7581
@@ -77,7 +83,7 @@
7783
78add_executable (list-apps84add_executable (list-apps
79 list-apps.cpp)85 list-apps.cpp)
80target_link_libraries (list-apps gtest ${GTEST_LIBS} launcher-static)86target_link_libraries (list-apps gtest_main ${GTEST_LIBS} launcher-static)
81add_test (NAME list-apps COMMAND ${CMAKE_CURRENT_BINARY_DIR}/list-apps)87add_test (NAME list-apps COMMAND ${CMAKE_CURRENT_BINARY_DIR}/list-apps)
8288
83# Application Info Desktop89# Application Info Desktop
@@ -85,7 +91,7 @@
85add_executable (application-info-desktop-test91add_executable (application-info-desktop-test
86 application-info-desktop.cpp92 application-info-desktop.cpp
87)93)
88target_link_libraries (application-info-desktop-test gtest ${GTEST_LIBS} launcher-static)94target_link_libraries (application-info-desktop-test gtest_main ${GTEST_LIBS} launcher-static)
8995
90add_test (NAME application-info-desktop-test COMMAND application-info-desktop-test)96add_test (NAME application-info-desktop-test COMMAND application-info-desktop-test)
9197
@@ -97,7 +103,7 @@
97103
98 #sources104 #sources
99 ${CMAKE_SOURCE_DIR}/libubuntu-app-launch/application-icon-finder.cpp)105 ${CMAKE_SOURCE_DIR}/libubuntu-app-launch/application-icon-finder.cpp)
100target_link_libraries (application-icon-finder-test gtest ${GTEST_LIBS} ubuntu-launcher)106target_link_libraries (application-icon-finder-test gtest_main ${GTEST_LIBS} ubuntu-launcher)
101107
102add_test (NAME application-icon-finder-test COMMAND application-icon-finder-test)108add_test (NAME application-icon-finder-test COMMAND application-icon-finder-test)
103109
@@ -109,7 +115,7 @@
109115
110add_executable (failure-test116add_executable (failure-test
111 failure-test.cc)117 failure-test.cc)
112target_link_libraries (failure-test gtest ${GTEST_LIBS} ubuntu-launcher)118target_link_libraries (failure-test gtest_main ${GTEST_LIBS} ubuntu-launcher)
113add_test (failure-test failure-test)119add_test (failure-test failure-test)
114120
115# ZG Test121# ZG Test
@@ -118,7 +124,7 @@
118124
119add_executable (zg-test125add_executable (zg-test
120 zg-test.cc)126 zg-test.cc)
121target_link_libraries (zg-test gtest ${GTEST_LIBS} ${DBUSTEST_LIBRARIES} ${GIO2_LIBRARIES})127target_link_libraries (zg-test gtest_main ${GTEST_LIBS} ${DBUSTEST_LIBRARIES} ${GIO2_LIBRARIES})
122add_test (zg-test zg-test)128add_test (zg-test zg-test)
123129
124# Exec Line Exec Test130# Exec Line Exec Test
@@ -130,7 +136,7 @@
130136
131add_executable (exec-util-test137add_executable (exec-util-test
132 exec-util-test.cc)138 exec-util-test.cc)
133target_link_libraries (exec-util-test gtest ubuntu-launcher ${GTEST_LIBS} ${DBUSTEST_LIBRARIES} ${GIO2_LIBRARIES})139target_link_libraries (exec-util-test gtest_main ubuntu-launcher ${GTEST_LIBS} ${DBUSTEST_LIBRARIES} ${GIO2_LIBRARIES})
134add_test (exec-util-test exec-util-test)140add_test (exec-util-test exec-util-test)
135141
136# CGroup Reap Test142# CGroup Reap Test
@@ -139,7 +145,7 @@
139145
140add_executable (cgroup-reap-test146add_executable (cgroup-reap-test
141 cgroup-reap-test.cc)147 cgroup-reap-test.cc)
142target_link_libraries (cgroup-reap-test gtest ${GTEST_LIBS} ${DBUSTEST_LIBRARIES} ${GIO2_LIBRARIES})148target_link_libraries (cgroup-reap-test gtest_main ${GTEST_LIBS} ${DBUSTEST_LIBRARIES} ${GIO2_LIBRARIES})
143add_test (cgroup-reap-test cgroup-reap-test)149add_test (cgroup-reap-test cgroup-reap-test)
144150
145# Desktop Hook Test151# Desktop Hook Test
@@ -161,6 +167,7 @@
161 libual-cpp-test.cc167 libual-cpp-test.cc
162 list-apps.cpp168 list-apps.cpp
163 eventually-fixture.h169 eventually-fixture.h
170 jobs-base-test.cpp
164 snapd-info-test.cpp171 snapd-info-test.cpp
165 snapd-mock.h172 snapd-mock.h
166 zg-test.cc173 zg-test.cc
167174
=== modified file 'tests/exec-util-test.cc'
--- tests/exec-util-test.cc 2016-09-15 17:13:04 +0000
+++ tests/exec-util-test.cc 2016-11-01 14:53:56 +0000
@@ -45,6 +45,7 @@
45 g_setenv("XDG_DATA_HOME", CMAKE_SOURCE_DIR "/libertine-home", TRUE);45 g_setenv("XDG_DATA_HOME", CMAKE_SOURCE_DIR "/libertine-home", TRUE);
46 g_setenv("UBUNTU_APP_LAUNCH_LIBERTINE_LAUNCH", "libertine-launch", TRUE);46 g_setenv("UBUNTU_APP_LAUNCH_LIBERTINE_LAUNCH", "libertine-launch", TRUE);
47 g_setenv("UBUNTU_APP_LAUNCH_SNAPD_SOCKET", "/this/should/not/exist", TRUE);47 g_setenv("UBUNTU_APP_LAUNCH_SNAPD_SOCKET", "/this/should/not/exist", TRUE);
48 g_setenv("UBUNTU_APP_LAUNCH_SYSTEMD_PATH", "/this/should/not/exist", TRUE);
4849
49 service = dbus_test_service_new(NULL);50 service = dbus_test_service_new(NULL);
5051
@@ -184,12 +185,15 @@
184 EXPECT_STREQ("grep", value); }},185 EXPECT_STREQ("grep", value); }},
185 {"APP_ID", [](const gchar * value) {186 {"APP_ID", [](const gchar * value) {
186 EXPECT_STREQ("com.test.good_application_1.2.3", value); }},187 EXPECT_STREQ("com.test.good_application_1.2.3", value); }},
188 {"APP_EXEC_POLICY", [](const gchar * value) {
189 EXPECT_STREQ("com.test.good_application_1.2.3", value); }},
187 {"APP_LAUNCHER_PID", [](const gchar * value) {190 {"APP_LAUNCHER_PID", [](const gchar * value) {
188 EXPECT_EQ(getpid(), atoi(value)); }},191 EXPECT_EQ(getpid(), atoi(value)); }},
189 {"APP_DESKTOP_FILE_PATH", [](const gchar * value) {192 {"APP_DESKTOP_FILE_PATH", [](const gchar * value) {
190 EXPECT_STREQ(APP_DIR "/application.desktop", value); }},193 EXPECT_STREQ(APP_DIR "/application.desktop", value); }},
191 {"APP_XMIR_ENABLE", [](const gchar * value) {194 {"APP_XMIR_ENABLE", [](const gchar * value) {
192 EXPECT_STREQ("0", value); }},195 EXPECT_STREQ("0", value); }},
196 {"QML2_IMPORT_PATH", nocheck},
193 });197 });
194198
195#undef APP_DIR199#undef APP_DIR
@@ -267,10 +271,13 @@
267 {"APP_EXEC", nocheck},271 {"APP_EXEC", nocheck},
268 {"APP_ID", [](const gchar * value) {272 {"APP_ID", [](const gchar * value) {
269 EXPECT_STREQ("com.test.mir_mir_1", value); }},273 EXPECT_STREQ("com.test.mir_mir_1", value); }},
274 {"APP_EXEC_POLICY", [](const gchar * value) {
275 EXPECT_STREQ("com.test.mir_mir_1", value); }},
270 {"APP_LAUNCHER_PID", nocheck},276 {"APP_LAUNCHER_PID", nocheck},
271 {"APP_DESKTOP_FILE_PATH", nocheck},277 {"APP_DESKTOP_FILE_PATH", nocheck},
272 {"APP_XMIR_ENABLE", [](const gchar * value) {278 {"APP_XMIR_ENABLE", [](const gchar * value) {
273 EXPECT_STREQ("1", value); }},279 EXPECT_STREQ("1", value); }},
280 {"QML2_IMPORT_PATH", nocheck},
274 });281 });
275}282}
276283
@@ -289,10 +296,13 @@
289 {"APP_EXEC", nocheck},296 {"APP_EXEC", nocheck},
290 {"APP_ID", [](const gchar * value) {297 {"APP_ID", [](const gchar * value) {
291 EXPECT_STREQ("com.test.mir_nomir_1", value); }},298 EXPECT_STREQ("com.test.mir_nomir_1", value); }},
299 {"APP_EXEC_POLICY", [](const gchar * value) {
300 EXPECT_STREQ("com.test.mir_nomir_1", value); }},
292 {"APP_LAUNCHER_PID", nocheck},301 {"APP_LAUNCHER_PID", nocheck},
293 {"APP_DESKTOP_FILE_PATH", nocheck},302 {"APP_DESKTOP_FILE_PATH", nocheck},
294 {"APP_XMIR_ENABLE", [](const gchar * value) {303 {"APP_XMIR_ENABLE", [](const gchar * value) {
295 EXPECT_STREQ("0", value); }},304 EXPECT_STREQ("0", value); }},
305 {"QML2_IMPORT_PATH", nocheck},
296 });306 });
297}307}
298308
299309
=== added file 'tests/jobs-base-test.cpp'
--- tests/jobs-base-test.cpp 1970-01-01 00:00:00 +0000
+++ tests/jobs-base-test.cpp 2016-11-01 14:53:56 +0000
@@ -0,0 +1,364 @@
1/*
2 * Copyright © 2016 Canonical Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License version 3, as published
6 * by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranties of
10 * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11 * PURPOSE. See the GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License along
14 * with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authors:
17 * Ted Gould <ted.gould@canonical.com>
18 */
19
20#include "jobs-base.h"
21#include "appid.h"
22#include "registry-impl.h"
23#include "registry.h"
24
25#include "eventually-fixture.h"
26#include <gmock/gmock.h>
27#include <gtest/gtest.h>
28
29class RegistryImplMock : public ubuntu::app_launch::Registry::Impl
30{
31public:
32 RegistryImplMock(ubuntu::app_launch::Registry* reg)
33 : ubuntu::app_launch::Registry::Impl(reg)
34 {
35 }
36
37 MOCK_METHOD2(zgSendEvent, void(ubuntu::app_launch::AppID, const std::string& eventtype));
38};
39
40class RegistryMock : public ubuntu::app_launch::Registry
41{
42public:
43 RegistryMock()
44 {
45 impl = std::unique_ptr<RegistryImplMock>(new RegistryImplMock(this));
46 }
47};
48
49class instanceMock : public ubuntu::app_launch::jobs::instance::Base
50{
51public:
52 instanceMock(const ubuntu::app_launch::AppID& appId,
53 const std::string& job,
54 const std::string& instance,
55 const std::vector<ubuntu::app_launch::Application::URL>& urls,
56 const std::shared_ptr<ubuntu::app_launch::Registry>& registry)
57 : ubuntu::app_launch::jobs::instance::Base(appId, job, instance, urls, registry)
58 {
59 }
60
61 MOCK_METHOD0(primaryPid, pid_t());
62 MOCK_METHOD0(logPath, std::string());
63 MOCK_METHOD0(pids, std::vector<pid_t>());
64
65 MOCK_METHOD0(stop, void());
66};
67
68class SpewMaster
69{
70public:
71 SpewMaster()
72 : thread(
73 [this]() {
74 gint spewstdout = 0;
75 std::array<const gchar*, 2> spewline{SPEW_UTILITY, nullptr};
76 ASSERT_TRUE(g_spawn_async_with_pipes(NULL, /* directory */
77 (char**)spewline.data(), /* command line */
78 NULL, /* environment */
79 G_SPAWN_DEFAULT, /* flags */
80 NULL, /* child setup */
81 NULL, /* child setup */
82 &pid_, /* pid */
83 NULL, /* stdin */
84 &spewstdout, /* stdout */
85 NULL, /* stderr */
86 NULL)); /* error */
87
88 spewoutchan = g_io_channel_unix_new(spewstdout);
89 g_io_channel_set_flags(spewoutchan, G_IO_FLAG_NONBLOCK, NULL);
90
91 iosource = g_io_create_watch(spewoutchan, G_IO_IN);
92 g_source_set_callback(iosource, (GSourceFunc)datain, this, nullptr);
93 g_source_attach(iosource, g_main_context_get_thread_default());
94
95 /* Setup our OOM adjust file */
96 gchar* procdir = g_strdup_printf(CMAKE_BINARY_DIR "/jobs-base-proc/%d", pid_);
97 ASSERT_EQ(0, g_mkdir_with_parents(procdir, 0700));
98 oomadjfile = g_strdup_printf("%s/oom_score_adj", procdir);
99 g_free(procdir);
100 ASSERT_TRUE(g_file_set_contents(oomadjfile, "0", -1, NULL));
101 },
102 [this]() {
103 /* Clean up */
104 gchar* killstr = g_strdup_printf("kill -9 %d", pid_);
105 ASSERT_TRUE(g_spawn_command_line_sync(killstr, NULL, NULL, NULL, NULL));
106 g_free(killstr);
107
108 g_source_destroy(iosource);
109 g_io_channel_unref(spewoutchan);
110 g_clear_pointer(&oomadjfile, g_free);
111 })
112 {
113 datacnt_ = 0;
114 }
115
116 ~SpewMaster()
117 {
118 }
119
120 std::string oomScore()
121 {
122 gchar* oomvalue = nullptr;
123 g_file_get_contents(oomadjfile, &oomvalue, nullptr, nullptr);
124 if (oomvalue != nullptr)
125 {
126 return std::string(oomvalue);
127 }
128 else
129 {
130 return {};
131 }
132 }
133
134 GPid pid()
135 {
136 return pid_;
137 }
138
139 gsize dataCnt()
140 {
141 g_debug("Data Count for %d: %d", pid_, int(datacnt_));
142 return datacnt_;
143 }
144
145 void reset()
146 {
147 bool endofqueue = thread.executeOnThread<bool>([this]() {
148 while (G_IO_STATUS_AGAIN == g_io_channel_flush(spewoutchan, nullptr))
149 ;
150 return true; /* the main loop has processed */
151 });
152 g_debug("Reset %d", pid_);
153 if (endofqueue)
154 datacnt_ = 0;
155 else
156 g_warning("Unable to clear mainloop on reset");
157 }
158
159 std::atomic<gsize> datacnt_;
160
161private:
162 GPid pid_ = 0;
163 gchar* oomadjfile = nullptr;
164 GIOChannel* spewoutchan = nullptr;
165 GSource* iosource = nullptr;
166 GLib::ContextThread thread;
167
168 static gboolean datain(GIOChannel* source, GIOCondition cond, gpointer data)
169 {
170 auto spew = static_cast<SpewMaster*>(data);
171 gchar* str = NULL;
172 gsize len = 0;
173 GError* error = NULL;
174
175 g_io_channel_read_line(source, &str, &len, NULL, &error);
176 g_free(str);
177
178 if (error != NULL)
179 {
180 g_warning("Unable to read from channel: %s", error->message);
181 g_error_free(error);
182 }
183
184 spew->datacnt_ += len;
185
186 return TRUE;
187 }
188};
189
190class JobBaseTest : public EventuallyFixture
191{
192protected:
193 std::shared_ptr<RegistryMock> registry;
194
195 virtual void SetUp()
196 {
197 registry = std::make_shared<RegistryMock>();
198 }
199
200 virtual void TearDown()
201 {
202 registry.reset();
203 }
204
205 ubuntu::app_launch::AppID simpleAppID()
206 {
207 return {ubuntu::app_launch::AppID::Package::from_raw("package"),
208 ubuntu::app_launch::AppID::AppName::from_raw("appname"),
209 ubuntu::app_launch::AppID::Version::from_raw("version")};
210 }
211
212 std::shared_ptr<instanceMock> simpleInstance()
213 {
214 return std::make_shared<instanceMock>(simpleAppID(), "application-job", "1234567890",
215 std::vector<ubuntu::app_launch::Application::URL>{}, registry);
216 }
217};
218
219TEST_F(JobBaseTest, InitTest)
220{
221 auto instance = simpleInstance();
222
223 instance.reset();
224}
225
226TEST_F(JobBaseTest, isRunning)
227{
228 auto instance = simpleInstance();
229
230 EXPECT_CALL(*instance, primaryPid()).WillOnce(testing::Return(0));
231
232 EXPECT_FALSE(instance->isRunning());
233
234 EXPECT_CALL(*instance, primaryPid()).WillOnce(testing::Return(100));
235
236 EXPECT_TRUE(instance->isRunning());
237}
238
239TEST_F(JobBaseTest, pauseResume)
240{
241 g_setenv("UBUNTU_APP_LAUNCH_OOM_PROC_PATH", CMAKE_BINARY_DIR "/jobs-base-proc", TRUE);
242
243 /* Setup some spew */
244 SpewMaster spew;
245 std::vector<pid_t> pids{spew.pid()};
246
247 /* Build our instance */
248 auto instance = simpleInstance();
249 EXPECT_CALL(*instance, pids()).WillRepeatedly(testing::Return(pids));
250
251 /* Setup registry */
252 EXPECT_CALL(dynamic_cast<RegistryImplMock&>(*registry->impl), zgSendEvent(simpleAppID(), ZEITGEIST_ZG_LEAVE_EVENT))
253 .WillOnce(testing::Return());
254
255 /* Make sure it is running */
256 EXPECT_EVENTUALLY_NE(0, spew.datacnt_);
257
258 /*** Do Pause ***/
259 instance->pause();
260
261 spew.reset();
262 pause(100); // give spew a chance to send data if it is running
263
264 EXPECT_EQ(0, spew.dataCnt());
265
266 EXPECT_EQ(std::to_string(int(ubuntu::app_launch::oom::paused())), spew.oomScore());
267
268 /* Setup for Resume */
269 EXPECT_CALL(dynamic_cast<RegistryImplMock&>(*registry->impl), zgSendEvent(simpleAppID(), ZEITGEIST_ZG_ACCESS_EVENT))
270 .WillOnce(testing::Return());
271
272 spew.reset();
273 EXPECT_EQ(0, spew.dataCnt());
274
275 /*** Do Resume ***/
276 instance->resume();
277
278 EXPECT_EVENTUALLY_NE(0, spew.datacnt_);
279
280 EXPECT_EQ(std::to_string(int(ubuntu::app_launch::oom::focused())), spew.oomScore());
281}
282
283TEST_F(JobBaseTest, pauseResumeNone)
284{
285 std::vector<pid_t> pids{};
286
287 /* Build our instance */
288 auto instance = simpleInstance();
289 EXPECT_CALL(*instance, pids()).WillRepeatedly(testing::Return(pids));
290
291 /* Setup registry */
292 EXPECT_CALL(dynamic_cast<RegistryImplMock&>(*registry->impl), zgSendEvent(simpleAppID(), ZEITGEIST_ZG_LEAVE_EVENT))
293 .WillOnce(testing::Return());
294
295 /*** Do Pause ***/
296 instance->pause();
297
298 /* Setup for Resume */
299 EXPECT_CALL(dynamic_cast<RegistryImplMock&>(*registry->impl), zgSendEvent(simpleAppID(), ZEITGEIST_ZG_ACCESS_EVENT))
300 .WillOnce(testing::Return());
301
302 /*** Do Resume ***/
303 instance->resume();
304}
305
306TEST_F(JobBaseTest, pauseResumeMany)
307{
308 g_setenv("UBUNTU_APP_LAUNCH_OOM_PROC_PATH", CMAKE_BINARY_DIR "/jobs-base-proc", TRUE);
309
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches