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