Merge lp:~upstart-devel/upstart/stateful-reexec into lp:upstart

Proposed by James Hunt
Status: Merged
Merged at revision: 1382
Proposed branch: lp:~upstart-devel/upstart/stateful-reexec
Merge into: lp:upstart
Diff against target: 15453 lines (+11010/-675)
41 files modified
ChangeLog (+16/-0)
configure.ac (+33/-0)
extra/man/upstart-udev-bridge.8 (+4/-1)
init/Makefile.am (+64/-33)
init/blocked.c (+53/-0)
init/blocked.h (+8/-0)
init/conf.c (+93/-35)
init/conf.h (+12/-4)
init/control.c (+145/-2)
init/control.h (+15/-0)
init/event.c (+331/-0)
init/event.h (+22/-0)
init/event_operator.c (+175/-7)
init/event_operator.h (+7/-4)
init/job.c (+726/-7)
init/job.h (+14/-2)
init/job_class.c (+712/-3)
init/job_class.h (+36/-1)
init/job_process.c (+44/-7)
init/job_process.h (+6/-0)
init/log.c (+213/-15)
init/log.h (+13/-5)
init/main.c (+114/-26)
init/parse_job.c (+42/-1)
init/parse_job.h (+5/-0)
init/process.c (+223/-0)
init/process.h (+38/-3)
init/session.c (+298/-33)
init/session.h (+19/-6)
init/state.c (+2008/-0)
init/state.h (+1226/-0)
init/tests/test_conf.c (+1/-43)
init/tests/test_event_operator.c (+92/-1)
init/tests/test_job.c (+215/-60)
init/tests/test_job_process.c (+508/-291)
init/tests/test_log.c (+19/-35)
init/tests/test_state.c (+2637/-0)
init/tests/test_util.c (+27/-0)
init/tests/test_util.h (+304/-0)
util/man/telinit.8 (+4/-3)
util/tests/test_initctl.c (+488/-47)
To merge this branch: bzr merge lp:~upstart-devel/upstart/stateful-reexec
Reviewer Review Type Date Requested Status
Steve Langasek Approve
Review via email: mp+133275@code.launchpad.net

This proposal supersedes a proposal from 2012-11-07.

Description of the change

* Added stateful re-exec support such that when Upstart is asked
  to restart itself using 'telinit u', the new instance of PID 1
  will retain knowledge of all system jobs and running instances.

See also: https://wiki.ubuntu.com/FoundationsTeam/Specs/QuantalUpstartStatefulReexec

To post a comment you must log in.
Revision history for this message
Steve Langasek (vorlon) wrote :
Download full text (5.5 KiB)

Hi James,

Reviewing just the parts of the diff that are new since the last time I looked at the branch.

This part of init/session.c looks strange:

+ if (getenv ("UPSTART_TESTS"))
+ return;
+
        if (conf_source_reload (source) < 0) {
                NihError *err;

The commit message only says:
    - session_create_conf_source(): Don't reload conf sources when in
      testing mode.
without explaining why.

In init/tests/test_util.c:

+ * Returns 0 if strings are identical or both NULL, else 1.
+ **/
+int
+string_check (const char *a, const char *b)
+{
+ if ((a == b) && !a)
+ return 0;
+

This '&& !a' seems unnecessary - surely if a == b and are /not/ null, there's no need to call strcmp to confirm that the strings are equal.

=== modified file 'init/blocked.c'
--- init/blocked.c 2012-07-30 14:07:37 +0000
+++ init/blocked.c 2012-11-13 06:01:51 +0000
@@ -131,6 +131,9 @@ blocked_type_enum_to_str (BlockedType ty
 BlockedType
 blocked_type_str_to_enum (const char *type)
 {
+ if (! type)
+ goto error;
+
        state_str_to_enum (BLOCKED_JOB, type);
        state_str_to_enum (BLOCKED_EVENT, type);
        state_str_to_enum (BLOCKED_EMIT_METHOD, type);
@@ -141,5 +144,6 @@ blocked_type_str_to_enum (const char *ty
        state_str_to_enum (BLOCKED_INSTANCE_STOP_METHOD, type);
        state_str_to_enum (BLOCKED_INSTANCE_RESTART_METHOD, type);

+error:
        return -1;
 }

The above is documented as "Made resilient to error conditions". But this doesn't appear to be a real-world error condition at all; the only place in the code that blocked_type_str_to_enum() is called is in state.c:

        if (! state_get_json_string_var_strict (json, "type", NULL, blocked_type_str))
                goto error;

        blocked_type = blocked_type_str_to_enum (blocked_type_str);

This means there's no possibility of the function ever being called with a null argument (and likewise for the other str_to_enum functions), so this must be here just for the test suite. I don't think it's a good idea to complicate either the code or the test suite by handling errors that the code is written to never allow! I've pushed a branch to address this at
lp:~vorlon/upstart/stateful-reexec-dont-shape-code-to-impossible-tests and raised an MP for your consideration.

=== modified file 'init/job_process.c'
--- init/job_process.c 2012-08-25 08:53:37 +0000
+++ init/job_process.c 2012-11-13 10:47:50 +0000
@@ -211,7 +211,7 @@ job_process_run (Job *job,
                while (p && (*p == '\n'))
                        p++;

- if ((! nl) || (! *p)) {
+ if ((! nl) || (p && ! *p)) {
                        /* Strip off the newline(s) */
                        if (nl)
                                *nl = '\0';

This change is documented as "Stop potential NULL-pointer dereference". But here's the full context:

                p = nl = strchr (script, '\n');
                while (p && (*p == '\n'))
                        p++;

                if ((! nl) || (p && ! *p)) {
                        /* Strip off the newline(s) */
                        if (nl)
     ...

Read more...

review: Approve
1474. By James Hunt

Sync with lp:~upstart-devel/upstart/stateful-reexec.

1475. By James Hunt

* init/job_class.c: Formatting.
* init/log.c: Corrected documentation for log_unflushed_files.
* init/state.h: Explained why log_unflushed_files is not currently
  serialised.

1476. By James Hunt

* extra/man/upstart-udev-bridge.8: Corrected explanation of actions in
  udev events (thanks to Stefan Bader for pointing out this problem).
* init/blocked.c: blocked_type_str_to_enum(): Assert argument.
* init/job_class.c: job_class_expect_type_str_to_enum(): Assert
  argument.
* init/job_process.c: job_process_run(): Reverted unnecessary check on 'p'.
* init/log.c: log_deserialise(): Added cast and explanation why return code
  from state_toggle_cloexec() not checked.
* init/process.c: process_type_str_to_enum(): Assert argument.
* init/session.c:
  - session_create_conf_source(): Removed redundant getenv() call.
  - session_deserialise(): Removed incorrect uid_t initialisation.
* init/state.h:
  - state_get_json_str_array_generic(): Actually use env parameter.
  - Remove warn_unused_result from state_toggle_cloexec() to avoid compile
    failure with strict flags.
* init/tests/test_state.c:
  - Use rlim_t.
  - test_enums():
    - Don't call *_str_to_enum() if previous *_enum_to_str() failed.
    - Add additional checks for invalid enum values.
  - main(): Remove redundant setenv().
* init/tests/test_util.c: string_check(): Make initial test less
  cryptic.

Revision history for this message
James Hunt (jamesodhunt) wrote :
Download full text (6.9 KiB)

> Hi James,
>
> Reviewing just the parts of the diff that are new since the last time I looked at the branch.
>
> This part of init/session.c looks strange:
>
> + if (getenv ("UPSTART_TESTS"))
> + return;
> +
> if (conf_source_reload (source) < 0) {
> NihError *err;
>
> The commit message only says:
> - session_create_conf_source(): Don't reload conf sources when in
> testing mode.
> without explaining why.

I've now removed this: it was required for an earlier iteration of the test code, but is now redundant.

>
> In init/tests/test_util.c:
>
> + * Returns 0 if strings are identical or both NULL, else 1.
> + **/
> +int
> +string_check (const char *a, const char *b)
> +{
> + if ((a == b) && !a)
> + return 0;
> +
>
> This '&& !a' seems unnecessary - surely if a == b and are /not/ null, there's no need to call strcmp to confirm that the strings are equal.

That test is a just a more compact way of saying:

    if (a == NULL && b == NULL)

... such that -- considered with the following test -- we never pass NULL to strcmp(). However, it is slightly cryptic, so for symmetry with the test that follows it I've changed the test to:

    if (!a && !b)

>
>
> === modified file 'init/blocked.c'
> --- init/blocked.c 2012-07-30 14:07:37 +0000
> +++ init/blocked.c 2012-11-13 06:01:51 +0000
> @@ -131,6 +131,9 @@ blocked_type_enum_to_str (BlockedType ty
> BlockedType
> blocked_type_str_to_enum (const char *type)
> {
> + if (! type)
> + goto error;
> +
> state_str_to_enum (BLOCKED_JOB, type);
> state_str_to_enum (BLOCKED_EVENT, type);
> state_str_to_enum (BLOCKED_EMIT_METHOD, type);
> @@ -141,5 +144,6 @@ blocked_type_str_to_enum (const char *ty
> state_str_to_enum (BLOCKED_INSTANCE_STOP_METHOD, type);
> state_str_to_enum (BLOCKED_INSTANCE_RESTART_METHOD, type);
>
> +error:
> return -1;
> }
>
>
> The above is documented as "Made resilient to error conditions". But this doesn't appear to be a real-world error condition at all; the only place in the code that blocked_type_str_to_enum() is called is in state.c:
>
> if (! state_get_json_string_var_strict (json, "type", NULL, blocked_type_str))
> goto error;
>
> blocked_type = blocked_type_str_to_enum (blocked_type_str);
>
> This means there's no possibility of the function ever being called with a null argument (and likewise for the other str_to_enum functions), so this must be here just for the test suite. I don't think it's a good idea to complicate either the code or the test suite by handling errors that the code is written to never allow! I've pushed a branch to address this at
> lp:~vorlon/upstart/stateful-reexec-dont-shape-code-to-impossible-tests and raised an MP for your consideration.

Thanks. I've changed this and the other *_str_to_enum() functions to assert their argument and updated the tests accordingly.

>
> === modified file 'init/job_process.c'
> --- init/job_process.c 2012-08-25 08:53:37 +0000
> +++ init/job_process.c 2012-11-13 10:47:50 +0000
> @@ -211,7 +211,7 @@ job_process_run (...

Read more...

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'ChangeLog'
2--- ChangeLog 2012-10-22 13:54:50 +0000
3+++ ChangeLog 2012-11-14 14:49:20 +0000
4@@ -1,3 +1,19 @@
5+2012-11-07 James Hunt <james.hunt@ubuntu.com>
6+
7+ * Added stateful re-exec support such that when Upstart is asked
8+ to restart itself using 'telinit u', the new instance of PID 1
9+ will retain knowledge of all system jobs and running instances.
10+
11+2012-09-10 James Hunt <james.hunt@ubuntu.com>
12+
13+ * Merged lp:~jconti/upstart/fix_empty_chroot.
14+
15+2012-05-23 James Hunt <james.hunt@ubuntu.com>
16+
17+ * init/main.c: Add in "bare" re-exec handling from Ubuntu
18+ branch.
19+ * init/main.c: Unhide "restart" option.
20+
21 2012-10-22 James Hunt <james.hunt@ubuntu.com>
22
23 * init/parse_job.c: stanza_kill(): Actually save parsed
24
25=== modified file 'configure.ac'
26--- configure.ac 2012-03-22 11:37:20 +0000
27+++ configure.ac 2012-11-14 14:49:20 +0000
28@@ -32,6 +32,24 @@
29 PKG_CHECK_MODULES([DBUS], [dbus-1 >= 1.2.16])
30 PKG_CHECK_MODULES([UDEV], [libudev >= 146], [have_udev=yes], [have_udev=no])
31
32+# Reasons for requiring this library version:
33+#
34+# 1) RFC 4627, the JSON "memo" (it is *NOT* a standard!) helpfully fails
35+# to specify the maximum size of an integer type. And yet, JSON is
36+# supposedly a subset of ECMA-262, which specifies a Number type to
37+# be 64-bits.
38+#
39+# The loose JSON "memo" might expalain why older versions of JSON-C encode
40+# a JSON Number in sizoef(int) bytes which is only 32-bits on 32-bit
41+# systems. This is not acceptable for Upstart which needs to be able
42+# to encode 'size_t' and 'unsigned long' types which can be larger
43+# than 'int'.
44+#
45+# 2) New json_tokener_parse_verbose() function required to detect invalid
46+# JSON (!)
47+#
48+PKG_CHECK_MODULES([JSON], [json >= 0.10])
49+
50 AM_CONDITIONAL([HAVE_UDEV], [test "$have_udev" = yes])
51
52 # Checks for header files.
53@@ -42,6 +60,21 @@
54 AM_PROG_CC_C_O
55 NIH_C_THREAD
56
57+AC_CHECK_SIZEOF(int)
58+AC_CHECK_SIZEOF(uid_t)
59+AC_CHECK_SIZEOF(pid_t)
60+AC_CHECK_SIZEOF(time_t)
61+AC_CHECK_SIZEOF(mode_t)
62+AC_CHECK_SIZEOF(size_t)
63+AC_CHECK_SIZEOF(ssize_t)
64+
65+# Unlikely to hit this limit for a while, but it pays to be safe.
66+for type in int uid_t pid_t time_t mode_t size_t ssize_t
67+do
68+ eval value="\$ac_cv_sizeof_${type}"
69+ test "$value" -gt 8 && AC_MSG_ERROR([type $type is $value bytes, larger than JSON-C can represent])
70+done
71+
72 # Checks for library functions.
73
74 # Other checks
75
76=== modified file 'extra/man/upstart-udev-bridge.8'
77--- extra/man/upstart-udev-bridge.8 2011-12-15 16:11:14 +0000
78+++ extra/man/upstart-udev-bridge.8 2012-11-14 14:49:20 +0000
79@@ -16,7 +16,10 @@
80 events for them.
81
82 It emits events which match the pattern "\fIS\fP\-device\-\fIA\fP" where
83-\(aqS\(aq is the udev \fIsubsystem\fP and \(aqA\(aq is the udev \fIaction\fP.
84+\(aqS\(aq is the udev \fIsubsystem\fP and \(aqA\(aq is one of
85+\fIadded\fR, \fIchanged\fR or \fIremoved\fR,
86+which correspond to the past tense of the udev \(aqaction\(aq.
87+
88 See \fBudev\fP(7) and for further details.
89
90 Assuming \fI/sys\fP is mounted, possible values for \fIsubsystem\fP for
91
92=== modified file 'init/Makefile.am'
93--- init/Makefile.am 2012-10-22 13:54:50 +0000
94+++ init/Makefile.am 2012-11-14 14:49:20 +0000
95@@ -5,7 +5,8 @@
96 AM_CFLAGS = \
97 $(NIH_CFLAGS) \
98 $(NIH_DBUS_CFLAGS) \
99- $(DBUS_CFLAGS)
100+ $(DBUS_CFLAGS) \
101+ $(JSON_CFLAGS)
102
103 AM_CPPFLAGS = \
104 -DLOCALEDIR="\"$(localedir)\"" \
105@@ -41,6 +42,7 @@
106 environ.c environ.h \
107 process.c process.h \
108 session.c session.h \
109+ state.c state.h \
110 job_class.c job_class.h \
111 job_process.c job_process.h \
112 job.c job.h \
113@@ -62,6 +64,7 @@
114 $(NIH_LIBS) \
115 $(NIH_DBUS_LIBS) \
116 $(DBUS_LIBS) \
117+ $(JSON_LIBS) \
118 -lrt
119
120
121@@ -109,7 +112,6 @@
122 --default-interface=com.ubuntu.Upstart0_6.Instance \
123 --output=$@ $<
124
125-
126 # These have to be built sources because we can't compile object files
127 # without the header file existing first
128 BUILT_SOURCES = \
129@@ -125,6 +127,8 @@
130
131 EXTRA_DIST = init.supp
132
133+test_util_SOURCES = \
134+ tests/test_util.c tests/test_util.h
135
136 TESTS = \
137 test_system \
138@@ -134,6 +138,7 @@
139 test_job_process \
140 test_job \
141 test_log \
142+ test_state \
143 test_event \
144 test_event_operator \
145 test_blocked \
146@@ -156,41 +161,44 @@
147 environ.o \
148 $(NIH_LIBS)
149
150-test_process_SOURCES = tests/test_process.c
151+test_process_SOURCES = tests/test_process.c $(test_util_SOURCES)
152 test_process_LDADD = \
153 system.o environ.o process.o \
154 job_class.o job_process.o job.o event.o event_operator.o blocked.o \
155 parse_job.o parse_conf.o conf.o control.o \
156- session.o log.o \
157+ session.o log.o state.o \
158 com.ubuntu.Upstart.o \
159 com.ubuntu.Upstart.Job.o com.ubuntu.Upstart.Instance.o \
160 $(NIH_LIBS) \
161 $(NIH_DBUS_LIBS) \
162- $(DBUS_LIBS)
163+ $(DBUS_LIBS) \
164+ $(JSON_LIBS)
165
166 test_job_class_SOURCES = tests/test_job_class.c
167 test_job_class_LDADD = \
168 system.o environ.o process.o \
169 job_class.o job_process.o job.o event.o event_operator.o blocked.o \
170 parse_job.o parse_conf.o conf.o control.o \
171- session.o log.o \
172+ session.o log.o state.o \
173 com.ubuntu.Upstart.o \
174 com.ubuntu.Upstart.Job.o com.ubuntu.Upstart.Instance.o \
175 $(NIH_LIBS) \
176 $(NIH_DBUS_LIBS) \
177- $(DBUS_LIBS)
178+ $(DBUS_LIBS) \
179+ $(JSON_LIBS)
180
181-test_job_process_SOURCES = tests/test_job_process.c
182+test_job_process_SOURCES = tests/test_job_process.c $(test_util_SOURCES)
183 test_job_process_LDADD = \
184 system.o environ.o process.o \
185 job_class.o job_process.o job.o event.o event_operator.o blocked.o \
186 parse_job.o parse_conf.o conf.o control.o \
187- session.o log.o \
188+ session.o log.o state.o \
189 com.ubuntu.Upstart.o \
190 com.ubuntu.Upstart.Job.o com.ubuntu.Upstart.Instance.o \
191 $(NIH_LIBS) \
192 $(NIH_DBUS_LIBS) \
193 $(DBUS_LIBS) \
194+ $(JSON_LIBS) \
195 -lutil
196
197 test_job_SOURCES = tests/test_job.c
198@@ -198,24 +206,40 @@
199 system.o environ.o process.o \
200 job_class.o job_process.o job.o event.o event_operator.o blocked.o \
201 parse_job.o parse_conf.o conf.o control.o \
202- session.o log.o \
203+ session.o log.o state.o \
204 com.ubuntu.Upstart.o \
205 com.ubuntu.Upstart.Job.o com.ubuntu.Upstart.Instance.o \
206 $(NIH_LIBS) \
207 $(NIH_DBUS_LIBS) \
208- $(DBUS_LIBS)
209+ $(DBUS_LIBS) \
210+ $(JSON_LIBS)
211
212-test_log_SOURCES = tests/test_log.c
213+test_log_SOURCES = tests/test_log.c $(test_util_SOURCES)
214 test_log_LDADD = \
215 system.o environ.o process.o \
216 job_class.o job_process.o job.o event.o event_operator.o blocked.o \
217 parse_job.o parse_conf.o conf.o control.o \
218- session.o log.o \
219- com.ubuntu.Upstart.o \
220- com.ubuntu.Upstart.Job.o com.ubuntu.Upstart.Instance.o \
221- $(NIH_LIBS) \
222- $(NIH_DBUS_LIBS) \
223- $(DBUS_LIBS) \
224+ session.o log.o state.o \
225+ com.ubuntu.Upstart.o \
226+ com.ubuntu.Upstart.Job.o com.ubuntu.Upstart.Instance.o \
227+ $(NIH_LIBS) \
228+ $(NIH_DBUS_LIBS) \
229+ $(DBUS_LIBS) \
230+ $(JSON_LIBS) \
231+ -lutil
232+
233+test_state_SOURCES = tests/test_state.c $(test_util_SOURCES)
234+test_state_LDADD = \
235+ system.o environ.o process.o \
236+ job_class.o job_process.o job.o event.o event_operator.o blocked.o \
237+ parse_job.o parse_conf.o conf.o control.o \
238+ session.o log.o state.o \
239+ com.ubuntu.Upstart.o \
240+ com.ubuntu.Upstart.Job.o com.ubuntu.Upstart.Instance.o \
241+ $(NIH_LIBS) \
242+ $(NIH_DBUS_LIBS) \
243+ $(DBUS_LIBS) \
244+ $(JSON_LIBS) \
245 -lutil
246
247 test_event_SOURCES = tests/test_event.c
248@@ -223,84 +247,91 @@
249 system.o environ.o process.o \
250 job_class.o job_process.o job.o event.o event_operator.o blocked.o \
251 parse_job.o parse_conf.o conf.o control.o \
252- session.o log.o \
253+ session.o log.o state.o \
254 com.ubuntu.Upstart.o \
255 com.ubuntu.Upstart.Job.o com.ubuntu.Upstart.Instance.o \
256 $(NIH_LIBS) \
257 $(NIH_DBUS_LIBS) \
258- $(DBUS_LIBS)
259+ $(DBUS_LIBS) \
260+ $(JSON_LIBS)
261
262 test_event_operator_SOURCES = tests/test_event_operator.c
263 test_event_operator_LDADD = \
264 system.o environ.o process.o \
265 job_class.o job_process.o job.o event.o event_operator.o blocked.o \
266 parse_job.o parse_conf.o conf.o control.o \
267- session.o log.o \
268+ session.o log.o state.o \
269 com.ubuntu.Upstart.o \
270 com.ubuntu.Upstart.Job.o com.ubuntu.Upstart.Instance.o \
271 $(NIH_LIBS) \
272 $(NIH_DBUS_LIBS) \
273- $(DBUS_LIBS)
274+ $(DBUS_LIBS) \
275+ $(JSON_LIBS)
276
277 test_blocked_SOURCES = tests/test_blocked.c
278 test_blocked_LDADD = \
279 system.o environ.o process.o \
280 job_class.o job_process.o job.o event.o event_operator.o blocked.o \
281 parse_job.o parse_conf.o conf.o control.o \
282- session.o log.o \
283+ session.o log.o state.o \
284 com.ubuntu.Upstart.o \
285 com.ubuntu.Upstart.Job.o com.ubuntu.Upstart.Instance.o \
286 $(NIH_LIBS) \
287 $(NIH_DBUS_LIBS) \
288- $(DBUS_LIBS)
289+ $(DBUS_LIBS) \
290+ $(JSON_LIBS)
291
292 test_parse_job_SOURCES = tests/test_parse_job.c
293 test_parse_job_LDADD = \
294 system.o environ.o process.o \
295 job_class.o job_process.o job.o event.o event_operator.o blocked.o \
296 parse_job.o parse_conf.o conf.o control.o \
297- session.o log.o \
298+ session.o log.o state.o \
299 com.ubuntu.Upstart.o \
300 com.ubuntu.Upstart.Job.o com.ubuntu.Upstart.Instance.o \
301 $(NIH_LIBS) \
302 $(NIH_DBUS_LIBS) \
303- $(DBUS_LIBS)
304+ $(DBUS_LIBS) \
305+ $(JSON_LIBS)
306
307 test_parse_conf_SOURCES = tests/test_parse_conf.c
308 test_parse_conf_LDADD = \
309 system.o environ.o process.o \
310 job_class.o job_process.o job.o event.o event_operator.o blocked.o \
311 parse_job.o parse_conf.o conf.o control.o \
312- session.o log.o \
313+ session.o log.o state.o \
314 com.ubuntu.Upstart.o \
315 com.ubuntu.Upstart.Job.o com.ubuntu.Upstart.Instance.o \
316 $(NIH_LIBS) \
317 $(NIH_DBUS_LIBS) \
318- $(DBUS_LIBS)
319+ $(DBUS_LIBS) \
320+ $(JSON_LIBS)
321
322-test_conf_SOURCES = tests/test_conf.c
323+test_conf_SOURCES = tests/test_conf.c $(test_util_SOURCES)
324 test_conf_LDADD = \
325 system.o environ.o process.o \
326 job_class.o job_process.o job.o event.o event_operator.o blocked.o \
327 parse_job.o parse_conf.o conf.o control.o \
328- session.o log.o \
329+ session.o log.o state.o \
330 com.ubuntu.Upstart.o \
331 com.ubuntu.Upstart.Job.o com.ubuntu.Upstart.Instance.o \
332 $(NIH_LIBS) \
333 $(NIH_DBUS_LIBS) \
334- $(DBUS_LIBS)
335+ $(DBUS_LIBS) \
336+ $(JSON_LIBS)
337
338 test_control_SOURCES = tests/test_control.c
339 test_control_LDADD = \
340 system.o environ.o process.o \
341 job_class.o job_process.o job.o event.o event_operator.o blocked.o \
342 parse_job.o parse_conf.o conf.o control.o \
343- session.o log.o \
344+ session.o log.o state.o \
345 com.ubuntu.Upstart.o \
346 com.ubuntu.Upstart.Job.o com.ubuntu.Upstart.Instance.o \
347 $(NIH_LIBS) \
348 $(NIH_DBUS_LIBS) \
349- $(DBUS_LIBS)
350+ $(DBUS_LIBS) \
351+ $(JSON_LIBS)
352
353
354 install-data-local:
355
356=== modified file 'init/blocked.c'
357--- init/blocked.c 2009-06-23 09:29:35 +0000
358+++ init/blocked.c 2012-11-14 14:49:20 +0000
359@@ -92,3 +92,56 @@
360
361 return blocked;
362 }
363+
364+
365+/**
366+ * blocked_enum_to_str:
367+ *
368+ * @type: BlockedType.
369+ *
370+ * Convert Blocked to a string representation.
371+ *
372+ * Returns: string representation of @type, or NULL if not known.
373+ **/
374+const char *
375+blocked_type_enum_to_str (BlockedType type)
376+{
377+ state_enum_to_str (BLOCKED_JOB, type);
378+ state_enum_to_str (BLOCKED_EVENT, type);
379+ state_enum_to_str (BLOCKED_EMIT_METHOD, type);
380+ state_enum_to_str (BLOCKED_JOB_START_METHOD, type);
381+ state_enum_to_str (BLOCKED_JOB_STOP_METHOD, type);
382+ state_enum_to_str (BLOCKED_JOB_RESTART_METHOD, type);
383+ state_enum_to_str (BLOCKED_INSTANCE_START_METHOD, type);
384+ state_enum_to_str (BLOCKED_INSTANCE_STOP_METHOD, type);
385+ state_enum_to_str (BLOCKED_INSTANCE_RESTART_METHOD, type);
386+
387+ return NULL;
388+}
389+
390+/**
391+ * blocked_type_str_to_enum:
392+ *
393+ * @type: string BlockedType value.
394+ *
395+ * Convert @type back into enum value.
396+ *
397+ * Returns: BlockedType representation of @type, or -1 if not known.
398+ **/
399+BlockedType
400+blocked_type_str_to_enum (const char *type)
401+{
402+ nih_assert (type);
403+
404+ state_str_to_enum (BLOCKED_JOB, type);
405+ state_str_to_enum (BLOCKED_EVENT, type);
406+ state_str_to_enum (BLOCKED_EMIT_METHOD, type);
407+ state_str_to_enum (BLOCKED_JOB_START_METHOD, type);
408+ state_str_to_enum (BLOCKED_JOB_STOP_METHOD, type);
409+ state_str_to_enum (BLOCKED_JOB_RESTART_METHOD, type);
410+ state_str_to_enum (BLOCKED_INSTANCE_START_METHOD, type);
411+ state_str_to_enum (BLOCKED_INSTANCE_STOP_METHOD, type);
412+ state_str_to_enum (BLOCKED_INSTANCE_RESTART_METHOD, type);
413+
414+ return -1;
415+}
416
417=== modified file 'init/blocked.h'
418--- init/blocked.h 2009-06-23 09:29:35 +0000
419+++ init/blocked.h 2012-11-14 14:49:20 +0000
420@@ -79,6 +79,14 @@
421 Blocked *blocked_new (const void *parent, BlockedType type, void *data)
422 __attribute__ ((warn_unused_result, malloc));
423
424+const char *
425+blocked_type_enum_to_str (BlockedType type)
426+ __attribute__ ((warn_unused_result));
427+
428+BlockedType
429+blocked_type_str_to_enum (const char *type)
430+ __attribute__ ((warn_unused_result));
431+
432 NIH_END_EXTERN
433
434 #endif /* INIT_BLOCKED_H */
435
436=== modified file 'init/conf.c'
437--- init/conf.c 2011-06-06 17:05:11 +0000
438+++ init/conf.c 2012-11-14 14:49:20 +0000
439@@ -952,8 +952,8 @@
440 **/
441 static int
442 conf_reload_path (ConfSource *source,
443- const char *path,
444- const char *override_path)
445+ const char *path,
446+ const char *override_path)
447 {
448 ConfFile *file = NULL;
449 nih_local char *buf = NULL;
450@@ -1209,52 +1209,58 @@
451 }
452
453 void
454-debug_show_job_class (const JobClass *job)
455+debug_show_job_class (const JobClass *class)
456 {
457- int i;
458- char **env = (char **)job->env;
459- char **export = (char **)job->export;
460+ nih_local char *start_on = NULL;
461+ nih_local char *stop_on = NULL;
462
463- nih_assert (job);
464+ nih_assert (class);
465
466 nih_debug ("JobClass %p: name='%s', path='%s', task=%d, "
467 "respawn=%d, console=%x, deleted=%d, debug=%d",
468- job, job->name, job->path, job->task,
469- job->respawn, job->console, job->deleted, job->debug);
470-
471- nih_debug ("\tstart_on=%p, stop_on=%p, emits=%p, process=%p",
472- job->start_on, job->stop_on, job->emits, job->process);
473+ class, class->name, class->path, class->task,
474+ class->respawn, class->console, class->deleted, class->debug);
475+
476+ if (class->start_on) start_on = event_operator_collapse (class->start_on);
477+ if (class->stop_on) stop_on = event_operator_collapse (class->stop_on);
478+
479+ nih_debug ("\tstart_on=%p (%s), stop_on=%p (%s), emits=%p, process=%p",
480+ class->start_on, start_on ? start_on : "",
481+ class->stop_on, stop_on ? stop_on : "",
482+ class->emits, class->process);
483+
484+ for (int i = 0; i < PROCESS_LAST; i++) {
485+ if (class->process[i]) {
486+ nih_debug ("Process[%d]=%p: script=%d, cmd='%s'",
487+ i, class->process[i],
488+ class->process[i]->script,
489+ class->process[i]->command);
490+ } else {
491+ nih_debug ("Process[%d]=%p",
492+ i, class->process[i]);
493+ }
494+
495+
496+ }
497
498 nih_debug ("\tauthor='%s', description='%s'",
499- job->author, job->description);
500+ class->author, class->description);
501
502- if (env && *env) {
503- nih_debug ("\tenv:");
504- i = 0;
505- while ( *env ) {
506- nih_debug ("\t\tenv[%d]='%s' (len=%u+1)",
507- i, *env, strlen (*env));
508- env++;
509- ++i;
510- }
511+ if (class->env && *class->env) {
512+ nih_local char *env = state_collapse_env ((const char **)class->env);
513+ nih_debug ("\tenv:%s", env);
514 } else {
515 nih_debug ("\tenv: none.");
516 }
517
518-
519- if (export && *export) {
520- nih_debug ("\texport:");
521- i = 0;
522- while ( *export ) {
523- nih_debug ("\t\tenv[%d]='%s' (len=%u+1)",
524- i, *export, strlen (*export));
525- export++;
526- ++i;
527- }
528- }
529- else {
530+ if (class->export && *class->export) {
531+ nih_local char *export = state_collapse_env ((const char **)class->export);
532+ nih_debug ("\texport:%s", export);
533+ } else {
534 nih_debug ("\texport: none");
535 }
536+
537+ debug_show_jobs (class->instances);
538 }
539
540 void
541@@ -1269,6 +1275,58 @@
542 }
543
544 void
545+debug_show_job (const Job *job)
546+{
547+ nih_local char *env = NULL;
548+ nih_local char *start_env = NULL;
549+ nih_local char *stop_env = NULL;
550+ nih_local char *stop_on = NULL;
551+
552+ nih_assert (job);
553+
554+ if (job->env)
555+ env = state_collapse_env ((const char **)job->env);
556+
557+ if (job->start_env)
558+ start_env = state_collapse_env ((const char **)job->start_env);
559+
560+ if (job->stop_env)
561+ stop_env = state_collapse_env ((const char **)job->stop_env);
562+
563+ if (job->stop_on)
564+ stop_on = event_operator_collapse (job->stop_on);
565+
566+ nih_debug ("Job %p: name=%s, class=%p (%s), path=%s, env='%s'"
567+ "start_env='%s', stop_env='%s', stop_on='%s', "
568+ "goal=%d, state=%d, failed=%d, exit_status=%d",
569+ job,
570+ job->name ? job->name : "",
571+ job->class, job->class->name,
572+ job->path,
573+ env ? env : "",
574+ start_env ? start_env : "",
575+ stop_env ? stop_env : "",
576+ stop_on ? stop_on : "",
577+ job->goal,
578+ job->state,
579+ job->failed,
580+ job->exit_status);
581+}
582+
583+void
584+debug_show_jobs (const NihHash *instances)
585+{
586+ nih_assert (instances);
587+
588+ nih_debug ("jobs:");
589+
590+ NIH_HASH_FOREACH (instances, iter) {
591+ Job *job = (Job *)iter;
592+ debug_show_job (job);
593+ }
594+}
595+
596+void
597 debug_show_event (const Event *event)
598 {
599 nih_assert (event);
600@@ -1308,7 +1366,7 @@
601 source, source->path, source->type, source->flag);
602
603 nih_debug ("ConfSource %p files (%d):", source,
604- debug_count_hash_entries (source->files));
605+ (int)debug_count_hash_entries (source->files));
606
607 NIH_HASH_FOREACH (source->files, file_iter) {
608 ConfFile *file = (ConfFile *)file_iter;
609
610=== modified file 'init/conf.h'
611--- init/conf.h 2011-06-06 12:52:08 +0000
612+++ init/conf.h 2012-11-14 14:49:20 +0000
613@@ -39,9 +39,9 @@
614 * what they define within themselves.
615 **/
616 typedef enum conf_source_type {
617- CONF_FILE,
618- CONF_DIR,
619- CONF_JOB_DIR,
620+ CONF_FILE, /* solitary file */
621+ CONF_DIR, /* FIXME: */
622+ CONF_JOB_DIR, /* directory tree of jobs */
623 } ConfSourceType;
624
625
626@@ -133,6 +133,7 @@
627 #ifdef DEBUG
628
629 /* used for debugging only */
630+#include "job.h"
631
632 size_t
633 debug_count_hash_entries (const NihHash *hash);
634@@ -142,7 +143,7 @@
635 __attribute__ ((unused));
636
637 void
638-debug_show_job_class (const JobClass *job)
639+debug_show_job_class (const JobClass *class)
640 __attribute__ ((unused));
641
642 void
643@@ -150,6 +151,13 @@
644 __attribute__ ((unused));
645
646 void
647+debug_show_job (const Job *job)
648+ __attribute__ ((unused));
649+
650+void
651+debug_show_jobs (const NihHash *instances)
652+ __attribute__ ((unused));
653+void
654 debug_show_event (const Event *event)
655 __attribute__ ((unused));
656
657
658=== modified file 'init/control.c'
659--- init/control.c 2012-03-16 21:02:13 +0000
660+++ init/control.c 2012-11-14 14:49:20 +0000
661@@ -54,6 +54,7 @@
662 #include "conf.h"
663 #include "control.h"
664 #include "errors.h"
665+#include "state.h"
666
667 #include "com.ubuntu.Upstart.h"
668
669@@ -62,6 +63,8 @@
670 static void control_disconnected (DBusConnection *conn);
671 static void control_register_all (DBusConnection *conn);
672
673+static void control_bus_flush (void);
674+
675 /**
676 * use_session_bus:
677 *
678@@ -188,7 +191,8 @@
679 void
680 control_server_close (void)
681 {
682- nih_assert (control_server != NULL);
683+ if (! control_server)
684+ return;
685
686 dbus_server_disconnect (control_server);
687 dbus_server_unref (control_server);
688@@ -292,7 +296,7 @@
689 *
690 * This function is called when the connection to the D-Bus system bus,
691 * or a client connection to our D-Bus server, is dropped and our reference
692- * is about to be list. We clear the connection from our current list
693+ * is about to be lost. We clear the connection from our current list
694 * and drop the control_bus global if relevant.
695 **/
696 static void
697@@ -301,6 +305,10 @@
698 nih_assert (conn != NULL);
699
700 if (conn == control_bus) {
701+ DBusError error;
702+
703+ dbus_error_init (&error);
704+
705 nih_warn (_("Disconnected from system bus"));
706
707 control_bus = NULL;
708@@ -368,6 +376,8 @@
709 nih_assert (message != NULL);
710
711 nih_info (_("Reloading configuration"));
712+
713+ /* This can only be called after deserialisation */
714 conf_reload ();
715
716 return 0;
717@@ -806,3 +816,136 @@
718
719 return 0;
720 }
721+
722+/**
723+ * control_bus_flush:
724+ *
725+ * Drain any remaining messages in the D-Bus queue.
726+ **/
727+static void
728+control_bus_flush (void)
729+{
730+ control_init ();
731+
732+ if (! control_bus)
733+ return;
734+
735+ while (dbus_connection_dispatch (control_bus) == DBUS_DISPATCH_DATA_REMAINS)
736+ ;
737+}
738+
739+/**
740+ * control_prepare_reexec:
741+ *
742+ * Prepare for a re-exec by allowing the bus connection to be retained
743+ * over re-exec and clearing all queued messages.
744+ **/
745+void
746+control_prepare_reexec (void)
747+{
748+ control_init ();
749+
750+ /* Necessary to disallow further commands but also to allow the
751+ * new instance to open the control server.
752+ */
753+ if (control_server)
754+ control_server_close ();
755+
756+ control_bus_flush ();
757+
758+}
759+
760+
761+/**
762+ * control_conn_to_index:
763+ *
764+ * @event: event.
765+ *
766+ * Convert a control (DBusConnection) connection to an index number
767+ * the list of control connections.
768+ *
769+ * Returns: connection index, or -1 on error.
770+ **/
771+int
772+control_conn_to_index (const DBusConnection *connection)
773+{
774+ int conn_index = 0;
775+ int found = FALSE;
776+
777+ nih_assert (connection);
778+
779+ NIH_LIST_FOREACH (control_conns, iter) {
780+ NihListEntry *entry = (NihListEntry *)iter;
781+ DBusConnection *conn = (DBusConnection *)entry->data;
782+
783+ if (connection == conn) {
784+ found = TRUE;
785+ break;
786+ }
787+
788+ conn_index++;
789+ }
790+ if (! found)
791+ return -1;
792+
793+ return conn_index;
794+}
795+
796+/**
797+ * control_conn_from_index:
798+ *
799+ * @conn_index: control connection index number.
800+ *
801+ * Lookup control connection based on index number.
802+ *
803+ * Returns: existing connection on success, or NULL if connection
804+ * not found.
805+ **/
806+DBusConnection *
807+control_conn_from_index (int conn_index)
808+{
809+ int i = 0;
810+
811+ nih_assert (conn_index >= 0);
812+ nih_assert (control_conns);
813+
814+ NIH_LIST_FOREACH (control_conns, iter) {
815+ NihListEntry *entry = (NihListEntry *)iter;
816+ DBusConnection *conn = (DBusConnection *)entry->data;
817+
818+ if (i == conn_index)
819+ return conn;
820+ i++;
821+ }
822+
823+ return NULL;
824+}
825+
826+/**
827+ * control_bus_release_name:
828+ *
829+ * Unregister well-known D-Bus name.
830+ *
831+ * Returns: 0 on success, -1 on raised error.
832+ **/
833+int
834+control_bus_release_name (void)
835+{
836+ DBusError error;
837+ int ret;
838+
839+ if (! control_bus)
840+ return 0;
841+
842+ dbus_error_init (&error);
843+ ret = dbus_bus_release_name (control_bus,
844+ DBUS_SERVICE_UPSTART,
845+ &error);
846+ if (ret < 0) {
847+ nih_dbus_error_raise (error.name, error.message);
848+ dbus_error_free (&error);
849+ return -1;
850+ }
851+
852+ return 0;
853+}
854
855=== modified file 'init/control.h'
856--- init/control.h 2011-06-06 17:05:11 +0000
857+++ init/control.h 2012-11-14 14:49:20 +0000
858@@ -23,10 +23,13 @@
859 #include <dbus/dbus.h>
860
861 #include <nih/macros.h>
862+#include <nih/list.h>
863
864 #include <nih-dbus/dbus_connection.h>
865 #include <nih-dbus/dbus_message.h>
866
867+#include <json.h>
868+
869 /**
870 * USE_SESSION_BUS_ENV:
871 *
872@@ -90,6 +93,18 @@
873
874 void control_handle_bus_type (void);
875
876+void control_prepare_reexec (void);
877+
878+int control_conn_to_index (const DBusConnection *connection)
879+ __attribute__ ((warn_unused_result));
880+
881+DBusConnection *
882+control_conn_from_index (int conn_index)
883+ __attribute__ ((warn_unused_result));
884+
885+int control_bus_release_name (void)
886+ __attribute__ ((warn_unused_result));
887+
888 NIH_END_EXTERN
889
890 #endif /* INIT_CONTROL_H */
891
892=== modified file 'init/event.c'
893--- init/event.c 2011-06-06 17:05:11 +0000
894+++ init/event.c 2012-11-14 14:49:20 +0000
895@@ -52,6 +52,14 @@
896 static void event_pending_handle_jobs (Event *event);
897 static void event_finished (Event *event);
898
899+static const char * event_progress_enum_to_str (EventProgress progress)
900+ __attribute__ ((warn_unused_result));
901+
902+static EventProgress
903+event_progress_str_to_enum (const char *name)
904+ __attribute__ ((warn_unused_result));
905+
906+extern json_object *json_events;
907
908 /**
909 * events:
910@@ -501,3 +509,326 @@
911
912 nih_free (event);
913 }
914+
915+/**
916+ * event_serialise:
917+ * @event: event to serialise.
918+ *
919+ * Convert @event into a JSON representation for serialisation.
920+ * Caller must free returned value using json_object_put().
921+ *
922+ * Note that event->blocking is NOT serialised. Instead, those objects
923+ * which event->blocking refers to encode the fact that they are blocked
924+ * on the event (index in the JSON) in question. Deserialisation
925+ * restores event->blocking via a 2-pass technique. event->blockers is
926+ * recorded to allow a double-check.
927+ *
928+ * Returns: JSON-serialised Event object, or NULL on error.
929+ **/
930+json_object *
931+event_serialise (const Event *event)
932+{
933+ json_object *json;
934+ int session_index;
935+
936+ nih_assert (event);
937+ nih_assert (event->name);
938+
939+ json = json_object_new_object ();
940+ if (! json)
941+ return NULL;
942+
943+ session_index = session_get_index (event->session);
944+ if (session_index < 0)
945+ goto error;
946+
947+ if (! state_set_json_int_var (json, "session", session_index))
948+ goto error;
949+
950+ if (! state_set_json_string_var_from_obj (json, event, name))
951+ goto error;
952+
953+ if (event->env) {
954+ if (! state_set_json_str_array_from_obj (json, event, env))
955+ goto error;
956+ }
957+
958+ if (! state_set_json_int_var_from_obj (json, event, fd))
959+ goto error;
960+
961+ if (! state_set_json_enum_var (json,
962+ event_progress_enum_to_str,
963+ "progress", event->progress))
964+ goto error;
965+
966+ if (! state_set_json_int_var_from_obj (json, event, failed))
967+ goto error;
968+
969+ if (! state_set_json_int_var_from_obj (json, event, blockers))
970+ goto error;
971+
972+ if (! NIH_LIST_EMPTY (&event->blocking)) {
973+ json_object *json_blocking;
974+
975+ json_blocking = state_serialise_blocking (&event->blocking);
976+ if (! json_blocking)
977+ goto error;
978+
979+ json_object_object_add (json, "blocking", json_blocking);
980+ }
981+
982+ return json;
983+
984+error:
985+ json_object_put (json);
986+ return NULL;
987+}
988+
989+/**
990+ * event_serialise_all:
991+ *
992+ * Convert existing Event objects to JSON representation.
993+ *
994+ * Returns: JSON object containing array of Events, or NULL on error.
995+ **/
996+json_object *
997+event_serialise_all (void)
998+{
999+ json_object *json;
1000+
1001+ event_init ();
1002+
1003+ json = json_object_new_array ();
1004+ if (! json)
1005+ return NULL;
1006+
1007+ NIH_LIST_FOREACH (events, iter) {
1008+ Event *event = (Event *)iter;
1009+ json_object *json_event;
1010+
1011+ json_event = event_serialise (event);
1012+
1013+ if (! json_event)
1014+ goto error;
1015+
1016+ json_object_array_add (json, json_event);
1017+ }
1018+
1019+ return json;
1020+
1021+error:
1022+ json_object_put (json);
1023+ return NULL;
1024+}
1025+
1026+/**
1027+ * event_deserialise:
1028+ * @json: JSON-serialised Event object to deserialise.
1029+ *
1030+ * Convert @json into an Event object.
1031+ *
1032+ * Returns: Event object, or NULL on error.
1033+ **/
1034+Event *
1035+event_deserialise (json_object *json)
1036+{
1037+ json_object *json_env;
1038+ Event *event = NULL;
1039+ nih_local char *name = NULL;
1040+ char **env = NULL;
1041+ int session_index = -1;
1042+
1043+ nih_assert (json);
1044+
1045+ if (! state_check_json_type (json, object))
1046+ return NULL;
1047+
1048+ if (! state_get_json_string_var_strict (json, "name", NULL, name))
1049+ goto error;
1050+
1051+ if (json_object_object_get (json, "env")) {
1052+ if (! state_get_json_var_full (json, "env", array, json_env))
1053+ goto error;
1054+
1055+ if (! state_deserialise_str_array (NULL, json_env, &env))
1056+ goto error;
1057+ }
1058+
1059+ event = event_new (NULL, name, env);
1060+ if (! event)
1061+ return NULL;
1062+
1063+ if (! state_get_json_int_var_to_obj (json, event, fd))
1064+ goto error;
1065+
1066+ if (! state_get_json_int_var (json, "session", session_index))
1067+ goto error;
1068+
1069+ /* can't check return value here (as all values are legitimate) */
1070+ event->session = session_from_index (session_index);
1071+
1072+ if (! state_get_json_enum_var (json,
1073+ event_progress_str_to_enum,
1074+ "progress", event->progress))
1075+ goto error;
1076+
1077+ if (! state_set_json_int_var_from_obj (json, event, failed))
1078+ goto error;
1079+
1080+ return event;
1081+
1082+error:
1083+ nih_free (event);
1084+ return NULL;
1085+}
1086+
1087+/**
1088+ * event_deserialise_all:
1089+ *
1090+ * @json: root of JSON-serialised state.
1091+ *
1092+ * Convert JSON representation of events back into Event objects.
1093+ *
1094+ * Returns: 0 on success, -1 on error.
1095+ **/
1096+int
1097+event_deserialise_all (json_object *json)
1098+{
1099+ Event *event;
1100+
1101+ nih_assert (json);
1102+
1103+ event_init ();
1104+
1105+ nih_assert (NIH_LIST_EMPTY (events));
1106+ json_events = json_object_object_get (json, "events");
1107+
1108+ if (! json_events)
1109+ goto error;
1110+
1111+ if (! state_check_json_type (json_events, array))
1112+ goto error;
1113+
1114+ for (int i = 0; i < json_object_array_length (json_events); i++) {
1115+ json_object *json_event;
1116+
1117+ json_event = json_object_array_get_idx (json_events, i);
1118+ if (! json_event)
1119+ goto error;
1120+
1121+ if (! state_check_json_type (json_event, object))
1122+ goto error;
1123+
1124+ event = event_deserialise (json_event);
1125+ if (! event)
1126+ goto error;
1127+ }
1128+
1129+ return 0;
1130+
1131+error:
1132+ return -1;
1133+}
1134+
1135+/**
1136+ * event_progress_enum_to_str:
1137+ *
1138+ * @progress: event progress.
1139+ *
1140+ * Convert EventProgress to a string representation.
1141+ *
1142+ * Returns: string representation of @progress, or NULL if not known.
1143+ **/
1144+static const char *
1145+event_progress_enum_to_str (EventProgress progress)
1146+{
1147+ state_enum_to_str (EVENT_PENDING, progress);
1148+ state_enum_to_str (EVENT_HANDLING, progress);
1149+ state_enum_to_str (EVENT_FINISHED, progress);
1150+
1151+ return NULL;
1152+}
1153+
1154+/**
1155+ * event_progress_str_to_enum:
1156+ *
1157+ * @: name of EventOperator value.
1158+ *
1159+ * Convert string representation of EventProgress into a
1160+ * real EventProgress value.
1161+ *
1162+ * Returns: EventProgress representing @progress, or -1 if not known.
1163+ **/
1164+static EventProgress
1165+event_progress_str_to_enum (const char *progress)
1166+{
1167+ state_str_to_enum (EVENT_PENDING, progress);
1168+ state_str_to_enum (EVENT_HANDLING, progress);
1169+ state_str_to_enum (EVENT_FINISHED, progress);
1170+
1171+ return -1;
1172+}
1173+
1174+/**
1175+ * event_to_index:
1176+ *
1177+ * @event: event.
1178+ *
1179+ * Convert an Event to an index number within
1180+ * the list of events.
1181+ *
1182+ * Returns: event index, or -1 on error.
1183+ **/
1184+int
1185+event_to_index (const Event *event)
1186+{
1187+ int event_index = 0;
1188+ int found = FALSE;
1189+
1190+ nih_assert (event);
1191+ event_init ();
1192+
1193+ NIH_LIST_FOREACH (events, iter) {
1194+ Event *tmp = (Event *)iter;
1195+
1196+ if (tmp == event) {
1197+ found = TRUE;
1198+ break;
1199+ }
1200+
1201+ event_index++;
1202+ }
1203+
1204+ if (! found)
1205+ return -1;
1206+
1207+ return event_index;
1208+}
1209+
1210+/**
1211+ * event_from_index:
1212+ *
1213+ * @event_index: event index number.
1214+ *
1215+ * Lookup Event based on index number.
1216+ *
1217+ * Returns: existing Event on success, or NULL if event not found.
1218+ **/
1219+Event *
1220+event_from_index (int event_index)
1221+{
1222+ int i = 0;
1223+
1224+ nih_assert (event_index >= 0);
1225+ event_init ();
1226+
1227+ NIH_LIST_FOREACH (events, iter) {
1228+ Event *event = (Event *)iter;
1229+
1230+ if (i == event_index)
1231+ return event;
1232+ i++;
1233+ }
1234+
1235+ return NULL;
1236+}
1237
1238=== modified file 'init/event.h'
1239--- init/event.h 2011-06-06 17:05:11 +0000
1240+++ init/event.h 2012-11-14 14:49:20 +0000
1241@@ -24,7 +24,9 @@
1242 #include <nih/list.h>
1243
1244 #include "session.h"
1245+#include "state.h"
1246
1247+#include <json.h>
1248
1249 /**
1250 * EventProgress:
1251@@ -45,6 +47,8 @@
1252 * @session: session the event is attached to,
1253 * @name: string name of the event,
1254 * @env: NULL-terminated array of environment variables,
1255+ * @fd: open file descriptor associated with a particular
1256+ * socket-bridge socket (see socket-event(8)),
1257 * @progress: progress of event,
1258 * @failed: whether this event has failed,
1259 * @blockers: number of blockers for finishing,
1260@@ -93,6 +97,24 @@
1261
1262 void event_poll (void);
1263
1264+json_object *event_serialise (const Event *event)
1265+ __attribute__ ((malloc, warn_unused_result));
1266+
1267+Event *event_deserialise (json_object *json)
1268+ __attribute__ ((malloc, warn_unused_result));
1269+
1270+json_object * event_serialise_all (void)
1271+ __attribute__ ((malloc, warn_unused_result));
1272+
1273+int event_deserialise_all (json_object *json)
1274+ __attribute__ ((warn_unused_result));
1275+
1276+int event_to_index (const Event *event)
1277+ __attribute__ ((warn_unused_result));
1278+
1279+Event * event_from_index (int event_index)
1280+ __attribute__ ((warn_unused_result));
1281+
1282 NIH_END_EXTERN
1283
1284 #endif /* INIT_EVENT_H */
1285
1286=== modified file 'init/event_operator.c'
1287--- init/event_operator.c 2010-12-10 07:18:34 +0000
1288+++ init/event_operator.c 2012-11-14 14:49:20 +0000
1289@@ -552,14 +552,31 @@
1290 return *env;
1291 }
1292
1293+/**
1294+ * event_operator_fds:
1295+ * @root: operator tree to update,
1296+ * @parent: parent object for new array,
1297+ * @fds: output location for array of ints
1298+ * @num_fds: number of elements in @fds,
1299+ * @env: NULL-terminated array of environment variables to add to,
1300+ * @len: length of @env,
1301+ * @key: key of variable to contain event names.
1302+ *
1303+ * Iterate over tree rooted at @root adding all file descriptor values found
1304+ * to the dynamically allocated @fds array. In addition, all file
1305+ * descriptors found are also added to @env will contain a new entry with key @key
1306+ * whose value is a space-separated list of file descriptor numbers.
1307+ *
1308+ * Returns: 1 on success, NULL on failure.
1309+ **/
1310 int *
1311-event_operator_fds (EventOperator *root,
1312- const void *parent,
1313- int **fds,
1314- size_t *num_fds,
1315- char ***env,
1316- size_t *len,
1317- const char *key)
1318+event_operator_fds (EventOperator *root,
1319+ const void *parent,
1320+ int **fds,
1321+ size_t *num_fds,
1322+ char ***env,
1323+ size_t *len,
1324+ const char *key)
1325 {
1326 nih_local char *evlist = NULL;
1327
1328@@ -696,3 +713,154 @@
1329 }
1330 }
1331 }
1332+
1333+/**
1334+ * event_operator_collapse:
1335+ *
1336+ * @condition: start on/stop on condition.
1337+ *
1338+ * Collapsed condition will be fully bracketed. Note that as such it may
1339+ * not be lexicographically identical to the original expression that
1340+ * resulted in @condition, but it will be logically identical.
1341+ *
1342+ * The condition is reconstructed from the EventOperator tree by using
1343+ * a post-order traversal (since this allows the tree to be traversed
1344+ * bottom-to-top). Leaf nodes (EVENT_MATCH) are ignored when visited,
1345+ * allowing non-leaf nodes (EVENT_AND and EVENT_OR) to simply grab the
1346+ * value of their children, construct a bracketed expression and add it
1347+ * to a stack. If a child is a leaf node, the value can be read
1348+ * directly. If a child is a non-leaf node, the value is obtained by
1349+ * popping the stack before adding the new value back onto the stack.
1350+ * When finally the root node is visited, the final expression can be
1351+ * removed from the stack and returned. A single-node tree (comprising a
1352+ * lone EVENT_MATCH at the root) is special-cased.
1353+ *
1354+ * Returns: newly-allocated flattened string representing @condition
1355+ * on success, or NULL on error.
1356+ **/
1357+char *
1358+event_operator_collapse (EventOperator *condition)
1359+{
1360+ nih_local NihList *stack = NULL;
1361+ NihListEntry *latest = NULL;
1362+ NihTree *root;
1363+
1364+ nih_assert (condition);
1365+
1366+ root = &condition->node;
1367+
1368+ stack = NIH_MUST (nih_list_new (NULL));
1369+
1370+ NIH_TREE_FOREACH_POST (root, iter) {
1371+ EventOperator *oper = (EventOperator *)iter;
1372+ EventOperator *left;
1373+ EventOperator *right;
1374+ NihListEntry *expr;
1375+ NihTree *tree;
1376+ nih_local char *left_expr = NULL;
1377+ nih_local char *right_expr = NULL;
1378+
1379+ tree = &oper->node;
1380+
1381+ left = (EventOperator *)tree->left;
1382+ right = (EventOperator *)tree->right;
1383+
1384+ if (oper->type == EVENT_MATCH) {
1385+ /* Entire expression comprises a single event,
1386+ * so push it and leave.
1387+ */
1388+ if (tree == root) {
1389+ nih_local char *env = NULL;
1390+
1391+ if (oper->env)
1392+ env = NIH_MUST (state_collapse_env ((const char **)oper->env));
1393+
1394+ expr = NIH_MUST (nih_list_entry_new (stack));
1395+ expr->str = NIH_MUST (nih_sprintf (expr, "%s%s%s",
1396+ oper->name,
1397+ env ? " " : "",
1398+ env ? env : ""));
1399+ nih_list_add_after (stack, &expr->entry);
1400+ break;
1401+ }
1402+ else {
1403+ /* We build the expression from visiting the logical
1404+ * operators (and their children) only.
1405+ */
1406+ continue;
1407+ }
1408+ }
1409+
1410+ /* oper cannot now be a leaf node, so must have children */
1411+ nih_assert (left);
1412+ nih_assert (right);
1413+
1414+ expr = NIH_MUST (nih_list_entry_new (stack));
1415+
1416+ /* If a child is an EVENT_MATCH, expand its event
1417+ * details and push onto the stack.
1418+ * If a child is not an EVENT_MATCH, to obtains it
1419+ * value, pop the stack.
1420+ *
1421+ * Having obtained the child values, construct a new
1422+ * bracketed expression and push onto the stack.
1423+ *
1424+ * Note that we must consider the right child first.
1425+ * This is because since the tree is traversed
1426+ * left-child first, any value pushed onto the stack by
1427+ * a right child is at the top so must be removed before
1428+ * any left child value. Failure to do this results in
1429+ * tree reflection which although logically equivalent
1430+ * to the original could confuse as the resultant
1431+ * expression will look rather different.
1432+ */
1433+ if (right->type != EVENT_MATCH) {
1434+ nih_assert (! NIH_LIST_EMPTY (stack));
1435+
1436+ latest = (NihListEntry *)nih_list_remove (stack->next);
1437+ right_expr = NIH_MUST (nih_strdup (NULL, latest->str));
1438+ } else {
1439+ nih_local char *env = NULL;
1440+
1441+ if (right->env)
1442+ env = NIH_MUST (state_collapse_env ((const char **)right->env));
1443+
1444+ right_expr = NIH_MUST (nih_sprintf (NULL, "%s%s%s",
1445+ right->name,
1446+ env ? " " : "",
1447+ env ? env : ""));
1448+ }
1449+
1450+ if (left->type != EVENT_MATCH) {
1451+ nih_assert (! NIH_LIST_EMPTY (stack));
1452+
1453+ latest = (NihListEntry *)nih_list_remove (stack->next);
1454+ left_expr = NIH_MUST (nih_strdup (NULL, latest->str));
1455+ } else {
1456+ nih_local char *env = NULL;
1457+
1458+ if (left->env)
1459+ env = NIH_MUST (state_collapse_env ((const char **)left->env));
1460+
1461+ left_expr = NIH_MUST (nih_sprintf (NULL, "%s%s%s",
1462+ left->name,
1463+ env ? " " : "",
1464+ env ? env : ""));
1465+ }
1466+
1467+ expr->str = NIH_MUST (nih_sprintf (expr, "(%s %s %s)",
1468+ left_expr,
1469+ oper->type == EVENT_OR ? "or" : "and",
1470+ right_expr));
1471+
1472+ nih_list_add_after (stack, &expr->entry);
1473+ }
1474+
1475+ nih_assert (! NIH_LIST_EMPTY (stack));
1476+
1477+ latest = (NihListEntry *)nih_list_remove (stack->next);
1478+
1479+ nih_assert (NIH_LIST_EMPTY (stack));
1480+
1481+ return NIH_MUST (nih_strdup (NULL, latest->str));
1482+}
1483
1484=== modified file 'init/event_operator.h'
1485--- init/event_operator.h 2010-12-10 07:18:34 +0000
1486+++ init/event_operator.h 2012-11-14 14:49:20 +0000
1487@@ -96,10 +96,10 @@
1488 const void *parent, size_t *len,
1489 const char *key);
1490 int *
1491-event_operator_fds (EventOperator *root,
1492- const void *parent,
1493- int **fds,
1494- size_t *num_fds,
1495+event_operator_fds (EventOperator *root,
1496+ const void *parent,
1497+ int **fds,
1498+ size_t *num_fds,
1499 char ***env,
1500 size_t *len,
1501 const char *key);
1502@@ -108,6 +108,9 @@
1503
1504 void event_operator_reset (EventOperator *root);
1505
1506+char *event_operator_collapse (EventOperator *condition)
1507+ __attribute__ ((malloc, warn_unused_result));
1508+
1509 NIH_END_EXTERN
1510
1511 #endif /* INIT_EVENT_OPERATOR_H */
1512
1513=== modified file 'init/job.c'
1514--- init/job.c 2012-03-07 12:13:28 +0000
1515+++ init/job.c 2012-11-14 14:49:20 +0000
1516@@ -23,7 +23,6 @@
1517 # include <config.h>
1518 #endif /* HAVE_CONFIG_H */
1519
1520-
1521 #include <sys/types.h>
1522
1523 #include <errno.h>
1524@@ -55,10 +54,43 @@
1525 #include "event_operator.h"
1526 #include "blocked.h"
1527 #include "control.h"
1528+#include "parse_job.h"
1529+#include "state.h"
1530
1531 #include "com.ubuntu.Upstart.Job.h"
1532 #include "com.ubuntu.Upstart.Instance.h"
1533
1534+/* Prototypes for static functions */
1535+static const char *
1536+job_goal_enum_to_str (JobGoal goal)
1537+ __attribute__ ((warn_unused_result));
1538+
1539+static JobGoal job_goal_str_to_enum (const char *goal)
1540+ __attribute__ ((warn_unused_result));
1541+
1542+static const char *
1543+job_state_enum_to_str (JobState state)
1544+ __attribute__ ((warn_unused_result));
1545+
1546+static JobState
1547+job_state_str_to_enum (const char *state)
1548+ __attribute__ ((warn_unused_result));
1549+
1550+static const char *
1551+job_trace_state_enum_to_str (TraceState state)
1552+ __attribute__ ((warn_unused_result));
1553+
1554+static TraceState
1555+job_trace_state_str_to_enum (const char *state)
1556+ __attribute__ ((warn_unused_result));
1557+
1558+static json_object *
1559+job_serialise_kill_timer (NihTimer *timer)
1560+ __attribute__ ((malloc, warn_unused_result));
1561+
1562+static NihTimer *
1563+job_deserialise_kill_timer (json_object *json)
1564+ __attribute__ ((malloc, warn_unused_result));
1565
1566 /**
1567 * job_new:
1568@@ -119,6 +151,7 @@
1569 job->stop_env = NULL;
1570
1571 job->stop_on = NULL;
1572+
1573 if (class->stop_on) {
1574 job->stop_on = event_operator_copy (job, class->stop_on);
1575 if (! job->stop_on)
1576@@ -152,10 +185,10 @@
1577 nih_list_init (&job->blocking);
1578
1579 job->kill_timer = NULL;
1580- job->kill_process = -1;
1581+ job->kill_process = PROCESS_INVALID;
1582
1583 job->failed = FALSE;
1584- job->failed_process = -1;
1585+ job->failed_process = PROCESS_INVALID;
1586 job->exit_status = 0;
1587
1588 job->respawn_time = 0;
1589@@ -262,7 +295,7 @@
1590 * will finish naturally, so all we need do is change the goal and
1591 * we'll change direction through the state machine at that point.
1592 *
1593- * The exceptions are the natural rest sates of waiting and a
1594+ * The exceptions are the natural rest states of waiting and a
1595 * running process; these need induction to get them moving.
1596 */
1597 switch (goal) {
1598@@ -358,7 +391,7 @@
1599
1600 /* Clear any old failed information */
1601 job->failed = FALSE;
1602- job->failed_process = -1;
1603+ job->failed_process = PROCESS_INVALID;
1604 job->exit_status = 0;
1605
1606 job->blocker = job_emit_event (job);
1607@@ -882,7 +915,7 @@
1608 * if it was a respawn failure, we use the special "respawn"
1609 * argument instead of the process name,
1610 */
1611- if ((job->failed_process != (ProcessType)-1)
1612+ if ((job->failed_process != PROCESS_INVALID)
1613 && (job->exit_status != -1)) {
1614 NIH_MUST (environ_set (&env, NULL, &len, TRUE,
1615 "PROCESS=%s",
1616@@ -907,7 +940,7 @@
1617 NIH_MUST (environ_set (&env, NULL, &len, TRUE,
1618 "EXIT_STATUS=%d", job->exit_status));
1619 }
1620- } else if (job->failed_process != (ProcessType)-1) {
1621+ } else if (job->failed_process != PROCESS_INVALID) {
1622 NIH_MUST (environ_set (&env, NULL, &len, TRUE,
1623 "PROCESS=%s",
1624 process_name (job->failed_process)));
1625@@ -1492,3 +1525,689 @@
1626
1627 return 0;
1628 }
1629+
1630+/**
1631+ * job_serialise:
1632+ * @job: job serialise.
1633+ *
1634+ * Convert @job into a JSON representation for serialisation.
1635+ * Caller must free returned value using json_object_put().
1636+ *
1637+ * Note that the 'class' element is not encoded - it is assumed the
1638+ * caller will encode the returned JSON Job object as a child of the
1639+ * parent JSON-encoded JobClass object so a reference is not required.
1640+ *
1641+ * Returns: JSON-serialised Job object, or NULL on error.
1642+ **/
1643+json_object *
1644+job_serialise (const Job *job)
1645+{
1646+ json_object *json;
1647+ json_object *json_pid;
1648+ json_object *json_fds;
1649+ json_object *json_logs;
1650+ nih_local char *stop_on = NULL;
1651+
1652+ nih_assert (job);
1653+
1654+ json = json_object_new_object ();
1655+ if (! json)
1656+ return NULL;
1657+
1658+ if (! state_set_json_string_var_from_obj (json, job, name))
1659+ goto error;
1660+
1661+ if (! state_set_json_string_var_from_obj (json, job, path))
1662+ goto error;
1663+
1664+ if (! state_set_json_enum_var (json,
1665+ job_goal_enum_to_str,
1666+ "goal", job->goal))
1667+ goto error;
1668+
1669+ if (! state_set_json_enum_var (json,
1670+ job_state_enum_to_str,
1671+ "state", job->state))
1672+ goto error;
1673+
1674+ if (! state_set_json_str_array_from_obj (json, job, env))
1675+ goto error;
1676+
1677+ if (! state_set_json_str_array_from_obj (json, job, start_env))
1678+ goto error;
1679+
1680+ if (! state_set_json_str_array_from_obj (json, job, stop_env))
1681+ goto error;
1682+
1683+ if (job->stop_on) {
1684+ stop_on = event_operator_collapse (job->stop_on);
1685+ if (! stop_on)
1686+ goto error;
1687+
1688+ if (! state_set_json_string_var (json, "stop_on", stop_on))
1689+ goto error;
1690+ }
1691+
1692+ json_fds = state_serialise_int_array (int, job->fds, job->num_fds);
1693+ if (! json_fds)
1694+ goto error;
1695+
1696+ json_object_object_add (json, "fds", json_fds);
1697+
1698+ json_pid = state_serialise_int_array (pid_t, job->pid,
1699+ PROCESS_LAST);
1700+ if (! json_pid)
1701+ goto error;
1702+
1703+ json_object_object_add (json, "pid", json_pid);
1704+
1705+ /* Encode the blocking event as an index number which represents
1706+ * the event's position in the JSON events array.
1707+ */
1708+ if (job->blocker) {
1709+ int event_index;
1710+
1711+ event_index = event_to_index (job->blocker);
1712+ if (event_index < 0)
1713+ goto error;
1714+
1715+ /* For consistency, it would be preferable to encode the
1716+ * event name, but the index is actually better since it is
1717+ * simple and unambiguous - encoding the name would also require
1718+ * us to encode the env to make the event unique.
1719+ */
1720+ if (! state_set_json_int_var (json, "blocker", event_index))
1721+ goto error;
1722+
1723+ }
1724+
1725+ if (! NIH_LIST_EMPTY (&job->blocking)) {
1726+ json_object *json_blocking;
1727+
1728+ json_blocking = state_serialise_blocking (&job->blocking);
1729+ if (! json_blocking)
1730+ goto error;
1731+
1732+ json_object_object_add (json, "blocking", json_blocking);
1733+ }
1734+
1735+ /* conditionally encode kill timer */
1736+ if (job->kill_timer) {
1737+ json_object *kill_timer;
1738+
1739+ kill_timer = job_serialise_kill_timer (job->kill_timer);
1740+
1741+ if (! kill_timer)
1742+ goto error;
1743+
1744+ json_object_object_add (json, "kill_timer", kill_timer);
1745+ }
1746+
1747+ if (! state_set_json_enum_var (json,
1748+ process_type_enum_to_str,
1749+ "kill_process", job->kill_process))
1750+ goto error;
1751+
1752+ if (! state_set_json_int_var_from_obj (json, job, failed))
1753+ goto error;
1754+
1755+ if (! state_set_json_enum_var (json,
1756+ process_type_enum_to_str,
1757+ "failed_process", job->failed_process))
1758+ goto error;
1759+
1760+ if (! state_set_json_int_var_from_obj (json, job, exit_status))
1761+ goto error;
1762+
1763+ if (! state_set_json_int_var_from_obj (json, job, respawn_time))
1764+ goto error;
1765+
1766+ if (! state_set_json_int_var_from_obj (json, job, respawn_count))
1767+ goto error;
1768+
1769+ if (! state_set_json_int_var_from_obj (json, job, trace_forks))
1770+ goto error;
1771+
1772+ if (! state_set_json_enum_var (json,
1773+ job_trace_state_enum_to_str,
1774+ "trace_state", job->trace_state))
1775+ goto error;
1776+
1777+ json_logs = json_object_new_array ();
1778+
1779+ if (! json_logs)
1780+ return json;
1781+
1782+ for (int process = 0; process < PROCESS_LAST; process++) {
1783+ json_object *json_log;
1784+
1785+ json_log = log_serialise (job->log[process]);
1786+ if (! json_log)
1787+ goto error;
1788+
1789+ if (json_object_array_add (json_logs, json_log) < 0)
1790+ goto error;
1791+ }
1792+
1793+ json_object_object_add (json, "log", json_logs);
1794+
1795+ return json;
1796+
1797+error:
1798+ json_object_put (json);
1799+ return NULL;
1800+}
1801+
1802+/**
1803+ * job_serialise_all:
1804+ *
1805+ * Convert existing Session objects to JSON representation.
1806+ *
1807+ * Returns: JSON object containing array of Job objects, or NULL on error.
1808+ **/
1809+json_object *
1810+job_serialise_all (const NihHash *jobs)
1811+{
1812+ int count = 0;
1813+ json_object *json;
1814+
1815+ nih_assert (jobs);
1816+
1817+ json = json_object_new_array ();
1818+ if (! json)
1819+ return NULL;
1820+
1821+ NIH_HASH_FOREACH (jobs, iter) {
1822+ json_object *json_job;
1823+ Job *job = (Job *)iter;
1824+
1825+ count++;
1826+ json_job = job_serialise (job);
1827+
1828+ if (! json_job)
1829+ goto error;
1830+
1831+ json_object_array_add (json, json_job);
1832+ }
1833+
1834+ /* Raise an error to avoid serialising job classes with
1835+ * no associated jobs.
1836+ */
1837+ if (! count)
1838+ goto error;
1839+
1840+ return json;
1841+
1842+error:
1843+ json_object_put (json);
1844+ return NULL;
1845+}
1846+
1847+/**
1848+ * job_deserialise:
1849+ * @parent: job class for JSON-encoded jobs,
1850+ * @json: JSON-serialised Job object to deserialise.
1851+ *
1852+ * XXX: All events must have been deserialised prior to this function
1853+ * XXX: being called.
1854+ *
1855+ * Returns: Job object, or NULL on error.
1856+ **/
1857+Job *
1858+job_deserialise (JobClass *parent, json_object *json)
1859+{
1860+ nih_local char *name = NULL;
1861+ Job *job = NULL;
1862+ json_object *json_kill_timer;
1863+ json_object *blocker;
1864+ json_object *json_fds;
1865+ json_object *json_pid;
1866+ json_object *json_logs;
1867+ size_t len;
1868+ int ret;
1869+
1870+ nih_assert (parent);
1871+ nih_assert (json);
1872+
1873+ if (! state_check_json_type (json, object))
1874+ return NULL;
1875+
1876+ if (! state_get_json_string_var_strict (json, "name", NULL, name))
1877+ goto error;
1878+
1879+ job = NIH_MUST (job_new (parent, name));
1880+
1881+ if (! job)
1882+ return NULL;
1883+
1884+ if (! state_get_json_string_var_to_obj_strict (json, job, path))
1885+ goto error;
1886+
1887+ if (! state_get_json_enum_var (json,
1888+ job_goal_str_to_enum,
1889+ "goal", job->goal))
1890+ goto error;
1891+
1892+ if (! state_get_json_enum_var (json,
1893+ job_state_str_to_enum,
1894+ "state", job->state))
1895+ goto error;
1896+
1897+ if (! state_get_json_env_array_to_obj (json, job, env))
1898+ goto error;
1899+
1900+ if (! state_get_json_env_array_to_obj (json, job, start_env))
1901+ goto error;
1902+
1903+ if (! state_get_json_env_array_to_obj (json, job, stop_env))
1904+ goto error;
1905+
1906+ if (json_object_object_get (json, "stop_on")) {
1907+ nih_local char *stop_on = NULL;
1908+
1909+ if (! state_get_json_string_var_strict (json, "stop_on", NULL, stop_on))
1910+ goto error;
1911+
1912+ if (*stop_on) {
1913+ nih_local JobClass *tmp = NULL;
1914+
1915+ tmp = NIH_MUST (job_class_new (NULL, "tmp", NULL));
1916+
1917+ tmp->stop_on = parse_on_simple (tmp, "stop", stop_on);
1918+ if (! tmp->stop_on) {
1919+ NihError *err;
1920+
1921+ err = nih_error_get ();
1922+
1923+ nih_error ("%s %s: %s",
1924+ _("BUG"),
1925+ _("instance 'stop on' parse error"),
1926+ err->message);
1927+
1928+ nih_free (err);
1929+
1930+ goto error;
1931+ }
1932+
1933+ nih_free (job->stop_on);
1934+ job->stop_on = event_operator_copy (job, tmp->stop_on);
1935+ if (! job->stop_on)
1936+ goto error;
1937+ }
1938+ }
1939+
1940+ /* fds and num_fds handled by caller */
1941+ /* pid handled by caller */
1942+
1943+ /* blocking is handled by state_deserialise_blocking() */
1944+
1945+ blocker = json_object_object_get (json, "blocker");
1946+
1947+ if (blocker) {
1948+ int event_index = -1;
1949+
1950+ if (! state_get_json_int_var (json, "blocker", event_index))
1951+ goto error;
1952+ job->blocker = event_from_index (event_index);
1953+
1954+ if (! job->blocker)
1955+ goto error;
1956+ }
1957+
1958+ if (! state_get_json_enum_var (json,
1959+ process_type_str_to_enum,
1960+ "kill_process", job->kill_process))
1961+ goto error;
1962+
1963+ /* Check to see if a kill timer exists first since we do not
1964+ * want to end up creating a real but empty timer.
1965+ */
1966+ json_kill_timer = json_object_object_get (json, "kill_timer");
1967+
1968+ if (json_kill_timer) {
1969+ /* Found a partial kill timer, so create a new one and
1970+ * adjust its due time. By the time the main loop gets
1971+ * called, the due time will probably be in the past
1972+ * such that the job will be stopped.
1973+ *
1974+ * To be completely fair we should:
1975+ *
1976+ * - encode the time at the point of serialisation in a
1977+ * JSON 'meta' header.
1978+ * - query the time post-deserialisation and calculate
1979+ * the delta (being the time to perform the stateful
1980+ * re-exec).
1981+ * - add that time to all jobs with active kill timers
1982+ * to give their processes the full amount of time to
1983+ * end.
1984+ */
1985+ nih_local NihTimer *kill_timer = job_deserialise_kill_timer (json_kill_timer);
1986+ if (! kill_timer)
1987+ goto error;
1988+
1989+ nih_assert (job->kill_process);
1990+ job_process_set_kill_timer (job, job->kill_process,
1991+ kill_timer->timeout);
1992+ job_process_adj_kill_timer (job, kill_timer->due);
1993+ }
1994+
1995+ if (! state_get_json_int_var_to_obj (json, job, failed))
1996+ goto error;
1997+
1998+ if (! state_get_json_enum_var (json,
1999+ process_type_str_to_enum,
2000+ "failed_process", job->failed_process))
2001+ goto error;
2002+
2003+ if (! state_get_json_int_var_to_obj (json, job, exit_status))
2004+ goto error;
2005+
2006+ if (! state_get_json_int_var_to_obj (json, job, respawn_time))
2007+ goto error;
2008+
2009+ if (! state_get_json_int_var_to_obj (json, job, respawn_count))
2010+ goto error;
2011+
2012+ json_fds = json_object_object_get (json, "fds");
2013+ if (! json_fds)
2014+ goto error;
2015+
2016+ ret = state_deserialise_int_array (job, json_fds,
2017+ int, &job->fds, &job->num_fds);
2018+ if (ret < 0)
2019+ goto error;
2020+
2021+ json_pid = json_object_object_get (json, "pid");
2022+ if (! json_pid)
2023+ goto error;
2024+
2025+ ret = state_deserialise_int_array (job, json_pid,
2026+ pid_t, &job->pid, &len);
2027+ if (ret < 0)
2028+ goto error;
2029+
2030+ if (len != PROCESS_LAST)
2031+ goto error;
2032+
2033+ if (! state_get_json_int_var_to_obj (json, job, trace_forks))
2034+ goto error;
2035+
2036+ if (! state_get_json_enum_var (json,
2037+ job_trace_state_str_to_enum,
2038+ "trace_state", job->trace_state))
2039+ goto error;
2040+
2041+ json_logs = json_object_object_get (json, "log");
2042+
2043+ if (! json_logs)
2044+ goto error;
2045+
2046+ if (! state_check_json_type (json_logs, array))
2047+ goto error;
2048+
2049+ for (int process = 0; process < PROCESS_LAST; process++) {
2050+ json_object *json_log;
2051+
2052+ json_log = json_object_array_get_idx (json_logs, process);
2053+ if (! json_log)
2054+ goto error;
2055+
2056+ /* NULL if there was no log configured, or we failed to
2057+ * deserialise it; either way, this should be non-fatal.
2058+ */
2059+ job->log[process] = log_deserialise (job->log, json_log);
2060+ }
2061+
2062+ return job;
2063+
2064+error:
2065+ nih_free (job);
2066+ return NULL;
2067+}
2068+
2069+/**
2070+ * job_deserialise_all:
2071+ *
2072+ * @parent: job class for JSON-encoded jobs,
2073+ * @json: root of JSON-serialised state.
2074+ *
2075+ * Convert JSON representation of jobs back into Job objects associated
2076+ * with @parent.
2077+ *
2078+ * Returns: 0 on success, -1 on error.
2079+ **/
2080+int
2081+job_deserialise_all (JobClass *parent, json_object *json)
2082+{
2083+ json_object *json_jobs;
2084+ Job *job;
2085+
2086+ nih_assert (parent);
2087+ nih_assert (json);
2088+
2089+ json_jobs = json_object_object_get (json, "jobs");
2090+
2091+ if (! json_jobs)
2092+ goto error;
2093+
2094+ if (! state_check_json_type (json_jobs, array))
2095+ goto error;
2096+
2097+ for (int i = 0; i < json_object_array_length (json_jobs); i++) {
2098+ json_object *json_job;
2099+
2100+ json_job = json_object_array_get_idx (json_jobs, i);
2101+ if (! json_job)
2102+ goto error;
2103+
2104+ if (! state_check_json_type (json_job, object))
2105+ goto error;
2106+
2107+ job = job_deserialise (parent, json_job);
2108+ if (! job)
2109+ goto error;
2110+ }
2111+
2112+ return 0;
2113+
2114+error:
2115+ return -1;
2116+}
2117+
2118+/**
2119+ * job_goal_enum_to_str:
2120+ *
2121+ * @goal: JobGoal.
2122+ *
2123+ * Convert JobGoal to a string representation.
2124+ *
2125+ * Returns: string representation of @goal, or NULL if not known.
2126+ **/
2127+static const char *
2128+job_goal_enum_to_str (JobGoal goal)
2129+{
2130+ state_enum_to_str (JOB_STOP, goal);
2131+ state_enum_to_str (JOB_START, goal);
2132+ state_enum_to_str (JOB_RESPAWN, goal);
2133+
2134+ return NULL;
2135+}
2136+
2137+/**
2138+ * job_goal_str_to_enum:
2139+ *
2140+ * @goal: string JobGoal value.
2141+ *
2142+ * Convert @goal back into enum value.
2143+ *
2144+ * Returns: JobGoal representation of @goal, or -1 if not known.
2145+ **/
2146+static JobGoal
2147+job_goal_str_to_enum (const char *goal)
2148+{
2149+ state_str_to_enum (JOB_STOP, goal);
2150+ state_str_to_enum (JOB_START, goal);
2151+ state_str_to_enum (JOB_RESPAWN, goal);
2152+
2153+ return -1;
2154+}
2155+
2156+/**
2157+ * job_state_enum_to_str:
2158+ *
2159+ * @state: JobState.
2160+ *
2161+ * Convert JobState to a string representation.
2162+ *
2163+ * Returns: string representation of @state, or NULL if not known.
2164+ **/
2165+static const char *
2166+job_state_enum_to_str (JobState state)
2167+{
2168+ state_enum_to_str (JOB_WAITING, state);
2169+ state_enum_to_str (JOB_STARTING, state);
2170+ state_enum_to_str (JOB_PRE_START, state);
2171+ state_enum_to_str (JOB_SPAWNED, state);
2172+ state_enum_to_str (JOB_POST_START, state);
2173+ state_enum_to_str (JOB_RUNNING, state);
2174+ state_enum_to_str (JOB_PRE_STOP, state);
2175+ state_enum_to_str (JOB_STOPPING, state);
2176+ state_enum_to_str (JOB_KILLED, state);
2177+ state_enum_to_str (JOB_POST_STOP, state);
2178+
2179+ return NULL;
2180+}
2181+
2182+/**
2183+ * job_state_str_to_enum:
2184+ *
2185+ * @state: string JobState value.
2186+ *
2187+ * Convert @state back into enum value.
2188+ *
2189+ * Returns: JobState representation of @state, or -1 if not known.
2190+ **/
2191+static JobState
2192+job_state_str_to_enum (const char *state)
2193+{
2194+ state_str_to_enum (JOB_WAITING, state);
2195+ state_str_to_enum (JOB_STARTING, state);
2196+ state_str_to_enum (JOB_PRE_START, state);
2197+ state_str_to_enum (JOB_SPAWNED, state);
2198+ state_str_to_enum (JOB_POST_START, state);
2199+ state_str_to_enum (JOB_RUNNING, state);
2200+ state_str_to_enum (JOB_PRE_STOP, state);
2201+ state_str_to_enum (JOB_STOPPING, state);
2202+ state_str_to_enum (JOB_KILLED, state);
2203+ state_str_to_enum (JOB_POST_STOP, state);
2204+
2205+ return -1;
2206+}
2207+
2208+/**
2209+ * job_state_enum_to_str:
2210+ *
2211+ * @state: TraceState.
2212+ *
2213+ * Convert TraceState to a string representation.
2214+ *
2215+ * Returns: string representation of @state, or NULL if not known.
2216+ **/
2217+static const char *
2218+job_trace_state_enum_to_str (TraceState state)
2219+{
2220+ state_enum_to_str (TRACE_NONE, state);
2221+ state_enum_to_str (TRACE_NEW, state);
2222+ state_enum_to_str (TRACE_NEW_CHILD, state);
2223+ state_enum_to_str (TRACE_NORMAL, state);
2224+
2225+ return NULL;
2226+}
2227+
2228+/**
2229+ * job_trace_state_str_to_enum:
2230+ *
2231+ * @state: string TraceState value.
2232+ *
2233+ * Convert @state back into enum value.
2234+ *
2235+ * Returns: TraceState representation of @state, or -1 if not known.
2236+ **/
2237+static TraceState
2238+job_trace_state_str_to_enum (const char *state)
2239+{
2240+ state_str_to_enum (TRACE_NONE, state);
2241+ state_str_to_enum (TRACE_NEW, state);
2242+ state_str_to_enum (TRACE_NEW_CHILD, state);
2243+ state_str_to_enum (TRACE_NORMAL, state);
2244+
2245+ return -1;
2246+}
2247+
2248+/**
2249+ * job_serialise_kill_timer:
2250+ *
2251+ * @timer: NihTimer to serialise.
2252+ *
2253+ * Serialise @timer into JSON.
2254+ *
2255+ * Returns: JSON-serialised NihTimer object, or NULL on error.
2256+ **/
2257+static json_object *
2258+job_serialise_kill_timer (NihTimer *timer)
2259+{
2260+ json_object *json;
2261+
2262+ nih_assert (timer);
2263+
2264+ json = json_object_new_object ();
2265+ if (! json)
2266+ return NULL;
2267+
2268+ if (! state_set_json_int_var_from_obj (json, timer, timeout))
2269+ goto error;
2270+
2271+ if (! state_set_json_int_var_from_obj (json, timer, due))
2272+ goto error;
2273+
2274+ return json;
2275+
2276+error:
2277+ json_object_put (json);
2278+ return NULL;
2279+}
2280+
2281+/**
2282+ * job_deserialise_kill_timer:
2283+ *
2284+ * @json: JSON representation of NihTimer.
2285+ *
2286+ * Deserialise @json back into an NihTimer.
2287+ *
2288+ * Returns: NihTimer on NULL on error.
2289+ **/
2290+static NihTimer *
2291+job_deserialise_kill_timer (json_object *json)
2292+{
2293+ NihTimer *timer;
2294+
2295+ nih_assert (json);
2296+
2297+ timer = nih_new (NULL, NihTimer);
2298+ if (! timer)
2299+ return NULL;
2300+
2301+ memset (timer, '\0', sizeof (NihTimer));
2302+
2303+ if (! state_get_json_int_var_to_obj (json, timer, due))
2304+ goto error;
2305+
2306+ if (! state_get_json_int_var_to_obj (json, timer, timeout))
2307+ goto error;
2308+
2309+ return timer;
2310+
2311+error:
2312+ nih_free (timer);
2313+ return NULL;
2314+}
2315
2316=== modified file 'init/job.h'
2317--- init/job.h 2012-03-07 12:13:28 +0000
2318+++ init/job.h 2012-11-14 14:49:20 +0000
2319@@ -103,6 +103,9 @@
2320 * @start_env: environment to use next time the job is started,
2321 * @stop_env: environment to add for the next pre-stop script,
2322 * @stop_on: event operator expression that can stop this job.
2323+ * @fds: array of file descriptors associated with events in parent
2324+ * JobClasses @start_on condition,
2325+ * @num_fds: number of elements in @fds,
2326 * @pid: current process ids,
2327 * @blocker: emitted event we're waiting to finish,
2328 * @blocking: list of events we're blocking from finishing,
2329@@ -136,8 +139,8 @@
2330 char **stop_env;
2331 EventOperator *stop_on;
2332
2333- int *fds;
2334- size_t num_fds;
2335+ int *fds;
2336+ size_t num_fds;
2337
2338 pid_t *pid;
2339 Event *blocker;
2340@@ -208,6 +211,15 @@
2341 JobProcessesElement ***processes)
2342 __attribute__ ((warn_unused_result));
2343
2344+json_object *job_serialise (const Job *job);
2345+Job *job_deserialise (JobClass *parent, json_object *json);
2346+
2347+json_object *job_serialise_all (const NihHash *jobs)
2348+ __attribute__ ((malloc, warn_unused_result));
2349+
2350+int job_deserialise_all (JobClass *parent, json_object *json)
2351+ __attribute__ ((warn_unused_result));
2352+
2353 NIH_END_EXTERN
2354
2355 #endif /* INIT_JOB_H */
2356
2357=== modified file 'init/job_class.c'
2358--- init/job_class.c 2012-03-01 11:55:19 +0000
2359+++ init/job_class.c 2012-11-14 14:49:20 +0000
2360@@ -52,14 +52,18 @@
2361 #include "blocked.h"
2362 #include "conf.h"
2363 #include "control.h"
2364+#include "parse_job.h"
2365
2366 #include "com.ubuntu.Upstart.h"
2367 #include "com.ubuntu.Upstart.Job.h"
2368
2369+#include <json.h>
2370+
2371+extern json_object *json_classes;
2372
2373 /* Prototypes for static functions */
2374-static void job_class_add (JobClass *class);
2375-static int job_class_remove (JobClass *class, const Session *session);
2376+static void job_class_add (JobClass *class);
2377+static int job_class_remove (JobClass *class, const Session *session);
2378
2379 /**
2380 * default_console:
2381@@ -356,6 +360,30 @@
2382 }
2383
2384 /**
2385+ * job_class_add_safe:
2386+ * @class: new class to select.
2387+ *
2388+ * Adds @class to the hash table iff no existing entry of the
2389+ * same name exists.
2390+ **/
2391+void
2392+job_class_add_safe (JobClass *class)
2393+{
2394+ JobClass *existing = NULL;
2395+
2396+ nih_assert (class);
2397+ nih_assert (class->name);
2398+
2399+ control_init ();
2400+
2401+ existing = (JobClass *)nih_hash_search (job_classes, class->name, NULL);
2402+
2403+ nih_assert (! existing);
2404+
2405+ job_class_add (class);
2406+}
2407+
2408+/**
2409 * job_class_remove:
2410 * @class: class to remove,
2411 * @session: Session of @class.
2412@@ -720,7 +748,7 @@
2413 * If the instance goal is already start,
2414 * the com.ubuntu.Upstart.Error.AlreadyStarted D-Bus error will be returned
2415 * immediately. If the instance fails to start, the
2416- * com.ubuntu.Upstart.Error.JobFailed D-BUs error will be returned when the
2417+ * com.ubuntu.Upstart.Error.JobFailed D-Bus error will be returned when the
2418 * problem occurs.
2419 *
2420 * When @wait is TRUE the method call will not return until the job has
2421@@ -1112,6 +1140,39 @@
2422 return 0;
2423 }
2424
2425+/**
2426+ * job_class_get:
2427+ *
2428+ * @name: name of job class,
2429+ * @session: session of job class.
2430+ *
2431+ * Obtain JobClass with name @name and session @session.
2432+ *
2433+ * Returns: JobClass, or NULL if no matching job class found.
2434+ **/
2435+JobClass *
2436+job_class_get (const char *name, Session *session)
2437+{
2438+ JobClass *class = NULL;
2439+ NihList *prev = NULL;
2440+
2441+ nih_assert (name);
2442+
2443+ do {
2444+ class = (JobClass *)nih_hash_search (job_classes, name, prev);
2445+ if (! class)
2446+ return NULL;
2447+ if (class && class->session == session)
2448+ return class;
2449+
2450+ nih_assert (class);
2451+
2452+ prev = (NihList *)class;
2453+ } while (TRUE);
2454+
2455+ nih_assert_not_reached ();
2456+}
2457+
2458
2459 /**
2460 * job_class_get_name:
2461@@ -1500,3 +1561,651 @@
2462
2463 return 0;
2464 }
2465+
2466+
2467+/**
2468+ * job_class_serialise:
2469+ * @class: job class to serialise.
2470+ *
2471+ * Convert @class into a JSON representation for serialisation.
2472+ * Caller must free returned value using json_object_put().
2473+ *
2474+ * Returns: JSON-serialised JobClass object, or NULL on error.
2475+ **/
2476+json_object *
2477+job_class_serialise (const JobClass *class)
2478+{
2479+ json_object *json;
2480+ json_object *json_export;
2481+ json_object *json_emits;
2482+ json_object *json_processes;
2483+ json_object *json_normalexit;
2484+ json_object *json_limits;
2485+ json_object *json_jobs;
2486+ nih_local char *start_on = NULL;
2487+ nih_local char *stop_on = NULL;
2488+ int session_index;
2489+
2490+ nih_assert (class);
2491+ nih_assert (job_classes);
2492+
2493+ json = json_object_new_object ();
2494+ if (! json)
2495+ return NULL;
2496+
2497+ session_index = session_get_index (class->session);
2498+ if (session_index < 0)
2499+ goto error;
2500+
2501+ if (! state_set_json_int_var (json, "session", session_index))
2502+ goto error;
2503+
2504+ if (! state_set_json_string_var_from_obj (json, class, name))
2505+ goto error;
2506+
2507+ if (! state_set_json_string_var_from_obj (json, class, path))
2508+ goto error;
2509+
2510+ if (! state_set_json_string_var_from_obj (json, class, instance))
2511+ goto error;
2512+
2513+ json_jobs = job_serialise_all (class->instances);
2514+
2515+ if (! json_jobs)
2516+ goto error;
2517+
2518+ json_object_object_add (json, "jobs", json_jobs);
2519+
2520+ if (! state_set_json_string_var_from_obj (json, class, description))
2521+ goto error;
2522+
2523+ if (! state_set_json_string_var_from_obj (json, class, author))
2524+ goto error;
2525+
2526+ if (! state_set_json_string_var_from_obj (json, class, version))
2527+ goto error;
2528+
2529+ if (! state_set_json_str_array_from_obj (json, class, env))
2530+ goto error;
2531+
2532+ json_export = class->export
2533+ ? state_serialise_str_array (class->export)
2534+ : json_object_new_array ();
2535+
2536+ if (! json_export)
2537+ goto error;
2538+ json_object_object_add (json, "export", json_export);
2539+
2540+ if (class->start_on) {
2541+ start_on = event_operator_collapse (class->start_on);
2542+ if (! start_on)
2543+ goto error;
2544+
2545+ if (! state_set_json_string_var (json, "start_on", start_on))
2546+ goto error;
2547+ }
2548+
2549+ if (class->stop_on) {
2550+ stop_on = event_operator_collapse (class->stop_on);
2551+ if (! stop_on)
2552+ goto error;
2553+
2554+ if (! state_set_json_string_var (json, "stop_on", stop_on))
2555+ goto error;
2556+ }
2557+
2558+ json_emits = class->emits
2559+ ? state_serialise_str_array (class->emits)
2560+ : json_object_new_array ();
2561+
2562+ if (! json_emits)
2563+ goto error;
2564+ json_object_object_add (json, "emits", json_emits);
2565+
2566+ json_processes = process_serialise_all (
2567+ (const Process const * const * const)class->process);
2568+ if (! json_processes)
2569+ goto error;
2570+ json_object_object_add (json, "process", json_processes);
2571+
2572+ if (! state_set_json_enum_var (json,
2573+ job_class_expect_type_enum_to_str,
2574+ "expect", class->expect))
2575+ goto error;
2576+
2577+ if (! state_set_json_int_var_from_obj (json, class, task))
2578+ goto error;
2579+
2580+ if (! state_set_json_int_var_from_obj (json, class, kill_timeout))
2581+ goto error;
2582+
2583+ if (! state_set_json_int_var_from_obj (json, class, kill_signal))
2584+ goto error;
2585+
2586+ if (! state_set_json_int_var_from_obj (json, class, respawn))
2587+ goto error;
2588+
2589+ if (! state_set_json_int_var_from_obj (json, class, respawn_limit))
2590+ goto error;
2591+
2592+ if (! state_set_json_int_var_from_obj (json, class, respawn_interval))
2593+ goto error;
2594+
2595+ json_normalexit = state_serialise_int_array (int, class->normalexit,
2596+ class->normalexit_len);
2597+ if (! json_normalexit)
2598+ goto error;
2599+
2600+ json_object_object_add (json, "normalexit", json_normalexit);
2601+
2602+ if (! state_set_json_enum_var (json,
2603+ job_class_console_type_enum_to_str,
2604+ "console", class->console))
2605+ goto error;
2606+
2607+ if (! state_set_json_int_var_from_obj (json, class, umask))
2608+ goto error;
2609+
2610+ if (! state_set_json_int_var_from_obj (json, class, nice))
2611+ goto error;
2612+
2613+ if (! state_set_json_int_var_from_obj (json, class, oom_score_adj))
2614+ goto error;
2615+
2616+ json_limits = state_rlimit_serialise_all (class->limits);
2617+ if (! json_limits)
2618+ goto error;
2619+ json_object_object_add (json, "limits", json_limits);
2620+
2621+ if (! state_set_json_string_var_from_obj (json, class, chroot))
2622+ goto error;
2623+
2624+ if (! state_set_json_string_var_from_obj (json, class, chdir))
2625+ goto error;
2626+
2627+ if (! state_set_json_string_var_from_obj (json, class, setuid))
2628+ goto error;
2629+
2630+ if (! state_set_json_string_var_from_obj (json, class, setgid))
2631+ goto error;
2632+
2633+ if (! state_set_json_int_var_from_obj (json, class, deleted))
2634+ goto error;
2635+
2636+ if (! state_set_json_int_var_from_obj (json, class, debug))
2637+ goto error;
2638+
2639+ if (! state_set_json_string_var_from_obj (json, class, usage))
2640+ goto error;
2641+
2642+ return json;
2643+
2644+error:
2645+ json_object_put (json);
2646+ return NULL;
2647+}
2648+
2649+/**
2650+ * job_class_serialise_all:
2651+ *
2652+ * Convert existing JobClass objects to JSON representation.
2653+ *
2654+ * Returns: JSON object containing array of JobClass objects,
2655+ * or NULL on error.
2656+ **/
2657+json_object *
2658+job_class_serialise_all (void)
2659+{
2660+ json_object *json;
2661+
2662+ job_class_init ();
2663+
2664+ json = json_object_new_array ();
2665+ if (! json)
2666+ return NULL;
2667+
2668+ NIH_HASH_FOREACH (job_classes, iter) {
2669+ json_object *json_class;
2670+ JobClass *class = (JobClass *)iter;
2671+
2672+ json_class = job_class_serialise (class);
2673+
2674+ /* No object returned means the class doesn't need to be
2675+ * serialised. Even if this is a real failure, it's always
2676+ * better to serialise as much of the state as possible.
2677+ */
2678+ if (! json_class)
2679+ continue;
2680+
2681+ json_object_array_add (json, json_class);
2682+ }
2683+
2684+ return json;
2685+}
2686+
2687+/**
2688+ * job_class_deserialise:
2689+ * @json: JSON-serialised JobClass object to deserialise.
2690+ *
2691+ * Create JobClass from provided JSON and add to the
2692+ * job classes table.
2693+ *
2694+ * Returns: JobClass object, or NULL on error.
2695+ **/
2696+JobClass *
2697+job_class_deserialise (json_object *json)
2698+{
2699+ json_object *json_normalexit;
2700+ JobClass *class = NULL;
2701+ int session_index = -1;
2702+ int ret;
2703+ nih_local char *name = NULL;
2704+ nih_local char *path = NULL;
2705+
2706+ nih_assert (json);
2707+ nih_assert (job_classes);
2708+
2709+ if (! state_check_json_type (json, object))
2710+ goto error;
2711+
2712+ if (! state_get_json_int_var (json, "session", session_index))
2713+ goto error;
2714+
2715+ if (session_index < 0)
2716+ goto error;
2717+
2718+ if (! state_get_json_string_var_strict (json, "name", NULL, name))
2719+ goto error;
2720+
2721+ class = NIH_MUST (job_class_new (NULL, name,
2722+ session_from_index (session_index)));
2723+ if (! class)
2724+ goto error;
2725+
2726+ if (class->session != NULL) {
2727+ nih_warn ("XXX: WARNING (%s:%d): deserialisation of "
2728+ "user jobs and chroot sessions not currently supported",
2729+ __func__, __LINE__);
2730+ goto error;
2731+ }
2732+
2733+ /* job_class_new() sets path */
2734+ if (! state_get_json_string_var_strict (json, "path", NULL, path))
2735+ goto error;
2736+
2737+ nih_assert (! strcmp (class->path, path));
2738+
2739+ /* Discard default instance as we're about to be handed a fresh
2740+ * string from the JSON.
2741+ */
2742+ nih_free (class->instance);
2743+
2744+ if (! state_get_json_string_var_to_obj (json, class, instance))
2745+ goto error;
2746+
2747+ if (! state_get_json_string_var_to_obj (json, class, description))
2748+ goto error;
2749+
2750+ if (! state_get_json_string_var_to_obj (json, class, author))
2751+ goto error;
2752+
2753+ if (! state_get_json_string_var_to_obj (json, class, version))
2754+ goto error;
2755+
2756+ if (! state_get_json_env_array_to_obj (json, class, env))
2757+ goto error;
2758+
2759+ if (! state_get_json_env_array_to_obj (json, class, export))
2760+ goto error;
2761+
2762+ /* start and stop conditions are optional */
2763+ if (json_object_object_get (json, "start_on")) {
2764+ nih_local char *start_on = NULL;
2765+
2766+ if (! state_get_json_string_var_strict (json, "start_on", NULL, start_on))
2767+ goto error;
2768+
2769+ if (*start_on) {
2770+ class->start_on = parse_on_simple (class, "start", start_on);
2771+ if (! class->start_on) {
2772+ NihError *err;
2773+
2774+ err = nih_error_get ();
2775+
2776+ nih_error ("%s %s: %s",
2777+ _("BUG"),
2778+ _("'start on' parse error"),
2779+ err->message);
2780+
2781+ nih_free (err);
2782+
2783+ goto error;
2784+ }
2785+ }
2786+ }
2787+
2788+ if (json_object_object_get (json, "stop_on")) {
2789+ nih_local char *stop_on = NULL;
2790+
2791+ if (! state_get_json_string_var_strict (json, "stop_on", NULL, stop_on))
2792+ goto error;
2793+
2794+ if (*stop_on) {
2795+ class->stop_on = parse_on_simple (class, "stop", stop_on);
2796+ if (! class->stop_on) {
2797+ NihError *err;
2798+
2799+ err = nih_error_get ();
2800+
2801+ nih_error ("%s %s: %s",
2802+ _("BUG"),
2803+ _("'stop on' parse error"),
2804+ err->message);
2805+
2806+ nih_free (err);
2807+
2808+ goto error;
2809+ }
2810+ }
2811+ }
2812+
2813+ if (! state_get_json_str_array_to_obj (json, class, emits))
2814+ goto error;
2815+
2816+ if (! state_get_json_enum_var (json,
2817+ job_class_expect_type_str_to_enum,
2818+ "expect", class->expect))
2819+ goto error;
2820+
2821+ if (! state_get_json_int_var_to_obj (json, class, task))
2822+ goto error;
2823+
2824+ if (! state_get_json_int_var_to_obj (json, class, kill_timeout))
2825+ goto error;
2826+
2827+ if (! state_get_json_int_var_to_obj (json, class, kill_signal))
2828+ goto error;
2829+
2830+ if (! state_get_json_int_var_to_obj (json, class, respawn))
2831+ goto error;
2832+
2833+ if (! state_get_json_int_var_to_obj (json, class, respawn_limit))
2834+ goto error;
2835+
2836+ if (! state_get_json_int_var_to_obj (json, class, respawn_interval))
2837+ goto error;
2838+
2839+ if (! state_get_json_enum_var (json,
2840+ job_class_console_type_str_to_enum,
2841+ "console", class->console))
2842+ goto error;
2843+
2844+ if (! state_get_json_int_var_to_obj (json, class, umask))
2845+ goto error;
2846+
2847+ if (! state_get_json_int_var_to_obj (json, class, nice))
2848+ goto error;
2849+
2850+ if (! state_get_json_int_var_to_obj (json, class, oom_score_adj))
2851+ goto error;
2852+
2853+ if (! state_get_json_string_var_to_obj (json, class, chroot))
2854+ goto error;
2855+
2856+ if (! state_get_json_string_var_to_obj (json, class, chdir))
2857+ goto error;
2858+
2859+ if (! state_get_json_string_var_to_obj (json, class, setuid))
2860+ goto error;
2861+
2862+ if (! state_get_json_string_var_to_obj (json, class, setgid))
2863+ goto error;
2864+
2865+ if (! state_get_json_int_var_to_obj (json, class, deleted))
2866+ goto error;
2867+
2868+ if (! state_get_json_int_var_to_obj (json, class, debug))
2869+ goto error;
2870+
2871+ if (! state_get_json_string_var_to_obj (json, class, usage))
2872+ goto error;
2873+
2874+ json_normalexit = json_object_object_get (json, "normalexit");
2875+ if (! json_normalexit)
2876+ goto error;
2877+
2878+ ret = state_deserialise_int_array (class, json_normalexit,
2879+ int, &class->normalexit, &class->normalexit_len);
2880+ if (ret < 0)
2881+ goto error;
2882+
2883+ if (state_rlimit_deserialise_all (json, class, &class->limits) < 0)
2884+ goto error;
2885+
2886+ if (process_deserialise_all (json, class->process, class->process) < 0)
2887+ goto error;
2888+
2889+ /* Force class to be known.
2890+ *
2891+ * We cannot use job_class_*consider() since the
2892+ * JobClasses have no associated ConfFile.
2893+ */
2894+ job_class_add_safe (class);
2895+
2896+ /* Any jobs must be added after the class is registered
2897+ * (since you cannot add a job to a partially-created
2898+ * class).
2899+ */
2900+ if (job_deserialise_all (class, json) < 0)
2901+ goto error;
2902+
2903+ return class;
2904+
2905+error:
2906+ nih_free (class);
2907+ return NULL;
2908+}
2909+
2910+/**
2911+ * job_class_deserialise_all:
2912+ *
2913+ * @json: root of JSON-serialised state.
2914+ *
2915+ * Convert JSON representation of JobClasses back into JobClass objects.
2916+ *
2917+ * Returns: 0 on success, -1 on error.
2918+ **/
2919+int
2920+job_class_deserialise_all (json_object *json)
2921+{
2922+ JobClass *class = NULL;
2923+
2924+ nih_assert (json);
2925+
2926+ job_class_init ();
2927+
2928+ json_classes = json_object_object_get (json, "job_classes");
2929+
2930+ if (! json_classes)
2931+ goto error;
2932+
2933+ if (! state_check_json_type (json_classes, array))
2934+ goto error;
2935+
2936+ for (int i = 0; i < json_object_array_length (json_classes); i++) {
2937+ json_object *json_class;
2938+
2939+ json_class = json_object_array_get_idx (json_classes, i);
2940+ if (! json_class)
2941+ goto error;
2942+
2943+ if (! state_check_json_type (json_class, object))
2944+ goto error;
2945+
2946+ class = job_class_deserialise (json_class);
2947+ if (! class)
2948+ goto error;
2949+
2950+ /* FIXME:
2951+ *
2952+ * If user sessions exist (ie 'initctl --session list'
2953+ * has been run), we get this failure:
2954+ *
2955+ * serialised path='/com/ubuntu/Upstart/jobs/1000/bang'
2956+ * path set by job_class_new()='/com/ubuntu/Upstart/jobs/_/1000/bang'
2957+ *
2958+ */
2959+
2960+ }
2961+
2962+ return 0;
2963+
2964+error:
2965+ if (class)
2966+ nih_free (class);
2967+
2968+ return -1;
2969+}
2970+
2971+
2972+/**
2973+ * job_class_expect_type_enum_to_str:
2974+ *
2975+ * @expect: ExpectType.
2976+ *
2977+ * Convert ExpectType to a string representation.
2978+ *
2979+ * Returns: string representation of @expect, or NULL if not known.
2980+ **/
2981+const char *
2982+job_class_expect_type_enum_to_str (ExpectType expect)
2983+{
2984+ state_enum_to_str (EXPECT_NONE, expect);
2985+ state_enum_to_str (EXPECT_STOP, expect);
2986+ state_enum_to_str (EXPECT_DAEMON, expect);
2987+ state_enum_to_str (EXPECT_FORK, expect);
2988+
2989+ return NULL;
2990+}
2991+
2992+/**
2993+ * job_class_expect_type_str_to_enum:
2994+ *
2995+ * @expect: string ExpectType value.
2996+ *
2997+ * Convert @expect back into an enum value.
2998+ *
2999+ * Returns: ExpectType representing @expect, or -1 if not known.
3000+ **/
3001+ExpectType
3002+job_class_expect_type_str_to_enum (const char *expect)
3003+{
3004+ nih_assert (expect);
3005+
3006+ state_str_to_enum (EXPECT_NONE, expect);
3007+ state_str_to_enum (EXPECT_STOP, expect);
3008+ state_str_to_enum (EXPECT_DAEMON, expect);
3009+ state_str_to_enum (EXPECT_FORK, expect);
3010+
3011+ return -1;
3012+}
3013+
3014+/**
3015+ * job_class_console_type_enum_to_str:
3016+ *
3017+ * @console: ConsoleType.
3018+ *
3019+ * Convert ConsoleType to a string representation.
3020+ *
3021+ * Returns: string representation of @console, or NULL if not known.
3022+ **/
3023+const char *
3024+job_class_console_type_enum_to_str (ConsoleType console)
3025+{
3026+ state_enum_to_str (CONSOLE_NONE, console);
3027+ state_enum_to_str (CONSOLE_OUTPUT, console);
3028+ state_enum_to_str (CONSOLE_OWNER, console);
3029+ state_enum_to_str (CONSOLE_LOG, console);
3030+
3031+ return NULL;
3032+}
3033+
3034+/**
3035+ * job_class_console_type_str_to_enum:
3036+ *
3037+ * @console: string ConsoleType value.
3038+ *
3039+ * Convert @console back into enum value.
3040+ *
3041+ * Returns: ExpectType representing @console, or -1 if not known.
3042+ **/
3043+ConsoleType
3044+job_class_console_type_str_to_enum (const char *console)
3045+{
3046+ if (! console)
3047+ goto error;
3048+
3049+ state_str_to_enum (CONSOLE_NONE, console);
3050+ state_str_to_enum (CONSOLE_OUTPUT, console);
3051+ state_str_to_enum (CONSOLE_OWNER, console);
3052+ state_str_to_enum (CONSOLE_LOG, console);
3053+
3054+error:
3055+ return -1;
3056+}
3057+
3058+/**
3059+ * job_class_prepare_reexec:
3060+ *
3061+ * Prepare for a re-exec by clearing the CLOEXEC bit on all log object
3062+ * file descriptors associated with their parent jobs.
3063+ **/
3064+void
3065+job_class_prepare_reexec (void)
3066+{
3067+ job_class_init ();
3068+
3069+ NIH_HASH_FOREACH (job_classes, iter) {
3070+ JobClass *class = (JobClass *)iter;
3071+
3072+ NIH_HASH_FOREACH (class->instances, job_iter) {
3073+ Job *job = (Job *)job_iter;
3074+
3075+ nih_assert (job->log);
3076+
3077+ for (int process = 0; process < PROCESS_LAST; process++) {
3078+ int fd;
3079+ Log *log;
3080+
3081+ log = job->log[process];
3082+
3083+ /* No associated job process or logger has detected
3084+ * remote end of pty has closed.
3085+ */
3086+ if (! log || ! log->io)
3087+ continue;
3088+
3089+ nih_assert (log->io->watch);
3090+
3091+ fd = log->io->watch->fd;
3092+ if (fd < 0)
3093+ continue;
3094+
3095+ if (state_toggle_cloexec (fd, FALSE) < 0)
3096+ goto error;
3097+
3098+ fd = log->fd;
3099+ if (fd < 0)
3100+ continue;
3101+
3102+ if (state_toggle_cloexec (fd, FALSE) < 0)
3103+ goto error;
3104+ }
3105+ }
3106+ }
3107+
3108+ return;
3109+
3110+error:
3111+ nih_warn (_("unable to clear CLOEXEC bit on log fd"));
3112+}
3113
3114=== modified file 'init/job_class.h'
3115--- init/job_class.h 2012-02-16 15:45:41 +0000
3116+++ init/job_class.h 2012-11-14 14:49:20 +0000
3117@@ -196,7 +196,7 @@
3118 int task;
3119
3120 time_t kill_timeout;
3121- int kill_signal;
3122+ int kill_signal;
3123
3124 int respawn;
3125 int respawn_limit;
3126@@ -238,6 +238,8 @@
3127 int job_class_consider (JobClass *class);
3128 int job_class_reconsider (JobClass *class);
3129
3130+void job_class_add_safe (JobClass *class);
3131+
3132 void job_class_register (JobClass *class,
3133 DBusConnection *conn, int signal);
3134 void job_class_unregister (JobClass *class,
3135@@ -306,9 +308,42 @@
3136 NihDBusMessage *message,
3137 char **usage);
3138
3139+const char *
3140+job_class_console_type_enum_to_str (ConsoleType console)
3141+ __attribute__ ((warn_unused_result));
3142+
3143+ConsoleType
3144+job_class_console_type_str_to_enum (const char *name)
3145+ __attribute__ ((warn_unused_result));
3146+
3147+const char *
3148+job_class_expect_type_enum_to_str (ExpectType expect)
3149+ __attribute__ ((warn_unused_result));
3150+
3151+ExpectType
3152+job_class_expect_type_str_to_enum (const char *name)
3153+ __attribute__ ((warn_unused_result));
3154+
3155 ConsoleType job_class_console_type (const char *console)
3156 __attribute__ ((warn_unused_result));
3157
3158+json_object *job_class_serialise (const JobClass *class)
3159+ __attribute__ ((warn_unused_result, malloc));
3160+
3161+JobClass *job_class_deserialise (json_object *json)
3162+ __attribute__ ((malloc, warn_unused_result));
3163+
3164+json_object * job_class_serialise_all (void)
3165+ __attribute__ ((warn_unused_result, malloc));
3166+
3167+int job_class_deserialise_all (json_object *json)
3168+ __attribute__ ((warn_unused_result));
3169+
3170+JobClass * job_class_get (const char *name, Session *session)
3171+ __attribute__ ((warn_unused_result));
3172+
3173+void job_class_prepare_reexec (void);
3174+
3175 NIH_END_EXTERN
3176
3177 #endif /* INIT_JOB_CLASS_H */
3178
3179=== modified file 'init/job_process.c'
3180--- init/job_process.c 2012-08-02 08:57:20 +0000
3181+++ init/job_process.c 2012-11-14 14:49:20 +0000
3182@@ -637,7 +637,7 @@
3183 */
3184 setsid ();
3185
3186- /* Set the process environment from the function paramters. */
3187+ /* Set the process environment from the function parameters. */
3188 environ = (char **)env;
3189
3190 /* Handle unprivileged user job by dropping privileges to
3191@@ -1226,7 +1226,7 @@
3192 nih_assert (job != NULL);
3193 nih_assert (job->pid[process] > 0);
3194 nih_assert (job->kill_timer == NULL);
3195- nih_assert (job->kill_process = -1);
3196+ nih_assert (job->kill_process == PROCESS_INVALID);
3197
3198 nih_info (_("Sending %s signal to %s %s process (%d)"),
3199 nih_signal_to_name (job->class->kill_signal),
3200@@ -1246,13 +1246,50 @@
3201 return;
3202 }
3203
3204+ job_process_set_kill_timer (job, process, job->class->kill_timeout);
3205+}
3206+
3207+/**
3208+ * job_process_set_kill_timer:
3209+ * @job: job to set kill timer for,
3210+ * @process: process to be killed,
3211+ * @timeout: timeout to apply for timer.
3212+ *
3213+ * Set kill timer for specified @job @process with timeout @timeout.
3214+ **/
3215+void
3216+job_process_set_kill_timer (Job *job,
3217+ ProcessType process,
3218+ time_t timeout)
3219+{
3220+ nih_assert (job);
3221+ nih_assert (timeout);
3222+
3223 job->kill_process = process;
3224 job->kill_timer = NIH_MUST (nih_timer_add_timeout (
3225- job, job->class->kill_timeout,
3226+ job, timeout,
3227 (NihTimerCb)job_process_kill_timer, job));
3228 }
3229
3230 /**
3231+ * job_process_adj_kill_timer:
3232+ *
3233+ * @job: job whose kill timer is to be modified,
3234+ * @due: new due time to set for job kill timer.
3235+ *
3236+ * Adjust due time for @job's kill timer to @due.
3237+ **/
3238+void
3239+job_process_adj_kill_timer (Job *job, time_t due)
3240+{
3241+ nih_assert (job);
3242+ nih_assert (job->kill_timer);
3243+ nih_assert (due);
3244+
3245+ job->kill_timer->due = due;
3246+}
3247+
3248+/**
3249 * job_process_kill_timer:
3250 * @job: job to kill process of,
3251 * @timer: timer that caused us to be called.
3252@@ -1270,13 +1307,13 @@
3253 nih_assert (job != NULL);
3254 nih_assert (timer != NULL);
3255 nih_assert (job->kill_timer == timer);
3256- nih_assert (job->kill_process != (ProcessType)-1);
3257+ nih_assert (job->kill_process != PROCESS_INVALID);
3258
3259 process = job->kill_process;
3260 nih_assert (job->pid[process] > 0);
3261
3262 job->kill_timer = NULL;
3263- job->kill_process = -1;
3264+ job->kill_process = PROCESS_INVALID;
3265
3266 nih_info (_("Sending %s signal to %s %s process (%d)"),
3267 "KILL",
3268@@ -1547,7 +1584,7 @@
3269 job_name (job));
3270
3271 failed = FALSE;
3272- job_failed (job, -1, 0);
3273+ job_failed (job, PROCESS_INVALID, 0);
3274 } else {
3275 nih_warn (_("%s %s process ended, respawning"),
3276 job_name (job),
3277@@ -1631,7 +1668,7 @@
3278 if (job->kill_timer) {
3279 nih_unref (job->kill_timer, job);
3280 job->kill_timer = NULL;
3281- job->kill_process = -1;
3282+ job->kill_process = PROCESS_INVALID;
3283 }
3284
3285 if (job->class->console == CONSOLE_LOG && job->log[process]) {
3286
3287=== modified file 'init/job_process.h'
3288--- init/job_process.h 2012-08-02 08:57:20 +0000
3289+++ init/job_process.h 2012-11-14 14:49:20 +0000
3290@@ -143,6 +143,12 @@
3291 char *job_process_log_path (Job *job, int user_job)
3292 __attribute__ ((malloc, warn_unused_result));
3293
3294+void job_process_set_kill_timer (Job *job,
3295+ ProcessType process,
3296+ time_t timeout);
3297+
3298+void job_process_adj_kill_timer (Job *job, time_t due);
3299+
3300 NIH_END_EXTERN
3301
3302 #endif /* INIT_JOB_PROCESS_H */
3303
3304=== modified file 'init/log.c'
3305--- init/log.c 2012-03-16 21:06:11 +0000
3306+++ init/log.c 2012-11-14 14:49:20 +0000
3307@@ -48,8 +48,13 @@
3308 /**
3309 * log_unflushed_files:
3310 *
3311- * List of known sources of configuration; each item is an
3312- * NihListEntry.
3313+ * List of NihListEntry objects containing Log objects which are no
3314+ * longer associated with Job processes.
3315+ *
3316+ * All the entries in the list contain unflushed Log data.
3317+ *
3318+ * Used to capture job process output early in the boot process for
3319+ * jobs that end before the log partition is mounted and writeable.
3320 **/
3321 NihList *log_unflushed_files = NULL;
3322
3323@@ -393,7 +398,7 @@
3324 *
3325 * Opens log file associated with @log if not already open.
3326 *
3327- * Returns 0 on success, -1 on failure.
3328+ * Returns: 0 on success, -1 on failure.
3329 **/
3330 static int
3331 log_file_open (Log *log)
3332@@ -505,7 +510,7 @@
3333 * a corrupted log file should space later become
3334 * available.
3335 *
3336- * Returns 0 on success, -1 on failure.
3337+ * Returns: 0 on success, -1 on failure.
3338 **/
3339 static int
3340 log_file_write (Log *log, const char *buf, size_t len)
3341@@ -638,6 +643,8 @@
3342 if (nih_io_buffer_resize (io->recv_buf, LOG_READ_SIZE) < 0)
3343 break;
3344
3345+ errno = 0;
3346+
3347 /* Append to buffer */
3348 len = read (io->watch->fd,
3349 io->recv_buf->buf + io->recv_buf->len,
3350@@ -680,22 +687,23 @@
3351 * causes the loop to be exited.
3352 */
3353 if (len <= 0) {
3354+ /* Job process has ended and we've drained all the data the job
3355+ * produced, so remote end must have closed.
3356+ *
3357+ * This cannot be handled entirely by log_io_error_handler()
3358+ * since the job may produce some output prior to disks being
3359+ * writeable, then end without producing further output.
3360+ * In this scenario the error handler is never called.
3361+ *
3362+ */
3363+ if (saved && saved != EAGAIN && saved != EWOULDBLOCK)
3364+ log->remote_closed = 1;
3365+
3366 close (log->fd);
3367 log->fd = -1;
3368 break;
3369 }
3370 }
3371-
3372- /* Job process has ended and we've drained all the data the job
3373- * produced, so remote end must have closed.
3374- *
3375- * This cannot be handled entirely by log_io_error_handler()
3376- * since the job may produce some output prior to disks being
3377- * writeable, then end without producing further output.
3378- * In this scenario the error handler is never called.
3379- *
3380- */
3381- log->remote_closed = 1;
3382 }
3383
3384 /**
3385@@ -810,3 +818,193 @@
3386
3387 return 0;
3388 }
3389+
3390+/**
3391+ * log_serialise:
3392+ * @log: log to serialise.
3393+ *
3394+ * Convert @log into a JSON representation for serialisation.
3395+ * Caller must free returned value using json_object_put().
3396+ *
3397+ * Returns: JSON-serialised Log object, or NULL on error.
3398+ **/
3399+json_object *
3400+log_serialise (Log *log)
3401+{
3402+ json_object *json;
3403+ nih_local char *unflushed_hex = NULL;
3404+
3405+ json = json_object_new_object ();
3406+ if (! json)
3407+ return NULL;
3408+
3409+ if (! log || (! log->io && log->unflushed && ! log->unflushed->len)) {
3410+ /* Create a "placeholder" log object for non-existent
3411+ * log objects and for those that are no longer usable.
3412+ */
3413+ if (! state_set_json_string_var (json, "path", NULL))
3414+ goto error;
3415+ return json;
3416+ }
3417+
3418+ /* Attempt to flush any cached data */
3419+ if (log->unflushed->len) {
3420+ /* Don't check return values since if this fails and
3421+ * unflushed data remains, we encode it below.
3422+ */
3423+ if (log->fd < 0)
3424+ (void)log_file_open (log);
3425+ if (log->fd != -1)
3426+ (void)log_file_write (log, NULL, 0);
3427+ }
3428+
3429+ nih_assert (log->io);
3430+
3431+ if (! state_set_json_int_var_from_obj (json, log, fd))
3432+ goto error;
3433+
3434+ nih_assert (log->io->watch);
3435+
3436+ if (! state_set_json_int_var (json, "io_watch_fd", log->io->watch->fd))
3437+ goto error;
3438+
3439+ if (! state_set_json_string_var_from_obj (json, log, path))
3440+ goto error;
3441+
3442+ /* log->io itself is not encoded */
3443+
3444+ if (! state_set_json_int_var_from_obj (json, log, uid))
3445+ goto error;
3446+
3447+ /* Encode unflushed data as hex to ensure any embedded
3448+ * nulls are handled.
3449+ */
3450+ if (log->unflushed->len) {
3451+ unflushed_hex = state_data_to_hex (NULL,
3452+ log->unflushed->buf,
3453+ log->unflushed->len);
3454+
3455+ if (! unflushed_hex)
3456+ goto error;
3457+
3458+ if (! state_set_json_string_var (json, "unflushed", unflushed_hex))
3459+ goto error;
3460+ }
3461+
3462+ if (! state_set_json_int_var_from_obj (json, log, detached))
3463+ goto error;
3464+
3465+ if (! state_set_json_int_var_from_obj (json, log, remote_closed))
3466+ goto error;
3467+
3468+ if (! state_set_json_int_var_from_obj (json, log, open_errno))
3469+ goto error;
3470+
3471+ return json;
3472+
3473+error:
3474+ json_object_put (json);
3475+ return NULL;
3476+}
3477+
3478+/**
3479+ * log_deserialise:
3480+ * @json: JSON-serialised Log object to deserialise.
3481+ *
3482+ * Convert @json into a Log object.
3483+ *
3484+ * Returns: Log object, or NULL on error.
3485+ **/
3486+Log *
3487+log_deserialise (const void *parent,
3488+ json_object *json)
3489+{
3490+ Log *log;
3491+ nih_local char *unflushed_hex = NULL;
3492+ nih_local char *unflushed = NULL;
3493+ int ret;
3494+ size_t len;
3495+ json_object *json_unflushed;
3496+ nih_local char *path = NULL;
3497+ int io_watch_fd = -1;
3498+ uid_t uid = (uid_t)-1;
3499+
3500+ nih_assert (json);
3501+
3502+ log_unflushed_init ();
3503+
3504+ if (! state_check_json_type (json, object))
3505+ return NULL;
3506+
3507+ if (! state_get_json_string_var (json, "path", NULL, path))
3508+ return NULL;
3509+
3510+ if (! path) {
3511+ /* placeholder log object */
3512+ return NULL;
3513+ }
3514+
3515+ if (! state_get_json_int_var (json, "io_watch_fd", io_watch_fd))
3516+ return NULL;
3517+
3518+ nih_assert (io_watch_fd != -1);
3519+
3520+ /* re-apply CLOEXEC flag to stop job fd being leaked to children */
3521+ if (state_toggle_cloexec (io_watch_fd, TRUE) < 0)
3522+ return NULL;
3523+
3524+ if (! state_get_json_int_var (json, "uid", uid))
3525+ return NULL;
3526+
3527+ log = log_new (parent, path, io_watch_fd, uid);
3528+ if (! log)
3529+ return NULL;
3530+
3531+ if (! state_get_json_int_var_to_obj (json, log, fd))
3532+ goto error;
3533+
3534+ /* Re-apply CLOEXEC flag to stop log file fd being leaked to children.
3535+ *
3536+ * Note we discard return value since if this fails,
3537+ * we would never close the fd.
3538+ */
3539+ if (log->fd != -1)
3540+ (void)state_toggle_cloexec (log->fd, TRUE);
3541+
3542+ log->unflushed = nih_io_buffer_new (log);
3543+ if (! log->unflushed)
3544+ goto error;
3545+
3546+ json_unflushed = json_object_object_get (json, "unflushed");
3547+ if (json_unflushed) {
3548+ if (! state_get_json_string_var_strict (json, "unflushed", NULL, unflushed_hex))
3549+ goto error;
3550+
3551+ ret = state_hex_to_data (NULL,
3552+ unflushed_hex,
3553+ strlen (unflushed_hex),
3554+ &unflushed,
3555+ &len);
3556+
3557+ if (ret < 0)
3558+ goto error;
3559+
3560+ if (nih_io_buffer_push (log->unflushed, unflushed, len) < 0)
3561+ goto error;
3562+ }
3563+
3564+ if (! state_get_json_int_var_to_obj (json, log, detached))
3565+ goto error;
3566+
3567+ if (! state_get_json_int_var_to_obj (json, log, remote_closed))
3568+ goto error;
3569+
3570+ if (! state_get_json_int_var_to_obj (json, log, open_errno))
3571+ goto error;
3572+
3573+ return log;
3574+
3575+error:
3576+ nih_free (log);
3577+ return NULL;
3578+}
3579
3580=== modified file 'init/log.h'
3581--- init/log.h 2012-02-14 16:47:34 +0000
3582+++ init/log.h 2012-11-14 14:49:20 +0000
3583@@ -29,6 +29,8 @@
3584 #include <nih/logging.h>
3585 #include <nih/error.h>
3586
3587+#include "state.h"
3588+
3589 /** LOG_DEFAULT_UMASK:
3590 *
3591 * The default file creation mask for log files.
3592@@ -55,7 +57,7 @@
3593 * @io: NihIo associated with jobs stdout and stderr,
3594 * @uid: User ID of caller,
3595 * @unflushed: Unflushed data,
3596- * @detached: TRUE if log is no longer associated with a parent,(job),
3597+ * @detached: TRUE if log is no longer associated with a parent (job),
3598 * @remote_closed: TRUE if remote end of pty has been closed,
3599 * @open_errno: value of errno immediately after last attempt to open @path.
3600 **/
3601@@ -79,11 +81,17 @@
3602 __attribute__ ((warn_unused_result, malloc));
3603 void log_io_reader (Log *log, NihIo *io, const char *buf, size_t len);
3604 void log_io_error_handler (Log *log, NihIo *io);
3605-int log_destroy (Log *log);
3606-int log_handle_unflushed (void *parent, Log *log);
3607-int log_clear_unflushed (void);
3608+int log_destroy (Log *log)
3609+ __attribute__ ((warn_unused_result));
3610+int log_handle_unflushed (void *parent, Log *log)
3611+ __attribute__ ((warn_unused_result));
3612+int log_clear_unflushed (void)
3613+ __attribute__ ((warn_unused_result));
3614 void log_unflushed_init (void);
3615-
3616+json_object * log_serialise (Log *log)
3617+ __attribute__ ((warn_unused_result, malloc));
3618+Log * log_deserialise (const void *parent, json_object *json)
3619+ __attribute__ ((warn_unused_result, malloc));
3620
3621 NIH_END_EXTERN
3622
3623
3624=== modified file 'init/main.c'
3625--- init/main.c 2012-08-07 16:39:00 +0000
3626+++ init/main.c 2012-11-14 14:49:20 +0000
3627@@ -60,12 +60,16 @@
3628 #include "event.h"
3629 #include "conf.h"
3630 #include "control.h"
3631+#include "state.h"
3632
3633
3634 /* Prototypes for static functions */
3635 #ifndef DEBUG
3636 static int logger_kmsg (NihLogLevel priority, const char *message);
3637 static void crash_handler (int signum);
3638+#endif /* DEBUG */
3639+static void term_handler (void *data, NihSignal *signal);
3640+#ifndef DEBUG
3641 static void cad_handler (void *data, NihSignal *signal);
3642 static void kbd_handler (void *data, NihSignal *signal);
3643 static void pwr_handler (void *data, NihSignal *signal);
3644@@ -79,20 +83,12 @@
3645
3646
3647 /**
3648- * argv0:
3649- *
3650- * Path to program executed, used for re-executing the init binary from the
3651- * same location we were executed from.
3652- **/
3653-static const char *argv0 = NULL;
3654-
3655-/**
3656- * restart:
3657- *
3658- * This is set to TRUE if we're being re-exec'd by an existing init
3659- * process.
3660- **/
3661-static int restart = FALSE;
3662+ * state_fd:
3663+ *
3664+ * File descriptor to read serialised state from when performing
3665+ * stateful re-exec. If value is not -1, attempt stateful re-exec.
3666+ **/
3667+static int state_fd = -1;
3668
3669 /**
3670 * conf_dir:
3671@@ -147,7 +143,13 @@
3672 { 0, "no-startup-event", N_("do not emit any startup event (for testing)"),
3673 NULL, NULL, &disable_startup_event, NULL },
3674
3675- { 0, "restart", NULL, NULL, NULL, &restart, NULL },
3676+ /* Must be specified for both stateful and stateless re-exec */
3677+ { 0, "restart", N_("flag a re-exec has occurred"),
3678+ NULL, NULL, &restart, NULL },
3679+
3680+ /* Required for stateful re-exec */
3681+ { 0, "state-fd", N_("specify file descriptor to read serialisation data from"),
3682+ NULL, "FD", &state_fd, nih_option_int },
3683
3684 { 0, "session", N_("use D-Bus session bus rather than system bus (for testing)"),
3685 NULL, NULL, &use_session_bus, NULL },
3686@@ -166,11 +168,12 @@
3687 main (int argc,
3688 char *argv[])
3689 {
3690- char **args;
3691+ char **args = NULL;
3692 int ret;
3693
3694- argv0 = argv[0];
3695- nih_main_init (argv0);
3696+ args_copy = NIH_MUST (nih_str_array_copy (NULL, NULL, argv));
3697+
3698+ nih_main_init (args_copy[0]);
3699
3700 nih_option_set_synopsis (_("Process management daemon."));
3701 nih_option_set_help (
3702@@ -295,8 +298,9 @@
3703 */
3704 if (system_setup_console (CONSOLE_OUTPUT, (! restart)) < 0) {
3705 NihError *err;
3706-
3707+
3708 err = nih_error_get ();
3709+
3710 nih_warn ("%s: %s", _("Unable to initialize console, will try /dev/null"),
3711 err->message);
3712 nih_free (err);
3713@@ -411,7 +415,16 @@
3714 /* SIGUSR1 instructs us to reconnect to D-Bus */
3715 nih_signal_set_handler (SIGUSR1, nih_signal_handler);
3716 NIH_MUST (nih_signal_add_handler (NULL, SIGUSR1, usr1_handler, NULL));
3717+
3718 }
3719+
3720+ /* SIGTERM instructs us to re-exec ourselves; this should be the
3721+ * last in the list to ensure that all other signals are handled
3722+ * before a SIGTERM.
3723+ */
3724+ nih_signal_set_handler (SIGTERM, nih_signal_handler);
3725+ NIH_MUST (nih_signal_add_handler (NULL, SIGTERM, term_handler, NULL));
3726+
3727 #endif /* DEBUG */
3728
3729
3730@@ -456,6 +469,48 @@
3731 }
3732
3733
3734+ if (restart) {
3735+ if (state_fd == -1) {
3736+ nih_warn ("%s",
3737+ _("Stateful re-exec supported but stateless re-exec requested"));
3738+ } else if (state_read (state_fd) < 0) {
3739+ nih_local char *arg = NULL;
3740+
3741+ /* Stateful re-exec has failed so try once more by
3742+ * degrading to stateless re-exec, which even in
3743+ * the case of low-memory scenarios will work.
3744+ */
3745+
3746+ /* Inform the child we've given up on stateful
3747+ * re-exec.
3748+ */
3749+ close (state_fd);
3750+
3751+ nih_error ("%s - %s",
3752+ _("Failed to read serialisation data"),
3753+ _("reverting to stateless re-exec"));
3754+
3755+ /* Remove any existing (but now stale) state fd
3756+ * args which will effectively disable stateful
3757+ * re-exec.
3758+ */
3759+ clean_args (&args_copy);
3760+
3761+ /* Attempt stateless re-exec */
3762+ perform_reexec ();
3763+
3764+ nih_error ("%s",
3765+ _("Both stateful and stateless re-execs failed"));
3766+
3767+ /* Out of options */
3768+ nih_assert_not_reached ();
3769+ } else {
3770+ close (state_fd);
3771+
3772+ nih_info ("Stateful re-exec completed");
3773+ }
3774+ }
3775+
3776 /* Read configuration */
3777 NIH_MUST (conf_source_new (NULL, CONFFILE, CONF_FILE));
3778 NIH_MUST (conf_source_new (NULL, conf_dir, CONF_JOB_DIR));
3779@@ -479,7 +534,8 @@
3780 }
3781
3782 /* Open connection to the appropriate D-Bus bus; we normally expect this to
3783- * fail and will try again later - don't let ENOMEM stop us though.
3784+ * fail (since dbus-daemon probably isn't running yet) and will try again
3785+ * later - don't let ENOMEM stop us though.
3786 */
3787 while (control_bus_open () < 0) {
3788 NihError *err;
3789@@ -529,9 +585,17 @@
3790 }
3791
3792 } else {
3793- sigset_t mask;
3794-
3795- /* We're ok to receive signals again */
3796+ sigset_t mask;
3797+
3798+ /* We have been re-exec'd. Don't emit an initial event
3799+ * as only Upstart is restarting - we don't want to restart
3800+ * the system (another reason being that we don't yet support
3801+ * upstart-in-initramfs to upstart-in-root-filesystem
3802+ * state-passing transitions).
3803+ */
3804+
3805+ /* We're ok to receive signals again so restore signals
3806+ * disabled by the term_handler */
3807 sigemptyset (&mask);
3808 sigprocmask (SIG_SETMASK, &mask, NULL);
3809 }
3810@@ -631,7 +695,7 @@
3811 {
3812 pid_t pid;
3813
3814- nih_assert (argv0 != NULL);
3815+ nih_assert (args_copy[0] != NULL);
3816
3817 pid = fork ();
3818 if (pid == 0) {
3819@@ -685,7 +749,30 @@
3820 /* Goodbye, cruel world. */
3821 exit (signum);
3822 }
3823-
3824+#endif
3825+
3826+/**
3827+ * term_handler:
3828+ * @data: unused,
3829+ * @signal: signal caught.
3830+ *
3831+ * This is called when we receive the TERM signal, which instructs us
3832+ * to reexec ourselves.
3833+ **/
3834+static void
3835+term_handler (void *data,
3836+ NihSignal *signal)
3837+{
3838+ nih_assert (args_copy[0] != NULL);
3839+ nih_assert (signal != NULL);
3840+
3841+ nih_warn (_("Re-executing %s"), args_copy[0]);
3842+
3843+ stateful_reexec ();
3844+}
3845+
3846+
3847+#ifndef DEBUG
3848 /**
3849 * cad_handler:
3850 * @data: unused,
3851@@ -835,7 +922,7 @@
3852 * NihOption setter function to handle selection of default console
3853 * type.
3854 *
3855- * Returns 0 on success, -1 on invalid console type.
3856+ * Returns: 0 on success, -1 on invalid console type.
3857 **/
3858 static int
3859 console_type_setter (NihOption *option, const char *arg)
3860@@ -851,3 +938,4 @@
3861
3862 return 0;
3863 }
3864+
3865
3866=== modified file 'init/parse_job.c'
3867--- init/parse_job.c 2012-10-22 13:29:45 +0000
3868+++ init/parse_job.c 2012-11-14 14:49:20 +0000
3869@@ -37,7 +37,6 @@
3870 #include <nih/string.h>
3871 #include <nih/list.h>
3872 #include <nih/signal.h>
3873-#include <nih/config.h>
3874 #include <nih/logging.h>
3875 #include <nih/error.h>
3876
3877@@ -625,6 +624,48 @@
3878 }
3879
3880 /**
3881+ * parse_on_simple:
3882+ * @class: job class being parsed,
3883+ * @stanza_name: name of stanza type to parse ("start" or "stop"),
3884+ * @string: string to parse.
3885+ *
3886+ * Parse either a "start" or "stop" condition from @string (which must
3887+ * start with the first byte beyond either "start on" or "stop on".
3888+ *
3889+ * Returns: EventOperator at root of expression tree on success, NULL
3890+ * on raised error.
3891+ **/
3892+EventOperator *
3893+parse_on_simple (JobClass *class, const char *stanza_name, const char *string)
3894+{
3895+ EventOperator *root = NULL;
3896+ NihConfigStanza *stanza = NULL;
3897+ size_t pos = 0;
3898+ size_t lineno = 0;
3899+ size_t len;
3900+
3901+ nih_assert (class);
3902+ nih_assert (stanza_name);
3903+ nih_assert (string);
3904+
3905+ /* Find the appropriate config stanza */
3906+ for (NihConfigStanza *s = stanzas; s->name; s++) {
3907+ if (! strcmp (stanza_name, s->name)) {
3908+ stanza = s;
3909+ break;
3910+ }
3911+ }
3912+
3913+ nih_assert (stanza);
3914+
3915+ len = strlen (string);
3916+
3917+ root = parse_on (class, stanza, string, len, &pos, &lineno);
3918+
3919+ return root;
3920+}
3921+
3922+/**
3923 * parse_on_operator:
3924 * @class: job class being parsed,
3925 * @stanza: stanza found,
3926
3927=== modified file 'init/parse_job.h'
3928--- init/parse_job.h 2011-06-06 12:52:08 +0000
3929+++ init/parse_job.h 2012-11-14 14:49:20 +0000
3930@@ -21,6 +21,7 @@
3931 #define INIT_PARSE_JOB_H
3932
3933 #include <nih/macros.h>
3934+#include <nih/config.h>
3935
3936 #include "session.h"
3937 #include "job_class.h"
3938@@ -34,6 +35,10 @@
3939 size_t *pos, size_t *lineno)
3940 __attribute__ ((warn_unused_result, malloc));
3941
3942+EventOperator *
3943+parse_on_simple (JobClass *class, const char *stanza_name,
3944+ const char *string)
3945+ __attribute__ ((warn_unused_result, malloc));
3946 NIH_END_EXTERN
3947
3948 #endif /* INIT_PARSE_JOB_H */
3949
3950=== modified file 'init/process.c'
3951--- init/process.c 2009-06-23 09:29:35 +0000
3952+++ init/process.c 2012-11-14 14:49:20 +0000
3953@@ -29,8 +29,10 @@
3954 #include <nih/macros.h>
3955 #include <nih/alloc.h>
3956 #include <nih/logging.h>
3957+#include <nih/string.h>
3958
3959 #include "process.h"
3960+#include "state.h"
3961
3962
3963 /**
3964@@ -116,3 +118,224 @@
3965 return -1;
3966 }
3967 }
3968+
3969+/**
3970+ * process_serialise:
3971+ * @process: process to serialise.
3972+ *
3973+ * Convert @process into a JSON representation for serialisation.
3974+ * Caller must free returned value using json_object_put().
3975+ *
3976+ * Returns: JSON-serialised Process object, or NULL on error.
3977+ **/
3978+json_object *
3979+process_serialise (const Process *process)
3980+{
3981+ json_object *json;
3982+
3983+ nih_assert (process);
3984+
3985+ json = json_object_new_object ();
3986+ if (! json)
3987+ return NULL;
3988+
3989+ if (! state_set_json_int_var_from_obj (json, process, script))
3990+ goto error;
3991+
3992+ if (! state_set_json_string_var_from_obj (json, process, command))
3993+ goto error;
3994+
3995+ return json;
3996+
3997+error:
3998+ json_object_put (json);
3999+ return NULL;
4000+
4001+}
4002+
4003+/**
4004+ * process_serialise_all:
4005+ *
4006+ * @processes: array of Processes.
4007+ *
4008+ * Convert array of Process objects to JSON representation.
4009+ *
4010+ * Returns: JSON object containing array of Processes, or NULL on error.
4011+ */
4012+json_object *
4013+process_serialise_all (const Process * const * const processes)
4014+{
4015+ json_object *json;
4016+ json_object *json_process;
4017+ Process dummy = { 0, NULL };
4018+
4019+ nih_assert (processes);
4020+
4021+ json = json_object_new_array ();
4022+ if (! json)
4023+ return NULL;
4024+
4025+ for (int i = 0; i < PROCESS_LAST; i++) {
4026+ /* We must encode a blank entry for missing array elements
4027+ * to ensure correct deserialisation.
4028+ */
4029+ json_process = process_serialise (processes[i]
4030+ ? processes[i]
4031+ : &dummy);
4032+
4033+ if (! json_process)
4034+ goto error;
4035+
4036+ if (json_object_array_add (json, json_process) < 0)
4037+ goto error;
4038+ }
4039+
4040+ return json;
4041+
4042+error:
4043+ json_object_put (json);
4044+ return NULL;
4045+}
4046+
4047+/**
4048+ * process_deserialise:
4049+ * @json: JSON-serialised Process object to deserialise.
4050+ *
4051+ * Convert @json into a Process object.
4052+ *
4053+ * Returns: Process object, or NULL on error.
4054+ **/
4055+Process *
4056+process_deserialise (json_object *json, const void *parent)
4057+{
4058+ Process *process = NULL;
4059+
4060+ nih_assert (json);
4061+
4062+ if (! state_check_json_type (json, object))
4063+ goto error;
4064+
4065+ process = NIH_MUST (process_new (parent));
4066+
4067+ if (! state_get_json_int_var_to_obj (json, process, script))
4068+ goto error;
4069+
4070+ if (! state_get_json_string_var_to_obj (json, process, command))
4071+ goto error;
4072+
4073+ /* All Process slots have to be serialised in the JSON to
4074+ * guarantee ordering on deserialisation.
4075+ *
4076+ * However, here we've found a Process that was merely
4077+ * an ordering placeholder - no command has been defined,
4078+ * so ignore it.
4079+ */
4080+ if (! process->command)
4081+ goto error;
4082+
4083+ return process;
4084+
4085+error:
4086+ nih_free (process);
4087+ return NULL;
4088+}
4089+
4090+/**
4091+ * process_deserialise_all:
4092+ *
4093+ * @json: root of JSON-serialised state,
4094+ * @parent: parent of @processes,
4095+ * @processes: newly-allocated array of Process objects.
4096+ *
4097+ * Convert JSON representation of processes back into
4098+ * an array of Process objects.
4099+ *
4100+ * Returns: 0 on success, -1 on error.
4101+ **/
4102+int
4103+process_deserialise_all (json_object *json, const void *parent,
4104+ Process **processes)
4105+{
4106+ json_object *json_processes;
4107+ int i;
4108+
4109+ nih_assert (json);
4110+ nih_assert (parent);
4111+ nih_assert (processes);
4112+
4113+ json_processes = json_object_object_get (json, "process");
4114+
4115+ if (! json_processes)
4116+ goto error;
4117+
4118+ if (! state_check_json_type (json_processes, array))
4119+ goto error;
4120+
4121+ for (i = 0; i < json_object_array_length (json_processes); i++) {
4122+ json_object *json_process;
4123+
4124+ nih_assert (i <= PROCESS_LAST);
4125+
4126+ json_process = json_object_array_get_idx (json_processes, i);
4127+ if (! json_process)
4128+ goto error;
4129+
4130+ if (! state_check_json_type (json_process, object))
4131+ goto error;
4132+
4133+ processes[i] = process_deserialise (json_process, parent);
4134+
4135+ }
4136+
4137+ return 0;
4138+
4139+error:
4140+ return -1;
4141+}
4142+
4143+/**
4144+ * process_type_enum_to_str:
4145+ *
4146+ * @type: ProcessType.
4147+ *
4148+ * Convert ProcessType to a string representation.
4149+ *
4150+ * Returns: string representation of @type, or NULL if not known.
4151+ **/
4152+const char *
4153+process_type_enum_to_str (ProcessType type)
4154+{
4155+ state_enum_to_str (PROCESS_INVALID, type);
4156+ state_enum_to_str (PROCESS_MAIN, type);
4157+ state_enum_to_str (PROCESS_PRE_START, type);
4158+ state_enum_to_str (PROCESS_POST_START, type);
4159+ state_enum_to_str (PROCESS_PRE_STOP, type);
4160+ state_enum_to_str (PROCESS_POST_STOP, type);
4161+
4162+ return NULL;
4163+}
4164+
4165+/**
4166+ * process_type_str_to_enum:
4167+ *
4168+ * @type: string ProcessType.
4169+ *
4170+ * Convert @type back into enum value.
4171+ *
4172+ * Returns: ProcessType representation of @type, or -1 if not known.
4173+ **/
4174+ProcessType
4175+process_type_str_to_enum (const char *type)
4176+{
4177+ nih_assert (type);
4178+
4179+ state_str_to_enum (PROCESS_INVALID, type);
4180+ state_str_to_enum (PROCESS_MAIN, type);
4181+ state_str_to_enum (PROCESS_PRE_START, type);
4182+ state_str_to_enum (PROCESS_POST_START, type);
4183+ state_str_to_enum (PROCESS_PRE_STOP, type);
4184+ state_str_to_enum (PROCESS_POST_STOP, type);
4185+
4186+ return -1;
4187+}
4188+
4189
4190=== modified file 'init/process.h'
4191--- init/process.h 2009-06-23 09:29:35 +0000
4192+++ init/process.h 2012-11-14 14:49:20 +0000
4193@@ -22,20 +22,30 @@
4194
4195 #include <nih/macros.h>
4196
4197+#include <json.h>
4198
4199 /**
4200 * ProcessType:
4201 *
4202 * This is used to enumerate the array of process definitions attached to
4203 * a job class, and the array of pids attached to a job instance.
4204+ *
4205+ * Note that PROCESS_INVALID would ideally be -1 but that isn't possible
4206+ * since process_type_str_to_enum() would then not be able to distinguish
4207+ * between an invalid ProcessType and the default value assigned to a
4208+ * ProcessType. It also cannot be zero since that would upset iterating
4209+ * through the (non-invalid) entries.
4210 **/
4211 typedef enum process_type {
4212- PROCESS_MAIN,
4213+ /* initial value denoting no process */
4214+ PROCESS_INVALID = -2,
4215+
4216+ PROCESS_MAIN = 0,
4217 PROCESS_PRE_START,
4218 PROCESS_POST_START,
4219 PROCESS_PRE_STOP,
4220 PROCESS_POST_STOP,
4221- PROCESS_LAST
4222+ PROCESS_LAST,
4223 } ProcessType;
4224
4225
4226@@ -65,7 +75,32 @@
4227
4228 const char *process_name (ProcessType process)
4229 __attribute__ ((const));
4230-ProcessType process_from_name (const char *process);
4231+
4232+json_object *process_serialise (const Process *process)
4233+ __attribute__ ((malloc, warn_unused_result));
4234+
4235+Process *process_deserialise (json_object *json, const void *parent)
4236+ __attribute__ ((malloc, warn_unused_result));
4237+
4238+json_object *
4239+process_serialise_all (const Process * const * const processes)
4240+ __attribute__ ((malloc, warn_unused_result));
4241+
4242+ProcessType process_from_name (const char *process)
4243+ __attribute__ ((warn_unused_result));
4244+
4245+int
4246+process_deserialise_all (json_object *json, const void *parent,
4247+ Process **processes)
4248+ __attribute__ ((warn_unused_result));
4249+
4250+const char *
4251+process_type_enum_to_str (ProcessType type)
4252+ __attribute__ ((warn_unused_result));
4253+
4254+ProcessType
4255+process_type_str_to_enum (const char *type)
4256+ __attribute__ ((warn_unused_result));
4257
4258 NIH_END_EXTERN
4259
4260
4261=== modified file 'init/session.c'
4262--- init/session.c 2011-12-09 14:07:11 +0000
4263+++ init/session.c 2012-11-14 14:49:20 +0000
4264@@ -47,6 +47,15 @@
4265 #include "conf.h"
4266 #include "paths.h"
4267
4268+extern json_object *json_sessions;
4269+
4270+/* Prototypes for static functions */
4271+static json_object *session_serialise (const Session *session)
4272+ __attribute__ ((malloc, warn_unused_result));
4273+
4274+static Session *session_deserialise (json_object *json)
4275+ __attribute__ ((malloc, warn_unused_result));
4276+
4277
4278 /**
4279 * sessions:
4280@@ -66,8 +75,7 @@
4281
4282
4283 /* Prototypes for static functions */
4284-static void session_create_conf_source (Session *sesson);
4285-
4286+static void session_create_conf_source (Session *sesson, int deserialised);
4287
4288 /**
4289 * session_init:
4290@@ -90,7 +98,7 @@
4291 *
4292 * Create a new session.
4293 *
4294- * Return new Session, or NULL on error.
4295+ * Returns: new Session, or NULL on error.
4296 **/
4297 Session *
4298 session_new (const void *parent,
4299@@ -137,7 +145,7 @@
4300 *
4301 * Create a new session, based on the specified D-Bus message.
4302 *
4303- * Return new Session, or NULL on error.
4304+ * Returns: new Session, or NULL on error.
4305 **/
4306 Session *
4307 session_from_dbus (const void *parent,
4308@@ -248,7 +256,7 @@
4309 continue;
4310
4311 /* Found a user with the same uid but different
4312- * conf_dir to the existing session user. Either the
4313+ * conf_path to the existing session user. Either the
4314 * original user has been deleted and a new user created
4315 * with the same uid, or the original users home
4316 * directory has changed since they first started
4317@@ -273,7 +281,7 @@
4318 }
4319
4320 if (! session->conf_path)
4321- session_create_conf_source (session);
4322+ session_create_conf_source (session, FALSE);
4323
4324 return session;
4325 }
4326@@ -282,46 +290,54 @@
4327 /* Didn't find one, make a new one */
4328 session = NIH_MUST (session_new (parent, unix_process_id ? root : NULL,
4329 unix_user));
4330- session_create_conf_source (session);
4331+ session_create_conf_source (session, FALSE);
4332
4333 return session;
4334 }
4335
4336 /**
4337 * session_create_conf_source:
4338- * @session: Session.
4339+ * @session: Session,
4340+ * @deserialised: TRUE if ConfSource is to be created from a deserialised
4341+ * session object.
4342 *
4343- * Create a new ConfSouce object and associate the specified session
4344+ * Create a new ConfSource object and associate the specified Session
4345 * with it.
4346 **/
4347 static void
4348-session_create_conf_source (Session *session)
4349+session_create_conf_source (Session *session, int deserialised)
4350 {
4351 ConfSource *source;
4352
4353 nih_assert (session != NULL);
4354- nih_assert (session->conf_path == NULL);
4355-
4356- if (session->chroot)
4357- session->conf_path = NIH_MUST (nih_strdup (NULL, session->chroot));
4358- if (session->user) {
4359- struct passwd *pwd;
4360-
4361- pwd = getpwuid (session->user);
4362- if (! pwd) {
4363- nih_error ("%d: %s: %s", session->user,
4364- _("Unable to lookup home directory"),
4365- strerror (errno));
4366-
4367- nih_free (session->conf_path);
4368- session->conf_path = NULL;
4369- return;
4370+ nih_assert (deserialised
4371+ ? session->conf_path != NULL
4372+ : session->conf_path == NULL);
4373+
4374+ session_init ();
4375+
4376+ if (! deserialised) {
4377+ if (session->chroot)
4378+ session->conf_path = NIH_MUST (nih_strdup (NULL, session->chroot));
4379+ if (session->user) {
4380+ struct passwd *pwd;
4381+
4382+ pwd = getpwuid (session->user);
4383+ if (! pwd) {
4384+ nih_error ("%d: %s: %s", session->user,
4385+ _("Unable to lookup home directory"),
4386+ strerror (errno));
4387+
4388+ nih_free (session->conf_path);
4389+ session->conf_path = NULL;
4390+ return;
4391+ }
4392+
4393+ NIH_MUST (nih_strcat_sprintf (&session->conf_path, NULL, "%s/%s",
4394+ pwd->pw_dir, USERCONFDIR));
4395+ } else {
4396+ NIH_MUST (nih_strcat (&session->conf_path, NULL, CONFDIR));
4397 }
4398-
4399- NIH_MUST (nih_strcat_sprintf (&session->conf_path, NULL, "%s/%s",
4400- pwd->pw_dir, USERCONFDIR));
4401- } else {
4402- NIH_MUST (nih_strcat (&session->conf_path, NULL, CONFDIR));
4403 }
4404
4405 source = NIH_MUST (conf_source_new (session, session->conf_path,
4406@@ -339,8 +355,257 @@
4407 nih_free (err);
4408
4409 nih_free (source);
4410- nih_free (session->conf_path);
4411- session->conf_path = NULL;
4412 return;
4413 }
4414 }
4415+
4416+/**
4417+ * session_serialise:
4418+ * @session: session to serialise.
4419+ *
4420+ * Convert @session into a JSON representation for serialisation.
4421+ *
4422+ * Caller must free returned value using json_object_put().
4423+ *
4424+ * Returns: JSON-serialised Session object, or NULL on error.
4425+ **/
4426+static json_object *
4427+session_serialise (const Session *session)
4428+{
4429+ json_object *json;
4430+ json_object *conf_path = NULL;
4431+ json_object *chroot = NULL;
4432+ json_object *user;
4433+
4434+ nih_assert (session);
4435+
4436+ session_init ();
4437+
4438+ json = json_object_new_object ();
4439+ if (! json)
4440+ return NULL;
4441+
4442+ if (session->chroot) {
4443+ chroot = json_object_new_string (session->chroot);
4444+ if (! chroot)
4445+ goto error;
4446+ }
4447+
4448+ json_object_object_add (json, "chroot", chroot);
4449+
4450+ user = state_new_json_int (session->user);
4451+ if (! user)
4452+ goto error;
4453+
4454+ json_object_object_add (json, "user", user);
4455+
4456+ if (session->conf_path) {
4457+ conf_path = json_object_new_string (session->conf_path);
4458+ if (! conf_path)
4459+ goto error;
4460+ }
4461+
4462+ json_object_object_add (json, "conf_path", conf_path);
4463+
4464+ return json;
4465+
4466+error:
4467+ json_object_put (json);
4468+ return NULL;
4469+
4470+}
4471+
4472+/**
4473+ * session_serialise_all:
4474+ *
4475+ * Convert existing Session objects to JSON representation.
4476+ *
4477+ * Returns: JSON object containing array of Sessions, or NULL on error.
4478+ **/
4479+json_object *
4480+session_serialise_all (void)
4481+{
4482+ json_object *json;
4483+ json_object *json_session;
4484+
4485+ session_init ();
4486+
4487+ json = json_object_new_array ();
4488+ if (! json)
4489+ return NULL;
4490+
4491+ NIH_LIST_FOREACH (sessions, iter) {
4492+ Session *session = (Session *)iter;
4493+
4494+ json_session = session_serialise (session);
4495+
4496+ if (! json_session)
4497+ goto error;
4498+
4499+ if (json_object_array_add (json, json_session) < 0)
4500+ goto error;
4501+ }
4502+
4503+ return json;
4504+
4505+error:
4506+ json_object_put (json);
4507+ return NULL;
4508+}
4509+
4510+/**
4511+ * session_deserialise:
4512+ * @json: JSON-serialised Session object to deserialise.
4513+ *
4514+ * Convert @json into a Session object.
4515+ *
4516+ * Returns: Session object, or NULL on error.
4517+ **/
4518+static Session *
4519+session_deserialise (json_object *json)
4520+{
4521+ Session *session = NULL;
4522+ nih_local const char *chroot = NULL;
4523+ uid_t user;
4524+
4525+ nih_assert (json);
4526+
4527+ if (! state_check_json_type (json, object))
4528+ return NULL;
4529+
4530+ /* Note no check on value returned since chroot may be NULL */
4531+ if (! state_get_json_string_var (json, "chroot", NULL, chroot))
4532+ return NULL;
4533+
4534+ if (! state_get_json_int_var (json, "user", user))
4535+ return NULL;
4536+
4537+ /* Create a new session */
4538+ session = NIH_MUST (session_new (NULL, chroot, user));
4539+
4540+ if (! state_get_json_string_var_to_obj (json, session, conf_path))
4541+ goto error;
4542+
4543+ return session;
4544+
4545+error:
4546+ nih_free (session);
4547+ return NULL;
4548+}
4549+
4550+/**
4551+ * session_deserialise_all:
4552+ *
4553+ * @json: root of JSON-serialised state.
4554+ *
4555+ * Convert JSON representation of sessions back into Session objects.
4556+ *
4557+ * Returns: 0 on success, -1 on error.
4558+ **/
4559+int
4560+session_deserialise_all (json_object *json)
4561+{
4562+ Session *session;
4563+
4564+ nih_assert (json);
4565+
4566+ session_init ();
4567+
4568+ nih_assert (NIH_LIST_EMPTY (sessions));
4569+
4570+ json_sessions = json_object_object_get (json, "sessions");
4571+
4572+ if (! json_sessions)
4573+ goto error;
4574+
4575+ if (! state_check_json_type (json_sessions, array))
4576+ goto error;
4577+
4578+ for (int i = 0; i < json_object_array_length (json_sessions); i++) {
4579+ json_object *json_session;
4580+
4581+ json_session = json_object_array_get_idx (json_sessions, i);
4582+ if (! json_session)
4583+ goto error;
4584+
4585+ if (! state_check_json_type (json_session, object))
4586+ goto error;
4587+
4588+ session = session_deserialise (json_session);
4589+ if (! session)
4590+ goto error;
4591+
4592+ session_create_conf_source (session, TRUE);
4593+ }
4594+
4595+ return 0;
4596+
4597+error:
4598+ return -1;
4599+}
4600+
4601+/**
4602+ * session_get_index:
4603+ *
4604+ * @session: session.
4605+ *
4606+ * Determine JSON-serialised array index for specified @session.
4607+ *
4608+ * Returns: zero-based array index for @session, or -1 on error.
4609+ **/
4610+int
4611+session_get_index (const Session *session)
4612+{
4613+ int i;
4614+
4615+ /* Handle NULL session (which is not encoded) */
4616+ if (! session)
4617+ return 0;
4618+
4619+ /* Sessions are serialised in order, so just return the list
4620+ * index.
4621+ */
4622+ i = 1;
4623+ NIH_LIST_FOREACH (sessions, iter) {
4624+ Session *s = (Session *)iter;
4625+
4626+ if (s == session)
4627+ return i;
4628+
4629+ ++i;
4630+ }
4631+
4632+ return -1;
4633+}
4634+
4635+/**
4636+ * session_from_index:
4637+ *
4638+ * @idx: zero-based index.
4639+ *
4640+ * Lookup session by index number.
4641+ *
4642+ * Returns: Session (which may be the NULL session).
4643+ **/
4644+Session *
4645+session_from_index (int idx)
4646+{
4647+ int i;
4648+ Session *session;
4649+
4650+ nih_assert (idx >= 0);
4651+
4652+ /* NULL session */
4653+ if (! idx)
4654+ return NULL;
4655+
4656+ i = 1;
4657+ NIH_LIST_FOREACH (sessions, iter) {
4658+ session = (Session *)iter;
4659+
4660+ if (i == idx)
4661+ return session;
4662+ }
4663+
4664+ nih_assert_not_reached ();
4665+}
4666
4667=== modified file 'init/session.h'
4668--- init/session.h 2011-07-25 14:28:33 +0000
4669+++ init/session.h 2012-11-14 14:49:20 +0000
4670@@ -27,6 +27,7 @@
4671
4672 #include <nih-dbus/dbus_message.h>
4673
4674+#include <json.h>
4675
4676 /**
4677 * Session:
4678@@ -77,12 +78,24 @@
4679
4680 extern NihList *sessions;
4681
4682-void session_init (void);
4683-
4684-Session *session_new (const void *parent, const char *chroot, uid_t user)
4685- __attribute__ ((malloc, warn_unused_result));
4686-
4687-Session *session_from_dbus (const void *parent, NihDBusMessage *message);
4688+void session_init (void);
4689+
4690+Session * session_new (const void *parent, const char *chroot, uid_t user)
4691+ __attribute__ ((malloc, warn_unused_result));
4692+
4693+Session * session_from_dbus (const void *parent, NihDBusMessage *message);
4694+
4695+json_object * session_serialise_all (void)
4696+ __attribute__ ((malloc, warn_unused_result));
4697+
4698+int session_deserialise_all (json_object *json)
4699+ __attribute__ ((warn_unused_result));
4700+
4701+int session_get_index (const Session *session)
4702+ __attribute__ ((warn_unused_result));
4703+
4704+Session * session_from_index (int idx)
4705+ __attribute__ ((warn_unused_result));
4706
4707 NIH_END_EXTERN
4708
4709
4710=== added file 'init/state.c'
4711--- init/state.c 1970-01-01 00:00:00 +0000
4712+++ init/state.c 2012-11-14 14:49:20 +0000
4713@@ -0,0 +1,2008 @@
4714+/* upstart
4715+ *
4716+ * state.c - serialisation and deserialisation support.
4717+ *
4718+ * Copyright © 2012 Canonical Ltd.
4719+ * Author: James Hunt <james.hunt@canonical.com>.
4720+ *
4721+ * This program is free software; you can redistribute it and/or modify
4722+ * it under the terms of the GNU General Public License version 2, as
4723+ * published by the Free Software Foundation.
4724+ *
4725+ * This program is distributed in the hope that it will be useful,
4726+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
4727+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4728+ * GNU General Public License for more details.
4729+ *
4730+ * You should have received a copy of the GNU General Public License along
4731+ * with this program; if not, write to the Free Software Foundation, Inc.,
4732+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
4733+ */
4734+
4735+#include <string.h>
4736+#include <sys/types.h>
4737+#include <signal.h>
4738+#include <unistd.h>
4739+#include <errno.h>
4740+#include <sys/select.h>
4741+#include <unistd.h>
4742+#include <fcntl.h>
4743+
4744+#include <nih/macros.h>
4745+#include <nih/logging.h>
4746+#include <nih/string.h>
4747+#include <nih/io.h>
4748+
4749+#include "paths.h"
4750+#include "state.h"
4751+#include "session.h"
4752+#include "event.h"
4753+#include "job_class.h"
4754+#include "job.h"
4755+#include "environ.h"
4756+#include "blocked.h"
4757+#include "conf.h"
4758+#include "control.h"
4759+
4760+json_object *json_sessions = NULL;
4761+json_object *json_events = NULL;
4762+json_object *json_classes = NULL;
4763+
4764+extern int use_session_bus;
4765+
4766+/**
4767+ * args_copy:
4768+ *
4769+ * Copy of original argv used when re-executing to ensure same
4770+ * command-line is used. Required since we clear the actual args for
4771+ * ps(1) et al.
4772+ */
4773+char **args_copy = NULL;
4774+
4775+/**
4776+ * restart:
4777+ *
4778+ * This is set to TRUE if we're being re-exec'd by an existing init
4779+ * process.
4780+ **/
4781+int restart = FALSE;
4782+
4783+/* Prototypes for static functions */
4784+static json_object *
4785+state_serialise_blocked (const Blocked *blocked)
4786+ __attribute__ ((malloc, warn_unused_result));
4787+
4788+static Blocked *
4789+state_deserialise_blocked (void *parent, json_object *json, NihList *list)
4790+ __attribute__ ((malloc, warn_unused_result));
4791+
4792+static JobClass *
4793+state_index_to_job_class (int job_class_index)
4794+ __attribute__ ((warn_unused_result));
4795+
4796+static Job *
4797+state_get_job (const char *job_class, const char *job_name)
4798+ __attribute__ ((warn_unused_result));
4799+
4800+/**
4801+ * state_read:
4802+ *
4803+ * @fd: Open file descriptor to read JSON from.
4804+ *
4805+ * Read JSON-encoded state from specified file descriptor and recreate
4806+ * all internal objects based on JSON representation. The read will
4807+ * timeout, resulting in a failure after STATE_WAIT_SECS seconds
4808+ * indicating a problem with the child.
4809+ *
4810+ * Returns: 0 on success, or -1 on error.
4811+ **/
4812+int
4813+state_read (int fd)
4814+{
4815+ int nfds;
4816+ int ret;
4817+ fd_set readfds;
4818+ struct timeval timeout;
4819+
4820+ nih_assert (fd != -1);
4821+
4822+ state_get_timeout (timeout.tv_sec);
4823+ timeout.tv_usec = 0;
4824+
4825+ nfds = 1 + fd;
4826+
4827+ while (TRUE) {
4828+ FD_ZERO (&readfds);
4829+ FD_SET (fd, &readfds);
4830+
4831+ ret = select (nfds, &readfds, NULL, NULL,
4832+ timeout.tv_sec < 0 ? NULL : &timeout);
4833+
4834+ if (ret < 0 && (errno != EINTR && errno != EAGAIN && errno != EWOULDBLOCK))
4835+ return -1;
4836+
4837+ if (FD_ISSET (fd, &readfds))
4838+ break;
4839+ }
4840+
4841+ nih_assert (ret == 1);
4842+
4843+ /* Now, read the data */
4844+ if (state_read_objects (fd) < 0)
4845+ return -1;
4846+
4847+ return 0;
4848+}
4849+
4850+/**
4851+ * state_write:
4852+ *
4853+ * @fd: Open file descriptor to write JSON to,
4854+ * @state_data: JSON string representing internal object state,
4855+ * @len: length of @state_data.
4856+ *
4857+ * Write internal state to specified file descriptor in JSON format.
4858+ *
4859+ * Signals are assumed to be blocked when this call is made.
4860+ *
4861+ * Note the timeout - it is possible that the new PID 1 instance may be
4862+ * unable to read from its end of the file descriptor, either due to
4863+ * some error scenario or more likely due to it not supporting stateful
4864+ * re-exec. Hence, we must have a way to detect this and abort the
4865+ * child.
4866+ *
4867+ * Returns: 0 on success, or -1 on error.
4868+ **/
4869+int
4870+state_write (int fd, const char *state_data, size_t len)
4871+{
4872+ int nfds;
4873+ int ret;
4874+ fd_set writefds;
4875+ struct timeval timeout;
4876+
4877+ nih_assert (fd != -1);
4878+ nih_assert (state_data);
4879+ nih_assert (len);
4880+
4881+ /* must be called from child process */
4882+ nih_assert (getpid () != (pid_t)1);
4883+
4884+ state_get_timeout (timeout.tv_sec);
4885+ timeout.tv_usec = 0;
4886+
4887+ nfds = 1 + fd;
4888+
4889+ while (TRUE) {
4890+ FD_ZERO (&writefds);
4891+ FD_SET (fd, &writefds);
4892+
4893+ ret = select (nfds, NULL, &writefds, NULL,
4894+ timeout.tv_sec < 0 ? NULL : &timeout);
4895+
4896+ if (ret < 0 && (errno != EINTR && errno != EAGAIN && errno != EWOULDBLOCK))
4897+ return -1;
4898+
4899+ if (FD_ISSET (fd, &writefds))
4900+ break;
4901+ }
4902+
4903+ nih_assert (ret == 1);
4904+
4905+ if (state_write_objects (fd, state_data, len) < 0)
4906+ return -1;
4907+
4908+ return 0;
4909+}
4910+
4911+
4912+/**
4913+ * state_read_objects:
4914+ *
4915+ * @fd: file descriptor to read serialisation data from.
4916+ *
4917+ * Read serialisation data from specified file descriptor.
4918+ * @fd is assumed to be open and readable.
4919+ *
4920+ * Returns: 0 on success, -1 on error.
4921+ **/
4922+int
4923+state_read_objects (int fd)
4924+{
4925+ ssize_t ret;
4926+ int initial_size = 4096;
4927+ nih_local NihIoBuffer *buffer = NULL;
4928+ nih_local char *buf = NULL;
4929+
4930+ nih_assert (fd != -1);
4931+
4932+ buffer = nih_io_buffer_new (NULL);
4933+
4934+ buf = nih_alloc (NULL, initial_size);
4935+ if (! buf)
4936+ goto error;
4937+
4938+ /* Read the JSON data into the buffer */
4939+ do {
4940+ if (nih_io_buffer_resize (buffer, initial_size) < 0)
4941+ goto error;
4942+
4943+ ret = read (fd, buf, sizeof (buf));
4944+ if (ret < 0) {
4945+ if (errno != EINTR && errno != EAGAIN && errno != EWOULDBLOCK)
4946+ goto error;
4947+ continue;
4948+ } else if (! ret)
4949+ break;
4950+
4951+ if (nih_io_buffer_push (buffer, buf, ret) < 0)
4952+ goto error;
4953+ } while (TRUE);
4954+
4955+ /* Recreate internal state from JSON */
4956+ if (state_from_string (buffer->buf) < 0)
4957+ goto error;
4958+
4959+ return 0;
4960+
4961+error:
4962+ return -1;
4963+}
4964+
4965+/**
4966+ * state_write_objects:
4967+ *
4968+ * @fd: file descriptor to write serialisation data on,
4969+ * @state_data: JSON string representing internal object state,
4970+ * @len: length of @state_data.
4971+ *
4972+ * Write serialisation data to specified file descriptor.
4973+ * @fd is assumed to be open and valid to write to.
4974+ *
4975+ * Returns: 0 on success, -1 on error.
4976+ **/
4977+int
4978+state_write_objects (int fd, const char *state_data, size_t len)
4979+{
4980+ ssize_t ret;
4981+
4982+ nih_assert (fd != -1);
4983+ nih_assert (state_data);
4984+ nih_assert (len);
4985+
4986+ ret = write (fd, state_data, len);
4987+
4988+ return (ret < 0 ? -1 : 0);
4989+}
4990+
4991+/**
4992+ * state_to_string:
4993+ *
4994+ * @json_string; newly-allocated string,
4995+ * @len: length of @json_string.
4996+ *
4997+ * Serialise internal data structures to a JSON string.
4998+ *
4999+ * Returns: 0 on success, -1 on error.
5000+ **/
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches