Merge lp:~jamesodhunt/upstart/upstart-shutdown into lp:upstart

Proposed by James Hunt
Status: Merged
Merged at revision: 1433
Proposed branch: lp:~jamesodhunt/upstart/upstart-shutdown
Merge into: lp:upstart
Diff against target: 2291 lines (+1581/-107) (has conflicts)
22 files modified
ChangeLog (+77/-0)
dbus/com.ubuntu.Upstart.xml (+2/-0)
init/Makefile.am (+17/-15)
init/conf.c (+11/-0)
init/conf.h (+1/-0)
init/control.c (+51/-3)
init/control.h (+1/-0)
init/events.h (+7/-0)
init/job_class.c (+30/-0)
init/job_class.h (+3/-0)
init/job_process.c (+60/-1)
init/job_process.h (+4/-1)
init/main.c (+25/-21)
init/man/init.8 (+19/-2)
init/man/session-end.7 (+34/-0)
init/man/startup.7 (+8/-1)
init/quiesce.c (+248/-0)
init/quiesce.h (+76/-0)
init/session.c (+12/-0)
init/session.h (+1/-0)
util/initctl.c (+1/-1)
util/tests/test_initctl.c (+893/-62)
Text conflict in ChangeLog
Text conflict in init/job_process.c
To merge this branch: bzr merge lp:~jamesodhunt/upstart/upstart-shutdown
Reviewer Review Type Date Requested Status
Stéphane Graber (community) Approve
Upstart Reviewers Pending
Review via email: mp+148676@code.launchpad.net

Description of the change

This branch provides the Session Init shutdown facility.

= Session Init Shutdown as Result of Impending System Shutdown =

Sending SIGTERM to a Session Init is indicative of a system shutdown. In this scenario the Session Init will emit the 'session-end' event and immediately request all jobs stop.

The Session Init will (attempt) to wait for up to the maximum kill_timeout value specified in any running jobs .conf file (5 seconds by default) before they are forcibly killed (although note that system policy may not allow the Session Init to wait as long as it wishes).

= Session Init Shutdown as Result of Logout Request =

The branch also introduces an 'EndSession' D-Bus method which is designed to be called when the user wishes their session to be ended due to a 'logout' request.

In this scenario, the Session Init will emit the 'session-end' event and wait for up to 5 seconds for *new* jobs to react to this event. If no new jobs start or after 5 seconds, the final shutdown phase is entered where all running jobs are requested to stop and after the maximum kill_timeout value specified in any running jobs .conf file is reached (5 seconds by default), the Session Init will exit.

To post a comment you must log in.
Revision history for this message
Stéphane Graber (stgraber) wrote :

I'm using this code in my PPA and I can confirm that session logout works with it. In my case I'm not using the POSIX signals to trigger a shutdown but calling EndSession directly over DBus.

I haven't spent any particular time testing the timeouts and the shutdown signal as in my case everything is part of gnome-session and so is stopping when it does. However I had a quick look and I clearly see the signal being emitted, so that part looks good too.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'ChangeLog'
2--- ChangeLog 2013-02-08 16:55:25 +0000
3+++ ChangeLog 2013-02-15 11:31:39 +0000
4@@ -1,3 +1,4 @@
5+<<<<<<< TREE
6 2013-02-08 James Hunt <james.hunt@ubuntu.com>
7
8 * init/job_process.c: job_process_run(): Copy parent environment if
9@@ -5,6 +6,82 @@
10 * init/main.c: Added 'inherit-env' command-line option.
11 * init/man/init.8: Documented --inherit-env.
12
13+=======
14+2013-02-15 James Hunt <james.hunt@ubuntu.com>
15+
16+ * util/tests/test_initctl.c:
17+ - timed_waitpid(): Back off if no status change.
18+ - test_quiesce():
19+ - Set XDG_RUNTIME_DIR to a temporary value.
20+ - Remove stale session files.
21+ - test_job_env(): Remove stale session files.
22+
23+2013-02-14 James Hunt <james.hunt@ubuntu.com>
24+
25+ * util/tests/test_initctl.c:
26+ - wait_for_upstart(): Functional replacement of WAIT_FOR_UPSTART()
27+ macro. Now accepts @user.
28+ - set_upstart_session(): Poll to ensure we give Upstart time to
29+ initialise and write the session file.
30+ - _start_upstart(): Extra @user parameter.
31+
32+2013-02-14 James Hunt <james.hunt@ubuntu.com>
33+
34+ * init/Makefile.am: Added quiesce.o, now required by control.o.
35+ * init/main.c: main():
36+ - SIGHUP+SIGUSR1 handling now applies however you run init (since
37+ it should react to these signals when run as a Session Init).
38+ - Qualified sessions message to avoid confusion.
39+ * util/initctl.c: upstart_open(): Better handling for user_mode.
40+ * util/tests/test_initctl.c:
41+ - WAIT_FOR_UPSTART(): Made session-aware.
42+ - KILL_UPSTART(): Reset user mode flag (taken from STOP_UPSTART()).
43+ - set_upstart_session(): New function.
44+ - self_pipe_write(): New Function.
45+ - self_pipe_setup(): New Function.
46+ - timed_waitpid(): New function.
47+ - _start_upstart():
48+ - Signal handling and extra checks.
49+ - Discard init output (unless UPSTART_TEST_VERBOSE set)
50+ for saner logs.
51+ - test_list_sessions():
52+ - Removed need for a dbus-daemon.
53+ - Added required initctl reset.
54+ - test_quiesce(): Tests for Session Init shutdown (both
55+ system-initiated and end-session request).
56+ - test_usage(): Added extra checks and tidyup.
57+ - main(): Added call to test_quiesce().
58+
59+2013-02-08 James Hunt <james.hunt@ubuntu.com>
60+
61+ * dbus/com.ubuntu.Upstart.xml: Added 'EndSession' method.
62+ * init/Makefile.am: Updated for quiesce.[ch].
63+ * init/conf.c: conf_destroy(): Cleanup function.
64+ * init/conf.h: Prototype.
65+ * init/control.c: control_end_session(): 'EndSession' implemenation.
66+ * init/control.h: Include.
67+ * init/events.h: Added SESSION_END_EVENT.
68+ * init/job_class.c: job_class_max_kill_timeout(): New function.
69+ * init/job_class.h: Prototype.
70+ * init/job_process.c:
71+ - Added disable_respawn to disallow respawns.
72+ - job_process_jobs_running(): New function.
73+ - job_process_stop_all(): New function.
74+ - job_process_terminated(): Honours disable_respawn.
75+ * init/job_process.h: Prototypes.
76+ * init/main.c:
77+ - Typos.
78+ - term_handler(): Quiesce rather than re-exec on receipt of SIGTERM
79+ when running as a Session Init.
80+ - main(): Make quiesce() handle cleanup.
81+ * init/man/init.8: Update for Session Init SIGTERM handling.
82+ * init/man/startup.7: Update for Session Init.
83+ * init/session.c: session_destroy(): New function.
84+ * init/session.h: Prototype.
85+ * init/man/session-end.7: New man page.
86+ * init/quiesce.[ch]: New files.
87+
88+>>>>>>> MERGE-SOURCE
89 2013-02-02 James Hunt <james.hunt@ubuntu.com>
90
91 * util/initctl.c: Remove ability to specify explicitly job and/or job
92
93=== modified file 'dbus/com.ubuntu.Upstart.xml'
94--- dbus/com.ubuntu.Upstart.xml 2013-01-25 20:08:49 +0000
95+++ dbus/com.ubuntu.Upstart.xml 2013-02-15 11:31:39 +0000
96@@ -103,6 +103,8 @@
97 <method name="NotifyDiskWriteable">
98 </method>
99
100+ <method name="EndSession"/>
101+
102 <!-- Basic information about Upstart -->
103 <property name="version" type="s" access="read" />
104 <property name="log_priority" type="s" access="readwrite" />
105
106=== modified file 'init/Makefile.am'
107--- init/Makefile.am 2013-01-21 16:39:28 +0000
108+++ init/Makefile.am 2013-02-15 11:31:39 +0000
109@@ -31,7 +31,8 @@
110 man/keyboard-request.7 \
111 man/power-status-changed.7 \
112 man/upstart.7 \
113- man/inittab.5
114+ man/inittab.5 \
115+ man/session-end.7
116
117
118 sbin_PROGRAMS = init
119@@ -57,6 +58,7 @@
120 conf.c conf.h \
121 control.c control.h \
122 xdg.c xdg.h \
123+ quiesce.c quiesce.h \
124 errors.h
125 nodist_init_SOURCES = \
126 $(com_ubuntu_Upstart_OUTPUTS) \
127@@ -176,7 +178,7 @@
128 test_process_LDADD = \
129 system.o environ.o process.o \
130 job_class.o job_process.o job.o event.o event_operator.o blocked.o \
131- parse_job.o parse_conf.o conf.o control.o \
132+ parse_job.o parse_conf.o conf.o control.o quiesce.o \
133 session.o log.o state.o xdg.o \
134 com.ubuntu.Upstart.o \
135 com.ubuntu.Upstart.Job.o com.ubuntu.Upstart.Instance.o \
136@@ -190,7 +192,7 @@
137 test_job_class_LDADD = \
138 system.o environ.o process.o \
139 job_class.o job_process.o job.o event.o event_operator.o blocked.o \
140- parse_job.o parse_conf.o conf.o control.o \
141+ parse_job.o parse_conf.o conf.o control.o quiesce.o \
142 session.o log.o state.o xdg.o \
143 com.ubuntu.Upstart.o \
144 com.ubuntu.Upstart.Job.o com.ubuntu.Upstart.Instance.o \
145@@ -204,7 +206,7 @@
146 test_job_process_LDADD = \
147 system.o environ.o process.o \
148 job_class.o job_process.o job.o event.o event_operator.o blocked.o \
149- parse_job.o parse_conf.o conf.o control.o \
150+ parse_job.o parse_conf.o conf.o control.o quiesce.o \
151 session.o log.o state.o xdg.o \
152 com.ubuntu.Upstart.o \
153 com.ubuntu.Upstart.Job.o com.ubuntu.Upstart.Instance.o \
154@@ -218,7 +220,7 @@
155 test_job_LDADD = \
156 system.o environ.o process.o \
157 job_class.o job_process.o job.o event.o event_operator.o blocked.o \
158- parse_job.o parse_conf.o conf.o control.o \
159+ parse_job.o parse_conf.o conf.o control.o quiesce.o \
160 session.o log.o state.o xdg.o \
161 com.ubuntu.Upstart.o \
162 com.ubuntu.Upstart.Job.o com.ubuntu.Upstart.Instance.o \
163@@ -232,7 +234,7 @@
164 test_log_LDADD = \
165 system.o environ.o process.o \
166 job_class.o job_process.o job.o event.o event_operator.o blocked.o \
167- parse_job.o parse_conf.o conf.o control.o \
168+ parse_job.o parse_conf.o conf.o control.o quiesce.o \
169 session.o log.o state.o xdg.o \
170 com.ubuntu.Upstart.o \
171 com.ubuntu.Upstart.Job.o com.ubuntu.Upstart.Instance.o \
172@@ -246,7 +248,7 @@
173 test_state_LDADD = \
174 system.o environ.o process.o \
175 job_class.o job_process.o job.o event.o event_operator.o blocked.o \
176- parse_job.o parse_conf.o conf.o control.o \
177+ parse_job.o parse_conf.o conf.o control.o quiesce.o \
178 session.o log.o state.o xdg.o \
179 com.ubuntu.Upstart.o \
180 com.ubuntu.Upstart.Job.o com.ubuntu.Upstart.Instance.o \
181@@ -260,7 +262,7 @@
182 test_event_LDADD = \
183 system.o environ.o process.o \
184 job_class.o job_process.o job.o event.o event_operator.o blocked.o \
185- parse_job.o parse_conf.o conf.o control.o \
186+ parse_job.o parse_conf.o conf.o control.o quiesce.o \
187 session.o log.o state.o xdg.o \
188 com.ubuntu.Upstart.o \
189 com.ubuntu.Upstart.Job.o com.ubuntu.Upstart.Instance.o \
190@@ -274,7 +276,7 @@
191 test_event_operator_LDADD = \
192 system.o environ.o process.o \
193 job_class.o job_process.o job.o event.o event_operator.o blocked.o \
194- parse_job.o parse_conf.o conf.o control.o \
195+ parse_job.o parse_conf.o conf.o control.o quiesce.o \
196 session.o log.o state.o xdg.o \
197 com.ubuntu.Upstart.o \
198 com.ubuntu.Upstart.Job.o com.ubuntu.Upstart.Instance.o \
199@@ -288,7 +290,7 @@
200 test_blocked_LDADD = \
201 system.o environ.o process.o \
202 job_class.o job_process.o job.o event.o event_operator.o blocked.o \
203- parse_job.o parse_conf.o conf.o control.o \
204+ parse_job.o parse_conf.o conf.o control.o quiesce.o \
205 session.o log.o state.o xdg.o \
206 com.ubuntu.Upstart.o \
207 com.ubuntu.Upstart.Job.o com.ubuntu.Upstart.Instance.o \
208@@ -302,7 +304,7 @@
209 test_parse_job_LDADD = \
210 system.o environ.o process.o \
211 job_class.o job_process.o job.o event.o event_operator.o blocked.o \
212- parse_job.o parse_conf.o conf.o control.o \
213+ parse_job.o parse_conf.o conf.o control.o quiesce.o \
214 session.o log.o state.o xdg.o \
215 com.ubuntu.Upstart.o \
216 com.ubuntu.Upstart.Job.o com.ubuntu.Upstart.Instance.o \
217@@ -316,7 +318,7 @@
218 test_parse_conf_LDADD = \
219 system.o environ.o process.o \
220 job_class.o job_process.o job.o event.o event_operator.o blocked.o \
221- parse_job.o parse_conf.o conf.o control.o \
222+ parse_job.o parse_conf.o conf.o control.o quiesce.o \
223 session.o log.o state.o xdg.o \
224 com.ubuntu.Upstart.o \
225 com.ubuntu.Upstart.Job.o com.ubuntu.Upstart.Instance.o \
226@@ -330,7 +332,7 @@
227 test_conf_LDADD = \
228 system.o environ.o process.o \
229 job_class.o job_process.o job.o event.o event_operator.o blocked.o \
230- parse_job.o parse_conf.o conf.o control.o \
231+ parse_job.o parse_conf.o conf.o control.o quiesce.o \
232 session.o log.o state.o xdg.o \
233 com.ubuntu.Upstart.o \
234 com.ubuntu.Upstart.Job.o com.ubuntu.Upstart.Instance.o \
235@@ -344,7 +346,7 @@
236 test_conf_static_LDADD = \
237 system.o environ.o process.o \
238 job_class.o job_process.o job.o event.o event_operator.o blocked.o \
239- parse_job.o parse_conf.o control.o \
240+ parse_job.o parse_conf.o control.o quiesce.o \
241 session.o log.o state.o xdg.o \
242 com.ubuntu.Upstart.o \
243 com.ubuntu.Upstart.Job.o com.ubuntu.Upstart.Instance.o \
244@@ -365,7 +367,7 @@
245 test_control_LDADD = \
246 system.o environ.o process.o \
247 job_class.o job_process.o job.o event.o event_operator.o blocked.o \
248- parse_job.o parse_conf.o conf.o control.o \
249+ parse_job.o parse_conf.o conf.o control.o quiesce.o \
250 session.o log.o state.o xdg.o \
251 com.ubuntu.Upstart.o \
252 com.ubuntu.Upstart.Job.o com.ubuntu.Upstart.Instance.o \
253
254=== modified file 'init/conf.c'
255--- init/conf.c 2013-01-08 16:15:46 +0000
256+++ init/conf.c 2013-02-15 11:31:39 +0000
257@@ -321,6 +321,17 @@
258 conf_sources = NIH_MUST (nih_list_new (NULL));
259 }
260
261+/**
262+ * conf_destroy:
263+ *
264+ * Clear: the conf_sources list.
265+ **/
266+void
267+conf_destroy (void)
268+{
269+ if (conf_sources)
270+ nih_free (conf_sources);
271+}
272
273 /**
274 * conf_source_new:
275
276=== modified file 'init/conf.h'
277--- init/conf.h 2012-12-19 12:46:46 +0000
278+++ init/conf.h 2013-02-15 11:31:39 +0000
279@@ -112,6 +112,7 @@
280
281
282 void conf_init (void);
283+void conf_destroy (void);
284
285 ConfSource *conf_source_new (const void *parent, const char *path,
286 ConfSourceType type)
287
288=== modified file 'init/control.c'
289--- init/control.c 2013-01-31 17:23:55 +0000
290+++ init/control.c 2013-02-15 11:31:39 +0000
291@@ -57,6 +57,7 @@
292 #include "errors.h"
293 #include "state.h"
294 #include "event.h"
295+#include "events.h"
296 #include "paths.h"
297 #include "xdg.h"
298
299@@ -115,9 +116,9 @@
300 NihList *control_conns = NULL;
301
302 /* External definitions */
303-extern int user_mode;
304-
305-extern char *session_file;
306+extern int user_mode;
307+extern int disable_respawn;
308+extern char *session_file;
309
310 /**
311 * control_init:
312@@ -1744,3 +1745,50 @@
313 if (session_file)
314 (void)unlink (session_file);
315 }
316+
317+/**
318+ * control_session_end:
319+ *
320+ * @data: not used,
321+ * @message: D-Bus connection and message received.
322+ *
323+ * Implements the EndSession method of the com.ubuntu.Upstart
324+ * interface.
325+ *
326+ * Called to request that Upstart stop all jobs and exit. Only
327+ * appropriate when running as a Session Init and user wishes to
328+ * 'logout'.
329+ *
330+ * Returns: zero on success, negative value on raised error.
331+ **/
332+int
333+control_end_session (void *data,
334+ NihDBusMessage *message)
335+{
336+ Session *session;
337+
338+ nih_assert (message);
339+
340+ /* Not supported at the system level */
341+ if (getpid () == 1)
342+ return 0;
343+
344+ if (! control_check_permission (message)) {
345+ nih_dbus_error_raise_printf (
346+ DBUS_INTERFACE_UPSTART ".Error.PermissionDenied",
347+ _("You do not have permission to end session"));
348+ return -1;
349+ }
350+
351+ /* Get the relevant session */
352+ session = session_from_dbus (NULL, message);
353+
354+ if (session && session->chroot) {
355+ nih_warn (_("Ignoring session end request from chroot session"));
356+ return 0;
357+ }
358+
359+ quiesce (QUIESCE_REQUESTER_SESSION);
360+
361+ return 0;
362+}
363
364=== modified file 'init/control.h'
365--- init/control.h 2013-01-31 17:23:55 +0000
366+++ init/control.h 2013-02-15 11:31:39 +0000
367@@ -31,6 +31,7 @@
368 #include <json.h>
369
370 #include "event.h"
371+#include "quiesce.h"
372
373 /**
374 * USE_SESSION_BUS_ENV:
375
376=== modified file 'init/events.h'
377--- init/events.h 2009-06-23 09:29:35 +0000
378+++ init/events.h 2013-02-15 11:31:39 +0000
379@@ -31,6 +31,13 @@
380 #define STARTUP_EVENT "debug"
381 #endif
382
383+/**
384+ * SESSION_END_EVENT:
385+ *
386+ * Name of the event Upstart emits to denote a Session Init
387+ * is shutting down.
388+ **/
389+#define SESSION_END_EVENT "session-end"
390
391 /**
392 * CTRLALTDEL_EVENT:
393
394=== modified file 'init/job_class.c'
395--- init/job_class.c 2013-01-31 17:23:55 +0000
396+++ init/job_class.c 2013-02-15 11:31:39 +0000
397@@ -2344,3 +2344,33 @@
398
399 return class;
400 }
401+
402+/**
403+ * job_class_max_kill_timeout:
404+ *
405+ * Determine maximum kill timeout for all running jobs.
406+ *
407+ * Returns: Maximum kill timeout (seconds).
408+ **/
409+time_t
410+job_class_max_kill_timeout (void)
411+{
412+ time_t kill_timeout = JOB_DEFAULT_KILL_TIMEOUT;
413+
414+ job_class_init ();
415+
416+ NIH_HASH_FOREACH (job_classes, iter) {
417+ JobClass *class = (JobClass *)iter;
418+
419+ NIH_HASH_FOREACH (class->instances, job_iter) {
420+ Job *job = (Job *)job_iter;
421+
422+ if (job->class->kill_timeout > kill_timeout) {
423+ kill_timeout = job->class->kill_timeout;
424+ break;
425+ }
426+ }
427+ }
428+
429+ return kill_timeout;
430+}
431
432=== modified file 'init/job_class.h'
433--- init/job_class.h 2013-01-25 20:08:49 +0000
434+++ init/job_class.h 2013-02-15 11:31:39 +0000
435@@ -359,6 +359,9 @@
436 JobClass * job_class_find (const Session *session, const char *name)
437 __attribute__ ((warn_unused_result));
438
439+time_t job_class_max_kill_timeout (void)
440+ __attribute__ ((warn_unused_result));
441+
442 NIH_END_EXTERN
443
444 #endif /* INIT_JOB_CLASS_H */
445
446=== modified file 'init/job_process.c'
447--- init/job_process.c 2013-02-14 02:54:10 +0000
448+++ init/job_process.c 2013-02-15 11:31:39 +0000
449@@ -99,6 +99,13 @@
450 **/
451 char *log_dir = NULL;
452
453+/**
454+ * disable_respawn:
455+ *
456+ * If TRUE, disallow respawning.
457+ **/
458+int disable_respawn = FALSE;
459+
460 /* Prototypes for static functions */
461 static void job_process_error_abort (int fd, JobProcessErrorType type,
462 int arg)
463@@ -136,8 +143,14 @@
464 static void job_process_trace_fork (Job *job, ProcessType process);
465 static void job_process_trace_exec (Job *job, ProcessType process);
466
467+<<<<<<< TREE
468 extern int user_mode;
469+=======
470+>>>>>>> MERGE-SOURCE
471 extern char *control_server_address;
472+extern int user_mode;
473+extern int session_end;
474+extern time_t quiesce_phase_time;
475
476 /**
477 * job_process_run:
478@@ -1231,6 +1244,52 @@
479 }
480
481 /**
482+ * job_process_jobs_running:
483+ *
484+ * Determine if any jobs are running.
485+ *
486+ * Returns: TRUE if jobs are still running, else FALSE.
487+ **/
488+int
489+job_process_jobs_running (void)
490+{
491+ job_class_init ();
492+
493+ NIH_HASH_FOREACH (job_classes, iter) {
494+ JobClass *class = (JobClass *)iter;
495+
496+ NIH_HASH_FOREACH (class->instances, job_iter)
497+ return TRUE;
498+ }
499+
500+ return FALSE;
501+}
502+
503+
504+/**
505+ * job_process_stop_all:
506+ *
507+ * Stop all running jobs.
508+ **/
509+void
510+job_process_stop_all (void)
511+{
512+ job_class_init ();
513+
514+ NIH_HASH_FOREACH (job_classes, iter) {
515+ JobClass *class = (JobClass *)iter;
516+
517+ /* Note that instances get killed in a random order */
518+ NIH_HASH_FOREACH (class->instances, job_iter) {
519+ Job *job = (Job *)job_iter;
520+
521+ /* Request job instance stops */
522+ job_change_goal (job, JOB_STOP);
523+ }
524+ }
525+}
526+
527+/**
528 * job_process_set_kill_timer:
529 * @job: job to set kill timer for,
530 * @process: process to be killed,
531@@ -1559,7 +1618,7 @@
532 * that's a simple matter of doing nothing. Check
533 * the job isn't running away first though.
534 */
535- if (failed && job->class->respawn) {
536+ if (failed && job->class->respawn && ! disable_respawn) {
537 if (job_process_catch_runaway (job)) {
538 nih_warn (_("%s respawning too fast, stopped"),
539 job_name (job));
540
541=== modified file 'init/job_process.h'
542--- init/job_process.h 2012-11-26 19:30:41 +0000
543+++ init/job_process.h 2013-02-15 11:31:39 +0000
544@@ -63,7 +63,6 @@
545 #define JOB_PROCESS_LOG_FILE_EXT ".log"
546 #endif
547
548-
549 /**
550 * JobProcessErrorType:
551 *
552@@ -151,6 +150,10 @@
553
554 void job_process_adj_kill_timer (Job *job, time_t due);
555
556+int job_process_jobs_running (void);
557+
558+void job_process_stop_all (void);
559+
560 NIH_END_EXTERN
561
562 #endif /* INIT_JOB_PROCESS_H */
563
564=== modified file 'init/main.c'
565--- init/main.c 2013-02-14 13:36:06 +0000
566+++ init/main.c 2013-02-15 11:31:39 +0000
567@@ -425,19 +425,20 @@
568 nih_signal_set_handler (SIGPWR, nih_signal_handler);
569 NIH_MUST (nih_signal_add_handler (NULL, SIGPWR, pwr_handler, NULL));
570
571- /* SIGHUP instructs us to re-load our configuration */
572- nih_signal_set_handler (SIGHUP, nih_signal_handler);
573- NIH_MUST (nih_signal_add_handler (NULL, SIGHUP, hup_handler, NULL));
574-
575- /* SIGUSR1 instructs us to reconnect to D-Bus */
576- nih_signal_set_handler (SIGUSR1, nih_signal_handler);
577- NIH_MUST (nih_signal_add_handler (NULL, SIGUSR1, usr1_handler, NULL));
578-
579 }
580
581- /* SIGTERM instructs us to re-exec ourselves; this should be the
582- * last in the list to ensure that all other signals are handled
583- * before a SIGTERM.
584+ /* SIGHUP instructs us to re-load our configuration */
585+ nih_signal_set_handler (SIGHUP, nih_signal_handler);
586+ NIH_MUST (nih_signal_add_handler (NULL, SIGHUP, hup_handler, NULL));
587+
588+ /* SIGUSR1 instructs us to reconnect to D-Bus */
589+ nih_signal_set_handler (SIGUSR1, nih_signal_handler);
590+ NIH_MUST (nih_signal_add_handler (NULL, SIGUSR1, usr1_handler, NULL));
591+
592+ /* SIGTERM instructs us to re-exec ourselves when running as PID
593+ * 1, or to exit when running as a Session Init; this signal should
594+ * be the last in the list to ensure that all other signals are
595+ * handled before a SIGTERM.
596 */
597 nih_signal_set_handler (SIGTERM, nih_signal_handler);
598 NIH_MUST (nih_signal_add_handler (NULL, SIGTERM, term_handler, NULL));
599@@ -634,7 +635,7 @@
600 }
601
602 if (disable_sessions)
603- nih_debug ("Sessions disabled");
604+ nih_debug ("Chroot Sessions disabled");
605
606 /* Set us as the child subreaper.
607 * This ensures that even when init doesn't run as PID 1, it'll always be
608@@ -656,8 +657,6 @@
609 nih_main_loop_interrupt ();
610 ret = nih_main_loop ();
611
612- control_cleanup ();
613-
614 return ret;
615 }
616
617@@ -805,7 +804,8 @@
618 * @signal: signal caught.
619 *
620 * This is called when we receive the TERM signal, which instructs us
621- * to reexec ourselves.
622+ * to reexec ourselves when running as PID 1, or to perform a controlled
623+ * exit when running as a Session Init.
624 **/
625 static void
626 term_handler (void *data,
627@@ -814,8 +814,12 @@
628 nih_assert (args_copy[0] != NULL);
629 nih_assert (signal != NULL);
630
631+ if (user_mode) {
632+ quiesce (QUIESCE_REQUESTER_SYSTEM);
633+ return;
634+ }
635+
636 nih_warn (_("Re-executing %s"), args_copy[0]);
637-
638 stateful_reexec ();
639 }
640
641@@ -826,7 +830,7 @@
642 * @data: unused,
643 * @signal: signal that called this handler.
644 *
645- * Handle having recieved the SIGINT signal, sent to us when somebody
646+ * Handle having received the SIGINT signal, sent to us when somebody
647 * presses Ctrl-Alt-Delete on the console. We just generate a
648 * ctrlaltdel event.
649 **/
650@@ -842,7 +846,7 @@
651 * @data: unused,
652 * @signal: signal that called this handler.
653 *
654- * Handle having recieved the SIGWINCH signal, sent to us when somebody
655+ * Handle having received the SIGWINCH signal, sent to us when somebody
656 * presses Alt-UpArrow on the console. We just generate a
657 * kbdrequest event.
658 **/
659@@ -858,7 +862,7 @@
660 * @data: unused,
661 * @signal: signal that called this handler.
662 *
663- * Handle having recieved the SIGPWR signal, sent to us when powstatd
664+ * Handle having received the SIGPWR signal, sent to us when powstatd
665 * changes the /etc/powerstatus file. We just generate a
666 * power-status-changed event and jobs read the file.
667 **/
668@@ -874,7 +878,7 @@
669 * @data: unused,
670 * @signal: signal that called this handler.
671 *
672- * Handle having recieved the SIGHUP signal, which we use to instruct us to
673+ * Handle having received the SIGHUP signal, which we use to instruct us to
674 * reload our configuration.
675 **/
676 static void
677@@ -890,7 +894,7 @@
678 * @data: unused,
679 * @signal: signal that called this handler.
680 *
681- * Handle having recieved the SIGUSR signal, which we use to instruct us to
682+ * Handle having received the SIGUSR signal, which we use to instruct us to
683 * reconnect to D-Bus.
684 **/
685 static void
686
687=== modified file 'init/man/init.8'
688--- init/man/init.8 2013-02-08 16:55:25 +0000
689+++ init/man/init.8 2013-02-15 11:31:39 +0000
690@@ -149,8 +149,25 @@
691 and pass all arguments to that. See that manual page for further
692 details. However, if the
693 .B \-\-user
694-option is specified, it will read alternative configuration files and
695-manage the individual user session in a similar fashion.
696+option is specified, it will run as a
697+.B Session Init
698+and read alternative configuration files and manage the individual user
699+session in a similar fashion.
700+
701+Sending a Session Init a
702+.I SIGTERM
703+signal is taken as a request to shutdown due to an impending system
704+shutdown. In this scenario, the
705+Session Init will emit the
706+.B session\-end
707+event and request all running jobs stop. It will attempt to honour jobs
708+.B kill timeout
709+values (see
710+.BR init (5)
711+for further details). Note however that system policy will prevail: if
712+jobs request timeout values longer than the system policy allows for
713+complete system shutdown, it will not be possible to honour them before
714+the Session Init is killed by the system.
715 .\"
716 .SH ENVIRONMENT VARIABLES
717
718
719=== added file 'init/man/session-end.7'
720--- init/man/session-end.7 1970-01-01 00:00:00 +0000
721+++ init/man/session-end.7 2013-02-15 11:31:39 +0000
722@@ -0,0 +1,34 @@
723+.TH session\-end 7 2013-02-08 "Upstart"
724+.\"
725+.SH NAME
726+session\-end \- event signalling session shutdown
727+.\"
728+.SH SYNOPSIS
729+.B session\-end
730+.BI TYPE\fR= TYPE
731+.\"
732+.SH DESCRIPTION
733+The
734+.B session\-end
735+event is generated by the Upstart
736+.BR init (8)
737+daemon when running as a Session Init (where its pid is not 1) when
738+requested to stop.
739+
740+The
741+.B TYPE
742+environment variable will be set to either
743+.I shutdown
744+when the Session Init has received the
745+.I SIGTERM
746+signal, or
747+.I logout
748+when the Session Init has processed the
749+.B com.ubuntu.Upstart0_6.EndSession()
750+D\-Bus method call.
751+
752+.\"
753+.SH SEE ALSO
754+.BR startup (7)
755+.BR init (5)
756+.BR init (8)
757
758=== modified file 'init/man/startup.7'
759--- init/man/startup.7 2009-07-09 12:26:11 +0000
760+++ init/man/startup.7 2013-02-15 11:31:39 +0000
761@@ -27,12 +27,17 @@
762 .BR runlevel (7)
763 event. See that page for a more detailed explanation of this process.
764
765+This event is emitted when running both as pid 1 and as a Session Init.
766+
767 Paradoxically there is currently no corresponding Upstart-native event
768 signifying that the system is to be shutdown, only the System V compatible
769 .B runlevel 0
770 and
771 .B runlevel 6
772-events provide this functionality.
773+events provide this functionality. However, when running as a Session
774+Init, the
775+.BR session\-end
776+event performs this function.
777 .\"
778 .SH EXAMPLE
779 A service with no other dependencies run on startup might use:
780@@ -45,4 +50,6 @@
781 .\"
782 .SH SEE ALSO
783 .BR runlevel (7)
784+.BR session\-end (7)
785+.BR init (5)
786 .BR init (8)
787
788=== added file 'init/quiesce.c'
789--- init/quiesce.c 1970-01-01 00:00:00 +0000
790+++ init/quiesce.c 2013-02-15 11:31:39 +0000
791@@ -0,0 +1,248 @@
792+/* upstart
793+ *
794+ * quiesce.c - shutdown handling.
795+ *
796+ * Copyright © 2013 Canonical Ltd.
797+ * Author: James Hunt <james.hunt@canonical.com>
798+ *
799+ * This program is free software; you can redistribute it and/or modify
800+ * it under the terms of the GNU General Public License version 2, as
801+ * published by the Free Software Foundation.
802+ *
803+ * This program is distributed in the hope that it will be useful,
804+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
805+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
806+ * GNU General Public License for more details.
807+ *
808+ * You should have received a copy of the GNU General Public License along
809+ * with this program; if not, write to the Free Software Foundation, Inc.,
810+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
811+ */
812+
813+#ifdef HAVE_CONFIG_H
814+# include <config.h>
815+#endif /* HAVE_CONFIG_H */
816+
817+#include "quiesce.h"
818+#include "events.h"
819+#include "environ.h"
820+#include "conf.h"
821+#include "job_process.h"
822+#include "control.h"
823+
824+#include <nih/main.h>
825+
826+/**
827+ * quiesce_requester:
828+ *
829+ * Where the quiesce request originated. This determines
830+ * the shutdown behaviour.
831+ **/
832+static QuiesceRequester quiesce_requester = QUIESCE_REQUESTER_INVALID;
833+
834+/**
835+ * quiesce_phase:
836+ *
837+ * Current phase of shutdown.
838+ **/
839+static QuiescePhase quiesce_phase = QUIESCE_PHASE_NOT_QUIESCED;
840+
841+/**
842+ * max_kill_timeout:
843+ *
844+ * Maxiumum kill_timout value calculated from all running jobs used to
845+ * determine how long to wait before exiting.
846+ **/
847+static time_t max_kill_timeout = 0;
848+
849+/**
850+ * quiesce_phase_time:
851+ *
852+ * Time that a particular phase started.
853+ **/
854+static time_t quiesce_phase_time = 0;
855+
856+/* External definitions */
857+extern int disable_respawn;
858+
859+/**
860+ * quiesce:
861+ *
862+ * @requester: where the quiesce request originated.
863+ *
864+ * Commence Session Init shutdown.
865+ **/
866+void
867+quiesce (QuiesceRequester requester)
868+{
869+ nih_local char **env = NULL;
870+ const char *reason;
871+
872+ job_class_init ();
873+
874+ /* Quiesce already in progress */
875+ if (quiesce_phase != QUIESCE_PHASE_NOT_QUIESCED)
876+ return;
877+
878+ quiesce_requester = requester;
879+
880+ /* System shutdown skips the wait phase to ensure all running
881+ * jobs get signalled.
882+ *
883+ * Note that jobs which choose to start on SESSION_END_EVENT may
884+ * not complete (or even start), but no guarantee is possible in
885+ * the system shutdown scenario since Session Inits must not
886+ * hold up the system.
887+ */
888+ quiesce_phase = (requester == QUIESCE_REQUESTER_SYSTEM)
889+ ? QUIESCE_PHASE_KILL
890+ : QUIESCE_PHASE_WAIT;
891+
892+ reason = (requester == QUIESCE_REQUESTER_SESSION)
893+ ? _("logout") : _("shutdown");
894+
895+ nih_info (_("Quiescing due to %s request"), reason);
896+
897+ quiesce_phase_time = time (NULL);
898+
899+ /* Stop existing jobs from respawning */
900+ disable_respawn = TRUE;
901+
902+ /* Signal that the session is ending. This may start new jobs.
903+ *
904+ * Note that the event doesn't actually get emitted until the
905+ * next time the main loop gets a chance to run.
906+ */
907+ env = NIH_MUST (nih_str_array_new (NULL));
908+
909+ NIH_MUST (environ_set (&env, NULL, NULL, TRUE,
910+ "TYPE=%s", reason));
911+
912+ NIH_MUST (event_new (NULL, SESSION_END_EVENT, env));
913+
914+ if (requester == QUIESCE_REQUESTER_SYSTEM) {
915+ /* We'll attempt to wait for this long, but system
916+ * policy may prevent it such that we just get killed
917+ * and job processes reparented to PID 1.
918+ */
919+ max_kill_timeout = job_class_max_kill_timeout ();
920+
921+ job_process_stop_all ();
922+ }
923+
924+ /* Check every second to see if all jobs have finished. If so,
925+ * we can exit early.
926+ */
927+ NIH_MUST (nih_timer_add_periodic (NULL, 1,
928+ (NihTimerCb)quiesce_wait_callback, NULL));
929+}
930+
931+/**
932+ * quiesce_wait_callback:
933+ *
934+ * @data: not used,
935+ * @timer: timer that caused us to be called.
936+ *
937+ * Callback used to check if all jobs have finished and if so
938+ * finalise Session Init shutdown.
939+ **/
940+void
941+quiesce_wait_callback (void *data, NihTimer *timer)
942+{
943+ time_t now;
944+
945+ nih_assert (timer);
946+ nih_assert (quiesce_phase_time);
947+
948+ now = time (NULL);
949+
950+ nih_assert (quiesce_requester != QUIESCE_REQUESTER_INVALID);
951+
952+ if (quiesce_requester == QUIESCE_REQUESTER_SYSTEM) {
953+ nih_assert (quiesce_phase == QUIESCE_PHASE_KILL);
954+
955+ if ((now - quiesce_phase_time) > max_kill_timeout)
956+ goto out;
957+
958+ } else if (quiesce_phase == QUIESCE_PHASE_WAIT) {
959+
960+ if ((now - quiesce_phase_time) > QUIESCE_DEFAULT_JOB_RUNTIME) {
961+ quiesce_phase = QUIESCE_PHASE_KILL;
962+
963+ /* reset for new phase */
964+ quiesce_phase_time = time (NULL);
965+
966+ max_kill_timeout = job_class_max_kill_timeout ();
967+ job_process_stop_all ();
968+ }
969+ } else if (quiesce_phase == QUIESCE_PHASE_KILL) {
970+
971+ if ((now - quiesce_phase_time) > max_kill_timeout)
972+ goto out;
973+ } else {
974+ nih_assert_not_reached ();
975+ }
976+
977+ if (! job_process_jobs_running ())
978+ goto out;
979+
980+ return;
981+
982+out:
983+ quiesce_show_slow_jobs ();
984+
985+ /* Note that we might skip the kill phase for the session
986+ * requestor if no jobs are actually running at this point.
987+ */
988+ quiesce_phase = QUIESCE_PHASE_CLEANUP;
989+ quiesce_finalise ();
990+
991+ /* Deregister */
992+ nih_free (timer);
993+}
994+
995+/**
996+ * quiesce_show_slow_jobs:
997+ *
998+ * List jobs that are still running after their expected end time.
999+ **/
1000+void
1001+quiesce_show_slow_jobs (void)
1002+{
1003+ job_class_init ();
1004+
1005+ NIH_HASH_FOREACH (job_classes, iter) {
1006+ JobClass *class = (JobClass *)iter;
1007+
1008+ /* Note that instances get killed in a random order */
1009+ NIH_HASH_FOREACH (class->instances, job_iter) {
1010+ nih_local const char *name = NULL;
1011+ Job *job;
1012+
1013+ job = (Job *)job_iter;
1014+
1015+ name = job_name (job);
1016+
1017+ nih_message ("job %s failed to stop", name);
1018+ }
1019+ }
1020+}
1021+
1022+
1023+/**
1024+ * quiesce_finalise:
1025+ *
1026+ * Perform final shutdown operations.
1027+ **/
1028+void
1029+quiesce_finalise (void)
1030+{
1031+ nih_assert (quiesce_phase == QUIESCE_PHASE_CLEANUP);
1032+
1033+ /* Cleanup */
1034+ conf_destroy ();
1035+ session_destroy ();
1036+ control_cleanup ();
1037+
1038+ nih_main_loop_exit (0);
1039+}
1040
1041=== added file 'init/quiesce.h'
1042--- init/quiesce.h 1970-01-01 00:00:00 +0000
1043+++ init/quiesce.h 2013-02-15 11:31:39 +0000
1044@@ -0,0 +1,76 @@
1045+/* upstart
1046+ *
1047+ * Copyright © 2013 Canonical Ltd.
1048+ * Author: James Hunt <james.hunt@canonical.com>
1049+ *
1050+ * This program is free software; you can redistribute it and/or modify
1051+ * it under the terms of the GNU General Public License version 2, as
1052+ * published by the Free Software Foundation.
1053+ *
1054+ * This program is distributed in the hope that it will be useful,
1055+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1056+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1057+ * GNU General Public License for more details.
1058+ *
1059+ * You should have received a copy of the GNU General Public License along
1060+ * with this program; if not, write to the Free Software Foundation, Inc.,
1061+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
1062+ */
1063+
1064+#ifndef INIT_QUIESCE_H
1065+#define INIT_QUIESCE_H
1066+
1067+#include <nih/timer.h>
1068+
1069+/**
1070+ * QUIESCE_DEFAULT_JOB_RUNTIME:
1071+ *
1072+ * The default maximum length of time to wait after emitting the
1073+ * SESSION_END_EVENT event before stopping all jobs.
1074+ **/
1075+#define QUIESCE_DEFAULT_JOB_RUNTIME 5
1076+
1077+/**
1078+ * QuiesceRequester:
1079+ *
1080+ * Reason for Session Init wishing to shutdown; either the Session Init
1081+ * has been notified the system is being shutdown, or the session has
1082+ * requested it be ended (for example due to a user logout request).
1083+ **/
1084+typedef enum quiesce_requester {
1085+ QUIESCE_REQUESTER_INVALID = -1,
1086+ QUIESCE_REQUESTER_SYSTEM = 0,
1087+ QUIESCE_REQUESTER_SESSION,
1088+} QuiesceRequester;
1089+
1090+/**
1091+ * QuiescePhase:
1092+ *
1093+ * Phase 0: No quiesce operation in progress.
1094+ *
1095+ * Phase 1: Wait: Period between SESSION_END_EVENT being emitted and
1096+ * QUIESCE_DEFAULT_JOB_RUNTIME being reached.
1097+ *
1098+ * Phase 2: Kill: Period between QUIESCE_DEFAULT_JOB_RUNTIME being
1099+ * reached and kill signal being sent to all jobs.
1100+ *
1101+ * Phase 3: Cleanup: Period between all jobs having ended
1102+ * (either naturally or by induction) and final exit.
1103+ **/
1104+typedef enum quiesce_phase {
1105+ QUIESCE_PHASE_NOT_QUIESCED,
1106+ QUIESCE_PHASE_WAIT,
1107+ QUIESCE_PHASE_KILL,
1108+ QUIESCE_PHASE_CLEANUP,
1109+} QuiescePhase;
1110+
1111+NIH_BEGIN_EXTERN
1112+
1113+void quiesce (QuiesceRequester requester);
1114+void quiesce_wait_callback (void *data, NihTimer *timer);
1115+void quiesce_show_slow_jobs (void);
1116+void quiesce_finalise (void);
1117+
1118+NIH_END_EXTERN
1119+
1120+#endif /* INIT_QUIESCE_H */
1121
1122=== modified file 'init/session.c'
1123--- init/session.c 2013-01-25 09:01:00 +0000
1124+++ init/session.c 2013-02-15 11:31:39 +0000
1125@@ -89,6 +89,18 @@
1126 sessions = NIH_MUST (nih_list_new (NULL));
1127 }
1128
1129+/**
1130+ * session_destroy:
1131+ *
1132+ * Clean up the sessions list.
1133+ **/
1134+void
1135+session_destroy (void)
1136+{
1137+ if (sessions)
1138+ nih_free (sessions);
1139+}
1140+
1141
1142 /**
1143 * session_new:
1144
1145=== modified file 'init/session.h'
1146--- init/session.h 2013-01-25 09:01:00 +0000
1147+++ init/session.h 2013-02-15 11:31:39 +0000
1148@@ -78,6 +78,7 @@
1149 extern NihList *sessions;
1150
1151 void session_init (void);
1152+void session_destroy (void);
1153
1154 Session * session_new (const void *parent, const char *chroot)
1155 __attribute__ ((malloc, warn_unused_result));
1156
1157=== modified file 'util/initctl.c'
1158--- util/initctl.c 2013-02-02 16:09:52 +0000
1159+++ util/initctl.c 2013-02-15 11:31:39 +0000
1160@@ -372,7 +372,7 @@
1161
1162 dbus_connection_set_exit_on_disconnect (connection, FALSE);
1163 } else {
1164- if (dest_name) {
1165+ if (dest_name && ! user_mode) {
1166 fprintf (stderr, _("%s: --dest given without --system\n"),
1167 program_name);
1168 nih_main_suggest_help ();
1169
1170=== modified file 'util/tests/test_initctl.c'
1171--- util/tests/test_initctl.c 2013-02-02 16:09:52 +0000
1172+++ util/tests/test_initctl.c 2013-02-15 11:31:39 +0000
1173@@ -32,6 +32,7 @@
1174 #include <regex.h>
1175 #include <sys/types.h>
1176 #include <sys/stat.h>
1177+#include <ctype.h>
1178
1179 #include <nih-dbus/dbus_error.h>
1180 #include <nih-dbus/dbus_connection.h>
1181@@ -50,6 +51,9 @@
1182
1183 #include "dbus/upstart.h"
1184
1185+#include "com.ubuntu.Upstart.h"
1186+
1187+
1188 #ifndef UPSTART_BINARY
1189 #error unable to find init binary as UPSTART_BINARY not defined
1190 #endif /* UPSTART_BINARY */
1191@@ -60,6 +64,24 @@
1192
1193 #define BUFFER_SIZE 1024
1194
1195+/**
1196+ * TEST_QUIESCE_WAIT_PHASE:
1197+ *
1198+ * Maximum time we expect upstart to wait in the QUIESCE_PHASE_WAIT
1199+ * phase.
1200+ **/
1201+#define TEST_EXIT_TIME 5
1202+
1203+/**
1204+ * TEST_QUIESCE_KILL_PHASE:
1205+ *
1206+ * Maximum time we expect upstart to wait in the QUIESCE_PHASE_KILL
1207+ * phase.
1208+ **/
1209+#define TEST_QUIESCE_KILL_PHASE 5
1210+
1211+#define TEST_QUIESCE_TOTAL_WAIT_TIME (TEST_EXIT_TIME + TEST_QUIESCE_KILL_PHASE)
1212+
1213 /* A 'reasonable' path, but which also contains a marker at the end so
1214 * we know when we're looking at a PATH these tests have set.
1215 */
1216@@ -68,53 +90,66 @@
1217 /* Default value for TERM if not already set */
1218 #define TEST_INITCTL_DEFAULT_TERM "linux"
1219
1220+int
1221+set_upstart_session (void);
1222+
1223 /**
1224- * WAIT_FOR_UPSTART:
1225+ * wait_for_upstart:
1226+ *
1227+ * @user: TRUE if waiting for a Session Init (which uses a private bus
1228+ * rather than the session bus), else FALSE.
1229 *
1230 * Wait for Upstart to appear on D-Bus denoting its completion of
1231 * initialisation. Wait time is somewhat arbitrary (but more
1232 * than adequate!).
1233 **/
1234-#define WAIT_FOR_UPSTART() \
1235-{ \
1236- nih_local NihDBusProxy *upstart = NULL; \
1237- DBusConnection *connection; \
1238- char *address; \
1239- NihError *err; \
1240- int running = FALSE; \
1241- \
1242- /* XXX: arbitrary value */ \
1243- int attempts = 10; \
1244- \
1245- address = getenv ("DBUS_SESSION_BUS_ADDRESS"); \
1246- TEST_TRUE (address); \
1247- \
1248- while (attempts) { \
1249- attempts--; \
1250- sleep (1); \
1251- connection = nih_dbus_connect (address, NULL); \
1252- \
1253- if (! connection) { \
1254- err = nih_error_get (); \
1255- nih_free (err); \
1256- continue; \
1257- } \
1258- \
1259- upstart = nih_dbus_proxy_new (NULL, connection, \
1260- NULL, \
1261- DBUS_PATH_UPSTART, \
1262- NULL, NULL); \
1263- \
1264- if (! upstart) { \
1265- err = nih_error_get (); \
1266- nih_free (err); \
1267- dbus_connection_unref (connection); \
1268- } else { \
1269- running = TRUE; \
1270- break; \
1271- } \
1272- } \
1273- TEST_EQ (running, TRUE); \
1274+void
1275+wait_for_upstart (int user)
1276+{
1277+ nih_local NihDBusProxy *upstart = NULL;
1278+ DBusConnection *connection;
1279+ char *address;
1280+ NihError *err;
1281+ int running = FALSE;
1282+
1283+ /* XXX: arbitrary value */
1284+ int attempts = 10;
1285+
1286+ if (user) {
1287+ set_upstart_session ();
1288+ address = getenv ("UPSTART_SESSION");
1289+ } else {
1290+ address = getenv ("DBUS_SESSION_BUS_ADDRESS");
1291+ }
1292+
1293+ TEST_TRUE (address);
1294+
1295+ while (attempts) {
1296+ attempts--;
1297+ sleep (1);
1298+ connection = nih_dbus_connect (address, NULL);
1299+
1300+ if (! connection) {
1301+ err = nih_error_get ();
1302+ nih_free (err);
1303+ continue;
1304+ }
1305+
1306+ upstart = nih_dbus_proxy_new (NULL, connection,
1307+ NULL,
1308+ DBUS_PATH_UPSTART,
1309+ NULL, NULL);
1310+
1311+ if (! upstart) {
1312+ err = nih_error_get ();
1313+ nih_free (err);
1314+ dbus_connection_unref (connection);
1315+ } else {
1316+ running = TRUE;
1317+ break;
1318+ }
1319+ }
1320+ TEST_EQ (running, TRUE);
1321 }
1322
1323 /**
1324@@ -150,6 +185,10 @@
1325 TEST_TRUE (WIFSIGNALED (status)); \
1326 TEST_TRUE (WTERMSIG (status) == signo); \
1327 } \
1328+ /* reset since a subsequent start could specify a different \
1329+ * user_mode value. \
1330+ */ \
1331+ test_user_mode = FALSE; \
1332 }
1333
1334 /**
1335@@ -160,11 +199,7 @@
1336 * Stop upstart process @pid.
1337 **/
1338 #define STOP_UPSTART(pid) \
1339- KILL_UPSTART (pid, SIGKILL, TRUE); \
1340- /* reset since a subsequent start could specify a different \
1341- * user_mode value. \
1342- */ \
1343- test_user_mode = FALSE
1344+ KILL_UPSTART (pid, SIGKILL, TRUE)
1345
1346 /**
1347 * REEXEC_UPSTART:
1348@@ -175,7 +210,7 @@
1349 **/
1350 #define REEXEC_UPSTART(pid) \
1351 KILL_UPSTART (pid, SIGTERM, FALSE); \
1352- WAIT_FOR_UPSTART ()
1353+ wait_for_upstart (FALSE)
1354
1355 /**
1356 * RUN_COMMAND:
1357@@ -475,6 +510,204 @@
1358 int test_user_mode = FALSE;
1359
1360 /**
1361+ * set_upstart_session:
1362+ *
1363+ * Attempt to "enter" an Upstart session by setting UPSTART_SESSION to
1364+ * the value of the currently running session.
1365+ *
1366+ * It is only legitimate to call this function if you have previously
1367+ * started a Session Init process.
1368+ *
1369+ * Limitations: Assumes that at most 1 session is running.
1370+ *
1371+ * Returns: TRUE if it was possible to enter the currently running
1372+ * Upstart session, else FALSE.
1373+ **/
1374+int
1375+set_upstart_session (void)
1376+{
1377+ char *value;
1378+ nih_local char *cmd = NULL;
1379+ nih_local char **output = NULL;
1380+ size_t lines = 0;
1381+ int got = FALSE;
1382+ int i;
1383+
1384+ /* XXX: arbitrary value */
1385+ int loops = 5;
1386+
1387+ /* list-sessions relies on this */
1388+ if (! getenv ("XDG_RUNTIME_DIR"))
1389+ return FALSE;
1390+
1391+ cmd = nih_sprintf (NULL, "%s list-sessions 2>&1", INITCTL_BINARY);
1392+ TEST_NE_P (cmd, NULL);
1393+
1394+ /* We expect the list-sessions command to return a valid session
1395+ * within a reasonable period of time.
1396+ */
1397+ for (i = 0; i < loops; i++) {
1398+ sleep (1);
1399+
1400+ RUN_COMMAND (NULL, cmd, &output, &lines);
1401+ if (lines != 1)
1402+ continue;
1403+
1404+ /* No pid in output */
1405+ if (! isdigit(output[0][0]))
1406+ continue;
1407+
1408+ /* look for separator between pid and value of
1409+ * UPSTART_SESSION.
1410+ */
1411+ value = strstr (output[0], " ");
1412+ if (! value)
1413+ continue;
1414+
1415+ /* jump over space */
1416+ value += 1;
1417+ if (! value)
1418+ continue;
1419+
1420+ /* No socket address */
1421+ if (strstr (value, "unix:abstract") == value) {
1422+ got = TRUE;
1423+ break;
1424+ }
1425+ }
1426+
1427+ if (got != TRUE)
1428+ return FALSE;
1429+
1430+ assert0 (setenv ("UPSTART_SESSION", value, 1));
1431+
1432+ return TRUE;
1433+}
1434+
1435+/**
1436+ * selfpipe:
1437+ *
1438+ * Used to allow a timed process wait.
1439+ **/
1440+int selfpipe[2] = { -1, -1 };
1441+
1442+void
1443+selfpipe_write (int n)
1444+{
1445+ assert (selfpipe[1] != -1);
1446+
1447+ (void)write (selfpipe[1], "", 1);
1448+}
1449+
1450+/**
1451+ * selfpipe_setup:
1452+ *
1453+ * Arrange for SIGCHLD to write to selfpipe such that we can select(2)
1454+ * on child process status changes.
1455+ **/
1456+void
1457+selfpipe_setup (void)
1458+{
1459+ static struct sigaction act;
1460+ int read_flags;
1461+ int write_flags;
1462+
1463+ assert (selfpipe[0] == -1);
1464+
1465+ assert (! pipe (selfpipe));
1466+
1467+ /* Set non-blocking */
1468+ read_flags = fcntl (selfpipe[0], F_GETFL);
1469+ write_flags = fcntl (selfpipe[1], F_GETFL);
1470+
1471+ read_flags |= O_NONBLOCK;
1472+ write_flags |= O_NONBLOCK;
1473+
1474+ assert (fcntl (selfpipe[0], F_SETFL, read_flags) == 0);
1475+ assert (fcntl (selfpipe[1], F_SETFL, write_flags) == 0);
1476+
1477+ /* Don't leak */
1478+ assert (fcntl (selfpipe[0], F_SETFD, FD_CLOEXEC) == 0);
1479+ assert (fcntl (selfpipe[1], F_SETFD, FD_CLOEXEC) == 0);
1480+
1481+ memset (&act, 0, sizeof (act));
1482+
1483+ /* register SIGCHLD handler which will cause pipe write when child
1484+ * changes state.
1485+ */
1486+ act.sa_handler = selfpipe_write;
1487+
1488+ sigaction (SIGCHLD, &act, NULL);
1489+}
1490+
1491+/**
1492+ * timed_waitpid:
1493+ *
1494+ * @pid: pid to wait for,
1495+ * @timeout: seconds to wait for @pid to change state.
1496+ *
1497+ * Simplified waitpid(2) with timeout using a pipe to allow select(2)
1498+ * with timeout to be used to wait for process state change.
1499+ **/
1500+pid_t
1501+timed_waitpid (pid_t pid, time_t timeout)
1502+{
1503+ static char buffer[1];
1504+ fd_set read_fds;
1505+ struct timeval tv;
1506+ int status;
1507+ int nfds;
1508+ int ret;
1509+ pid_t ret2;
1510+
1511+ assert (pid);
1512+ assert (timeout);
1513+
1514+ if (selfpipe[0] == -1)
1515+ selfpipe_setup ();
1516+
1517+ FD_ZERO (&read_fds);
1518+ FD_SET (selfpipe[0], &read_fds);
1519+
1520+ nfds = 1 + selfpipe[0];
1521+
1522+ tv.tv_sec = timeout;
1523+ tv.tv_usec = 0;
1524+
1525+ /* wait for some activity */
1526+ ret = select (nfds, &read_fds, NULL, NULL, &tv);
1527+
1528+ if (! ret)
1529+ /* timed out */
1530+ return 0;
1531+
1532+ /* discard any data written to pipe */
1533+ while (read (selfpipe[0], buffer, sizeof (buffer)) > 0)
1534+ ;
1535+
1536+ while (TRUE) {
1537+ /* wait for status change or error */
1538+ ret2 = waitpid (pid, &status, WNOHANG);
1539+
1540+ if (ret2 < 0)
1541+ return -1;
1542+
1543+ if (! ret2)
1544+ /* give child a chance to change state */
1545+ sleep (1);
1546+
1547+ if (ret2) {
1548+ if (WIFEXITED (status))
1549+ return ret2;
1550+
1551+ /* unexpected status change */
1552+ return -1;
1553+ }
1554+ }
1555+}
1556+
1557+
1558+/**
1559 * get_initctl():
1560 *
1561 * Determine a suitable initctl command-line for testing purposes.
1562@@ -504,6 +737,8 @@
1563 * _start_upstart:
1564 *
1565 * @pid: PID of running instance,
1566+ * @user: TRUE if upstart will run in User Session mode (FALSE to
1567+ * use the users D-Bus session bus),
1568 * @args: optional list of arguments to specify.
1569 *
1570 * Start an instance of Upstart.
1571@@ -511,9 +746,10 @@
1572 * If the instance fails to start, abort(3) is called.
1573 **/
1574 void
1575-_start_upstart (pid_t *pid, char * const *args)
1576+_start_upstart (pid_t *pid, int user, char * const *args)
1577 {
1578 nih_local char **argv = NULL;
1579+ sigset_t child_set, orig_set;
1580
1581 assert (pid);
1582
1583@@ -525,12 +761,29 @@
1584 if (args)
1585 NIH_MUST (nih_str_array_append (&argv, NULL, NULL, args));
1586
1587+ sigfillset (&child_set);
1588+ sigprocmask (SIG_BLOCK, &child_set, &orig_set);
1589+
1590 TEST_NE (*pid = fork (), -1);
1591
1592- if (*pid == 0)
1593- execv (argv[0], argv);
1594-
1595- WAIT_FOR_UPSTART ();
1596+ if (! *pid) {
1597+ int fd;
1598+ nih_signal_reset ();
1599+ sigprocmask (SIG_SETMASK, &orig_set, NULL);
1600+
1601+ if (! getenv ("UPSTART_TEST_VERBOSE")) {
1602+ fd = open ("/dev/null", O_RDWR);
1603+ assert (fd >= 0);
1604+ assert (dup2 (fd, STDIN_FILENO) != -1);
1605+ assert (dup2 (fd, STDOUT_FILENO) != -1);
1606+ assert (dup2 (fd, STDERR_FILENO) != -1);
1607+ }
1608+
1609+ assert (execv (argv[0], argv) != -1);
1610+ }
1611+
1612+ sigprocmask (SIG_SETMASK, &orig_set, NULL);
1613+ wait_for_upstart (user);
1614 }
1615
1616 /**
1617@@ -588,7 +841,7 @@
1618 if (extra)
1619 NIH_MUST (nih_str_array_append (&args, NULL, NULL, extra));
1620
1621- _start_upstart (pid, args);
1622+ _start_upstart (pid, user, args);
1623 }
1624
1625 /**
1626@@ -11782,9 +12035,7 @@
1627 char dirname[PATH_MAX];
1628 char confdir[PATH_MAX];
1629 nih_local char *cmd = NULL;
1630- nih_local char **args = NULL;
1631 pid_t upstart_pid = 0;
1632- pid_t dbus_pid = 0;
1633 char **output;
1634 size_t lines;
1635 struct stat statbuf;
1636@@ -11839,12 +12090,10 @@
1637 TEST_EQ (setenv ("UPSTART_CONFDIR", confdir, 1), 0);
1638 TEST_EQ (setenv ("XDG_RUNTIME_DIR", dirname, 1), 0);
1639
1640- args = NIH_MUST (nih_str_array_new (NULL));
1641- NIH_MUST (nih_str_array_add (&args, NULL, NULL, "--user"));
1642+ /* Reset initctl global from previous tests */
1643+ dest_name = NULL;
1644
1645- /* Start to create session file */
1646- TEST_DBUS (dbus_pid);
1647- start_upstart_common (&upstart_pid, FALSE, NULL, NULL, args);
1648+ start_upstart_common (&upstart_pid, TRUE, NULL, NULL, NULL);
1649
1650 session_file = nih_sprintf (NULL, "%s/upstart/sessions/%d.session",
1651 dirname, (int)upstart_pid);
1652@@ -11877,7 +12126,6 @@
1653 nih_free (output);
1654
1655 STOP_UPSTART (upstart_pid);
1656- TEST_DBUS_END (dbus_pid);
1657
1658 /* Upstart cannot yet be instructed to shutdown cleanly, so for
1659 * now we have to remove the session file manually.
1660@@ -11908,6 +12156,571 @@
1661 }
1662
1663 void
1664+test_quiesce (void)
1665+{
1666+ char confdir[PATH_MAX];
1667+ char logdir[PATH_MAX];
1668+ char sessiondir[PATH_MAX];
1669+ nih_local char *cmd = NULL;
1670+ pid_t upstart_pid = 0;
1671+ nih_local char *logfile = NULL;
1672+ FILE *file;
1673+ char **output;
1674+ size_t lines;
1675+ nih_local NihDBusProxy *upstart = NULL;
1676+ nih_local char *orig_xdg_runtime_dir = NULL;
1677+ nih_local char *session_file = NULL;
1678+
1679+ TEST_GROUP ("Session Init quiesce");
1680+
1681+ TEST_FILENAME (confdir);
1682+ TEST_EQ (mkdir (confdir, 0755), 0);
1683+
1684+ TEST_FILENAME (logdir);
1685+ TEST_EQ (mkdir (logdir, 0755), 0);
1686+
1687+ TEST_FILENAME (sessiondir);
1688+ TEST_EQ (mkdir (sessiondir, 0755), 0);
1689+
1690+ /* Take care to avoid disrupting users environment by saving and
1691+ * restoring this variable (assuming the tests all pass...).
1692+ */
1693+ orig_xdg_runtime_dir = getenv ("XDG_RUNTIME_DIR");
1694+ if (orig_xdg_runtime_dir)
1695+ orig_xdg_runtime_dir = NIH_MUST (nih_strdup (NULL, orig_xdg_runtime_dir));
1696+
1697+ /* Use the "secret" interface */
1698+ TEST_EQ (setenv ("UPSTART_CONFDIR", confdir, 1), 0);
1699+ TEST_EQ (setenv ("UPSTART_LOGDIR", logdir, 1), 0);
1700+ TEST_EQ (setenv ("XDG_RUNTIME_DIR", sessiondir, 1), 0);
1701+
1702+ /* Reset initctl global from previous tests */
1703+ dest_name = NULL;
1704+
1705+ /*******************************************************************/
1706+ TEST_FEATURE ("system shutdown: no jobs");
1707+
1708+ start_upstart_common (&upstart_pid, TRUE, confdir, logdir, NULL);
1709+
1710+ /* Should be running */
1711+ assert0 (kill (upstart_pid, 0));
1712+
1713+ /* Trigger shutdown */
1714+ assert0 (kill (upstart_pid, SIGTERM));
1715+
1716+ /* Force reset */
1717+ test_user_mode = FALSE;
1718+
1719+ TEST_EQ (timed_waitpid (upstart_pid, TEST_QUIESCE_KILL_PHASE), upstart_pid);
1720+
1721+ /* Should not now be running */
1722+ TEST_EQ (kill (upstart_pid, 0), -1);
1723+
1724+ session_file = NIH_MUST (nih_sprintf (NULL, "%s/upstart/sessions/%d.session",
1725+ sessiondir, (int)upstart_pid));
1726+ unlink (session_file);
1727+
1728+ /*******************************************************************/
1729+ TEST_FEATURE ("system shutdown: one long-running job");
1730+
1731+ CREATE_FILE (confdir, "long-running.conf",
1732+ "exec sleep 999");
1733+
1734+ start_upstart_common (&upstart_pid, TRUE, confdir, logdir, NULL);
1735+
1736+ /* Should be running */
1737+ assert0 (kill (upstart_pid, 0));
1738+
1739+ cmd = nih_sprintf (NULL, "%s start %s 2>&1",
1740+ get_initctl (), "long-running");
1741+ TEST_NE_P (cmd, NULL);
1742+
1743+ RUN_COMMAND (NULL, cmd, &output, &lines);
1744+ TEST_EQ (lines, 1);
1745+ nih_free (output);
1746+
1747+ /* Trigger shutdown */
1748+ assert0 (kill (upstart_pid, SIGTERM));
1749+
1750+ /* Force reset */
1751+ test_user_mode = FALSE;
1752+
1753+ TEST_EQ (timed_waitpid (upstart_pid, TEST_QUIESCE_KILL_PHASE), upstart_pid);
1754+
1755+ /* Should not now be running */
1756+ TEST_EQ (kill (upstart_pid, 0), -1);
1757+
1758+ session_file = NIH_MUST (nih_sprintf (NULL, "%s/upstart/sessions/%d.session",
1759+ sessiondir, (int)upstart_pid));
1760+ unlink (session_file);
1761+
1762+ DELETE_FILE (confdir, "long-running.conf");
1763+
1764+ /*******************************************************************/
1765+ TEST_FEATURE ("system shutdown: one long-running job which ignores SIGTERM");
1766+
1767+ CREATE_FILE (confdir, "long-running-term.conf",
1768+ "script\n"
1769+ " trap '' TERM\n"
1770+ " sleep 999\n"
1771+ "end script");
1772+
1773+ start_upstart_common (&upstart_pid, TRUE, confdir, logdir, NULL);
1774+
1775+ /* Should be running */
1776+ assert0 (kill (upstart_pid, 0));
1777+
1778+ cmd = nih_sprintf (NULL, "%s start %s 2>&1",
1779+ get_initctl (), "long-running-term");
1780+ TEST_NE_P (cmd, NULL);
1781+
1782+ RUN_COMMAND (NULL, cmd, &output, &lines);
1783+ TEST_EQ (lines, 1);
1784+ nih_free (output);
1785+
1786+ /* Trigger shutdown */
1787+ assert0 (kill (upstart_pid, SIGTERM));
1788+
1789+ /* Force reset */
1790+ test_user_mode = FALSE;
1791+
1792+ TEST_EQ (timed_waitpid (upstart_pid, TEST_QUIESCE_KILL_PHASE), upstart_pid);
1793+
1794+ /* Should not now be running */
1795+ TEST_EQ (kill (upstart_pid, 0), -1);
1796+
1797+ session_file = NIH_MUST (nih_sprintf (NULL, "%s/upstart/sessions/%d.session",
1798+ sessiondir, (int)upstart_pid));
1799+ unlink (session_file);
1800+
1801+ DELETE_FILE (confdir, "long-running-term.conf");
1802+
1803+ /*******************************************************************/
1804+ TEST_FEATURE ("system shutdown: one job which starts on session-end");
1805+
1806+ CREATE_FILE (confdir, "session-end.conf",
1807+ "start on session-end\n"
1808+ "\n"
1809+ "script\n"
1810+ " echo hello\n"
1811+ " sleep 999\n"
1812+ "end script");
1813+
1814+ start_upstart_common (&upstart_pid, TRUE, confdir, logdir, NULL);
1815+
1816+ /* Should be running */
1817+ assert0 (kill (upstart_pid, 0));
1818+
1819+ /* Trigger shutdown */
1820+ assert0 (kill (upstart_pid, SIGTERM));
1821+
1822+ /* Force reset */
1823+ test_user_mode = FALSE;
1824+
1825+ TEST_EQ (timed_waitpid (upstart_pid, TEST_QUIESCE_KILL_PHASE), upstart_pid);
1826+
1827+ /* Should not now be running */
1828+ TEST_EQ (kill (upstart_pid, 0), -1);
1829+
1830+ logfile = NIH_MUST (nih_sprintf (NULL, "%s/%s",
1831+ logdir,
1832+ "session-end.log"));
1833+
1834+ file = fopen (logfile, "r");
1835+ TEST_NE_P (file, NULL);
1836+ TEST_FILE_EQ (file, "hello\r\n");
1837+ TEST_FILE_END (file);
1838+ TEST_EQ (fclose (file), 0);
1839+ assert0 (unlink (logfile));
1840+
1841+ session_file = NIH_MUST (nih_sprintf (NULL, "%s/upstart/sessions/%d.session",
1842+ sessiondir, (int)upstart_pid));
1843+ unlink (session_file);
1844+
1845+ DELETE_FILE (confdir, "session-end.conf");
1846+
1847+ /*******************************************************************/
1848+ TEST_FEATURE ("system shutdown: one job which starts on session-end and ignores SIGTERM");
1849+
1850+ CREATE_FILE (confdir, "session-end-term.conf",
1851+ "start on session-end\n"
1852+ "\n"
1853+ "script\n"
1854+ " trap '' TERM\n"
1855+ " echo hello\n"
1856+ " sleep 999\n"
1857+ "end script");
1858+
1859+ start_upstart_common (&upstart_pid, TRUE, confdir, logdir, NULL);
1860+
1861+ /* Should be running */
1862+ assert0 (kill (upstart_pid, 0));
1863+
1864+ /* Trigger shutdown */
1865+ assert0 (kill (upstart_pid, SIGTERM));
1866+
1867+ /* Force reset */
1868+ test_user_mode = FALSE;
1869+
1870+ TEST_EQ (timed_waitpid (upstart_pid, TEST_QUIESCE_KILL_PHASE), upstart_pid);
1871+
1872+ /* Should not now be running */
1873+ TEST_EQ (kill (upstart_pid, 0), -1);
1874+
1875+ logfile = NIH_MUST (nih_sprintf (NULL, "%s/%s",
1876+ logdir,
1877+ "session-end-term.log"));
1878+
1879+ file = fopen (logfile, "r");
1880+ TEST_NE_P (file, NULL);
1881+ TEST_FILE_EQ (file, "hello\r\n");
1882+ TEST_FILE_END (file);
1883+ TEST_EQ (fclose (file), 0);
1884+ assert0 (unlink (logfile));
1885+
1886+ session_file = NIH_MUST (nih_sprintf (NULL, "%s/upstart/sessions/%d.session",
1887+ sessiondir, (int)upstart_pid));
1888+ unlink (session_file);
1889+
1890+ DELETE_FILE (confdir, "session-end-term.conf");
1891+
1892+ /*******************************************************************/
1893+ TEST_FEATURE ("system shutdown: 2 jobs "
1894+ "(1 long-running job which ignores SIGTERM, "
1895+ "1 which starts on session-end and ignores SIGTERM)");
1896+
1897+ CREATE_FILE (confdir, "long-running-term.conf",
1898+ "script\n"
1899+ " trap '' TERM\n"
1900+ " sleep 999\n"
1901+ "end script");
1902+
1903+ CREATE_FILE (confdir, "session-end-term.conf",
1904+ "start on session-end\n"
1905+ "\n"
1906+ "script\n"
1907+ " trap '' TERM\n"
1908+ " sleep 999\n"
1909+ "end script");
1910+
1911+
1912+ start_upstart_common (&upstart_pid, TRUE, confdir, logdir, NULL);
1913+
1914+ /* Should be running */
1915+ assert0 (kill (upstart_pid, 0));
1916+
1917+ cmd = nih_sprintf (NULL, "%s start %s 2>&1",
1918+ get_initctl (), "long-running-term");
1919+ TEST_NE_P (cmd, NULL);
1920+
1921+ RUN_COMMAND (NULL, cmd, &output, &lines);
1922+ TEST_EQ (lines, 1);
1923+ nih_free (output);
1924+
1925+ /* Trigger shutdown */
1926+ assert0 (kill (upstart_pid, SIGTERM));
1927+
1928+ /* Force reset */
1929+ test_user_mode = FALSE;
1930+
1931+ TEST_EQ (timed_waitpid (upstart_pid, TEST_QUIESCE_KILL_PHASE), upstart_pid);
1932+
1933+ /* Should not now be running */
1934+ TEST_EQ (kill (upstart_pid, 0), -1);
1935+
1936+ session_file = NIH_MUST (nih_sprintf (NULL, "%s/upstart/sessions/%d.session",
1937+ sessiondir, (int)upstart_pid));
1938+ unlink (session_file);
1939+
1940+ DELETE_FILE (confdir, "long-running-term.conf");
1941+ DELETE_FILE (confdir, "session-end-term.conf");
1942+
1943+ /*******************************************************************/
1944+ TEST_FEATURE ("session shutdown: no jobs");
1945+
1946+ start_upstart_common (&upstart_pid, TRUE, confdir, logdir, NULL);
1947+
1948+ /* Further required initctl global resets. Shudder. */
1949+ user_mode = TRUE;
1950+ use_dbus = -1;
1951+ dbus_bus_type = DBUS_BUS_SESSION;
1952+ dbus_bus_type = -1;
1953+
1954+ upstart = upstart_open (NULL);
1955+ TEST_NE_P (upstart, NULL);
1956+
1957+ /* Should be running */
1958+ assert0 (kill (upstart_pid, 0));
1959+
1960+ /* Force reset */
1961+ test_user_mode = FALSE;
1962+
1963+ /* Trigger session shutdown */
1964+ assert0 (upstart_end_session_sync (NULL, upstart));
1965+
1966+ /* no jobs, so Session Init should shutdown "immediately" */
1967+ TEST_EQ (timed_waitpid (upstart_pid, TEST_QUIESCE_KILL_PHASE), upstart_pid);
1968+
1969+ /* Should not now be running */
1970+ TEST_EQ (kill (upstart_pid, 0), -1);
1971+
1972+ session_file = NIH_MUST (nih_sprintf (NULL, "%s/upstart/sessions/%d.session",
1973+ sessiondir, (int)upstart_pid));
1974+ unlink (session_file);
1975+
1976+ /*******************************************************************/
1977+ TEST_FEATURE ("session shutdown: one long-running job");
1978+
1979+ CREATE_FILE (confdir, "long-running.conf",
1980+ "exec sleep 999");
1981+
1982+ start_upstart_common (&upstart_pid, TRUE, confdir, logdir, NULL);
1983+
1984+ cmd = nih_sprintf (NULL, "%s start %s 2>&1",
1985+ get_initctl (), "long-running");
1986+ TEST_NE_P (cmd, NULL);
1987+
1988+ RUN_COMMAND (NULL, cmd, &output, &lines);
1989+ TEST_EQ (lines, 1);
1990+ nih_free (output);
1991+
1992+ upstart = upstart_open (NULL);
1993+ TEST_NE_P (upstart, NULL);
1994+
1995+ /* Should be running */
1996+ assert0 (kill (upstart_pid, 0));
1997+
1998+ /* Force reset */
1999+ test_user_mode = FALSE;
2000+
2001+ /* Trigger session shutdown */
2002+ assert0 (upstart_end_session_sync (NULL, upstart));
2003+
2004+ TEST_EQ (timed_waitpid (upstart_pid, TEST_QUIESCE_KILL_PHASE), upstart_pid);
2005+
2006+ /* Should not now be running */
2007+ TEST_EQ (kill (upstart_pid, 0), -1);
2008+
2009+ session_file = NIH_MUST (nih_sprintf (NULL, "%s/upstart/sessions/%d.session",
2010+ sessiondir, (int)upstart_pid));
2011+ unlink (session_file);
2012+
2013+ DELETE_FILE (confdir, "long-running.conf");
2014+
2015+ /*******************************************************************/
2016+ TEST_FEATURE ("session shutdown: one long-running job which ignores SIGTERM");
2017+
2018+ CREATE_FILE (confdir, "long-running-term.conf",
2019+ "script\n"
2020+ " trap '' TERM\n"
2021+ " sleep 999\n"
2022+ "end script");
2023+
2024+ start_upstart_common (&upstart_pid, TRUE, confdir, logdir, NULL);
2025+
2026+ cmd = nih_sprintf (NULL, "%s start %s 2>&1",
2027+ get_initctl (), "long-running");
2028+ TEST_NE_P (cmd, NULL);
2029+
2030+ RUN_COMMAND (NULL, cmd, &output, &lines);
2031+ TEST_EQ (lines, 1);
2032+ nih_free (output);
2033+
2034+ upstart = upstart_open (NULL);
2035+ TEST_NE_P (upstart, NULL);
2036+
2037+ /* Should be running */
2038+ assert0 (kill (upstart_pid, 0));
2039+
2040+ /* Force reset */
2041+ test_user_mode = FALSE;
2042+
2043+ /* Trigger session shutdown */
2044+ assert0 (upstart_end_session_sync (NULL, upstart));
2045+
2046+ TEST_EQ (timed_waitpid (upstart_pid, TEST_QUIESCE_KILL_PHASE), upstart_pid);
2047+
2048+ /* Should not now be running */
2049+ TEST_EQ (kill (upstart_pid, 0), -1);
2050+
2051+ session_file = NIH_MUST (nih_sprintf (NULL, "%s/upstart/sessions/%d.session",
2052+ sessiondir, (int)upstart_pid));
2053+ unlink (session_file);
2054+
2055+ DELETE_FILE (confdir, "long-running-term.conf");
2056+
2057+ /*******************************************************************/
2058+ TEST_FEATURE ("session shutdown: one job which starts on session-end");
2059+
2060+ CREATE_FILE (confdir, "session-end.conf",
2061+ "start on session-end\n"
2062+ "\n"
2063+ "script\n"
2064+ " echo hello\n"
2065+ " sleep 999\n"
2066+ "end script");
2067+
2068+ start_upstart_common (&upstart_pid, TRUE, confdir, logdir, NULL);
2069+
2070+ upstart = upstart_open (NULL);
2071+ TEST_NE_P (upstart, NULL);
2072+
2073+ /* Should be running */
2074+ assert0 (kill (upstart_pid, 0));
2075+
2076+ /* Force reset */
2077+ test_user_mode = FALSE;
2078+
2079+ /* Trigger session shutdown */
2080+ assert0 (upstart_end_session_sync (NULL, upstart));
2081+
2082+ TEST_EQ (timed_waitpid (upstart_pid, TEST_QUIESCE_KILL_PHASE), upstart_pid);
2083+
2084+ /* Should not now be running */
2085+ TEST_EQ (kill (upstart_pid, 0), -1);
2086+
2087+ logfile = NIH_MUST (nih_sprintf (NULL, "%s/%s",
2088+ logdir,
2089+ "session-end.log"));
2090+
2091+ file = fopen (logfile, "r");
2092+ TEST_NE_P (file, NULL);
2093+ TEST_FILE_EQ (file, "hello\r\n");
2094+ TEST_FILE_END (file);
2095+ TEST_EQ (fclose (file), 0);
2096+ assert0 (unlink (logfile));
2097+
2098+ session_file = NIH_MUST (nih_sprintf (NULL, "%s/upstart/sessions/%d.session",
2099+ sessiondir, (int)upstart_pid));
2100+ unlink (session_file);
2101+
2102+ DELETE_FILE (confdir, "session-end.conf");
2103+
2104+ /*******************************************************************/
2105+ TEST_FEATURE ("session shutdown: one job which starts on session-end");
2106+
2107+ CREATE_FILE (confdir, "session-end-term.conf",
2108+ "start on session-end\n"
2109+ "\n"
2110+ "script\n"
2111+ " trap '' TERM\n"
2112+ " echo hello\n"
2113+ " sleep 999\n"
2114+ "end script");
2115+
2116+ start_upstart_common (&upstart_pid, TRUE, confdir, logdir, NULL);
2117+
2118+ upstart = upstart_open (NULL);
2119+ TEST_NE_P (upstart, NULL);
2120+
2121+ /* Should be running */
2122+ assert0 (kill (upstart_pid, 0));
2123+
2124+ /* Force reset */
2125+ test_user_mode = FALSE;
2126+
2127+ /* Trigger session shutdown */
2128+ assert0 (upstart_end_session_sync (NULL, upstart));
2129+
2130+ TEST_EQ (timed_waitpid (upstart_pid, TEST_QUIESCE_KILL_PHASE), upstart_pid);
2131+
2132+ /* Should not now be running */
2133+ TEST_EQ (kill (upstart_pid, 0), -1);
2134+
2135+ logfile = NIH_MUST (nih_sprintf (NULL, "%s/%s",
2136+ logdir,
2137+ "session-end-term.log"));
2138+
2139+ file = fopen (logfile, "r");
2140+ TEST_NE_P (file, NULL);
2141+ TEST_FILE_EQ (file, "hello\r\n");
2142+ TEST_FILE_END (file);
2143+ TEST_EQ (fclose (file), 0);
2144+ assert0 (unlink (logfile));
2145+
2146+ session_file = NIH_MUST (nih_sprintf (NULL, "%s/upstart/sessions/%d.session",
2147+ sessiondir, (int)upstart_pid));
2148+ unlink (session_file);
2149+
2150+ DELETE_FILE (confdir, "session-end-term.conf");
2151+
2152+ /*******************************************************************/
2153+ TEST_FEATURE ("session shutdown: 2 jobs "
2154+ "(1 long-running job which ignores SIGTERM, "
2155+ "1 which starts on session-end and ignores SIGTERM)");
2156+
2157+ CREATE_FILE (confdir, "long-running-term.conf",
2158+ "script\n"
2159+ " trap '' TERM\n"
2160+ " sleep 999\n"
2161+ "end script");
2162+
2163+ CREATE_FILE (confdir, "session-end-term.conf",
2164+ "start on session-end\n"
2165+ "\n"
2166+ "script\n"
2167+ " trap '' TERM\n"
2168+ " sleep 999\n"
2169+ "end script");
2170+
2171+ start_upstart_common (&upstart_pid, TRUE, confdir, logdir, NULL);
2172+
2173+ cmd = nih_sprintf (NULL, "%s start %s 2>&1",
2174+ get_initctl (), "long-running-term");
2175+ TEST_NE_P (cmd, NULL);
2176+
2177+ RUN_COMMAND (NULL, cmd, &output, &lines);
2178+ TEST_EQ (lines, 1);
2179+ nih_free (output);
2180+
2181+ upstart = upstart_open (NULL);
2182+ TEST_NE_P (upstart, NULL);
2183+
2184+ /* Should be running */
2185+ assert0 (kill (upstart_pid, 0));
2186+
2187+ /* Force reset */
2188+ test_user_mode = FALSE;
2189+
2190+ /* Trigger session shutdown */
2191+ assert0 (upstart_end_session_sync (NULL, upstart));
2192+
2193+ TEST_EQ (timed_waitpid (upstart_pid, TEST_QUIESCE_TOTAL_WAIT_TIME), upstart_pid);
2194+
2195+ /* Should not now be running */
2196+ TEST_EQ (kill (upstart_pid, 0), -1);
2197+
2198+ session_file = NIH_MUST (nih_sprintf (NULL, "%s/upstart/sessions/%d.session",
2199+ sessiondir, (int)upstart_pid));
2200+ unlink (session_file);
2201+
2202+ DELETE_FILE (confdir, "long-running-term.conf");
2203+ DELETE_FILE (confdir, "session-end-term.conf");
2204+
2205+ /*******************************************************************/
2206+ assert0 (unsetenv ("UPSTART_CONFDIR"));
2207+ assert0 (unsetenv ("UPSTART_LOGDIR"));
2208+
2209+ if (orig_xdg_runtime_dir) {
2210+ /* restore */
2211+ setenv ("XDG_RUNTIME_DIR", orig_xdg_runtime_dir, 1);
2212+ } else {
2213+ assert0 (unsetenv ("XDG_RUNTIME_DIR"));
2214+ }
2215+
2216+ TEST_EQ (rmdir (logdir), 0);
2217+ TEST_EQ (rmdir (confdir), 0);
2218+
2219+ session_file = NIH_MUST (nih_sprintf (NULL, "%s/upstart/sessions", sessiondir));
2220+ TEST_EQ (rmdir (session_file), 0);
2221+ session_file = NIH_MUST (nih_sprintf (NULL, "%s/upstart", sessiondir));
2222+ TEST_EQ (rmdir (session_file), 0);
2223+ TEST_EQ (rmdir (sessiondir), 0);
2224+
2225+ /*******************************************************************/
2226+}
2227+
2228+void
2229 test_show_config (void)
2230 {
2231 char dirname[PATH_MAX];
2232@@ -15570,6 +16383,9 @@
2233 out = tmpfile ();
2234 err = tmpfile ();
2235
2236+ TEST_NE_P (out, NULL);
2237+ TEST_NE_P (err, NULL);
2238+
2239 TEST_DIVERT_STDOUT (out) {
2240 TEST_DIVERT_STDERR (err) {
2241 ret = status_action (&command, args);
2242@@ -15588,12 +16404,16 @@
2243 TEST_FILE_END (err);
2244 TEST_FILE_RESET (err);
2245
2246+ assert0 (fclose (out));
2247+ assert0 (fclose (err));
2248+
2249 DELETE_FILE (dirname, "foo.conf");
2250
2251-
2252 STOP_UPSTART (upstart_pid);
2253 TEST_EQ (unsetenv ("UPSTART_CONFDIR"), 0);
2254 TEST_DBUS_END (dbus_pid);
2255+
2256+ assert0 (rmdir (dirname));
2257 }
2258
2259 void
2260@@ -16525,6 +17345,7 @@
2261 nih_local char *orig_xdg_runtime_dir = NULL;
2262 nih_local char *cmd = NULL;
2263 char **output;
2264+ nih_local char *session_file = NULL;
2265
2266 TEST_GROUP ("job process table commands");
2267
2268@@ -16589,6 +17410,15 @@
2269 assert0 (unsetenv ("UPSTART_LOGDIR"));
2270 assert0 (unsetenv ("UPSTART_SESSION"));
2271
2272+ session_file = NIH_MUST (nih_sprintf (NULL, "%s/upstart/sessions/%d.session",
2273+ runtimedir, (int)upstart_pid));
2274+ unlink (session_file);
2275+ session_file = NIH_MUST (nih_sprintf (NULL, "%s/upstart/sessions", runtimedir));
2276+ TEST_EQ (rmdir (session_file), 0);
2277+ session_file = NIH_MUST (nih_sprintf (NULL, "%s/upstart", runtimedir));
2278+ TEST_EQ (rmdir (session_file), 0);
2279+ TEST_EQ (rmdir (runtimedir), 0);
2280+
2281 if (orig_xdg_runtime_dir) {
2282 /* restore */
2283 setenv ("XDG_RUNTIME_DIR", orig_xdg_runtime_dir, 1);
2284@@ -16675,6 +17505,7 @@
2285 test_job_env ();
2286 test_reexec ();
2287 test_list_sessions ();
2288+ test_quiesce ();
2289
2290 if (in_chroot () && !dbus_configured ()) {
2291 fprintf(stderr, "\n\n"

Subscribers

People subscribed via source and target branches