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

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

Description of the change

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

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

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

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

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

  job_class_add (class);

I suggest the following instead:

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

  control_init ();

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

  nih_assert (! existing);

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

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

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

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

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

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

Can be written more succinctly as:

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

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

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

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

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

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

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

Read more...

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

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

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

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

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

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

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

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

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

Read more...

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

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

> Agreed - this is cleaner: fixed.

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

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

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

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

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

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

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

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

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

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

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

Read more...

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

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

1392. By James Hunt

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

1393. By James Hunt

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

1394. By James Hunt

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

1395. By James Hunt

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

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'dbus/com.ubuntu.Upstart.xml'
--- dbus/com.ubuntu.Upstart.xml 2012-09-09 21:22:06 +0000
+++ dbus/com.ubuntu.Upstart.xml 2012-12-06 09:42:23 +0000
@@ -34,6 +34,10 @@
34 <arg name="jobs" type="ao" direction="out" />34 <arg name="jobs" type="ao" direction="out" />
35 </method>35 </method>
3636
37 <method name="GetState">
38 <arg name="state" type="s" direction="out" />
39 </method>
40
37 <!-- Signals for changes to the job list -->41 <!-- Signals for changes to the job list -->
38 <signal name="JobAdded">42 <signal name="JobAdded">
39 <arg name="job" type="o" />43 <arg name="job" type="o" />
4044
=== modified file 'init/Makefile.am'
--- init/Makefile.am 2012-11-18 05:57:58 +0000
+++ init/Makefile.am 2012-12-06 09:42:23 +0000
@@ -126,8 +126,15 @@
126 $(com_ubuntu_Upstart_Job_OUTPUTS) \126 $(com_ubuntu_Upstart_Job_OUTPUTS) \
127 $(com_ubuntu_Upstart_Instance_OUTPUTS)127 $(com_ubuntu_Upstart_Instance_OUTPUTS)
128128
129129TEST_DATA_DIR = $(PWD)/tests/data
130EXTRA_DIST = init.supp130
131AM_CPPFLAGS += -DTEST_DATA_DIR="\"$(TEST_DATA_DIR)\""
132
133TEST_DATA_FILES = \
134 $(TEST_DATA_DIR)/upstart-1.6-blocked.json
135
136EXTRA_DIST = init.supp $(TEST_DATA_FILES)
137
131138
132test_util_SOURCES = \139test_util_SOURCES = \
133 tests/test_util.c tests/test_util.h140 tests/test_util.c tests/test_util.h
134141
=== modified file 'init/control.c'
--- init/control.c 2012-09-20 15:16:52 +0000
+++ init/control.c 2012-12-06 09:42:23 +0000
@@ -949,3 +949,65 @@
949949
950 return 0;950 return 0;
951}951}
952
953/**
954 * control_get_state:
955 *
956 * @data: not used,
957 * @message: D-Bus connection and message received,
958 * @state: output string returned to client.
959 *
960 * Convert internal state to JSON string.
961 *
962 * Returns: zero on success, negative value on raised error.
963 **/
964int
965control_get_state (void *data,
966 NihDBusMessage *message,
967 char **state)
968{
969 Session *session;
970 uid_t uid;
971 size_t len;
972
973 nih_assert (message);
974 nih_assert (state);
975
976 uid = getuid ();
977
978 /* Get the relevant session */
979 session = session_from_dbus (NULL, message);
980
981 /* We don't want chroot sessions snooping outside their domain.
982 *
983 * Ideally, we'd allow them to query their own session, but the
984 * current implementation doesn't lend itself to that.
985 */
986 if (session && session->chroot) {
987 nih_warn (_("Ignoring state query from chroot session"));
988 return 0;
989 }
990
991 /* Disallow users from obtaining state details, unless they
992 * happen to own this process (which they may do in the test
993 * scenario and when running Upstart as a non-privileged user).
994 */
995 if (session && session->user != uid) {
996 nih_dbus_error_raise_printf (
997 DBUS_INTERFACE_UPSTART ".Error.PermissionDenied",
998 _("You do not have permission to request state"));
999 return -1;
1000 }
1001
1002 if (state_to_string (state, &len) < 0)
1003 goto error;
1004
1005 nih_ref (*state, message);
1006
1007 return 0;
1008
1009error:
1010 nih_dbus_error_raise_printf (DBUS_ERROR_NO_MEMORY,
1011 _("Out of Memory"));
1012 return -1;
1013}
9521014
=== modified file 'init/control.h'
--- init/control.h 2012-09-11 14:59:40 +0000
+++ init/control.h 2012-12-06 09:42:23 +0000
@@ -105,6 +105,11 @@
105int control_bus_release_name (void)105int control_bus_release_name (void)
106 __attribute__ ((warn_unused_result));106 __attribute__ ((warn_unused_result));
107107
108int control_get_state (void *data,
109 NihDBusMessage *message,
110 char **state)
111 __attribute__ ((warn_unused_result));
112
108NIH_END_EXTERN113NIH_END_EXTERN
109114
110#endif /* INIT_CONTROL_H */115#endif /* INIT_CONTROL_H */
111116
=== modified file 'init/job_class.c'
--- init/job_class.c 2012-11-14 14:47:19 +0000
+++ init/job_class.c 2012-12-06 09:42:23 +0000
@@ -271,9 +271,7 @@
271271
272 /* If we found an entry, ensure we only consider the appropriate session */272 /* If we found an entry, ensure we only consider the appropriate session */
273 while (registered && registered->session != class->session)273 while (registered && registered->session != class->session)
274 {
275 registered = (JobClass *)nih_hash_search (job_classes, class->name, &registered->entry);274 registered = (JobClass *)nih_hash_search (job_classes, class->name, &registered->entry);
276 }
277275
278 if (registered != best) {276 if (registered != best) {
279 if (registered)277 if (registered)
@@ -314,9 +312,7 @@
314312
315 /* If we found an entry, ensure we only consider the appropriate session */313 /* If we found an entry, ensure we only consider the appropriate session */
316 while (registered && registered->session != class->session)314 while (registered && registered->session != class->session)
317 {
318 registered = (JobClass *)nih_hash_search (job_classes, class->name, &registered->entry);315 registered = (JobClass *)nih_hash_search (job_classes, class->name, &registered->entry);
319 }
320316
321 if (registered == class) {317 if (registered == class) {
322 if (class != best) {318 if (class != best) {
@@ -364,7 +360,7 @@
364 * @class: new class to select.360 * @class: new class to select.
365 *361 *
366 * Adds @class to the hash table iff no existing entry of the362 * Adds @class to the hash table iff no existing entry of the
367 * same name exists.363 * same name exists for the same session.
368 **/364 **/
369void365void
370job_class_add_safe (JobClass *class)366job_class_add_safe (JobClass *class)
@@ -376,7 +372,11 @@
376372
377 control_init ();373 control_init ();
378374
379 existing = (JobClass *)nih_hash_search (job_classes, class->name, NULL);375 /* Ensure no existing class exists for the same session */
376 do {
377 existing = (JobClass *)nih_hash_search (job_classes,
378 class->name, existing ? &existing->entry : NULL);
379 } while (existing && existing->session != class->session);
380380
381 nih_assert (! existing);381 nih_assert (! existing);
382382
@@ -1592,6 +1592,15 @@
1592 json = json_object_new_object ();1592 json = json_object_new_object ();
1593 if (! json)1593 if (! json)
1594 return NULL;1594 return NULL;
1595
1596 /* XXX: user and chroot jobs are not currently supported
1597 * due to ConfSources not currently being serialised.
1598 */
1599 if (class->session) {
1600 nih_info ("WARNING: serialisation of user jobs and "
1601 "chroot sessions not currently supported");
1602 goto error;
1603 }
15951604
1596 session_index = session_get_index (class->session);1605 session_index = session_get_index (class->session);
1597 if (session_index < 0)1606 if (session_index < 0)
@@ -1797,6 +1806,7 @@
1797{1806{
1798 json_object *json_normalexit;1807 json_object *json_normalexit;
1799 JobClass *class = NULL;1808 JobClass *class = NULL;
1809 Session *session;
1800 int session_index = -1;1810 int session_index = -1;
1801 int ret;1811 int ret;
1802 nih_local char *name = NULL;1812 nih_local char *name = NULL;
@@ -1814,21 +1824,24 @@
1814 if (session_index < 0)1824 if (session_index < 0)
1815 goto error;1825 goto error;
18161826
1827 session = session_from_index (session_index);
1828
1829 /* XXX: user and chroot jobs are not currently supported
1830 * due to ConfSources not currently being serialised.
1831 */
1832 if (session) {
1833 nih_info ("WARNING: deserialisation of user jobs and "
1834 "chroot sessions not currently supported");
1835 goto error;
1836 }
1837
1817 if (! state_get_json_string_var_strict (json, "name", NULL, name))1838 if (! state_get_json_string_var_strict (json, "name", NULL, name))
1818 goto error;1839 goto error;
18191840
1820 class = NIH_MUST (job_class_new (NULL, name,1841 class = job_class_new (NULL, name, session);
1821 session_from_index (session_index)));
1822 if (! class)1842 if (! class)
1823 goto error;1843 goto error;
18241844
1825 if (class->session != NULL) {
1826 nih_warn ("XXX: WARNING (%s:%d): deserialisation of "
1827 "user jobs and chroot sessions not currently supported",
1828 __func__, __LINE__);
1829 goto error;
1830 }
1831
1832 /* job_class_new() sets path */1845 /* job_class_new() sets path */
1833 if (! state_get_json_string_var_strict (json, "path", NULL, path))1846 if (! state_get_json_string_var_strict (json, "path", NULL, path))
1834 goto error;1847 goto error;
@@ -2002,7 +2015,9 @@
2002 return class;2015 return class;
20032016
2004error:2017error:
2005 nih_free (class);2018 if (class)
2019 nih_free (class);
2020
2006 return NULL;2021 return NULL;
2007}2022}
20082023
@@ -2043,19 +2058,12 @@
2043 goto error;2058 goto error;
20442059
2045 class = job_class_deserialise (json_class);2060 class = job_class_deserialise (json_class);
2061
2062 /* For parity with the serialisation code, don't treat
2063 * errors as fatal for the entire deserialisation.
2064 */
2046 if (! class)2065 if (! class)
2047 goto error;2066 continue;
2048
2049 /* FIXME:
2050 *
2051 * If user sessions exist (ie 'initctl --session list'
2052 * has been run), we get this failure:
2053 *
2054 * serialised path='/com/ubuntu/Upstart/jobs/1000/bang'
2055 * path set by job_class_new()='/com/ubuntu/Upstart/jobs/_/1000/bang'
2056 *
2057 */
2058
2059 }2067 }
20602068
2061 return 0;2069 return 0;
20622070
=== modified file 'init/session.h'
--- init/session.h 2012-06-29 16:19:33 +0000
+++ init/session.h 2012-12-06 09:42:23 +0000
@@ -39,7 +39,8 @@
39 * with a chroot path)).39 * with a chroot path)).
40 *40 *
41 * This structure is used to identify collections of jobs41 * This structure is used to identify collections of jobs
42 * that share either a common @chroot and/or common @user.42 * that share either a common @chroot and/or common @user. Note that
43 * @conf_path is unique across all sessions.
43 *44 *
44 * Summary of Session values for different environments:45 * Summary of Session values for different environments:
45 *46 *
4647
=== modified file 'init/state.c'
--- init/state.c 2012-11-23 11:36:47 +0000
+++ init/state.c 2012-12-06 09:42:23 +0000
@@ -2,7 +2,7 @@
2 *2 *
3 * state.c - serialisation and deserialisation support.3 * state.c - serialisation and deserialisation support.
4 *4 *
5 * Copyright © 2012 Canonical Ltd.5 * Copyright 2012 Canonical Ltd.
6 * Author: James Hunt <james.hunt@canonical.com>.6 * Author: James Hunt <james.hunt@canonical.com>.
7 *7 *
8 * This program is free software; you can redistribute it and/or modify8 * This program is free software; you can redistribute it and/or modify
@@ -48,7 +48,8 @@
48json_object *json_events = NULL;48json_object *json_events = NULL;
49json_object *json_classes = NULL;49json_object *json_classes = NULL;
5050
51extern int use_session_bus;51extern int use_session_bus;
52extern char *log_dir;
5253
53/**54/**
54 * args_copy:55 * args_copy:
@@ -68,22 +69,17 @@
68int restart = FALSE;69int restart = FALSE;
6970
70/* Prototypes for static functions */71/* Prototypes for static functions */
71static json_object *
72state_serialise_blocked (const Blocked *blocked)
73 __attribute__ ((malloc, warn_unused_result));
74
75static Blocked *
76state_deserialise_blocked (void *parent, json_object *json, NihList *list)
77 __attribute__ ((malloc, warn_unused_result));
78
79static JobClass *72static JobClass *
80state_index_to_job_class (int job_class_index)73state_index_to_job_class (int job_class_index)
81 __attribute__ ((warn_unused_result));74 __attribute__ ((warn_unused_result));
8275
83static Job *76static Job *
84state_get_job (const char *job_class, const char *job_name)77state_get_job (const Session *session, const char *job_class,
78 const char *job_name)
85 __attribute__ ((warn_unused_result));79 __attribute__ ((warn_unused_result));
8680
81static void state_write_file (NihIoBuffer *buffer);
82
87/**83/**
88 * state_read:84 * state_read:
89 *85 *
@@ -246,10 +242,59 @@
246 return 0;242 return 0;
247243
248error:244error:
245 /* Failed to reconstruct internal state so attempt to write
246 * the JSON state data to a file to allow for manual post
247 * re-exec analysis.
248 */
249 if (buffer->len && log_dir)
250 state_write_file (buffer);
251
249 return -1;252 return -1;
250}253}
251254
252/**255/**
256 * state_write_file:
257 *
258 * @buffer: NihIoBuffer containing JSON data.
259 *
260 * Write JSON data contained in @buffer to STATE_FILE below log_dir.
261 *
262 * Failures are ignored since this is designed to be called in an error
263 * scenario anyway.
264 **/
265void
266state_write_file (NihIoBuffer *buffer)
267{
268 int fd;
269 ssize_t bytes;
270 nih_local char *state_file = NULL;
271
272 nih_assert (buffer);
273
274 state_file = nih_sprintf (NULL, "%s/%s", log_dir, STATE_FILE);
275 if (! state_file)
276 return;
277
278 /* Note the very restrictive permissions */
279 fd = open (state_file, (O_CREAT|O_WRONLY|O_TRUNC), S_IRUSR);
280 if (fd < 0)
281 return;
282
283 while (TRUE) {
284 bytes = write (fd, buffer->buf, buffer->len);
285
286 if (! bytes)
287 break;
288 else if (bytes > 0)
289 nih_io_buffer_shrink (buffer, (size_t)bytes);
290 else if (bytes < 0 && errno != EINTR)
291 break;
292 }
293
294 close (fd);
295}
296
297/**
253 * state_write_objects:298 * state_write_objects:
254 *299 *
255 * @fd: file descriptor to write serialisation data on,300 * @fd: file descriptor to write serialisation data on,
@@ -1139,6 +1184,12 @@
1139 if (! class)1184 if (! class)
1140 goto error;1185 goto error;
11411186
1187 /* XXX: user and chroot jobs are not currently supported
1188 * due to ConfSources not currently being serialised.
1189 */
1190 if (class->session)
1191 continue;
1192
1142 if (! state_get_json_var_full (json_class, "jobs", array, json_jobs))1193 if (! state_get_json_var_full (json_class, "jobs", array, json_jobs))
1143 goto error;1194 goto error;
11441195
@@ -1164,7 +1215,7 @@
1164 goto error;1215 goto error;
11651216
1166 /* lookup job */1217 /* lookup job */
1167 job = state_get_job (class->name, job_name);1218 job = state_get_job (class->session, class->name, job_name);
1168 if (! job)1219 if (! job)
1169 goto error;1220 goto error;
11701221
@@ -1200,11 +1251,12 @@
1200 *1251 *
1201 * Returns: JSON-serialised Blocked object, or NULL on error.1252 * Returns: JSON-serialised Blocked object, or NULL on error.
1202 **/1253 **/
1203static json_object *1254json_object *
1204state_serialise_blocked (const Blocked *blocked)1255state_serialise_blocked (const Blocked *blocked)
1205{1256{
1206 json_object *json;1257 json_object *json;
1207 json_object *json_blocked_data;1258 json_object *json_blocked_data;
1259 int session_index;
12081260
1209 nih_assert (blocked);1261 nih_assert (blocked);
12101262
@@ -1228,6 +1280,18 @@
1228 blocked->job->class->name))1280 blocked->job->class->name))
1229 goto error;1281 goto error;
12301282
1283 session_index = session_get_index (blocked->job->class->session);
1284 if (session_index < 0)
1285 goto error;
1286
1287 /* Encode parent classes session index to aid in
1288 * finding the correct job on deserialisation.
1289 */
1290 if (! state_set_json_int_var (json_blocked_data,
1291 "session",
1292 session_index))
1293 goto error;
1294
1231 if (! state_set_json_string_var (json_blocked_data,1295 if (! state_set_json_string_var (json_blocked_data,
1232 "name",1296 "name",
1233 blocked->job->name))1297 blocked->job->name))
@@ -1389,7 +1453,7 @@
1389 *1453 *
1390 * Returns: new Blocked object, or NULL on error.1454 * Returns: new Blocked object, or NULL on error.
1391 **/1455 **/
1392static Blocked *1456Blocked *
1393state_deserialise_blocked (void *parent, json_object *json,1457state_deserialise_blocked (void *parent, json_object *json,
1394 NihList *list)1458 NihList *list)
1395{1459{
@@ -1397,7 +1461,7 @@
1397 Blocked *blocked = NULL;1461 Blocked *blocked = NULL;
1398 nih_local char *blocked_type_str = NULL;1462 nih_local char *blocked_type_str = NULL;
1399 BlockedType blocked_type;1463 BlockedType blocked_type;
1400 int ret;1464 int ret;
14011465
1402 nih_assert (parent);1466 nih_assert (parent);
1403 nih_assert (json);1467 nih_assert (json);
@@ -1421,6 +1485,8 @@
1421 nih_local char *job_name = NULL;1485 nih_local char *job_name = NULL;
1422 nih_local char *job_class_name = NULL;1486 nih_local char *job_class_name = NULL;
1423 Job *job;1487 Job *job;
1488 Session *session;
1489 int session_index;
14241490
1425 if (! state_get_json_string_var_strict (json_blocked_data,1491 if (! state_get_json_string_var_strict (json_blocked_data,
1426 "name", NULL, job_name))1492 "name", NULL, job_name))
@@ -1430,7 +1496,19 @@
1430 "class", NULL, job_class_name))1496 "class", NULL, job_class_name))
1431 goto error;1497 goto error;
14321498
1433 job = state_get_job (job_class_name, job_name);1499 /* On error, assume NULL session since the likelihood
1500 * is we're upgrading from Upstart 1.6 that did not set
1501 * the 'session' JSON object.
1502 */
1503 if (! state_get_json_int_var (json_blocked_data, "session", session_index))
1504 session_index = 0;
1505
1506 if (session_index < 0)
1507 goto error;
1508
1509 session = session_from_index (session_index);
1510
1511 job = state_get_job (session, job_class_name, job_name);
1434 if (! job)1512 if (! job)
1435 goto error;1513 goto error;
14361514
@@ -1583,8 +1661,14 @@
1583 if (! json_blocked)1661 if (! json_blocked)
1584 goto error;1662 goto error;
15851663
1664
1665 /* Don't error in this scenario to allow for possibility
1666 * that version of Upstart that performed the
1667 * serialisation did not correctly handle user and
1668 * chroot jobs.
1669 */
1586 if (! state_deserialise_blocked (parent, json_blocked, list))1670 if (! state_deserialise_blocked (parent, json_blocked, list))
1587 goto error;1671 ;
1588 }1672 }
15891673
1590 return 0;1674 return 0;
@@ -1625,6 +1709,7 @@
1625/**1709/**
1626 * state_get_job:1710 * state_get_job:
1627 *1711 *
1712 * @session: session of job class,
1628 * @job_class: name of job class,1713 * @job_class: name of job class,
1629 * @job_name: name of job instance.1714 * @job_name: name of job instance.
1630 *1715 *
@@ -1635,15 +1720,21 @@
1635 * job not found.1720 * job not found.
1636 **/1721 **/
1637static Job *1722static Job *
1638state_get_job (const char *job_class, const char *job_name)1723state_get_job (const Session *session,
1724 const char *job_class,
1725 const char *job_name)
1639{1726{
1640 JobClass *class;1727 JobClass *class = NULL;
1641 Job *job;1728 Job *job;
16421729
1643 nih_assert (job_class);1730 nih_assert (job_class);
1644 nih_assert (job_classes);1731 nih_assert (job_classes);
16451732
1646 class = (JobClass *)nih_hash_lookup (job_classes, job_class);1733 do {
1734 class = (JobClass *)nih_hash_search (job_classes,
1735 job_class, class ? &class->entry : NULL);
1736 } while (class && class->session != session);
1737
1647 if (! class)1738 if (! class)
1648 goto error;1739 goto error;
16491740
16501741
=== modified file 'init/state.h'
--- init/state.h 2012-11-14 14:47:19 +0000
+++ init/state.h 2012-12-06 09:42:23 +0000
@@ -367,6 +367,18 @@
367#define STATE_WAIT_SECS_ENV "UPSTART_STATE_WAIT_SECS"367#define STATE_WAIT_SECS_ENV "UPSTART_STATE_WAIT_SECS"
368368
369/**369/**
370 * STATE_FILE:
371 *
372 * Name of file that is written below the job log directory if the
373 * newly re-exec'ed init instance failed to understand the JSON sent to
374 * it by the old instance.
375 *
376 * This could happen for example if the old instance generated invalid
377 * JSON, or JSON in an unexected format.
378 **/
379#define STATE_FILE "upstart.state"
380
381/**
370 * state_get_timeout:382 * state_get_timeout:
371 *383 *
372 * @var: name of long integer var to set to timeout value.384 * @var: name of long integer var to set to timeout value.
373385
=== added directory 'init/tests/data'
=== added file 'init/tests/data/upstart-1.6.json'
--- init/tests/data/upstart-1.6.json 1970-01-01 00:00:00 +0000
+++ init/tests/data/upstart-1.6.json 2012-12-06 09:42:23 +0000
@@ -0,0 +1,1 @@
1{ "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 } ] }
02
=== modified file 'init/tests/test_job_process.c'
=== modified file 'init/tests/test_state.c'
--- init/tests/test_state.c 2012-11-14 14:47:19 +0000
+++ init/tests/test_state.c 2012-12-06 09:42:23 +0000
@@ -50,11 +50,27 @@
50#include "control.h"50#include "control.h"
51#include "test_util.h"51#include "test_util.h"
5252
53#ifndef TEST_DATA_DIR
54#error ERROR: TEST_DATA_DIR not defined
55#endif
56
57/* These functions are 'protected'.
58 *
59 * The test code needs access, but they cannot be public due to
60 * header-file complications.
61 */
62json_object *
63state_serialise_blocked (const Blocked *blocked)
64 __attribute__ ((malloc, warn_unused_result));
65
66Blocked *
67state_deserialise_blocked (void *parent, json_object *json, NihList *list)
68 __attribute__ ((malloc, warn_unused_result));
5369
54/**70/**
55 * AlreadySeen:71 * AlreadySeen:
56 *72 *
57 * Used to allow objects that directly or indirectly reference on73 * Used to allow objects that directly or indirectly reference
58 * another to be inspected and compared without causing infinite74 * another to be inspected and compared without causing infinite
59 * recursion.75 * recursion.
60 *76 *
@@ -127,6 +143,43 @@
127int blocked_diff (const Blocked *a, const Blocked *b, AlreadySeen seen)143int blocked_diff (const Blocked *a, const Blocked *b, AlreadySeen seen)
128 __attribute__ ((warn_unused_result));144 __attribute__ ((warn_unused_result));
129145
146void test_upstart1_6_upgrade (const char *conf_file, const char *path);
147
148/**
149 * TestDataFile:
150 *
151 * @conf_file: Name of ConfFile that must be created prior to
152 * deserialising JSON data in @filename.
153 * @filename: basename of data file,
154 * @func: function to run to test @filename.
155 *
156 * Representation of a JSON data file used for ensuring that the current
157 * version of Upstart is able to deserialise all previous JSON data file
158 * format versions.
159 *
160 * @conf_file is required since we do not currently serialise ConfFile
161 * and ConfSource objects so these entities must be created immediately
162 * prior to attempting deserialisation.
163 *
164 * @func returns nothing so is expected to assert on any error.
165 **/
166typedef struct test_data_file {
167 char *conf_file;
168 char *filename;
169 void (*func) (const char *conf_file, const char *path);
170} TestDataFile;
171
172/**
173 * test_data_files:
174 *
175 * Array of data files to test.
176 **/
177TestDataFile test_data_files[] = {
178 { "bar", "upstart-1.6.json", test_upstart1_6_upgrade },
179
180 { NULL, NULL, NULL }
181};
182
130/* Data with some embedded nulls */183/* Data with some embedded nulls */
131const char test_data[] = {184const char test_data[] = {
132 'h', 'e', 'l', 'l', 'o', 0x0, 0x0, 0x0, ' ', 'w', 'o', 'r', 'l', 'd', '\n', '\r', '\0'185 'h', 'e', 'l', 'l', 'o', 0x0, 0x0, 0x0, ' ', 'w', 'o', 'r', 'l', 'd', '\n', '\r', '\0'
@@ -139,7 +192,7 @@
139int64_t values64[] = {INT64_MIN, -1, 0, 1, INT64_MAX};192int64_t values64[] = {INT64_MIN, -1, 0, 1, INT64_MAX};
140const Process test_procs[] = {193const Process test_procs[] = {
141 { 0, "echo hello" },194 { 0, "echo hello" },
142 { 1, "echo hello" },195 { 1, "echo hello" }
143};196};
144rlim_t rlimit_values[] = { 0, 1, 2, 3, 7, RLIM_INFINITY };197rlim_t rlimit_values[] = { 0, 1, 2, 3, 7, RLIM_INFINITY };
145198
@@ -993,17 +1046,23 @@
993void1046void
994test_blocking (void)1047test_blocking (void)
995{1048{
996 nih_local char *json_string = NULL;1049 nih_local char *json_string = NULL;
997 ConfSource *source = NULL;1050 nih_local char *parent_str = NULL;
998 ConfFile *file;1051 ConfSource *source = NULL;
999 JobClass *class;1052 ConfFile *file;
1000 JobClass *new_class;1053 JobClass *class;
1001 Job *job;1054 JobClass *new_class;
1002 Job *new_job;1055 Job *job;
1003 Event *event;1056 Job *new_job;
1004 Event *new_event;1057 Event *event;
1005 Blocked *blocked;1058 Event *new_event;
1006 size_t len;1059 Blocked *blocked;
1060 Blocked *new_blocked;
1061 NihList blocked_list;
1062 size_t len;
1063 json_object *json_blocked;
1064 Session *session;
1065 Session *new_session;
10071066
1008 conf_init ();1067 conf_init ();
1009 session_init ();1068 session_init ();
@@ -1014,6 +1073,278 @@
1014 TEST_GROUP ("Blocked serialisation and deserialisation");1073 TEST_GROUP ("Blocked serialisation and deserialisation");
10151074
1016 /*******************************/1075 /*******************************/
1076 TEST_FEATURE ("BLOCKED_JOB serialisation and deserialisation");
1077
1078 nih_list_init (&blocked_list);
1079 TEST_LIST_EMPTY (&blocked_list);
1080
1081 source = conf_source_new (NULL, "/tmp/foo", CONF_JOB_DIR);
1082 TEST_NE_P (source, NULL);
1083
1084 file = conf_file_new (source, "/tmp/foo/bar");
1085 TEST_NE_P (file, NULL);
1086 class = file->job = job_class_new (file, "bar", NULL);
1087
1088 TEST_NE_P (class, NULL);
1089 TEST_HASH_EMPTY (job_classes);
1090 TEST_TRUE (job_class_consider (class));
1091 TEST_HASH_NOT_EMPTY (job_classes);
1092
1093 job = job_new (class, "");
1094 TEST_NE_P (job, NULL);
1095
1096 parent_str = nih_strdup (NULL, "parent");
1097 TEST_NE_P (parent_str, NULL);
1098
1099 blocked = blocked_new (NULL, BLOCKED_JOB, job);
1100 TEST_NE_P (blocked, NULL);
1101
1102 json_blocked = state_serialise_blocked (blocked);
1103 TEST_NE_P (json_blocked, NULL);
1104
1105 new_blocked = state_deserialise_blocked (parent_str,
1106 json_blocked, &blocked_list);
1107 TEST_NE_P (new_blocked, NULL);
1108 TEST_LIST_NOT_EMPTY (&blocked_list);
1109
1110 assert0 (blocked_diff (blocked, new_blocked, ALREADY_SEEN_SET));
1111
1112 json_object_put (json_blocked);
1113 nih_free (source);
1114
1115 /*******************************/
1116 TEST_FEATURE ("BLOCKED_EVENT serialisation and deserialisation");
1117
1118 event = event_new (NULL, "event", NULL);
1119 TEST_NE_P (event, NULL);
1120
1121 nih_list_init (&blocked_list);
1122
1123 source = conf_source_new (NULL, "/tmp/foo", CONF_JOB_DIR);
1124 TEST_NE_P (source, NULL);
1125
1126 file = conf_file_new (source, "/tmp/foo/bar");
1127 TEST_NE_P (file, NULL);
1128 class = file->job = job_class_new (file, "bar", NULL);
1129
1130 TEST_NE_P (class, NULL);
1131 TEST_HASH_EMPTY (job_classes);
1132 TEST_TRUE (job_class_consider (class));
1133 TEST_HASH_NOT_EMPTY (job_classes);
1134
1135 job = job_new (class, "");
1136 TEST_NE_P (job, NULL);
1137
1138 TEST_LIST_EMPTY (&job->blocking);
1139
1140 blocked = blocked_new (NULL, BLOCKED_EVENT, event);
1141 TEST_NE_P (blocked, NULL);
1142
1143 nih_list_add (&job->blocking, &blocked->entry);
1144 TEST_LIST_NOT_EMPTY (&job->blocking);
1145
1146 event->blockers = 1;
1147
1148 parent_str = nih_strdup (NULL, "parent");
1149 TEST_NE_P (parent_str, NULL);
1150
1151 json_blocked = state_serialise_blocked (blocked);
1152 TEST_NE_P (json_blocked, NULL);
1153
1154 TEST_LIST_EMPTY (&blocked_list);
1155 new_blocked = state_deserialise_blocked (parent_str,
1156 json_blocked, &blocked_list);
1157 TEST_NE_P (new_blocked, NULL);
1158 TEST_LIST_NOT_EMPTY (&blocked_list);
1159
1160 assert0 (blocked_diff (blocked, new_blocked, ALREADY_SEEN_SET));
1161
1162 json_object_put (json_blocked);
1163 nih_free (source);
1164 nih_free (event);
1165
1166 /*******************************/
1167 /* Test Upstart 1.6+ behaviour
1168 *
1169 * The data serialisation format for this version now includes
1170 * a "session" entry in the JSON for the blocked job.
1171 *
1172 * Note that this test is NOT testing whether a JobClass with an
1173 * associated Upstart session is handled correctly, it is merely
1174 * testing that a JobClass with the NULL session is handled
1175 * correctly!
1176 */
1177 TEST_FEATURE ("BLOCKED_JOB with JSON session object");
1178
1179 TEST_LIST_EMPTY (sessions);
1180 TEST_LIST_EMPTY (events);
1181 TEST_LIST_EMPTY (conf_sources);
1182 TEST_HASH_EMPTY (job_classes);
1183
1184 event = event_new (NULL, "Christmas", NULL);
1185 TEST_NE_P (event, NULL);
1186 TEST_LIST_EMPTY (&event->blocking);
1187
1188 source = conf_source_new (NULL, "/tmp/foo", CONF_JOB_DIR);
1189 TEST_NE_P (source, NULL);
1190
1191 file = conf_file_new (source, "/tmp/foo/bar");
1192 TEST_NE_P (file, NULL);
1193 /* Create class with NULL session */
1194 class = file->job = job_class_new (NULL, "bar", NULL);
1195
1196 TEST_NE_P (class, NULL);
1197 TEST_HASH_EMPTY (job_classes);
1198 TEST_TRUE (job_class_consider (class));
1199 TEST_HASH_NOT_EMPTY (job_classes);
1200
1201 job = job_new (class, "");
1202 TEST_NE_P (job, NULL);
1203 TEST_HASH_NOT_EMPTY (class->instances);
1204
1205 blocked = blocked_new (event, BLOCKED_JOB, job);
1206 TEST_NE_P (blocked, NULL);
1207
1208 nih_list_add (&event->blocking, &blocked->entry);
1209 job->blocker = event;
1210
1211 TEST_LIST_NOT_EMPTY (&event->blocking);
1212
1213 assert0 (state_to_string (&json_string, &len));
1214 TEST_GT (len, 0);
1215
1216 /* XXX: We don't remove the source as these are not
1217 * recreated on re-exec, so we'll re-use the existing one.
1218 */
1219 nih_list_remove (&class->entry);
1220 nih_list_remove (&event->entry);
1221
1222 TEST_LIST_EMPTY (events);
1223 TEST_LIST_NOT_EMPTY (conf_sources);
1224 TEST_HASH_EMPTY (job_classes);
1225
1226 assert0 (state_from_string (json_string));
1227
1228 TEST_LIST_NOT_EMPTY (conf_sources);
1229 TEST_LIST_NOT_EMPTY (events);
1230 TEST_HASH_NOT_EMPTY (job_classes);
1231 TEST_LIST_EMPTY (sessions);
1232
1233 new_class = (JobClass *)nih_hash_lookup (job_classes, "bar");
1234 TEST_NE_P (new_class, NULL);
1235 nih_list_remove (&new_class->entry);
1236
1237 /* Upstart 1.6 can only deserialise the NULL session */
1238 TEST_EQ_P (class->session, NULL);
1239
1240 new_event = (Event *)nih_list_remove (events->next);
1241 TEST_LIST_EMPTY (events);
1242 TEST_LIST_NOT_EMPTY (&new_event->blocking);
1243 assert0 (event_diff (event, new_event, ALREADY_SEEN_SET));
1244
1245 nih_free (event);
1246 nih_free (new_event);
1247 nih_free (source);
1248 nih_free (new_class);
1249
1250 TEST_HASH_EMPTY (job_classes);
1251
1252 TEST_LIST_EMPTY (sessions);
1253 TEST_LIST_EMPTY (events);
1254 TEST_LIST_EMPTY (conf_sources);
1255 TEST_HASH_EMPTY (job_classes);
1256
1257 /*******************************/
1258 /* We don't currently handle user+chroot jobs, so let's assert
1259 * that behaviour.
1260 */
1261 TEST_FEATURE ("ensure BLOCKED_JOB with non-NULL session is ignored");
1262
1263 TEST_LIST_EMPTY (sessions);
1264 TEST_LIST_EMPTY (events);
1265 TEST_LIST_EMPTY (conf_sources);
1266 TEST_HASH_EMPTY (job_classes);
1267
1268 session = session_new (NULL, "/my/session", getuid ());
1269 TEST_NE_P (session, NULL);
1270 session->conf_path = NIH_MUST (nih_strdup (session, "/lives/here"));
1271 TEST_LIST_NOT_EMPTY (sessions);
1272
1273 /* We simulate a user job being blocked by a system event, hence
1274 * the session is not associated with the event.
1275 */
1276 event = event_new (NULL, "Christmas", NULL);
1277 TEST_NE_P (event, NULL);
1278 TEST_LIST_EMPTY (&event->blocking);
1279
1280 source = conf_source_new (NULL, "/tmp/foo", CONF_JOB_DIR);
1281 source->session = session;
1282 TEST_NE_P (source, NULL);
1283
1284 file = conf_file_new (source, "/tmp/foo/bar");
1285 TEST_NE_P (file, NULL);
1286
1287 /* Create class with non-NULL session, simulating a user job */
1288 class = file->job = job_class_new (NULL, "bar", session);
1289 TEST_NE_P (class, NULL);
1290
1291 TEST_HASH_EMPTY (job_classes);
1292 TEST_TRUE (job_class_consider (class));
1293 TEST_HASH_NOT_EMPTY (job_classes);
1294
1295 job = job_new (class, "");
1296 TEST_NE_P (job, NULL);
1297 TEST_HASH_NOT_EMPTY (class->instances);
1298
1299 blocked = blocked_new (event, BLOCKED_JOB, job);
1300 TEST_NE_P (blocked, NULL);
1301
1302 nih_list_add (&event->blocking, &blocked->entry);
1303 job->blocker = event;
1304
1305 TEST_LIST_NOT_EMPTY (&event->blocking);
1306
1307 assert0 (state_to_string (&json_string, &len));
1308 TEST_GT (len, 0);
1309
1310 /* XXX: We don't remove the source as these are not
1311 * recreated on re-exec, so we'll re-use the existing one.
1312 */
1313 nih_list_remove (&class->entry);
1314 nih_free (event);
1315 nih_list_remove (&session->entry);
1316
1317 TEST_LIST_EMPTY (events);
1318 TEST_LIST_NOT_EMPTY (conf_sources);
1319 TEST_HASH_EMPTY (job_classes);
1320
1321 assert0 (state_from_string (json_string));
1322
1323 TEST_LIST_NOT_EMPTY (conf_sources);
1324 TEST_LIST_NOT_EMPTY (events);
1325
1326 /* We don't expect any job_classes since the serialised one
1327 * related to a user session.
1328 */
1329 TEST_HASH_EMPTY (job_classes);
1330
1331 /* However, the session itself will exist */
1332 TEST_LIST_NOT_EMPTY (sessions);
1333
1334 new_session = (Session *)nih_list_remove (sessions->next);
1335
1336 nih_free (session);
1337 nih_free (new_session);
1338 event = (Event *)nih_list_remove (events->next);
1339 nih_free (event);
1340 nih_free (source);
1341
1342 TEST_LIST_EMPTY (sessions);
1343 TEST_LIST_EMPTY (events);
1344 TEST_LIST_EMPTY (conf_sources);
1345 TEST_HASH_EMPTY (job_classes);
1346
1347 /*******************************/
1017 TEST_FEATURE ("event blocking a job");1348 TEST_FEATURE ("event blocking a job");
10181349
1019 TEST_LIST_EMPTY (sessions);1350 TEST_LIST_EMPTY (sessions);
@@ -2608,6 +2939,141 @@
2608 /*******************************/2939 /*******************************/
2609}2940}
26102941
2942/**
2943 * test_upgrade:
2944 *
2945 * Run tests that simulate an upgrade by attempting to deserialise an
2946 * older version of the JSON data format than is currently used.
2947 **/
2948void
2949test_upgrade (void)
2950{
2951 TestDataFile *datafile;
2952
2953 TEST_GROUP ("upgrade tests");
2954
2955 for (datafile = test_data_files; datafile && datafile->filename; datafile++) {
2956 nih_local char *path = NULL;
2957 nih_local char *name = NULL;
2958
2959 nih_assert (datafile->func != NULL);
2960
2961 name = NIH_MUST (nih_sprintf (NULL, "with data file '%s'",
2962 datafile->filename));
2963 TEST_FEATURE (name);
2964
2965 path = NIH_MUST (nih_sprintf (NULL, "%s/%s",
2966 TEST_DATA_DIR, datafile->filename));
2967
2968 datafile->func (datafile->conf_file, path);
2969 }
2970}
2971
2972/**
2973 * test_upstart1_6_upgrade:
2974 *
2975 * @conf_file: name of ConfFile to create prior to running test,
2976 * @path: full path to JSON data file to deserialise.
2977 *
2978 * Test for original Upstart 1.6 serialisation data format containing
2979 * a blocked object that does not contain a 'session' element.
2980 *
2981 * Note that this test is NOT testing whether a JobClass with an
2982 * associated Upstart session is handled correctly, it is merely
2983 * testing that a JobClass with the NULL session encoded in the JSON
2984 * is handled correctly.
2985 **/
2986void
2987test_upstart1_6_upgrade (const char *conf_file, const char *path)
2988{
2989 nih_local char *json_string = NULL;
2990 Event *event;
2991 ConfSource *source;
2992 ConfFile *file;
2993 nih_local char *conf_file_path = NULL;
2994 struct stat statbuf;
2995 size_t len;
2996
2997 nih_assert (conf_file);
2998 nih_assert (path);
2999
3000 conf_init ();
3001 session_init ();
3002 event_init ();
3003 control_init ();
3004 job_class_init ();
3005
3006 TEST_LIST_EMPTY (sessions);
3007 TEST_LIST_EMPTY (events);
3008 TEST_LIST_EMPTY (conf_sources);
3009 TEST_HASH_EMPTY (job_classes);
3010
3011 /* Check data file exists */
3012 TEST_EQ (stat (path, &statbuf), 0);
3013
3014 json_string = nih_file_read (NULL, path, &len);
3015 TEST_NE_P (json_string, NULL);
3016
3017 /* Create the ConfSource and ConfFile objects to simulate
3018 * Upstart reading /etc/init on startup. Required since we
3019 * don't currently serialise these objects.
3020 */
3021 source = conf_source_new (NULL, "/tmp/foo", CONF_JOB_DIR);
3022 TEST_NE_P (source, NULL);
3023
3024 conf_file_path = NIH_MUST (nih_sprintf (NULL, "%s/%s",
3025 "/tmp/foo", conf_file));
3026
3027 file = conf_file_new (source, conf_file_path);
3028 TEST_NE_P (file, NULL);
3029
3030 /* Recreate state from JSON data file */
3031 assert0 (state_from_string (json_string));
3032
3033 TEST_LIST_NOT_EMPTY (conf_sources);
3034 TEST_LIST_NOT_EMPTY (events);
3035 TEST_HASH_NOT_EMPTY (job_classes);
3036 TEST_LIST_EMPTY (sessions);
3037
3038 event = (Event *)nih_list_remove (events->next);
3039 TEST_NE_P (event, NULL);
3040 TEST_EQ_STR (event->name, "Christmas");
3041
3042 NIH_HASH_FOREACH (job_classes, iter) {
3043 JobClass *class = (JobClass *)iter;
3044
3045 TEST_EQ_STR (class->name, "bar");
3046 TEST_EQ_STR (class->path, "/com/ubuntu/Upstart/jobs/bar");
3047 TEST_HASH_NOT_EMPTY (class->instances);
3048
3049 NIH_HASH_FOREACH (class->instances, iter2) {
3050 Blocked *blocked;
3051 Job *job = (Job *)iter2;
3052 nih_local char *instance_path = NULL;
3053
3054 /* instance name */
3055 TEST_EQ_STR (job->name, "");
3056
3057 instance_path = NIH_MUST (nih_sprintf (NULL, "%s/_", class->path));
3058 TEST_EQ_STR (job->path, instance_path);
3059
3060 /* job is blocked by the event */
3061 TEST_EQ (job->blocker, event);
3062
3063 /* First entry in list should be a Blocked
3064 * object pointing to the job.
3065 */
3066 TEST_LIST_NOT_EMPTY (&event->blocking);
3067 blocked = (Blocked *)(&event->blocking)->next;
3068 TEST_EQ (blocked->type, BLOCKED_JOB);
3069 TEST_EQ (blocked->job, job);
3070 }
3071 }
3072
3073 nih_free (event);
3074 nih_free (conf_sources);
3075}
3076
2611int3077int
2612main (int argc,3078main (int argc,
2613 char *argv[])3079 char *argv[])
@@ -2632,6 +3098,7 @@
2632 test_log_serialise ();3098 test_log_serialise ();
2633 test_job_serialise ();3099 test_job_serialise ();
2634 test_job_class_serialise ();3100 test_job_class_serialise ();
3101 test_upgrade ();
26353102
2636 return 0;3103 return 0;
2637}3104}

Subscribers

People subscribed via source and target branches