Merge lp:~jamesodhunt/upstart/bug-1079715 into lp:upstart

Proposed by James Hunt
Status: Merged
Merged at revision: 1398
Proposed branch: lp:~jamesodhunt/upstart/bug-1079715
Merge into: lp:upstart
Diff against target: 1119 lines (+722/-64)
10 files modified
dbus/com.ubuntu.Upstart.xml (+4/-0)
init/Makefile.am (+9/-2)
init/control.c (+62/-0)
init/control.h (+5/-0)
init/job_class.c (+36/-28)
init/session.h (+2/-1)
init/state.c (+111/-20)
init/state.h (+12/-0)
init/tests/data/upstart-1.6.json (+1/-0)
init/tests/test_state.c (+480/-13)
To merge this branch: bzr merge lp:~jamesodhunt/upstart/bug-1079715
Reviewer Review Type Date Requested Status
Steve Langasek Needs Fixing
Review via email: mp+135388@code.launchpad.net

Description of the change

* init/job_class.c:
  - job_class_consider(): Removed redundant braces.
  - job_class_reconsider(): Removed redundant braces.
  - job_class_add_safe(): Consider session before asserting
    (LP: #1079715).
  - job_class_serialise():
    - Explicitly disallow user and chroot sessions
      from being serialised since this scenario is not supported
      (due to our not serialising ConfSource objects yet).
  - job_class_deserialise():
    - Check session as early as possible.
    - Assert that we do not have user and chroot sessions to deal with.
    - Fix potential invalid free if error occurs before JobClass
      is created.
* init/session.h: Comment.
* init/state.c:
  - state_deserialise_resolve_deps(): Specify new session parameter to
    state_get_job().
  - state_serialise_blocked(): Encode session index for BLOCKED_JOB.
  - state_deserialise_blocked(): Extract session from index index for
    BLOCKED_JOB to pass to state_get_job().
  - state_get_job(): Add @session parameter to allow exact job match.

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

@@ -378,6 +374,10 @@

  existing = (JobClass *)nih_hash_search (job_classes, class->name, NULL);

+ /* Ensure no existing class exists for the same session */
+ while (existing && existing->session != class->session)
+ existing = (JobClass *)nih_hash_search (job_classes, class->name, &existing->entry);
+
  nih_assert (! existing);

  job_class_add (class);

I suggest the following instead:

@@ -372,11 +372,10 @@

  control_init ();

- existing = (JobClass *)nih_hash_search (job_classes, class->name, NULL);
-
  /* Ensure no existing class exists for the same session */
- while (existing && existing->session != class->session)
- existing = (JobClass *)nih_hash_search (job_classes, class->name, &existing->entry);
+ do {
+ existing = (JobClass *)nih_hash_search (job_classes, class->name, existing ? &existing->entry : NULL);
+ } while (existing && existing->session != class->session);

  nih_assert (! existing);

    - Fix potential invalid free if error occurs before JobClass
      is created.

class is initialized to NULL at the top of the function, so this seems to be no-op churn.

        if (session_index < 0)
- goto error;
+ goto out;
+
+ session = session_from_index (session_index);
+
+ /* XXX: user and chroot jobs are not currently supported
+ * due to ConfSources not currently being serialised.
+ */
+ nih_assert (session == NULL);

This is a warning on serialization and you're making it a fatal error on deserialization. Please fall back to skipping deserialization of user and chroot jobs (if found) instead of breaking init in this case.

@@ -1228,6 +1232,20 @@
                                                blocked->job->class->name))
                                goto error;

+ session = blocked->job->class->session;
+
+ session_index = session_get_index (session);

Can be written more succinctly as:

+ session_index = session_get_index (blocked->job->class->session);

@@ -1430,7 +1450,15 @@
                                                "class", NULL, job_class_name))
                                goto error;

- job = state_get_job (job_class_name, job_name);
+ if (! state_get_json_int_var (json_blocked_data, "session", session_index))
+ goto error;
+
+ if (session_index < 0)
+ goto error;
+

This is not part of the serialization format for upstart 1.6, so the absence of this field must not be considered an error.

This also underscores the need for test cases that embed serialized json data as generated by different releases of upstart, to test deserialization capabilities when faced with historical data.

@@ -1643,7 +1674,13 @@
        nih_assert (job_class);
        nih_assert (job_classes);

- class = (JobClass *)nih_hash_lookup (job_classes, job_class);
+ class = (JobClass *)nih_hash_search (job_classes, job_class, NULL);
+ if (! class)
+ goto error;
+
+ while (class && class->session != session)
+ class = (JobClass *)...

Read more...

review: Needs Fixing
lp:~jamesodhunt/upstart/bug-1079715 updated
1390. By James Hunt

* init/job_class.c:
  - job_class_consider(): Removed redundant braces.
  - job_class_reconsider(): Removed redundant braces.
  - job_class_add_safe(): Consider session before asserting
    (LP: #1079715).
  - job_class_serialise():
    - Explicitly disallow user and chroot sessions
      from being serialised since this scenario is not supported
      (due to our not serialising ConfSource objects yet).
  - job_class_deserialise():
    - Check session as early as possible.
    - Removed NIH_MUST() to allow check on job_class_new().
    - Assert that we do not have user and chroot sessions to deal with.
    - Fix potential invalid free if error occurs before JobClass
      is created.
  - job_class_deserialise_all():
    - Explicitly ignore attempted deserialisation of user and chroot
      sessions.
    - Removed invalid comment.
* init/session.h: Comment.
* init/state.c:
  - state_deserialise_resolve_deps():
    - Ignore classes associated with a user or chroot session.
    - Specify new session parameter to state_get_job().
  - state_serialise_blocked(): Encode session index for BLOCKED_JOB.
  - state_deserialise_blocked(): Extract session from index index for
    BLOCKED_JOB to pass to state_get_job().
  - state_get_job(): Add @session parameter to allow exact job match.

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

> @@ -378,6 +374,10 @@
>
> existing = (JobClass *)nih_hash_search (job_classes, class->name, NULL);
>
> + /* Ensure no existing class exists for the same session */
> + while (existing && existing->session != class->session)
> + existing = (JobClass *)nih_hash_search (job_classes, class->name, &existing->entry);
> +
> nih_assert (! existing);
>
> job_class_add (class);
>
> I suggest the following instead:
>
> @@ -372,11 +372,10 @@
>
> control_init ();
>
> - existing = (JobClass *)nih_hash_search (job_classes, class->name, NULL);
> -
> /* Ensure no existing class exists for the same session */
> - while (existing && existing->session != class->session)
> - existing = (JobClass *)nih_hash_search (job_classes, class->name, &existing->entry);
> + do {
> + existing = (JobClass *)nih_hash_search (job_classes, class->name, existing ? &existing->entry : NULL);
> + } while (existing && existing->session != class->session);
Agreed - this is cleaner: fixed.

>
> nih_assert (! existing);
>
> - Fix potential invalid free if error occurs before JobClass
> is created.
>
> class is initialized to NULL at the top of the function, so this seems to be no-op churn.
Actually, no - nih_free() has different semantics to free(3): you cannot
legitimately pass NULL.

>
> if (session_index < 0)
> - goto error;
> + goto out;
> +
> + session = session_from_index (session_index);
> +
> + /* XXX: user and chroot jobs are not currently supported
> + * due to ConfSources not currently being serialised.
> + */
> + nih_assert (session == NULL);
>
> This is a warning on serialization and you're making it a fatal error on deserialization. Please fall back to skipping deserialization of user and chroot jobs (if found) instead of breaking init in this case.
Done.

>
> @@ -1228,6 +1232,20 @@
> blocked->job->class->name))
> goto error;
>
> + session = blocked->job->class->session;
> +
> + session_index = session_get_index (session);
>
> Can be written more succinctly as:
>
> + session_index = session_get_index (blocked->job->class->session);
Of course. I wrote it that way to make it clearer and to avoid
particularly long lines. However, since we're not really adhering to any
line-length policies these days, I've changed it as suggested.

>
> @@ -1430,7 +1450,15 @@
> "class", NULL, job_class_name))
> goto error;
>
> - job = state_get_job (job_class_name, job_name);
> + if (! state_get_json_int_var (json_blocked_data, "session", session_index))
> + goto error;
> +
> + if (session_index < 0)
> + goto error;
> +
>
> This is not part of the serialization format for upstart 1.6, so the absence of this field must not be considered an error.
I've retained this as an error, but changed the logic in
state_deserialise_blocking() to ignore failures from
state_deserialise_blocked(). This is better than pretending the job has
a NULL session and then waiting for state_get_job() to error (maybe) and
has parity with the way we handle JobClasses.

>
> This also underscores the need for tes...

Read more...

Revision history for this message
Steve Langasek (vorlon) wrote :
Download full text (3.3 KiB)

On Thu, Nov 22, 2012 at 04:36:19PM -0000, James Hunt wrote:

> Agreed - this is cleaner: fixed.

It looks like you fixed this via a push --overwrite of the branch. The
merge proposal machinery is designed with the assumption that you will
instead push new commits to the branch on top of what is already there.
Could you please do that, in the future?

> > nih_assert (! existing);
> >
> > - Fix potential invalid free if error occurs before JobClass
> > is created.
> >
> > class is initialized to NULL at the top of the function, so this seems
> > to be no-op churn.
> Actually, no - nih_free() has different semantics to free(3): you cannot
> legitimately pass NULL.

Ah, I was unaware. Thanks for setting me straight. :)

> >
> > @@ -1228,6 +1232,20 @@
> > blocked->job->class->name))
> > goto error;
> >
> > + session = blocked->job->class->session;
> > +
> > + session_index = session_get_index (session);
> >
> > Can be written more succinctly as:
> >
> > + session_index = session_get_index (blocked->job->class->session);
> Of course. I wrote it that way to make it clearer and to avoid
> particularly long lines. However, since we're not really adhering to any
> line-length policies these days, I've changed it as suggested.

Well, I think we should avoid overly-long lines, I just don't think creating
single-use temp variables is a good solution for oversized lines. It would
IMHO also be fine to wrap this as:

   session_index = session_get_index
                         (blocked->job->class->session);

> > @@ -1430,7 +1450,15 @@
> > "class", NULL, job_class_name))
> > goto error;
> >
> > - job = state_get_job (job_class_name, job_name);
> > + if (! state_get_json_int_var (json_blocked_data, "session", session_index))
> > + goto error;
> > +
> > + if (session_index < 0)
> > + goto error;
> > +
> >
> > This is not part of the serialization format for upstart 1.6, so the
> > absence of this field must not be considered an error.

> I've retained this as an error, but changed the logic in
> state_deserialise_blocking() to ignore failures from
> state_deserialise_blocked(). This is better than pretending the job has
> a NULL session and then waiting for state_get_job() to error (maybe) and
> has parity with the way we handle JobClasses.

AIUI this means all information about blocked jobs will still be lost on
upgrade from 1.6. I don't think that's the right trade-off; I think it's
better to assume that a missing session field means the default session
(which it will, for the vast majority of users) and dump the "blocked" state
non-fatally only if we don't find a matching job on the default session.

> > This also underscores the need for test cases that embed serialized json data as generated by different releases of upstart, to test deserialization capabilities when faced with historical data.
> Quite - there is no such thing as too much testing ;D

There definitely is such a thing as too much testing. But when it comes to
changes to the serialization format, we definitely...

Read more...

lp:~jamesodhunt/upstart/bug-1079715 updated
1391. By James Hunt

* init/state.c: state_get_job(): Initialised class to ensure
  defined behaviour.

1392. By James Hunt

* init/state.c: state_deserialise_blocked(): Set session to NULL to
  handle Upstart-1.6 serialisation.
* init/tests/test_job_process.c: child(): Remove unused variables in and
  buffer.
* init/tests/test_state.c: test_blocking(): Additional tests to check
  that it is possible to deserialise Upstart 1.6 JSON format (which does
  not include the "session" JSON attribute for blocked objects. New
  tests:
  - "BLOCKED_JOB with no JSON session object".
  - "BLOCKED_JOB with JSON session object".

1393. By James Hunt

* init/tests/test_state.c: test_blocked():
  - Improved comments.
  - New test: "ensure BLOCKED_JOB with non-NULL session is ignored".

1394. By James Hunt

* dbus/com.ubuntu.Upstart.xml: Added 'GetState' method that returns
  internal state in JSON format.
* init/Makefile.am:
  - Added TEST_DATA_DIR to allow tests to find data files.
  - Added test data files to distribution.
* init/control.c: control_get_state(): Implementation for D-Bus
  'GetState' method.
* init/control.h: Prototype for control_get_state().
* init/state.c:
  - state_serialise_blocked: Remove static to allow tests to access them.
  - state_deserialise_blocked: Remove static to allow tests to access them.
  - state_read_objects(): Attempt to write the state to file
    STATE_FILE if deserialisation fails as an aid
    to diagnosing the cause of the failure.
* init/state.h: Define STATE_FILE ('/var/log/upstart/upstart.state').
* init/tests/test_state.c:
  - TestDataFile: New type to represent data files.
  - test_data_files: Array of data files to test.
  - test_blocking():
    - New tests:
      - "BLOCKED_JOB serialisation and deserialisation".
      - "BLOCKED_EVENT serialisation and deserialisation".
    - Removed test now handled by test_upstart1_6_upgrade():
      "BLOCKED_JOB iwth no JSON session object".
  - test_upgrade(): Iterate test_data_files, processing data files.
  - test_upstart1_6_upgrade(): Test for handling Upstart 1.6
    serialisation format.
  - main(): Call test_upgrade().
* init/tests/data/upstart-1.6.json: Test data used by test_state.c for
  upgrade testing.

1395. By James Hunt

* init/control.c: control_get_state():
  - Simplified session check.
  - Changed error message to refer to 'state', not 'restart'.
  - Don't call control_bus_flush() since it's not technically required
    in this non-re-exec scenario.
* init/state.c: state_write_file():
  - Added comment.
  - Check write for EINTR, not EAGAIN/EWOULDBLOCK.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'dbus/com.ubuntu.Upstart.xml'
2--- dbus/com.ubuntu.Upstart.xml 2012-09-09 21:22:06 +0000
3+++ dbus/com.ubuntu.Upstart.xml 2012-12-06 09:42:23 +0000
4@@ -34,6 +34,10 @@
5 <arg name="jobs" type="ao" direction="out" />
6 </method>
7
8+ <method name="GetState">
9+ <arg name="state" type="s" direction="out" />
10+ </method>
11+
12 <!-- Signals for changes to the job list -->
13 <signal name="JobAdded">
14 <arg name="job" type="o" />
15
16=== modified file 'init/Makefile.am'
17--- init/Makefile.am 2012-11-18 05:57:58 +0000
18+++ init/Makefile.am 2012-12-06 09:42:23 +0000
19@@ -126,8 +126,15 @@
20 $(com_ubuntu_Upstart_Job_OUTPUTS) \
21 $(com_ubuntu_Upstart_Instance_OUTPUTS)
22
23-
24-EXTRA_DIST = init.supp
25+TEST_DATA_DIR = $(PWD)/tests/data
26+
27+AM_CPPFLAGS += -DTEST_DATA_DIR="\"$(TEST_DATA_DIR)\""
28+
29+TEST_DATA_FILES = \
30+ $(TEST_DATA_DIR)/upstart-1.6-blocked.json
31+
32+EXTRA_DIST = init.supp $(TEST_DATA_FILES)
33+
34
35 test_util_SOURCES = \
36 tests/test_util.c tests/test_util.h
37
38=== modified file 'init/control.c'
39--- init/control.c 2012-09-20 15:16:52 +0000
40+++ init/control.c 2012-12-06 09:42:23 +0000
41@@ -949,3 +949,65 @@
42
43 return 0;
44 }
45+
46+/**
47+ * control_get_state:
48+ *
49+ * @data: not used,
50+ * @message: D-Bus connection and message received,
51+ * @state: output string returned to client.
52+ *
53+ * Convert internal state to JSON string.
54+ *
55+ * Returns: zero on success, negative value on raised error.
56+ **/
57+int
58+control_get_state (void *data,
59+ NihDBusMessage *message,
60+ char **state)
61+{
62+ Session *session;
63+ uid_t uid;
64+ size_t len;
65+
66+ nih_assert (message);
67+ nih_assert (state);
68+
69+ uid = getuid ();
70+
71+ /* Get the relevant session */
72+ session = session_from_dbus (NULL, message);
73+
74+ /* We don't want chroot sessions snooping outside their domain.
75+ *
76+ * Ideally, we'd allow them to query their own session, but the
77+ * current implementation doesn't lend itself to that.
78+ */
79+ if (session && session->chroot) {
80+ nih_warn (_("Ignoring state query from chroot session"));
81+ return 0;
82+ }
83+
84+ /* Disallow users from obtaining state details, unless they
85+ * happen to own this process (which they may do in the test
86+ * scenario and when running Upstart as a non-privileged user).
87+ */
88+ if (session && session->user != uid) {
89+ nih_dbus_error_raise_printf (
90+ DBUS_INTERFACE_UPSTART ".Error.PermissionDenied",
91+ _("You do not have permission to request state"));
92+ return -1;
93+ }
94+
95+ if (state_to_string (state, &len) < 0)
96+ goto error;
97+
98+ nih_ref (*state, message);
99+
100+ return 0;
101+
102+error:
103+ nih_dbus_error_raise_printf (DBUS_ERROR_NO_MEMORY,
104+ _("Out of Memory"));
105+ return -1;
106+}
107
108=== modified file 'init/control.h'
109--- init/control.h 2012-09-11 14:59:40 +0000
110+++ init/control.h 2012-12-06 09:42:23 +0000
111@@ -105,6 +105,11 @@
112 int control_bus_release_name (void)
113 __attribute__ ((warn_unused_result));
114
115+int control_get_state (void *data,
116+ NihDBusMessage *message,
117+ char **state)
118+ __attribute__ ((warn_unused_result));
119+
120 NIH_END_EXTERN
121
122 #endif /* INIT_CONTROL_H */
123
124=== modified file 'init/job_class.c'
125--- init/job_class.c 2012-11-14 14:47:19 +0000
126+++ init/job_class.c 2012-12-06 09:42:23 +0000
127@@ -271,9 +271,7 @@
128
129 /* If we found an entry, ensure we only consider the appropriate session */
130 while (registered && registered->session != class->session)
131- {
132 registered = (JobClass *)nih_hash_search (job_classes, class->name, &registered->entry);
133- }
134
135 if (registered != best) {
136 if (registered)
137@@ -314,9 +312,7 @@
138
139 /* If we found an entry, ensure we only consider the appropriate session */
140 while (registered && registered->session != class->session)
141- {
142 registered = (JobClass *)nih_hash_search (job_classes, class->name, &registered->entry);
143- }
144
145 if (registered == class) {
146 if (class != best) {
147@@ -364,7 +360,7 @@
148 * @class: new class to select.
149 *
150 * Adds @class to the hash table iff no existing entry of the
151- * same name exists.
152+ * same name exists for the same session.
153 **/
154 void
155 job_class_add_safe (JobClass *class)
156@@ -376,7 +372,11 @@
157
158 control_init ();
159
160- existing = (JobClass *)nih_hash_search (job_classes, class->name, NULL);
161+ /* Ensure no existing class exists for the same session */
162+ do {
163+ existing = (JobClass *)nih_hash_search (job_classes,
164+ class->name, existing ? &existing->entry : NULL);
165+ } while (existing && existing->session != class->session);
166
167 nih_assert (! existing);
168
169@@ -1592,6 +1592,15 @@
170 json = json_object_new_object ();
171 if (! json)
172 return NULL;
173+
174+ /* XXX: user and chroot jobs are not currently supported
175+ * due to ConfSources not currently being serialised.
176+ */
177+ if (class->session) {
178+ nih_info ("WARNING: serialisation of user jobs and "
179+ "chroot sessions not currently supported");
180+ goto error;
181+ }
182
183 session_index = session_get_index (class->session);
184 if (session_index < 0)
185@@ -1797,6 +1806,7 @@
186 {
187 json_object *json_normalexit;
188 JobClass *class = NULL;
189+ Session *session;
190 int session_index = -1;
191 int ret;
192 nih_local char *name = NULL;
193@@ -1814,21 +1824,24 @@
194 if (session_index < 0)
195 goto error;
196
197+ session = session_from_index (session_index);
198+
199+ /* XXX: user and chroot jobs are not currently supported
200+ * due to ConfSources not currently being serialised.
201+ */
202+ if (session) {
203+ nih_info ("WARNING: deserialisation of user jobs and "
204+ "chroot sessions not currently supported");
205+ goto error;
206+ }
207+
208 if (! state_get_json_string_var_strict (json, "name", NULL, name))
209 goto error;
210
211- class = NIH_MUST (job_class_new (NULL, name,
212- session_from_index (session_index)));
213+ class = job_class_new (NULL, name, session);
214 if (! class)
215 goto error;
216
217- if (class->session != NULL) {
218- nih_warn ("XXX: WARNING (%s:%d): deserialisation of "
219- "user jobs and chroot sessions not currently supported",
220- __func__, __LINE__);
221- goto error;
222- }
223-
224 /* job_class_new() sets path */
225 if (! state_get_json_string_var_strict (json, "path", NULL, path))
226 goto error;
227@@ -2002,7 +2015,9 @@
228 return class;
229
230 error:
231- nih_free (class);
232+ if (class)
233+ nih_free (class);
234+
235 return NULL;
236 }
237
238@@ -2043,19 +2058,12 @@
239 goto error;
240
241 class = job_class_deserialise (json_class);
242+
243+ /* For parity with the serialisation code, don't treat
244+ * errors as fatal for the entire deserialisation.
245+ */
246 if (! class)
247- goto error;
248-
249- /* FIXME:
250- *
251- * If user sessions exist (ie 'initctl --session list'
252- * has been run), we get this failure:
253- *
254- * serialised path='/com/ubuntu/Upstart/jobs/1000/bang'
255- * path set by job_class_new()='/com/ubuntu/Upstart/jobs/_/1000/bang'
256- *
257- */
258-
259+ continue;
260 }
261
262 return 0;
263
264=== modified file 'init/session.h'
265--- init/session.h 2012-06-29 16:19:33 +0000
266+++ init/session.h 2012-12-06 09:42:23 +0000
267@@ -39,7 +39,8 @@
268 * with a chroot path)).
269 *
270 * This structure is used to identify collections of jobs
271- * that share either a common @chroot and/or common @user.
272+ * that share either a common @chroot and/or common @user. Note that
273+ * @conf_path is unique across all sessions.
274 *
275 * Summary of Session values for different environments:
276 *
277
278=== modified file 'init/state.c'
279--- init/state.c 2012-11-23 11:36:47 +0000
280+++ init/state.c 2012-12-06 09:42:23 +0000
281@@ -2,7 +2,7 @@
282 *
283 * state.c - serialisation and deserialisation support.
284 *
285- * Copyright © 2012 Canonical Ltd.
286+ * Copyright 2012 Canonical Ltd.
287 * Author: James Hunt <james.hunt@canonical.com>.
288 *
289 * This program is free software; you can redistribute it and/or modify
290@@ -48,7 +48,8 @@
291 json_object *json_events = NULL;
292 json_object *json_classes = NULL;
293
294-extern int use_session_bus;
295+extern int use_session_bus;
296+extern char *log_dir;
297
298 /**
299 * args_copy:
300@@ -68,22 +69,17 @@
301 int restart = FALSE;
302
303 /* Prototypes for static functions */
304-static json_object *
305-state_serialise_blocked (const Blocked *blocked)
306- __attribute__ ((malloc, warn_unused_result));
307-
308-static Blocked *
309-state_deserialise_blocked (void *parent, json_object *json, NihList *list)
310- __attribute__ ((malloc, warn_unused_result));
311-
312 static JobClass *
313 state_index_to_job_class (int job_class_index)
314 __attribute__ ((warn_unused_result));
315
316 static Job *
317-state_get_job (const char *job_class, const char *job_name)
318+state_get_job (const Session *session, const char *job_class,
319+ const char *job_name)
320 __attribute__ ((warn_unused_result));
321
322+static void state_write_file (NihIoBuffer *buffer);
323+
324 /**
325 * state_read:
326 *
327@@ -246,10 +242,59 @@
328 return 0;
329
330 error:
331+ /* Failed to reconstruct internal state so attempt to write
332+ * the JSON state data to a file to allow for manual post
333+ * re-exec analysis.
334+ */
335+ if (buffer->len && log_dir)
336+ state_write_file (buffer);
337+
338 return -1;
339 }
340
341 /**
342+ * state_write_file:
343+ *
344+ * @buffer: NihIoBuffer containing JSON data.
345+ *
346+ * Write JSON data contained in @buffer to STATE_FILE below log_dir.
347+ *
348+ * Failures are ignored since this is designed to be called in an error
349+ * scenario anyway.
350+ **/
351+void
352+state_write_file (NihIoBuffer *buffer)
353+{
354+ int fd;
355+ ssize_t bytes;
356+ nih_local char *state_file = NULL;
357+
358+ nih_assert (buffer);
359+
360+ state_file = nih_sprintf (NULL, "%s/%s", log_dir, STATE_FILE);
361+ if (! state_file)
362+ return;
363+
364+ /* Note the very restrictive permissions */
365+ fd = open (state_file, (O_CREAT|O_WRONLY|O_TRUNC), S_IRUSR);
366+ if (fd < 0)
367+ return;
368+
369+ while (TRUE) {
370+ bytes = write (fd, buffer->buf, buffer->len);
371+
372+ if (! bytes)
373+ break;
374+ else if (bytes > 0)
375+ nih_io_buffer_shrink (buffer, (size_t)bytes);
376+ else if (bytes < 0 && errno != EINTR)
377+ break;
378+ }
379+
380+ close (fd);
381+}
382+
383+/**
384 * state_write_objects:
385 *
386 * @fd: file descriptor to write serialisation data on,
387@@ -1139,6 +1184,12 @@
388 if (! class)
389 goto error;
390
391+ /* XXX: user and chroot jobs are not currently supported
392+ * due to ConfSources not currently being serialised.
393+ */
394+ if (class->session)
395+ continue;
396+
397 if (! state_get_json_var_full (json_class, "jobs", array, json_jobs))
398 goto error;
399
400@@ -1164,7 +1215,7 @@
401 goto error;
402
403 /* lookup job */
404- job = state_get_job (class->name, job_name);
405+ job = state_get_job (class->session, class->name, job_name);
406 if (! job)
407 goto error;
408
409@@ -1200,11 +1251,12 @@
410 *
411 * Returns: JSON-serialised Blocked object, or NULL on error.
412 **/
413-static json_object *
414+json_object *
415 state_serialise_blocked (const Blocked *blocked)
416 {
417 json_object *json;
418 json_object *json_blocked_data;
419+ int session_index;
420
421 nih_assert (blocked);
422
423@@ -1228,6 +1280,18 @@
424 blocked->job->class->name))
425 goto error;
426
427+ session_index = session_get_index (blocked->job->class->session);
428+ if (session_index < 0)
429+ goto error;
430+
431+ /* Encode parent classes session index to aid in
432+ * finding the correct job on deserialisation.
433+ */
434+ if (! state_set_json_int_var (json_blocked_data,
435+ "session",
436+ session_index))
437+ goto error;
438+
439 if (! state_set_json_string_var (json_blocked_data,
440 "name",
441 blocked->job->name))
442@@ -1389,7 +1453,7 @@
443 *
444 * Returns: new Blocked object, or NULL on error.
445 **/
446-static Blocked *
447+Blocked *
448 state_deserialise_blocked (void *parent, json_object *json,
449 NihList *list)
450 {
451@@ -1397,7 +1461,7 @@
452 Blocked *blocked = NULL;
453 nih_local char *blocked_type_str = NULL;
454 BlockedType blocked_type;
455- int ret;
456+ int ret;
457
458 nih_assert (parent);
459 nih_assert (json);
460@@ -1421,6 +1485,8 @@
461 nih_local char *job_name = NULL;
462 nih_local char *job_class_name = NULL;
463 Job *job;
464+ Session *session;
465+ int session_index;
466
467 if (! state_get_json_string_var_strict (json_blocked_data,
468 "name", NULL, job_name))
469@@ -1430,7 +1496,19 @@
470 "class", NULL, job_class_name))
471 goto error;
472
473- job = state_get_job (job_class_name, job_name);
474+ /* On error, assume NULL session since the likelihood
475+ * is we're upgrading from Upstart 1.6 that did not set
476+ * the 'session' JSON object.
477+ */
478+ if (! state_get_json_int_var (json_blocked_data, "session", session_index))
479+ session_index = 0;
480+
481+ if (session_index < 0)
482+ goto error;
483+
484+ session = session_from_index (session_index);
485+
486+ job = state_get_job (session, job_class_name, job_name);
487 if (! job)
488 goto error;
489
490@@ -1583,8 +1661,14 @@
491 if (! json_blocked)
492 goto error;
493
494+
495+ /* Don't error in this scenario to allow for possibility
496+ * that version of Upstart that performed the
497+ * serialisation did not correctly handle user and
498+ * chroot jobs.
499+ */
500 if (! state_deserialise_blocked (parent, json_blocked, list))
501- goto error;
502+ ;
503 }
504
505 return 0;
506@@ -1625,6 +1709,7 @@
507 /**
508 * state_get_job:
509 *
510+ * @session: session of job class,
511 * @job_class: name of job class,
512 * @job_name: name of job instance.
513 *
514@@ -1635,15 +1720,21 @@
515 * job not found.
516 **/
517 static Job *
518-state_get_job (const char *job_class, const char *job_name)
519+state_get_job (const Session *session,
520+ const char *job_class,
521+ const char *job_name)
522 {
523- JobClass *class;
524+ JobClass *class = NULL;
525 Job *job;
526
527 nih_assert (job_class);
528 nih_assert (job_classes);
529
530- class = (JobClass *)nih_hash_lookup (job_classes, job_class);
531+ do {
532+ class = (JobClass *)nih_hash_search (job_classes,
533+ job_class, class ? &class->entry : NULL);
534+ } while (class && class->session != session);
535+
536 if (! class)
537 goto error;
538
539
540=== modified file 'init/state.h'
541--- init/state.h 2012-11-14 14:47:19 +0000
542+++ init/state.h 2012-12-06 09:42:23 +0000
543@@ -367,6 +367,18 @@
544 #define STATE_WAIT_SECS_ENV "UPSTART_STATE_WAIT_SECS"
545
546 /**
547+ * STATE_FILE:
548+ *
549+ * Name of file that is written below the job log directory if the
550+ * newly re-exec'ed init instance failed to understand the JSON sent to
551+ * it by the old instance.
552+ *
553+ * This could happen for example if the old instance generated invalid
554+ * JSON, or JSON in an unexected format.
555+ **/
556+#define STATE_FILE "upstart.state"
557+
558+/**
559 * state_get_timeout:
560 *
561 * @var: name of long integer var to set to timeout value.
562
563=== added directory 'init/tests/data'
564=== added file 'init/tests/data/upstart-1.6.json'
565--- init/tests/data/upstart-1.6.json 1970-01-01 00:00:00 +0000
566+++ init/tests/data/upstart-1.6.json 2012-12-06 09:42:23 +0000
567@@ -0,0 +1,1 @@
568+{ "sessions": [ ], "events": [ { "session": 0, "name": "Christmas", "fd": -1, "progress": "EVENT_PENDING", "failed": 0, "blockers": 0, "blocking": [ { "data": { "class": "bar", "name": "" }, "type": "BLOCKED_JOB" } ] } ], "job_classes": [ { "session": 0, "name": "bar", "path": "\/com\/ubuntu\/Upstart\/jobs\/bar", "instance": "", "jobs": [ { "name": "", "path": "\/com\/ubuntu\/Upstart\/jobs\/bar\/_", "goal": "JOB_STOP", "state": "JOB_WAITING", "env": [ ], "start_env": [ ], "stop_env": [ ], "fds": [ ], "pid": [ 0, 0, 0, 0, 0 ], "blocker": 0, "kill_process": "PROCESS_INVALID", "failed": 0, "failed_process": "PROCESS_INVALID", "exit_status": 0, "respawn_time": 0, "respawn_count": 0, "trace_forks": 0, "trace_state": "TRACE_NONE", "log": [ { "path": null }, { "path": null }, { "path": null }, { "path": null }, { "path": null } ] } ], "description": null, "author": null, "version": null, "env": [ ], "export": [ ], "emits": [ ], "process": [ { "script": 0, "command": null }, { "script": 0, "command": null }, { "script": 0, "command": null }, { "script": 0, "command": null }, { "script": 0, "command": null } ], "expect": "EXPECT_NONE", "task": 0, "kill_timeout": 5, "kill_signal": 15, "respawn": 0, "respawn_limit": 10, "respawn_interval": 5, "normalexit": [ ], "console": "CONSOLE_LOG", "umask": 18, "nice": 0, "oom_score_adj": 0, "limits": [ { "rlim_cur": 0, "rlim_max": 0 }, { "rlim_cur": 0, "rlim_max": 0 }, { "rlim_cur": 0, "rlim_max": 0 }, { "rlim_cur": 0, "rlim_max": 0 }, { "rlim_cur": 0, "rlim_max": 0 }, { "rlim_cur": 0, "rlim_max": 0 }, { "rlim_cur": 0, "rlim_max": 0 }, { "rlim_cur": 0, "rlim_max": 0 }, { "rlim_cur": 0, "rlim_max": 0 }, { "rlim_cur": 0, "rlim_max": 0 }, { "rlim_cur": 0, "rlim_max": 0 }, { "rlim_cur": 0, "rlim_max": 0 }, { "rlim_cur": 0, "rlim_max": 0 }, { "rlim_cur": 0, "rlim_max": 0 }, { "rlim_cur": 0, "rlim_max": 0 }, { "rlim_cur": 0, "rlim_max": 0 } ], "chroot": null, "chdir": null, "setuid": null, "setgid": null, "deleted": 0, "debug": 0, "usage": null } ] }
569
570=== modified file 'init/tests/test_job_process.c'
571=== modified file 'init/tests/test_state.c'
572--- init/tests/test_state.c 2012-11-14 14:47:19 +0000
573+++ init/tests/test_state.c 2012-12-06 09:42:23 +0000
574@@ -50,11 +50,27 @@
575 #include "control.h"
576 #include "test_util.h"
577
578+#ifndef TEST_DATA_DIR
579+#error ERROR: TEST_DATA_DIR not defined
580+#endif
581+
582+/* These functions are 'protected'.
583+ *
584+ * The test code needs access, but they cannot be public due to
585+ * header-file complications.
586+ */
587+json_object *
588+state_serialise_blocked (const Blocked *blocked)
589+ __attribute__ ((malloc, warn_unused_result));
590+
591+Blocked *
592+state_deserialise_blocked (void *parent, json_object *json, NihList *list)
593+ __attribute__ ((malloc, warn_unused_result));
594
595 /**
596 * AlreadySeen:
597 *
598- * Used to allow objects that directly or indirectly reference on
599+ * Used to allow objects that directly or indirectly reference
600 * another to be inspected and compared without causing infinite
601 * recursion.
602 *
603@@ -127,6 +143,43 @@
604 int blocked_diff (const Blocked *a, const Blocked *b, AlreadySeen seen)
605 __attribute__ ((warn_unused_result));
606
607+void test_upstart1_6_upgrade (const char *conf_file, const char *path);
608+
609+/**
610+ * TestDataFile:
611+ *
612+ * @conf_file: Name of ConfFile that must be created prior to
613+ * deserialising JSON data in @filename.
614+ * @filename: basename of data file,
615+ * @func: function to run to test @filename.
616+ *
617+ * Representation of a JSON data file used for ensuring that the current
618+ * version of Upstart is able to deserialise all previous JSON data file
619+ * format versions.
620+ *
621+ * @conf_file is required since we do not currently serialise ConfFile
622+ * and ConfSource objects so these entities must be created immediately
623+ * prior to attempting deserialisation.
624+ *
625+ * @func returns nothing so is expected to assert on any error.
626+ **/
627+typedef struct test_data_file {
628+ char *conf_file;
629+ char *filename;
630+ void (*func) (const char *conf_file, const char *path);
631+} TestDataFile;
632+
633+/**
634+ * test_data_files:
635+ *
636+ * Array of data files to test.
637+ **/
638+TestDataFile test_data_files[] = {
639+ { "bar", "upstart-1.6.json", test_upstart1_6_upgrade },
640+
641+ { NULL, NULL, NULL }
642+};
643+
644 /* Data with some embedded nulls */
645 const char test_data[] = {
646 'h', 'e', 'l', 'l', 'o', 0x0, 0x0, 0x0, ' ', 'w', 'o', 'r', 'l', 'd', '\n', '\r', '\0'
647@@ -139,7 +192,7 @@
648 int64_t values64[] = {INT64_MIN, -1, 0, 1, INT64_MAX};
649 const Process test_procs[] = {
650 { 0, "echo hello" },
651- { 1, "echo hello" },
652+ { 1, "echo hello" }
653 };
654 rlim_t rlimit_values[] = { 0, 1, 2, 3, 7, RLIM_INFINITY };
655
656@@ -993,17 +1046,23 @@
657 void
658 test_blocking (void)
659 {
660- nih_local char *json_string = NULL;
661- ConfSource *source = NULL;
662- ConfFile *file;
663- JobClass *class;
664- JobClass *new_class;
665- Job *job;
666- Job *new_job;
667- Event *event;
668- Event *new_event;
669- Blocked *blocked;
670- size_t len;
671+ nih_local char *json_string = NULL;
672+ nih_local char *parent_str = NULL;
673+ ConfSource *source = NULL;
674+ ConfFile *file;
675+ JobClass *class;
676+ JobClass *new_class;
677+ Job *job;
678+ Job *new_job;
679+ Event *event;
680+ Event *new_event;
681+ Blocked *blocked;
682+ Blocked *new_blocked;
683+ NihList blocked_list;
684+ size_t len;
685+ json_object *json_blocked;
686+ Session *session;
687+ Session *new_session;
688
689 conf_init ();
690 session_init ();
691@@ -1014,6 +1073,278 @@
692 TEST_GROUP ("Blocked serialisation and deserialisation");
693
694 /*******************************/
695+ TEST_FEATURE ("BLOCKED_JOB serialisation and deserialisation");
696+
697+ nih_list_init (&blocked_list);
698+ TEST_LIST_EMPTY (&blocked_list);
699+
700+ source = conf_source_new (NULL, "/tmp/foo", CONF_JOB_DIR);
701+ TEST_NE_P (source, NULL);
702+
703+ file = conf_file_new (source, "/tmp/foo/bar");
704+ TEST_NE_P (file, NULL);
705+ class = file->job = job_class_new (file, "bar", NULL);
706+
707+ TEST_NE_P (class, NULL);
708+ TEST_HASH_EMPTY (job_classes);
709+ TEST_TRUE (job_class_consider (class));
710+ TEST_HASH_NOT_EMPTY (job_classes);
711+
712+ job = job_new (class, "");
713+ TEST_NE_P (job, NULL);
714+
715+ parent_str = nih_strdup (NULL, "parent");
716+ TEST_NE_P (parent_str, NULL);
717+
718+ blocked = blocked_new (NULL, BLOCKED_JOB, job);
719+ TEST_NE_P (blocked, NULL);
720+
721+ json_blocked = state_serialise_blocked (blocked);
722+ TEST_NE_P (json_blocked, NULL);
723+
724+ new_blocked = state_deserialise_blocked (parent_str,
725+ json_blocked, &blocked_list);
726+ TEST_NE_P (new_blocked, NULL);
727+ TEST_LIST_NOT_EMPTY (&blocked_list);
728+
729+ assert0 (blocked_diff (blocked, new_blocked, ALREADY_SEEN_SET));
730+
731+ json_object_put (json_blocked);
732+ nih_free (source);
733+
734+ /*******************************/
735+ TEST_FEATURE ("BLOCKED_EVENT serialisation and deserialisation");
736+
737+ event = event_new (NULL, "event", NULL);
738+ TEST_NE_P (event, NULL);
739+
740+ nih_list_init (&blocked_list);
741+
742+ source = conf_source_new (NULL, "/tmp/foo", CONF_JOB_DIR);
743+ TEST_NE_P (source, NULL);
744+
745+ file = conf_file_new (source, "/tmp/foo/bar");
746+ TEST_NE_P (file, NULL);
747+ class = file->job = job_class_new (file, "bar", NULL);
748+
749+ TEST_NE_P (class, NULL);
750+ TEST_HASH_EMPTY (job_classes);
751+ TEST_TRUE (job_class_consider (class));
752+ TEST_HASH_NOT_EMPTY (job_classes);
753+
754+ job = job_new (class, "");
755+ TEST_NE_P (job, NULL);
756+
757+ TEST_LIST_EMPTY (&job->blocking);
758+
759+ blocked = blocked_new (NULL, BLOCKED_EVENT, event);
760+ TEST_NE_P (blocked, NULL);
761+
762+ nih_list_add (&job->blocking, &blocked->entry);
763+ TEST_LIST_NOT_EMPTY (&job->blocking);
764+
765+ event->blockers = 1;
766+
767+ parent_str = nih_strdup (NULL, "parent");
768+ TEST_NE_P (parent_str, NULL);
769+
770+ json_blocked = state_serialise_blocked (blocked);
771+ TEST_NE_P (json_blocked, NULL);
772+
773+ TEST_LIST_EMPTY (&blocked_list);
774+ new_blocked = state_deserialise_blocked (parent_str,
775+ json_blocked, &blocked_list);
776+ TEST_NE_P (new_blocked, NULL);
777+ TEST_LIST_NOT_EMPTY (&blocked_list);
778+
779+ assert0 (blocked_diff (blocked, new_blocked, ALREADY_SEEN_SET));
780+
781+ json_object_put (json_blocked);
782+ nih_free (source);
783+ nih_free (event);
784+
785+ /*******************************/
786+ /* Test Upstart 1.6+ behaviour
787+ *
788+ * The data serialisation format for this version now includes
789+ * a "session" entry in the JSON for the blocked job.
790+ *
791+ * Note that this test is NOT testing whether a JobClass with an
792+ * associated Upstart session is handled correctly, it is merely
793+ * testing that a JobClass with the NULL session is handled
794+ * correctly!
795+ */
796+ TEST_FEATURE ("BLOCKED_JOB with JSON session object");
797+
798+ TEST_LIST_EMPTY (sessions);
799+ TEST_LIST_EMPTY (events);
800+ TEST_LIST_EMPTY (conf_sources);
801+ TEST_HASH_EMPTY (job_classes);
802+
803+ event = event_new (NULL, "Christmas", NULL);
804+ TEST_NE_P (event, NULL);
805+ TEST_LIST_EMPTY (&event->blocking);
806+
807+ source = conf_source_new (NULL, "/tmp/foo", CONF_JOB_DIR);
808+ TEST_NE_P (source, NULL);
809+
810+ file = conf_file_new (source, "/tmp/foo/bar");
811+ TEST_NE_P (file, NULL);
812+ /* Create class with NULL session */
813+ class = file->job = job_class_new (NULL, "bar", NULL);
814+
815+ TEST_NE_P (class, NULL);
816+ TEST_HASH_EMPTY (job_classes);
817+ TEST_TRUE (job_class_consider (class));
818+ TEST_HASH_NOT_EMPTY (job_classes);
819+
820+ job = job_new (class, "");
821+ TEST_NE_P (job, NULL);
822+ TEST_HASH_NOT_EMPTY (class->instances);
823+
824+ blocked = blocked_new (event, BLOCKED_JOB, job);
825+ TEST_NE_P (blocked, NULL);
826+
827+ nih_list_add (&event->blocking, &blocked->entry);
828+ job->blocker = event;
829+
830+ TEST_LIST_NOT_EMPTY (&event->blocking);
831+
832+ assert0 (state_to_string (&json_string, &len));
833+ TEST_GT (len, 0);
834+
835+ /* XXX: We don't remove the source as these are not
836+ * recreated on re-exec, so we'll re-use the existing one.
837+ */
838+ nih_list_remove (&class->entry);
839+ nih_list_remove (&event->entry);
840+
841+ TEST_LIST_EMPTY (events);
842+ TEST_LIST_NOT_EMPTY (conf_sources);
843+ TEST_HASH_EMPTY (job_classes);
844+
845+ assert0 (state_from_string (json_string));
846+
847+ TEST_LIST_NOT_EMPTY (conf_sources);
848+ TEST_LIST_NOT_EMPTY (events);
849+ TEST_HASH_NOT_EMPTY (job_classes);
850+ TEST_LIST_EMPTY (sessions);
851+
852+ new_class = (JobClass *)nih_hash_lookup (job_classes, "bar");
853+ TEST_NE_P (new_class, NULL);
854+ nih_list_remove (&new_class->entry);
855+
856+ /* Upstart 1.6 can only deserialise the NULL session */
857+ TEST_EQ_P (class->session, NULL);
858+
859+ new_event = (Event *)nih_list_remove (events->next);
860+ TEST_LIST_EMPTY (events);
861+ TEST_LIST_NOT_EMPTY (&new_event->blocking);
862+ assert0 (event_diff (event, new_event, ALREADY_SEEN_SET));
863+
864+ nih_free (event);
865+ nih_free (new_event);
866+ nih_free (source);
867+ nih_free (new_class);
868+
869+ TEST_HASH_EMPTY (job_classes);
870+
871+ TEST_LIST_EMPTY (sessions);
872+ TEST_LIST_EMPTY (events);
873+ TEST_LIST_EMPTY (conf_sources);
874+ TEST_HASH_EMPTY (job_classes);
875+
876+ /*******************************/
877+ /* We don't currently handle user+chroot jobs, so let's assert
878+ * that behaviour.
879+ */
880+ TEST_FEATURE ("ensure BLOCKED_JOB with non-NULL session is ignored");
881+
882+ TEST_LIST_EMPTY (sessions);
883+ TEST_LIST_EMPTY (events);
884+ TEST_LIST_EMPTY (conf_sources);
885+ TEST_HASH_EMPTY (job_classes);
886+
887+ session = session_new (NULL, "/my/session", getuid ());
888+ TEST_NE_P (session, NULL);
889+ session->conf_path = NIH_MUST (nih_strdup (session, "/lives/here"));
890+ TEST_LIST_NOT_EMPTY (sessions);
891+
892+ /* We simulate a user job being blocked by a system event, hence
893+ * the session is not associated with the event.
894+ */
895+ event = event_new (NULL, "Christmas", NULL);
896+ TEST_NE_P (event, NULL);
897+ TEST_LIST_EMPTY (&event->blocking);
898+
899+ source = conf_source_new (NULL, "/tmp/foo", CONF_JOB_DIR);
900+ source->session = session;
901+ TEST_NE_P (source, NULL);
902+
903+ file = conf_file_new (source, "/tmp/foo/bar");
904+ TEST_NE_P (file, NULL);
905+
906+ /* Create class with non-NULL session, simulating a user job */
907+ class = file->job = job_class_new (NULL, "bar", session);
908+ TEST_NE_P (class, NULL);
909+
910+ TEST_HASH_EMPTY (job_classes);
911+ TEST_TRUE (job_class_consider (class));
912+ TEST_HASH_NOT_EMPTY (job_classes);
913+
914+ job = job_new (class, "");
915+ TEST_NE_P (job, NULL);
916+ TEST_HASH_NOT_EMPTY (class->instances);
917+
918+ blocked = blocked_new (event, BLOCKED_JOB, job);
919+ TEST_NE_P (blocked, NULL);
920+
921+ nih_list_add (&event->blocking, &blocked->entry);
922+ job->blocker = event;
923+
924+ TEST_LIST_NOT_EMPTY (&event->blocking);
925+
926+ assert0 (state_to_string (&json_string, &len));
927+ TEST_GT (len, 0);
928+
929+ /* XXX: We don't remove the source as these are not
930+ * recreated on re-exec, so we'll re-use the existing one.
931+ */
932+ nih_list_remove (&class->entry);
933+ nih_free (event);
934+ nih_list_remove (&session->entry);
935+
936+ TEST_LIST_EMPTY (events);
937+ TEST_LIST_NOT_EMPTY (conf_sources);
938+ TEST_HASH_EMPTY (job_classes);
939+
940+ assert0 (state_from_string (json_string));
941+
942+ TEST_LIST_NOT_EMPTY (conf_sources);
943+ TEST_LIST_NOT_EMPTY (events);
944+
945+ /* We don't expect any job_classes since the serialised one
946+ * related to a user session.
947+ */
948+ TEST_HASH_EMPTY (job_classes);
949+
950+ /* However, the session itself will exist */
951+ TEST_LIST_NOT_EMPTY (sessions);
952+
953+ new_session = (Session *)nih_list_remove (sessions->next);
954+
955+ nih_free (session);
956+ nih_free (new_session);
957+ event = (Event *)nih_list_remove (events->next);
958+ nih_free (event);
959+ nih_free (source);
960+
961+ TEST_LIST_EMPTY (sessions);
962+ TEST_LIST_EMPTY (events);
963+ TEST_LIST_EMPTY (conf_sources);
964+ TEST_HASH_EMPTY (job_classes);
965+
966+ /*******************************/
967 TEST_FEATURE ("event blocking a job");
968
969 TEST_LIST_EMPTY (sessions);
970@@ -2608,6 +2939,141 @@
971 /*******************************/
972 }
973
974+/**
975+ * test_upgrade:
976+ *
977+ * Run tests that simulate an upgrade by attempting to deserialise an
978+ * older version of the JSON data format than is currently used.
979+ **/
980+void
981+test_upgrade (void)
982+{
983+ TestDataFile *datafile;
984+
985+ TEST_GROUP ("upgrade tests");
986+
987+ for (datafile = test_data_files; datafile && datafile->filename; datafile++) {
988+ nih_local char *path = NULL;
989+ nih_local char *name = NULL;
990+
991+ nih_assert (datafile->func != NULL);
992+
993+ name = NIH_MUST (nih_sprintf (NULL, "with data file '%s'",
994+ datafile->filename));
995+ TEST_FEATURE (name);
996+
997+ path = NIH_MUST (nih_sprintf (NULL, "%s/%s",
998+ TEST_DATA_DIR, datafile->filename));
999+
1000+ datafile->func (datafile->conf_file, path);
1001+ }
1002+}
1003+
1004+/**
1005+ * test_upstart1_6_upgrade:
1006+ *
1007+ * @conf_file: name of ConfFile to create prior to running test,
1008+ * @path: full path to JSON data file to deserialise.
1009+ *
1010+ * Test for original Upstart 1.6 serialisation data format containing
1011+ * a blocked object that does not contain a 'session' element.
1012+ *
1013+ * Note that this test is NOT testing whether a JobClass with an
1014+ * associated Upstart session is handled correctly, it is merely
1015+ * testing that a JobClass with the NULL session encoded in the JSON
1016+ * is handled correctly.
1017+ **/
1018+void
1019+test_upstart1_6_upgrade (const char *conf_file, const char *path)
1020+{
1021+ nih_local char *json_string = NULL;
1022+ Event *event;
1023+ ConfSource *source;
1024+ ConfFile *file;
1025+ nih_local char *conf_file_path = NULL;
1026+ struct stat statbuf;
1027+ size_t len;
1028+
1029+ nih_assert (conf_file);
1030+ nih_assert (path);
1031+
1032+ conf_init ();
1033+ session_init ();
1034+ event_init ();
1035+ control_init ();
1036+ job_class_init ();
1037+
1038+ TEST_LIST_EMPTY (sessions);
1039+ TEST_LIST_EMPTY (events);
1040+ TEST_LIST_EMPTY (conf_sources);
1041+ TEST_HASH_EMPTY (job_classes);
1042+
1043+ /* Check data file exists */
1044+ TEST_EQ (stat (path, &statbuf), 0);
1045+
1046+ json_string = nih_file_read (NULL, path, &len);
1047+ TEST_NE_P (json_string, NULL);
1048+
1049+ /* Create the ConfSource and ConfFile objects to simulate
1050+ * Upstart reading /etc/init on startup. Required since we
1051+ * don't currently serialise these objects.
1052+ */
1053+ source = conf_source_new (NULL, "/tmp/foo", CONF_JOB_DIR);
1054+ TEST_NE_P (source, NULL);
1055+
1056+ conf_file_path = NIH_MUST (nih_sprintf (NULL, "%s/%s",
1057+ "/tmp/foo", conf_file));
1058+
1059+ file = conf_file_new (source, conf_file_path);
1060+ TEST_NE_P (file, NULL);
1061+
1062+ /* Recreate state from JSON data file */
1063+ assert0 (state_from_string (json_string));
1064+
1065+ TEST_LIST_NOT_EMPTY (conf_sources);
1066+ TEST_LIST_NOT_EMPTY (events);
1067+ TEST_HASH_NOT_EMPTY (job_classes);
1068+ TEST_LIST_EMPTY (sessions);
1069+
1070+ event = (Event *)nih_list_remove (events->next);
1071+ TEST_NE_P (event, NULL);
1072+ TEST_EQ_STR (event->name, "Christmas");
1073+
1074+ NIH_HASH_FOREACH (job_classes, iter) {
1075+ JobClass *class = (JobClass *)iter;
1076+
1077+ TEST_EQ_STR (class->name, "bar");
1078+ TEST_EQ_STR (class->path, "/com/ubuntu/Upstart/jobs/bar");
1079+ TEST_HASH_NOT_EMPTY (class->instances);
1080+
1081+ NIH_HASH_FOREACH (class->instances, iter2) {
1082+ Blocked *blocked;
1083+ Job *job = (Job *)iter2;
1084+ nih_local char *instance_path = NULL;
1085+
1086+ /* instance name */
1087+ TEST_EQ_STR (job->name, "");
1088+
1089+ instance_path = NIH_MUST (nih_sprintf (NULL, "%s/_", class->path));
1090+ TEST_EQ_STR (job->path, instance_path);
1091+
1092+ /* job is blocked by the event */
1093+ TEST_EQ (job->blocker, event);
1094+
1095+ /* First entry in list should be a Blocked
1096+ * object pointing to the job.
1097+ */
1098+ TEST_LIST_NOT_EMPTY (&event->blocking);
1099+ blocked = (Blocked *)(&event->blocking)->next;
1100+ TEST_EQ (blocked->type, BLOCKED_JOB);
1101+ TEST_EQ (blocked->job, job);
1102+ }
1103+ }
1104+
1105+ nih_free (event);
1106+ nih_free (conf_sources);
1107+}
1108+
1109 int
1110 main (int argc,
1111 char *argv[])
1112@@ -2632,6 +3098,7 @@
1113 test_log_serialise ();
1114 test_job_serialise ();
1115 test_job_class_serialise ();
1116+ test_upgrade ();
1117
1118 return 0;
1119 }

Subscribers

People subscribed via source and target branches