Merge lp:~jamesodhunt/upstart/bug-1079715 into lp:~upstart-devel/upstart/1.6
- bug-1079715
- Merge into 1.6
Status: | Merged |
---|---|
Merged at revision: | 1392 |
Proposed branch: | lp:~jamesodhunt/upstart/bug-1079715 |
Merge into: | lp:~upstart-devel/upstart/1.6 |
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:
|
Commit message
Description of the change
- 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.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
James Hunt (jamesodhunt) wrote : | # |
> + /* Get the relevant session */
> + session = session_from_dbus (NULL, message);
> +
> + /* We don't want chroot sessions snooping outside their domain.
> + *
> + * Ideally, we'd allow them to query their own session, but the
> + * current implementation doesn't lend itself to that.
> + */
> + if (session && session->chroot) {
> + nih_warn (_("Ignoring state query from chroot session"));
> + return 0;
> + }
> +
> + /* Disallow users from obtaining state details, unless they
> + * happen to own this process (which they may do in the test
> + * scenario and when running Upstart as a non-privileged user).
> + */
> + if (session && session->user && session->user != uid) {
>
> Is there ever any possibility of a non-null session, where session->chroot is null and session->user is 0? If such a session exists, all users will be able to read it.
>
> It's probably better to just do:
>
> + if (session && session->user != uid) {
>
> this way, we always block access unless the uid matches - much more future-proof.
Agreed - fixed.
>
> + control_bus_flush ();
>
> I'm not sure why this is needed in the GetState case. Is this because we can't serialize dbus state? Why would that be important in this context? Since we don't need to be able to deserialize the resulting state, I think this should be omitted unless not flushing somehow causes state_to_string() to fail.
This call is consistent with the re-exec behaviour, but you are correct in that it's not technically required (and a facility to query state shouldn't potentially be changing it), so I've removed the call for this scenario.
>
> + while (TRUE) {
> + bytes = write (fd, buffer->buf, buffer->len);
> +
> + if (! bytes)
> + break;
> + else if (bytes > 0)
> + nih_io_
> + else if (bytes < 0 && (errno != EAGAIN && errno != EWOULDBLOCK)) {
> + break;
> + }
> + }
> +
>
> nih_io_
This could indeed be written in a more efficient manner, but it's using the standard NIH idiom and is simple and clear I think. Also recall that this code only gets exercised in what is hopefully an impossible failure scenario anyway :-)
>
> Shouldn't EINTR be handled here? Also, EWOULDBLOCK is AIUI only a specified behavior in the case of a socket, which we know this is not. So I think you want (errno != EAGAIN && errno != EINTR).
>
Good catch! Had my socket cap on. Fixed :)
> Otherwise, this looks good. I hope you didn't have to construct that json test data by hand ("Christmas"? :).
Thankfully not ;-)
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:22 +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:22 +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:22 +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:22 +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:22 +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:22 +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:22 +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:22 +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:22 +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:22 +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 | } |
+ /* Get the relevant session */
+ session = session_from_dbus (NULL, message);
+
+ /* We don't want chroot sessions snooping outside their domain.
+ *
+ * Ideally, we'd allow them to query their own session, but the
+ * current implementation doesn't lend itself to that.
+ */
+ if (session && session->chroot) {
+ nih_warn (_("Ignoring state query from chroot session"));
+ return 0;
+ }
+
+ /* Disallow users from obtaining state details, unless they
+ * happen to own this process (which they may do in the test
+ * scenario and when running Upstart as a non-privileged user).
+ */
+ if (session && session->user && session->user != uid) {
Is there ever any possibility of a non-null session, where session->chroot is null and session->user is 0? If such a session exists, all users will be able to read it.
It's probably better to just do:
+ if (session && session->user != uid) {
this way, we always block access unless the uid matches - much more future-proof.
+ control_bus_flush ();
I'm not sure why this is needed in the GetState case. Is this because we can't serialize dbus state? Why would that be important in this context? Since we don't need to be able to deserialize the resulting state, I think this should be omitted unless not flushing somehow causes state_to_string() to fail.
+ while (TRUE) { buffer_ shrink (buffer, (size_t)bytes);
+ bytes = write (fd, buffer->buf, buffer->len);
+
+ if (! bytes)
+ break;
+ else if (bytes > 0)
+ nih_io_
+ else if (bytes < 0 && (errno != EAGAIN && errno != EWOULDBLOCK)) {
+ break;
+ }
+ }
+
nih_io_ buffer_ shrink( ) seems an awfully inefficient way to do this, but I guess it's consistent with the rest of the code.
Shouldn't EINTR be handled here? Also, EWOULDBLOCK is AIUI only a specified behavior in the case of a socket, which we know this is not. So I think you want (errno != EAGAIN && errno != EINTR).
Otherwise, this looks good. I hope you didn't have to construct that json test data by hand ("Christmas"? :).