Merge lp:~jamesodhunt/upstart/bug-1079715 into lp:upstart
- bug-1079715
- Merge into trunk
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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Steve Langasek | Needs Fixing | ||
Review via email: mp+135388@code.launchpad.net |
Commit message
Description of the change
* init/job_class.c:
- job_class_
- job_class_
- job_class_
(LP: #1079715).
- job_class_
- 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_
- 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_deseriali
state_
- state_serialise
- state_deseriali
BLOCKED_JOB to pass to state_get_job().
- state_get_job(): Add @session parameter to allow exact job match.
- 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.
James Hunt (jamesodhunt) wrote : | # |
> @@ -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-
> goto error;
>
> + session = blocked-
> +
> + session_index = session_get_index (session);
>
> Can be written more succinctly as:
>
> + session_index = session_get_index (blocked-
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_
> + 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_deseriali
state_deseriali
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...
Steve Langasek (vorlon) wrote : | # |
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-
> > goto error;
> >
> > + session = blocked-
> > +
> > + session_index = session_get_index (session);
> >
> > Can be written more succinctly as:
> >
> > + session_index = session_get_index (blocked-
> 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
> > @@ -1430,7 +1450,15 @@
> > "class", NULL, job_class_name))
> > goto error;
> >
> > - job = state_get_job (job_class_name, job_name);
> > + if (! state_get_
> > + 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_deseriali
> state_deseriali
> 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...
- 1391. By James Hunt
-
* init/state.c: state_get_job(): Initialised class to ensure
defined behaviour. - 1392. By James Hunt
-
* init/state.c: state_deseriali
se_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
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, ®istered->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, ®istered->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 | } |
@@ -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); json_int_ var (json_blocked_data, "session", session_index))
+ if (! state_get_
+ 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 *)...