Merge lp:~jamesodhunt/upstart/remove-basic-user-sessions into lp:upstart

Proposed by James Hunt
Status: Merged
Merged at revision: 1431
Proposed branch: lp:~jamesodhunt/upstart/remove-basic-user-sessions
Merge into: lp:upstart
Diff against target: 2363 lines (+321/-1503)
14 files modified
ChangeLog (+48/-0)
dbus/Upstart.conf (+36/-6)
init/control.c (+135/-37)
init/job.c (+1/-1)
init/job_class.c (+9/-32)
init/job_process.c (+2/-91)
init/man/init.5 (+16/-53)
init/session.c (+50/-168)
init/session.h (+12/-14)
init/state.h (+3/-3)
init/tests/test_conf.c (+2/-0)
init/tests/test_state.c (+4/-7)
util/tests/test_initctl.c (+3/-0)
util/tests/test_user_sessions.sh (+0/-1091)
To merge this branch: bzr merge lp:~jamesodhunt/upstart/remove-basic-user-sessions
Reviewer Review Type Date Requested Status
Colin Watson (community) Approve
Steve Langasek Needs Fixing
Review via email: mp+144873@code.launchpad.net

Description of the change

= Removal of Simple Sessions =

This branch removes the existing 'User Jobs' code since we are in the process of replacing that facility entirely by 'User Sessions' [1].

= D-Bus =

The branch reverts the D-Bus policy changes introduced to allow any user to manipulate any property and method (since with User Jobs, all commands were namespaced off by default.

Checks have been added to all control interfaces to disallow non-priv users from manipulating PID 1 properties and methods.

Note however that this branch does _not_ provide the same checks on job and job instance method calls (or log-priority method): we are reliant on D-Bus policy to police these for now. This is consistent with old pre-session behaviour but we should review this approach in a follow-on branch to make the internal D-Bus border checks fully consistent.

[1] - https://wiki.ubuntu.com/FoundationsTeam/Specs/RaringUpstartUserSessions

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

Several places in the code, you're using this construction:

  if (control_get_origin_uid (message, &origin_uid) && origin_uid != uid) {
      // EPERM

So if the control_get_origin_uid() function fails, access is granted. This seems like a bad idea, as it means anyone who can figure out a way to break the function can get access. I think it's probably better to do:

  if (!control_get_origin_uid (message, &origin_uid) || origin_uid != uid) {
      // EPERM

Otherwise, this looks good to me.

review: Needs Fixing
1430. By James Hunt

* init/control.c: More careful uid checking.

Revision history for this message
James Hunt (jamesodhunt) wrote :

Hi Steve,

Good catch! Fixed.

1431. By James Hunt

* init/control.c:
  - Typos.
  - Improved uid checks.
  - Replaced direct call to control_get_origin_uid() with call to new
    control_check_permission() (as early as possible) for clarity and
    to confine policy to one location.
  - control_set_log_prioity(): Added missing call to
    control_check_permission().
  - control_get_origin_uid(): Check message contents before allowing
    D-Bus calls.
* init/control.h: Added missing prototypes.

Revision history for this message
James Hunt (jamesodhunt) wrote :

Reworked branch D-Bus permission checking to simplify code and confine policy to a single location.

Revision history for this message
Colin Watson (cjwatson) wrote :

Looks better now, though I'd add a comment to control_check_permission explaining that we rely on dbus to limit session bus access to the same user (since this is a little non-obvious if you happen to be just looking at that function).

review: Approve
1432. By James Hunt

* init/control.c: Comments.

Revision history for this message
James Hunt (jamesodhunt) wrote :

Fixed. Thanks for reviewing.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'ChangeLog'
2--- ChangeLog 2013-01-24 08:37:53 +0000
3+++ ChangeLog 2013-01-30 16:15:24 +0000
4@@ -1,3 +1,51 @@
5+2013-01-30 James Hunt <james.hunt@ubuntu.com>
6+
7+ * init/control.c:
8+ - Typos.
9+ - Improved uid checks.
10+ - Replaced direct call to control_get_origin_uid() with call to new
11+ control_check_permission() (as early as possible) for clarity and
12+ to confine policy to one location.
13+ - control_set_log_prioity(): Added missing call to
14+ control_check_permission().
15+ - control_get_origin_uid(): Check message contents before allowing
16+ D-Bus calls.
17+
18+2013-01-29 James Hunt <james.hunt@ubuntu.com>
19+
20+ * init/control.c: More careful uid checking.
21+
22+2013-01-25 James Hunt <james.hunt@ubuntu.com>
23+
24+ * init/control.c:
25+ - control_reload_configuration(): Added missing permission checks.
26+ - control_emit_event_with_file(): Added missing permission checks.
27+ - Added calls to new function control_get_origin_uid() to allow
28+ D-Bus methods to be policed by filtering on uid, rather than
29+ relying on the same information that used to be stored in the
30+ old Session object.
31+ * init/job.c: Typo.
32+ * init/job_class.c:
33+ - job_class_new(): Removed user elements from session code.
34+ - job_class_serialise(): Comments.
35+ - job_class_deserialise(): Comments.
36+ * init/job_process.c: job_process_spawn(): Removed user session
37+ handling.
38+ * init/session.c: Removed user session handling since it is
39+ about to be replaced by the ability to run Upstart as a
40+ non-privileged user (aka a 'Session Init').
41+ * init/session.h: Updated prototypes.
42+ * init/state.h: Comments.
43+ * init/tests/test_conf.c: test_source_reload_job_dir(): Added missing
44+ check.
45+ * init/tests/test_state.c:
46+ - session_diff(): Removed user check.
47+ - Updated all calls to session_new().
48+ * util/tests/test_initctl.c: test_notify_disk_writeable(): Ensure this
49+ test is not run as root.
50+ * util/tests/test_user_sessions.sh: Removed.
51+ * init/man/init.5: Updated to reflect removal of user jobs.
52+
53 2013-01-21 Dmitrijs Ledkovs <xnox@ubuntu.com>
54
55 * init/xdg.[ch]: add xdg_get_cache_home and get_user_log_dir
56
57=== modified file 'dbus/Upstart.conf'
58--- dbus/Upstart.conf 2010-12-10 03:02:57 +0000
59+++ dbus/Upstart.conf 2013-01-30 16:15:24 +0000
60@@ -9,14 +9,12 @@
61 <allow own="com.ubuntu.Upstart" />
62 </policy>
63
64- <!-- Allow any user to invoke all of the methods on Upstart, its jobs
65- or their instances, and to get and set properties - since Upstart
66- isolates commands by user. -->
67- <policy context="default">
68- <allow send_destination="com.ubuntu.Upstart"
69- send_interface="org.freedesktop.DBus.Introspectable" />
70+ <!-- Permit the root user to invoke all of the methods on Upstart, its jobs
71+ or their instances, and to get and set properties. -->
72+ <policy user="root">
73 <allow send_destination="com.ubuntu.Upstart"
74 send_interface="org.freedesktop.DBus.Properties" />
75+
76 <allow send_destination="com.ubuntu.Upstart"
77 send_interface="com.ubuntu.Upstart0_6" />
78 <allow send_destination="com.ubuntu.Upstart"
79@@ -24,4 +22,36 @@
80 <allow send_destination="com.ubuntu.Upstart"
81 send_interface="com.ubuntu.Upstart0_6.Instance" />
82 </policy>
83+
84+ <!-- Allow any user to introspect Upstart's interfaces, to obtain the
85+ values of properties (but not set them) and to invoke selected
86+ methods on Upstart and its jobs that are used to walk information. -->
87+ <policy context="default">
88+ <allow send_destination="com.ubuntu.Upstart"
89+ send_interface="org.freedesktop.DBus.Introspectable" />
90+
91+ <allow send_destination="com.ubuntu.Upstart"
92+ send_interface="org.freedesktop.DBus.Properties"
93+ send_type="method_call" send_member="Get" />
94+ <allow send_destination="com.ubuntu.Upstart"
95+ send_interface="org.freedesktop.DBus.Properties"
96+ send_type="method_call" send_member="GetAll" />
97+
98+ <allow send_destination="com.ubuntu.Upstart"
99+ send_interface="com.ubuntu.Upstart0_6"
100+ send_type="method_call" send_member="GetJobByName" />
101+ <allow send_destination="com.ubuntu.Upstart"
102+ send_interface="com.ubuntu.Upstart0_6"
103+ send_type="method_call" send_member="GetAllJobs" />
104+
105+ <allow send_destination="com.ubuntu.Upstart"
106+ send_interface="com.ubuntu.Upstart0_6.Job"
107+ send_type="method_call" send_member="GetInstance" />
108+ <allow send_destination="com.ubuntu.Upstart"
109+ send_interface="com.ubuntu.Upstart0_6.Job"
110+ send_type="method_call" send_member="GetInstanceByName" />
111+ <allow send_destination="com.ubuntu.Upstart"
112+ send_interface="com.ubuntu.Upstart0_6.Job"
113+ send_type="method_call" send_member="GetAllInstances" />
114+ </policy>
115 </busconfig>
116
117=== modified file 'init/control.c'
118--- init/control.c 2013-01-23 12:56:00 +0000
119+++ init/control.c 2013-01-30 16:15:24 +0000
120@@ -2,7 +2,7 @@
121 *
122 * control.c - D-Bus connections, objects and methods
123 *
124- * Copyright © 2009-2011 Canonical Ltd.
125+ * Copyright 2009-2011 Canonical Ltd.
126 * Author: Scott James Remnant <scott@netsplit.com>.
127 *
128 * This program is free software; you can redistribute it and/or modify
129@@ -60,11 +60,15 @@
130 #include "com.ubuntu.Upstart.h"
131
132 /* Prototypes for static functions */
133-static int control_server_connect (DBusServer *server, DBusConnection *conn);
134-static void control_disconnected (DBusConnection *conn);
135-static void control_register_all (DBusConnection *conn);
136+static int control_server_connect (DBusServer *server, DBusConnection *conn);
137+static void control_disconnected (DBusConnection *conn);
138+static void control_register_all (DBusConnection *conn);
139
140-static void control_bus_flush (void);
141+static void control_bus_flush (void);
142+static int control_get_origin_uid (NihDBusMessage *message, uid_t *uid)
143+ __attribute__ ((warn_unused_result));
144+static int control_check_permission (NihDBusMessage *message)
145+ __attribute__ ((warn_unused_result));
146
147 /**
148 * use_session_bus:
149@@ -378,6 +382,8 @@
150 * Called to request that Upstart reloads its configuration from disk,
151 * useful when inotify is not available or the user is generally paranoid.
152 *
153+ * Notes: chroot sessions are permitted to make this call.
154+ *
155 * Returns: zero on success, negative value on raised error.
156 **/
157 int
158@@ -386,6 +392,13 @@
159 {
160 nih_assert (message != NULL);
161
162+ if (! control_check_permission (message)) {
163+ nih_dbus_error_raise_printf (
164+ DBUS_INTERFACE_UPSTART ".Error.PermissionDenied",
165+ _("You do not have permission to reload configuration"));
166+ return -1;
167+ }
168+
169 nih_info (_("Reloading configuration"));
170
171 /* This can only be called after deserialisation */
172@@ -574,13 +587,20 @@
173 int wait,
174 int file)
175 {
176- Event *event;
177- Blocked *blocked;
178+ Event *event;
179+ Blocked *blocked;
180
181 nih_assert (message != NULL);
182 nih_assert (name != NULL);
183 nih_assert (env != NULL);
184
185+ if (! control_check_permission (message)) {
186+ nih_dbus_error_raise_printf (
187+ DBUS_INTERFACE_UPSTART ".Error.PermissionDenied",
188+ _("You do not have permission to emit an event"));
189+ return -1;
190+ }
191+
192 /* Verify that the name is valid */
193 if (! strlen (name)) {
194 nih_dbus_error_raise_printf (DBUS_ERROR_INVALID_ARGS,
195@@ -741,6 +761,13 @@
196 nih_assert (message != NULL);
197 nih_assert (log_priority != NULL);
198
199+ if (! control_check_permission (message)) {
200+ nih_dbus_error_raise_printf (
201+ DBUS_INTERFACE_UPSTART ".Error.PermissionDenied",
202+ _("You do not have permission to set log priority"));
203+ return -1;
204+ }
205+
206 if (! strcmp (log_priority, "debug")) {
207 nih_log_set_priority (NIH_LOG_DEBUG);
208
209@@ -793,6 +820,11 @@
210 * Called to flush the job logs for all jobs that ended before the log
211 * disk became writeable.
212 *
213+ * Notes: Session Inits are permitted to make this call. In the common
214+ * case of starting a Session Init as a child of a Display Manager this
215+ * is somewhat meaningless, but it does mean that if a Session Init were
216+ * started from a system job, behaviour would be as expected.
217+ *
218 * Returns: zero on success, negative value on raised error.
219 **/
220 int
221@@ -804,16 +836,16 @@
222
223 nih_assert (message != NULL);
224
225- /* Get the relevant session */
226- session = session_from_dbus (NULL, message);
227-
228- if (session && session->user) {
229+ if (! control_check_permission (message)) {
230 nih_dbus_error_raise_printf (
231 DBUS_INTERFACE_UPSTART ".Error.PermissionDenied",
232 _("You do not have permission to notify disk is writeable"));
233 return -1;
234 }
235
236+ /* Get the relevant session */
237+ session = session_from_dbus (NULL, message);
238+
239 /* "nop" when run from a chroot */
240 if (session && session->chroot)
241 return 0;
242@@ -978,13 +1010,17 @@
243 char **state)
244 {
245 Session *session;
246- uid_t uid;
247 size_t len;
248
249 nih_assert (message);
250 nih_assert (state);
251
252- uid = getuid ();
253+ if (! control_check_permission (message)) {
254+ nih_dbus_error_raise_printf (
255+ DBUS_INTERFACE_UPSTART ".Error.PermissionDenied",
256+ _("You do not have permission to request state"));
257+ return -1;
258+ }
259
260 /* Get the relevant session */
261 session = session_from_dbus (NULL, message);
262@@ -999,17 +1035,6 @@
263 return 0;
264 }
265
266- /* Disallow users from obtaining state details, unless they
267- * happen to own this process (which they may do in the test
268- * scenario and when running Upstart as a non-privileged user).
269- */
270- if (session && session->user != uid) {
271- nih_dbus_error_raise_printf (
272- DBUS_INTERFACE_UPSTART ".Error.PermissionDenied",
273- _("You do not have permission to request state"));
274- return -1;
275- }
276-
277 if (state_to_string (state, &len) < 0)
278 goto error;
279
280@@ -1041,11 +1066,15 @@
281 NihDBusMessage *message)
282 {
283 Session *session;
284- uid_t uid;
285
286 nih_assert (message != NULL);
287
288- uid = getuid ();
289+ if (! control_check_permission (message)) {
290+ nih_dbus_error_raise_printf (
291+ DBUS_INTERFACE_UPSTART ".Error.PermissionDenied",
292+ _("You do not have permission to request restart"));
293+ return -1;
294+ }
295
296 /* Get the relevant session */
297 session = session_from_dbus (NULL, message);
298@@ -1061,17 +1090,6 @@
299 return 0;
300 }
301
302- /* Disallow users from restarting Upstart, unless they happen to
303- * own this process (which they may do in the test scenario and
304- * when running Upstart as a non-privileged user).
305- */
306- if (session && session->user != uid) {
307- nih_dbus_error_raise_printf (
308- DBUS_INTERFACE_UPSTART ".Error.PermissionDenied",
309- _("You do not have permission to request restart"));
310- return -1;
311- }
312-
313 nih_info (_("Restarting"));
314
315 stateful_reexec ();
316@@ -1117,3 +1135,83 @@
317 NIH_ZERO (control_emit_restarted (conn, DBUS_PATH_UPSTART));
318 }
319 }
320+
321+/**
322+ * control_get_origin_uid:
323+ * @message: D-Bus connection and message received,
324+ * @uid: returned uid value.
325+ *
326+ * Returns TRUE: if @uid now contains uid corresponding to @message,
327+ * else FALSE.
328+ **/
329+static int
330+control_get_origin_uid (NihDBusMessage *message, uid_t *uid)
331+{
332+ DBusError dbus_error;
333+ unsigned long unix_user = 0;
334+ const char *sender;
335+
336+ nih_assert (message);
337+ nih_assert (uid);
338+
339+ dbus_error_init (&dbus_error);
340+
341+ if (! message->message || ! message->connection)
342+ return FALSE;
343+
344+ sender = dbus_message_get_sender (message->message);
345+ if (sender) {
346+ unix_user = dbus_bus_get_unix_user (message->connection, sender,
347+ &dbus_error);
348+ if (unix_user == (unsigned long)-1) {
349+ dbus_error_free (&dbus_error);
350+ return FALSE;
351+ }
352+ } else {
353+ if (! dbus_connection_get_unix_user (message->connection,
354+ &unix_user)) {
355+ return FALSE;
356+ }
357+ }
358+
359+ *uid = (uid_t)unix_user;
360+
361+ return TRUE;
362+}
363+
364+/**
365+ * control_check_permission:
366+ *
367+ * @message: D-Bus connection and message received.
368+ *
369+ * Determine if caller should be allowed to make a control request.
370+ *
371+ * Note that these permission checks rely on D-Bus to limit
372+ * session bus access to the same user.
373+ *
374+ * Returns: TRUE if permission is granted, else FALSE.
375+ **/
376+static int
377+control_check_permission (NihDBusMessage *message)
378+{
379+ int ret;
380+ uid_t uid;
381+ pid_t pid;
382+ uid_t origin_uid = 0;
383+
384+ nih_assert (message);
385+
386+ uid = getuid ();
387+ pid = getpid ();
388+
389+ ret = control_get_origin_uid (message, &origin_uid);
390+
391+ /* Its possible that D-Bus might be unable to determine the user
392+ * making the request. In this case, deny the request unless
393+ * we're running as a Session Init or via the test harness.
394+ */
395+ if ((ret && origin_uid == uid) || user_mode || (uid && pid != 1))
396+ return TRUE;
397+
398+ return FALSE;
399+}
400
401=== modified file 'init/job.c'
402--- init/job.c 2012-11-07 11:56:33 +0000
403+++ init/job.c 2013-01-30 16:15:24 +0000
404@@ -1701,7 +1701,7 @@
405 /**
406 * job_serialise_all:
407 *
408- * Convert existing Session objects to JSON representation.
409+ * Convert existing Job objects to JSON representation.
410 *
411 * Returns: JSON object containing array of Job objects, or NULL on error.
412 **/
413
414=== modified file 'init/job_class.c'
415--- init/job_class.c 2012-12-14 20:55:41 +0000
416+++ init/job_class.c 2013-01-30 16:15:24 +0000
417@@ -142,40 +142,17 @@
418 goto error;
419
420 class->session = session;
421- if (class->session
422- && class->session->chroot
423- && class->session->user) {
424- nih_local char *uid = NULL;
425-
426- uid = nih_sprintf (NULL, "%d", class->session->user);
427- if (! uid)
428- goto error;
429-
430- class->path = nih_dbus_path (class, DBUS_PATH_UPSTART, "jobs",
431- session->chroot, uid,
432- class->name, NULL);
433-
434- } else if (class->session
435- && class->session->chroot) {
436+
437+ if (class->session && class->session->chroot) {
438 class->path = nih_dbus_path (class, DBUS_PATH_UPSTART, "jobs",
439 session->chroot,
440 class->name, NULL);
441
442- } else if (class->session
443- && class->session->user) {
444- nih_local char *uid = NULL;
445-
446- uid = nih_sprintf (NULL, "%d", class->session->user);
447- if (! uid)
448- goto error;
449-
450- class->path = nih_dbus_path (class, DBUS_PATH_UPSTART, "jobs",
451- uid, class->name, NULL);
452-
453 } else {
454 class->path = nih_dbus_path (class, DBUS_PATH_UPSTART, "jobs",
455 class->name, NULL);
456 }
457+
458 if (! class->path)
459 goto error;
460
461@@ -1595,12 +1572,12 @@
462 if (! json)
463 return NULL;
464
465- /* XXX: user and chroot jobs are not currently supported
466+ /* XXX: chroot jobs are not currently supported
467 * due to ConfSources not currently being serialised.
468 */
469 if (class->session) {
470- nih_info ("WARNING: serialisation of user jobs and "
471- "chroot sessions not currently supported");
472+ nih_info ("WARNING: serialisation of chroot "
473+ "sessions not currently supported");
474 goto error;
475 }
476
477@@ -1828,12 +1805,12 @@
478
479 session = session_from_index (session_index);
480
481- /* XXX: user and chroot jobs are not currently supported
482+ /* XXX: chroot jobs are not currently supported
483 * due to ConfSources not currently being serialised.
484 */
485 if (session) {
486- nih_info ("WARNING: deserialisation of user jobs and "
487- "chroot sessions not currently supported");
488+ nih_info ("WARNING: deserialisation of chroot "
489+ "sessions not currently supported");
490 goto error;
491 }
492
493
494=== modified file 'init/job_process.c'
495--- init/job_process.c 2013-01-23 12:40:36 +0000
496+++ init/job_process.c 2013-01-30 16:15:24 +0000
497@@ -420,8 +420,6 @@
498 char pts_name[PATH_MAX];
499 char filename[PATH_MAX];
500 FILE *fd;
501- int user_job = FALSE;
502- nih_local char *user_dir = NULL;
503 nih_local char *log_path = NULL;
504 JobClass *class;
505 uid_t job_setuid = -1;
506@@ -439,20 +437,14 @@
507
508 nih_assert (class != NULL);
509
510- if (class && class->session && class->session->user)
511- user_job = TRUE;
512-
513 /* Create a pipe to communicate with the child process until it
514 * execs so we know whether that was successful or an error occurred.
515 */
516 if (pipe (fds) < 0)
517 nih_return_system_error (-1);
518
519- /* Logging of user job output is not currently possible */
520- if (class->console == CONSOLE_LOG) {
521- if (disable_job_logging || user_job)
522+ if (class->console == CONSOLE_LOG && disable_job_logging)
523 class->console = CONSOLE_NONE;
524- }
525
526 if (class->console == CONSOLE_LOG) {
527 NihError *err;
528@@ -655,87 +647,6 @@
529 /* Set the process environment from the function parameters. */
530 environ = (char **)env;
531
532- /* Handle unprivileged user job by dropping privileges to
533- * their level as soon as possible to avoid privilege
534- * escalations when we set resource limits.
535- */
536- if (user_job) {
537- uid_t uid = class->session->user;
538- struct passwd *pw = NULL;
539-
540- /* D-Bus does not expose a public API call to allow
541- * us to query a users primary group.
542- * _dbus_user_info_fill_uid () seems to exist for this
543- * purpose, but is a "secret" API. It is unclear why
544- * D-Bus neglects the gid when it allows the uid
545- * to be queried directly.
546- *
547- * Our only recourse is to disallow user sessions in a
548- * chroot and assume that all other user sessions
549- * originate from the local system. In this way, we can
550- * bypass D-Bus and use getpwuid ().
551- */
552-
553- if (class->session->chroot) {
554- /* We cannot determine the group id of the user
555- * session in the chroot via D-Bus, so disallow
556- * all jobs in such an environment.
557- */
558- nih_error_raise (EPERM, "user jobs not supported in chroots");
559- job_process_error_abort (fds[1], JOB_PROCESS_ERROR_CHROOT, 0);
560- }
561-
562- pw = getpwuid (uid);
563-
564- if (!pw) {
565- nih_error_raise_system ();
566- job_process_error_abort (fds[1], JOB_PROCESS_ERROR_GETPWUID, 0);
567- }
568-
569- nih_assert (pw->pw_uid == uid);
570-
571- if (! pw->pw_dir) {
572- nih_error_raise_printf (ENOENT,
573- "no home directory for user with uid %d", uid);
574- job_process_error_abort (fds[1], JOB_PROCESS_ERROR_GETPWUID, 0);
575-
576- }
577-
578- /* Note we don't use NIH_MUST since this could result in a
579- * DoS for a (low priority) user job in low-memory scenarios.
580- */
581- user_dir = nih_strdup (NULL, pw->pw_dir);
582-
583- if (! user_dir) {
584- nih_error_raise_no_memory ();
585- job_process_error_abort (fds[1], JOB_PROCESS_ERROR_ALLOC, 0);
586- }
587-
588- /* Ensure the file associated with fd 9
589- * (/proc/self/fd/9) is owned by the user we're about to
590- * become to avoid EPERM.
591- */
592- if (script_fd != -1 && fchown (script_fd, pw->pw_uid, pw->pw_gid) < 0) {
593- nih_error_raise_system ();
594- job_process_error_abort (fds[1], JOB_PROCESS_ERROR_CHOWN, 0);
595- }
596-
597- if (geteuid () == 0 && initgroups (pw->pw_name, pw->pw_gid) < 0) {
598- nih_error_raise_system ();
599- job_process_error_abort (fds[1], JOB_PROCESS_ERROR_INITGROUPS, 0);
600- }
601-
602- if (pw->pw_gid && setgid (pw->pw_gid) < 0) {
603- nih_error_raise_system ();
604- job_process_error_abort (fds[1], JOB_PROCESS_ERROR_SETGID, 0);
605- }
606-
607- if (pw->pw_uid && setuid (pw->pw_uid) < 0) {
608- nih_error_raise_system ();
609- job_process_error_abort (fds[1], JOB_PROCESS_ERROR_SETUID, 0);
610- }
611- }
612-
613 /* Set the standard file descriptors to an output of our chosing;
614 * any other open descriptor must be intended for the child, or have
615 * the FD_CLOEXEC flag so it's automatically closed when we exec()
616@@ -854,7 +765,7 @@
617 * configured in the job, or to the root directory of the filesystem
618 * (or at least relative to the chroot).
619 */
620- if (chdir (class->chdir ? class->chdir : user_job ? user_dir : "/") < 0) {
621+ if (chdir (class->chdir ? class->chdir : "/") < 0) {
622 nih_error_raise_system ();
623 job_process_error_abort (fds[1], JOB_PROCESS_ERROR_CHDIR, 0);
624 }
625
626=== modified file 'init/man/init.5'
627--- init/man/init.5 2013-01-23 12:40:36 +0000
628+++ init/man/init.5 2013-01-30 16:15:24 +0000
629@@ -1,4 +1,4 @@
630-.TH init 5 2012-12-18 "Upstart"
631+.TH init 5 2013-01-25 "Upstart"
632 .\"
633 .SH NAME
634 init \- Upstart init daemon job configuration
635@@ -9,13 +9,14 @@
636 Default location of system job configuration files.
637 .\"
638 .TP
639-.B $HOME/.init/
640-Default location of user job configuration files.
641-.\"
642-.TP
643 .B $XDG_CONFIG_HOME/upstart/, $XDG_CONFIG_DIRS/upstart/
644 Default locations of user session job configuration files.
645 .\"
646+.TP
647+.B $HOME/.init/
648+Deprecated location of user job configuration files (still
649+honoured by User Session Mode).
650+.\"
651 .SH DESCRIPTION
652 On startup, the Upstart
653 .BR init (8)
654@@ -24,11 +25,6 @@
655 directory, and watches for future changes to these files using
656 .BR inotify (7).
657
658-If D\-Bus has been configured to allow non\-privileged users to invoke all
659-Upstart D\-Bus methods, Upstart is also able to manage User Jobs. See
660-.B User Jobs
661-for further details.
662-
663 If Upstart was invoked as a user process with \-\-user option, it will
664 run in User Session mode. See
665 .B User Session Mode
666@@ -85,33 +81,6 @@
667
668 Configuration files are plain text and should not be executable.
669 .\"
670-.SS User Jobs
671-
672-A User Job is a job configuration file created by a non\-privileged user
673-in their
674-.B $HOME/.init/
675-directory. Job configuration files in this directory have the same
676-syntax as system job configuration files.
677-Files in this directory will be read and an
678-.BR inotify (7)
679-watch created the first time a user runs
680-.BR initctl (8) "."
681-
682-Any user can create user jobs, but that user can control
683-.I only
684-jobs they create.
685-
686-Users are able to manage their jobs using the standard
687-.BR initctl (8)
688-facility.
689-
690-Note that stanzas which manipulate resources limits may cause a job to
691-fail to start should the value provided to such a stanza attempt to
692-exceed the maximum value the users privilege level allows.
693-
694-Note that a user job configuration file cannot have the same name as a
695-system job configuration file.
696-.\"
697 .SS Chroot Support
698
699 Upstart is able to manage jobs within a \fBchroot\fP(2). To control jobs
700@@ -124,10 +93,6 @@
701 .B Process environment
702 below).
703
704-Note that User Jobs
705-.B cannot
706-be used within a chroot environment.
707-
708 .\"
709 .SS User Session Mode
710
711@@ -447,9 +412,10 @@
712 .BR initctl (8)
713 utility to default to acting on the job the commands are called from.
714
715-When running in a user session, the additional UPSTART_SESSION environment
716-variable is also set and contains the DBus path to the private bus used by
717-the upstart instance daemon.
718+When running in a user session, the additional
719+.B UPSTART_SESSION
720+environment variable is also set and contains the DBus path to the
721+private bus used by the upstart instance daemon.
722
723 .TP
724 .B env \fIKEY\fR[=\fIVALUE\fR]
725@@ -667,12 +633,7 @@
726 .B log
727 .RS
728 .B
729-Only applies to system and user session jobs:
730-if specified by user jobs, the job will be considered to have specified
731-the value
732-.BR none "."
733-
734-For system and user session jobs jobs, if \fBlog\fR is specified, standard input is connected
735+If \fBlog\fR is specified, standard input is connected
736 to
737 .IR /dev/null ","
738 and standard output and standard error are connected to a pseudo-tty
739@@ -881,7 +842,7 @@
740 block is used for all job processes. If both stanzas are unspecified,
741 all job processes will run with its group ID set to 0 in the case of
742 system jobs, and as the primary group of the user in the case of User
743-Jobs.
744+Session jobs.
745 .\"
746 .SS Override File Handling
747 Override files allow a jobs environment to be changed without modifying
748@@ -1025,11 +986,13 @@
749 .
750 .TP
751 .I $HOME/.init/*.conf
752-User job configuration files.
753+User job configuration files
754+.BR (deprecated) .
755 .
756 .TP
757 .I $HOME/.init/*.override
758 User job override files.
759+.BR (deprecated) .
760 .
761 .TP
762 .I $XDG_CONFIG_HOME/upstart/*.conf
763@@ -1063,7 +1026,7 @@
764 .RB < https://launchpad.net/upstart/+bugs >
765 .\"
766 .SH COPYRIGHT
767-Copyright \(co 2009-2011 Canonical Ltd.
768+Copyright \(co 2009-2013 Canonical Ltd.
769 .br
770 This is free software; see the source for copying conditions. There is NO
771 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
772
773=== modified file 'init/session.c'
774--- init/session.c 2012-11-14 14:47:19 +0000
775+++ init/session.c 2013-01-30 16:15:24 +0000
776@@ -68,7 +68,7 @@
777 /**
778 * disable_sessions:
779 *
780- * If TRUE, disable user and chroot sessions, resulting in a
781+ * If TRUE, disable chroot sessions, resulting in a
782 * "traditional" (pre-session support) system.
783 **/
784 int disable_sessions = FALSE;
785@@ -93,21 +93,19 @@
786 /**
787 * session_new:
788 * @parent: parent,
789- * @chroot: full chroot path,
790- * @user: user id.
791+ * @chroot: full chroot path.
792 *
793- * Create a new session.
794+ * Create a new chroot session.
795 *
796 * Returns: new Session, or NULL on error.
797 **/
798 Session *
799 session_new (const void *parent,
800- const char *chroot,
801- uid_t user)
802+ const char *chroot)
803 {
804 Session *session;
805
806- nih_assert ((chroot != NULL) || (user != 0));
807+ nih_assert (chroot);
808
809 session_init ();
810
811@@ -117,18 +115,12 @@
812
813 nih_list_init (&session->entry);
814
815- if (chroot) {
816- session->chroot = nih_strdup (session, chroot);
817- if (! session->chroot) {
818- nih_free (session);
819- return NULL;
820- }
821- } else {
822- session->chroot = NULL;
823+ session->chroot = nih_strdup (session, chroot);
824+ if (! session->chroot) {
825+ nih_free (session);
826+ return NULL;
827 }
828
829- session->user = user;
830-
831 session->conf_path = NULL;
832
833 nih_alloc_set_destructor (session, nih_list_destroy);
834@@ -151,14 +143,13 @@
835 session_from_dbus (const void *parent,
836 NihDBusMessage *message)
837 {
838- const char *sender;
839- DBusError dbus_error;
840- unsigned long unix_user;
841- unsigned long unix_process_id;
842- char root[PATH_MAX];
843- Session *session;
844- struct passwd *pwd;
845- nih_local char *conf_path = NULL;
846+ DBusError dbus_error;
847+ unsigned long unix_process_id;
848+ char root[PATH_MAX];
849+ Session *session;
850+ nih_local char *conf_path = NULL;
851+ nih_local char *symlink = NULL;
852+ ssize_t len;
853
854 nih_assert (message != NULL);
855
856@@ -170,126 +161,43 @@
857
858 session_init ();
859
860- /* Ask D-Bus nicely for the origin uid and/or pid of the caller;
861- * sadly we can't ask the bus daemon for the origin pid, so that
862- * one will just have to stay user-session only.
863- */
864 dbus_error_init (&dbus_error);
865
866- sender = dbus_message_get_sender (message->message);
867- if (sender) {
868- unix_user = dbus_bus_get_unix_user (message->connection, sender,
869- &dbus_error);
870- if (unix_user == (unsigned long)-1) {
871- dbus_error_free (&dbus_error);
872- unix_user = 0;
873- }
874-
875- unix_process_id = 0;
876-
877- } else {
878- if (! dbus_connection_get_unix_user (message->connection,
879- &unix_user))
880- unix_process_id = 0;
881-
882- if (! dbus_connection_get_unix_process_id (message->connection,
883- &unix_process_id))
884- unix_process_id = 0;
885- }
886-
887- /* If we retrieved a process id, look up the root path for it;
888- * if it's just '/' don't worry so much about it.
889- */
890- if (unix_process_id) {
891- nih_local char *symlink = NULL;
892- ssize_t len;
893-
894- symlink = NIH_MUST (nih_sprintf (NULL, "/proc/%lu/root",
895- unix_process_id));
896- len = readlink (symlink, root, sizeof root);
897- if (len < 0)
898- return NULL;
899-
900- root[len] = '\0';
901-
902- if (! strcmp (root, "/")) {
903- unix_process_id = 0;
904- if (! unix_user)
905- return NULL;
906- }
907-
908- } else if (! unix_user) {
909- /* No process id or user id found, return the NULL session */
910- return NULL;
911- }
912-
913- if (unix_user) {
914- pwd = getpwuid (unix_user);
915-
916- if (! pwd || ! pwd->pw_dir) {
917- nih_error ("%lu: %s: %s", unix_user,
918- _("Unable to lookup home directory"),
919- strerror (errno));
920- return NULL;
921- }
922-
923- NIH_MUST (nih_strcat_sprintf (&conf_path, NULL, "%s/%s",
924- pwd->pw_dir, USERCONFDIR));
925- }
926-
927+ /* Query origin pid of the caller */
928+ if (! dbus_connection_get_unix_process_id (message->connection,
929+ &unix_process_id)) {
930+ return NULL;
931+ }
932+
933+ /* Look up the root path for retrieved pid */
934+ symlink = NIH_MUST (nih_sprintf (NULL, "/proc/%lu/root",
935+ unix_process_id));
936+ len = readlink (symlink, root, sizeof root);
937+ if (len < 0)
938+ return NULL;
939+
940+ root[len] = '\0';
941+
942+ /* Path is not inside a chroot */
943+ if (! strcmp (root, "/"))
944+ return NULL;
945
946 /* Now find in the existing Sessions list */
947 NIH_LIST_FOREACH_SAFE (sessions, iter) {
948 Session *session = (Session *)iter;
949
950- if (unix_process_id) {
951- if (! session->chroot)
952- continue;
953-
954+ if (strcmp (session->chroot, root))
955 /* ignore sessions relating to other chroots */
956- if (strcmp (session->chroot, root))
957- continue;
958- }
959-
960- /* ignore sessions relating to other users */
961- if (unix_user != session->user)
962 continue;
963
964- /* Found a user with the same uid but different
965- * conf_path to the existing session user. Either the
966- * original user has been deleted and a new user created
967- * with the same uid, or the original users home
968- * directory has changed since they first started
969- * running jobs. Whatever the reason, we (can only) honour
970- * the new value.
971- *
972- * Since multiple users with the same uid are considered
973- * to be "the same user", invalidate the old path,
974- * allowing the correct new path to be set below.
975- *
976- * Note that there may be a possibility of trouble if
977- * the scenario relates to a deleted user and that original
978- * user still has jobs running. However, if that were the
979- * case, those jobs would likely fail anyway since they
980- * would have no working directory due to the original
981- * users home directory being deleted/changed/made inaccessible.
982- */
983- if (unix_user && conf_path && session->conf_path &&
984- strcmp (conf_path, session->conf_path)) {
985- nih_free (session->conf_path);
986- session->conf_path = NULL;
987- }
988-
989 if (! session->conf_path)
990 session_create_conf_source (session, FALSE);
991
992 return session;
993 }
994
995-
996 /* Didn't find one, make a new one */
997- session = NIH_MUST (session_new (parent, unix_process_id ? root : NULL,
998- unix_user));
999+ session = NIH_MUST (session_new (parent, root));
1000 session_create_conf_source (session, FALSE);
1001
1002 return session;
1003@@ -317,27 +225,8 @@
1004 session_init ();
1005
1006 if (! deserialised) {
1007- if (session->chroot)
1008- session->conf_path = NIH_MUST (nih_strdup (NULL, session->chroot));
1009- if (session->user) {
1010- struct passwd *pwd;
1011-
1012- pwd = getpwuid (session->user);
1013- if (! pwd) {
1014- nih_error ("%d: %s: %s", session->user,
1015- _("Unable to lookup home directory"),
1016- strerror (errno));
1017-
1018- nih_free (session->conf_path);
1019- session->conf_path = NULL;
1020- return;
1021- }
1022-
1023- NIH_MUST (nih_strcat_sprintf (&session->conf_path, NULL, "%s/%s",
1024- pwd->pw_dir, USERCONFDIR));
1025- } else {
1026- NIH_MUST (nih_strcat (&session->conf_path, NULL, CONFDIR));
1027- }
1028+ session->conf_path = NIH_MUST (nih_strdup (NULL, session->chroot));
1029+ NIH_MUST (nih_strcat (&session->conf_path, NULL, CONFDIR));
1030 }
1031
1032 source = NIH_MUST (conf_source_new (session, session->conf_path,
1033@@ -375,9 +264,10 @@
1034 json_object *json;
1035 json_object *conf_path = NULL;
1036 json_object *chroot = NULL;
1037- json_object *user;
1038
1039 nih_assert (session);
1040+ nih_assert (session->chroot);
1041+ nih_assert (session->conf_path);
1042
1043 session_init ();
1044
1045@@ -385,26 +275,16 @@
1046 if (! json)
1047 return NULL;
1048
1049- if (session->chroot) {
1050- chroot = json_object_new_string (session->chroot);
1051- if (! chroot)
1052- goto error;
1053- }
1054+ chroot = json_object_new_string (session->chroot);
1055+ if (! chroot)
1056+ goto error;
1057
1058 json_object_object_add (json, "chroot", chroot);
1059
1060- user = state_new_json_int (session->user);
1061- if (! user)
1062+ conf_path = json_object_new_string (session->conf_path);
1063+ if (! conf_path)
1064 goto error;
1065
1066- json_object_object_add (json, "user", user);
1067-
1068- if (session->conf_path) {
1069- conf_path = json_object_new_string (session->conf_path);
1070- if (! conf_path)
1071- goto error;
1072- }
1073-
1074 json_object_object_add (json, "conf_path", conf_path);
1075
1076 return json;
1077@@ -459,6 +339,9 @@
1078 *
1079 * Convert @json into a Session object.
1080 *
1081+ * NOTE: Any user sessions seen when re-execing from an older version
1082+ * of Upstart are implicitly ignored.
1083+ *
1084 * Returns: Session object, or NULL on error.
1085 **/
1086 static Session *
1087@@ -466,7 +349,6 @@
1088 {
1089 Session *session = NULL;
1090 nih_local const char *chroot = NULL;
1091- uid_t user;
1092
1093 nih_assert (json);
1094
1095@@ -477,11 +359,11 @@
1096 if (! state_get_json_string_var (json, "chroot", NULL, chroot))
1097 return NULL;
1098
1099- if (! state_get_json_int_var (json, "user", user))
1100+ if (! chroot)
1101 return NULL;
1102
1103 /* Create a new session */
1104- session = NIH_MUST (session_new (NULL, chroot, user));
1105+ session = NIH_MUST (session_new (NULL, chroot));
1106
1107 if (! state_get_json_string_var_to_obj (json, session, conf_path))
1108 goto error;
1109
1110=== modified file 'init/session.h'
1111--- init/session.h 2012-11-22 16:32:36 +0000
1112+++ init/session.h 2013-01-30 16:15:24 +0000
1113@@ -33,14 +33,11 @@
1114 * Session:
1115 * @entry: list header,
1116 * @chroot: path all jobs are chrooted to,
1117- * @user: uid all jobs are switched to,
1118- * @conf_path: configuration path (either full path to chroot root, or
1119- * full path to users job directory (which may itself be prepended
1120- * with a chroot path)).
1121+ * @conf_path: configuration path (full path to chroot root).
1122 *
1123 * This structure is used to identify collections of jobs
1124- * that share either a common @chroot and/or common @user. Note that
1125- * @conf_path is unique across all sessions.
1126+ * that share a common @chroot (*). Note that @conf_path is
1127+ * unique across all sessions.
1128 *
1129 * Summary of Session values for different environments:
1130 *
1131@@ -50,27 +47,28 @@
1132 * | user | PID | chroot | uid | Object contents |
1133 * +------+------+--------+-----+-----------------------------------+
1134 * | 0 | >0 | no | 0 | NULL (*1) |
1135- * | >0 | "0" | no | >0 | uid + conf_path set to "~/.init". |
1136 * | 0 | >0 | yes | 0 | chroot + conf_path set |
1137- * | >0 | ?? | yes | >0 | XXX: fails (*2) |
1138+ * | >0 | ?? | yes | >0 | Not permitted (*2) |
1139 * +------+------+--------+-----+-----------------------------------+
1140 *
1141 * Notes:
1142 *
1143+ * (*) - this structure used to also store user session details (hence
1144+ * the name), but the functionality was removed with the advent of
1145+ * a true user mode.
1146+ *
1147 * (*1) - The "NULL session" represents the "traditional" environment
1148 * before sessions were introduced (namely a non-chroot environment
1149 * where all job and event operations were handled by uid 0 (root)).
1150 *
1151- * (*2) - error is:
1152+ * (*2) - User lookup is not reliable since the user to query exists
1153+ * within the chroot, but the only possible lookup is outside the
1154+ * chroot.
1155 *
1156- * initctl: Unable to connect to system bus: Failed to connect to socket
1157- * /var/run/dbus/system_bus_socket: No such file or directory
1158- *
1159 **/
1160 typedef struct session {
1161 NihList entry;
1162 char * chroot;
1163- uid_t user;
1164 char * conf_path;
1165 } Session;
1166
1167@@ -81,7 +79,7 @@
1168
1169 void session_init (void);
1170
1171-Session * session_new (const void *parent, const char *chroot, uid_t user)
1172+Session * session_new (const void *parent, const char *chroot)
1173 __attribute__ ((malloc, warn_unused_result));
1174
1175 Session * session_from_dbus (const void *parent, NihDBusMessage *message);
1176
1177=== modified file 'init/state.h'
1178--- init/state.h 2012-12-04 16:09:18 +0000
1179+++ init/state.h 2013-01-30 16:15:24 +0000
1180@@ -28,10 +28,10 @@
1181 * detect that is *has* changed filesystem context.
1182 *
1183 * - Since ConfSources are NOT serialised, it is currently not possible
1184- * to support user jobs and chroot jobs (because the only ConfSource
1185+ * to support chroot jobs (because the only ConfSource
1186 * objects created are those at startup (for '/etc/init/'): any
1187- * pre-existing ConfSources with non-NULL sessions representing
1188- * user jobs will be ignored).
1189+ * pre-existing ConfSources with non-NULL Session objects will
1190+ * be ignored).
1191 *
1192 * - parent/child timeout handling: we won't support down-grading initially.
1193 *
1194
1195=== modified file 'init/tests/test_conf.c'
1196--- init/tests/test_conf.c 2013-01-04 13:03:47 +0000
1197+++ init/tests/test_conf.c 2013-01-30 16:15:24 +0000
1198@@ -1404,6 +1404,8 @@
1199 fclose (f);
1200
1201 source = conf_source_new (NULL, dirname, CONF_JOB_DIR);
1202+ TEST_NE_P (source, NULL);
1203+
1204 ret = conf_source_reload (source);
1205
1206 TEST_EQ (ret, 0);
1207
1208=== modified file 'init/tests/test_state.c'
1209--- init/tests/test_state.c 2012-12-07 21:38:17 +0000
1210+++ init/tests/test_state.c 2013-01-30 16:15:24 +0000
1211@@ -217,9 +217,6 @@
1212 if (obj_string_check (a, b, chroot))
1213 goto fail;
1214
1215- if (obj_num_check (a, b, user))
1216- goto fail;
1217-
1218 if (obj_string_check (a, b, conf_path))
1219 goto fail;
1220
1221@@ -871,12 +868,12 @@
1222 TEST_NE_P (json, NULL);
1223
1224 /* Create a couple of sessions */
1225- session1 = session_new (NULL, "/abc", getuid ());
1226+ session1 = session_new (NULL, "/abc");
1227 TEST_NE_P (session1, NULL);
1228 session1->conf_path = NIH_MUST (nih_strdup (session1, "/def/ghi"));
1229 TEST_LIST_NOT_EMPTY (sessions);
1230
1231- session2 = session_new (NULL, "/foo", 0);
1232+ session2 = session_new (NULL, "/foo");
1233 TEST_NE_P (session2, NULL);
1234 session2->conf_path = NIH_MUST (nih_strdup (session2, "/bar/baz"));
1235
1236@@ -1264,7 +1261,7 @@
1237 TEST_LIST_EMPTY (conf_sources);
1238 TEST_HASH_EMPTY (job_classes);
1239
1240- session = session_new (NULL, "/my/session", getuid ());
1241+ session = session_new (NULL, "/my/session");
1242 TEST_NE_P (session, NULL);
1243 session->conf_path = NIH_MUST (nih_strdup (session, "/lives/here"));
1244 TEST_LIST_NOT_EMPTY (sessions);
1245@@ -1659,7 +1656,7 @@
1246 TEST_NE_P (env, NULL);
1247 TEST_NE_P (environ_add (&env, NULL, &len, TRUE, "FOO=BAR"), NULL);
1248
1249- session = session_new (NULL, "/abc", getuid ());
1250+ session = session_new (NULL, "/abc");
1251 TEST_NE_P (session, NULL);
1252 session->conf_path = NIH_MUST (nih_strdup (session, "/def/ghi"));
1253 TEST_LIST_NOT_EMPTY (sessions);
1254
1255=== modified file 'util/tests/test_initctl.c'
1256--- util/tests/test_initctl.c 2013-01-22 18:17:04 +0000
1257+++ util/tests/test_initctl.c 2013-01-30 16:15:24 +0000
1258@@ -12490,6 +12490,9 @@
1259 /* Ensure again that no log file written */
1260 TEST_LT (stat (logfile_name, &statbuf), 0);
1261
1262+ /* Must not be run as root */
1263+ TEST_TRUE (getuid ());
1264+
1265 cmd = nih_sprintf (NULL, "%s notify-disk-writeable 2>&1", INITCTL_BINARY);
1266 TEST_NE_P (cmd, NULL);
1267 RUN_COMMAND (NULL, cmd, &output, &lines);
1268
1269=== removed file 'util/tests/test_user_sessions.sh'
1270--- util/tests/test_user_sessions.sh 2012-01-26 08:59:08 +0000
1271+++ util/tests/test_user_sessions.sh 1970-01-01 00:00:00 +0000
1272@@ -1,1091 +0,0 @@
1273-#!/bin/sh
1274-#---------------------------------------------------------------------
1275-# Script to run minimal Upstart user session tests.
1276-#
1277-# Note that this script _cannot_ be run as part of the "make check"
1278-# tests since those tests stimulate functions and features of the
1279-# as-yet-uninstalled version of Upstart. However, this script needs to
1280-# run on a system where the version of Upstart under test has _already_
1281-# been fully installed.
1282-#---------------------------------------------------------------------
1283-#
1284-# Copyright (C) 2011 Canonical Ltd.
1285-#
1286-# Author: James Hunt <james.hunt@canonical.com>
1287-#
1288-# This program is free software: you can redistribute it and/or modify
1289-# it under the terms of the GNU General Public License as published by
1290-# the Free Software Foundation, version 3 of the License.
1291-#
1292-# This program is distributed in the hope that it will be useful,
1293-# but WITHOUT ANY WARRANTY; without even the implied warranty of
1294-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1295-# GNU General Public License for more details.
1296-#
1297-# You should have received a copy of the GNU General Public License
1298-# along with this program. If not, see <http://www.gnu.org/licenses/>.
1299-#
1300-#---------------------------------------------------------------------
1301-
1302-script_name=${0##*/}
1303-sys_job_dir="/etc/init"
1304-user_job_dir="$HOME/.init"
1305-user_log_dir="$HOME/.cache/upstart/log"
1306-sys_log_dir="/var/log/upstart"
1307-bug_url="https://bugs.launchpad.net/upstart/+filebug"
1308-test_dir=
1309-test_dir_suffix=
1310-user_to_create=
1311-uid=
1312-gid=
1313-opt=
1314-OPTARG=
1315-debug_enabled=0
1316-feature=
1317-
1318-# allow non-priv users to find 'initctl'
1319-export PATH=$PATH:/sbin
1320-
1321-# for assertions
1322-die()
1323-{
1324- msg="$*"
1325- echo "ERROR: $msg" >&2
1326- exit 1
1327-}
1328-
1329-debug()
1330-{
1331- str="$1"
1332- [ "$debug_enabled" = 1 ] && echo "DEBUG: $str"
1333-}
1334-
1335-get_job_pid()
1336-{
1337- job="$1"
1338- [ -z "$job" ] && die "need job"
1339-
1340- pid=$(initctl status "$job"|grep process|awk '{print $NF}')
1341- [ -z "$pid" ] && die "job $job has no pid"
1342-
1343- echo "$pid"
1344-}
1345-
1346-# take a string and convert it into a valid job name
1347-make_job_name()
1348-{
1349- str="$1"
1350-
1351- echo "$str" |\
1352- sed -e 's/>/ gt /g' -e 's/</ lt /g' -e 's/+/ and /g' |\
1353- sed -e 's/[[:punct:]]//g' -e 's/ */ /g' |\
1354- tr ' ' '-'
1355-}
1356-
1357-upstart_encode()
1358-{
1359- str="$1"
1360-
1361- echo "$str" | sed 's!/!_!g'
1362-}
1363-
1364-# take a string and convert it into a valid job log file name
1365-make_log_name()
1366-{
1367- str="$1"
1368- upstart_encode "$str"
1369-}
1370-
1371-TEST_FAILED()
1372-{
1373- args="$*"
1374-
1375- [ -z "$args" ] && die "need args"
1376-
1377- echo
1378- echo "ERROR: TEST FAILED ('$feature')"
1379- echo
1380- printf "BAD: ${args}\n"
1381- printf "\nPlease report a bug at $bug_url including the following details:\n"
1382- printf "\nUpstart:\n"
1383- /sbin/init --version|head -n1
1384- /sbin/initctl --version|head -n1
1385- echo
1386- printf "cmdline:\n"
1387- cat /proc/cmdline
1388- echo
1389- printf "Upstart Env:\n"
1390- set|grep UPSTART_
1391- echo
1392- printf "lsb:\n"
1393- lsb_release -a
1394- printf "\nuname:\n"
1395- uname -a
1396- echo
1397- sync
1398- echo "ERROR: TEST FAILED ('$feature')"
1399- echo
1400- exit 1
1401-}
1402-
1403-TEST_GROUP()
1404-{
1405- name="$1"
1406-
1407- [ -z "$name" ] && die "need name"
1408-
1409- printf "Testing %s\n" "$name"
1410-}
1411-
1412-TEST_FEATURE()
1413-{
1414- feature="$1"
1415-
1416- [ -z "$feature" ] && die "need feature"
1417-
1418- printf "...%s\n" "$feature"
1419-}
1420-
1421-TEST_NE()
1422-{
1423- cmd="$1"
1424- value="$2"
1425- expected="$3"
1426-
1427- # XXX: no checks on value or expected since they might be blank
1428- [ -z "$cmd" ] && die "need cmd"
1429-
1430- [ "$value" = "$expected" ] && TEST_FAILED \
1431- "wrong value for '$cmd', expected $expected got $value"
1432-}
1433-
1434-TEST_EQ()
1435-{
1436- cmd="$1"
1437- value="$2"
1438- expected="$3"
1439-
1440- # XXX: no checks on value or expected since they might be blank
1441- [ -z "$cmd" ] && die "need cmd"
1442-
1443- [ "$value" != "$expected" ] && TEST_FAILED \
1444- "wrong value for '$cmd', expected '$expected' got '$value'"
1445-}
1446-
1447-checks()
1448-{
1449- cmd=initctl
1450- [ -z "$(command -v $cmd)" ] && die "cannot find command $cmd"
1451-
1452- [ "$(id -u)" = 0 ] && die "ERROR: should not run this function as root"
1453-
1454- # This will fail for a non-root user unless D-Bus is correctly
1455- # configured
1456- $cmd emit foo || die \
1457- "You do not appear to have configured D-Bus for Upstart user sessions. See usage."
1458-}
1459-
1460-setup()
1461-{
1462- uid=$(id -u)
1463- gid=$(id -g)
1464-
1465- if [ "$uid" = 0 ]
1466- then
1467- [ -z "$user_to_create" ] && die "need '-u' option when running as root"
1468-
1469- getent passwd "$user_to_create" && \
1470- die "user '$user_to_create' already exists"
1471-
1472- echo "Creating user '$user_to_create'"
1473- cmd="useradd -mU -c 'Upstart Test User' $user_to_create"
1474- eval "$cmd"
1475- TEST_EQ "$cmd" $? 0
1476-
1477- echo "Locking account for user '$user_to_create'"
1478- cmd="usermod -L $user_to_create"
1479- eval "$cmd"
1480- TEST_EQ "$cmd" $? 0
1481-
1482- # Run ourselves again as the new user
1483- su -c "$0 -a" "$user_to_create"
1484- test_run_rc=$?
1485-
1486- if [ $test_run_rc -eq 0 ]
1487- then
1488- echo "Deleting user '$user_to_create'"
1489- cmd="userdel -r \"$user_to_create\""
1490- eval "$cmd"
1491- TEST_EQ "$cmd" $? 0
1492- fi
1493-
1494- exit $test_run_rc
1495- fi
1496-
1497- checks
1498-
1499- # setup
1500- if [ ! -d "$user_job_dir" ]
1501- then
1502- cmd="mkdir -p \"$user_job_dir\""
1503- eval $cmd
1504- TEST_EQ "$cmd" $? 0
1505-
1506- cmd="chmod 755 \"$user_job_dir\""
1507- eval "$cmd"
1508- TEST_EQ "$cmd" $? 0
1509- fi
1510-
1511- # create somewhere to store user jobs
1512- cmd="mktemp -d --tmpdir=\"$user_job_dir\""
1513- test_dir=$(eval "$cmd")
1514- TEST_EQ "$cmd" $? 0
1515- TEST_NE "$test_dir" "$test_dir" ""
1516- test_dir_suffix=${test_dir#${user_job_dir}/}
1517-
1518- # ensure files in this directory are accessible since
1519- # mktemp sets directory perms to 0700 regardless of umask.
1520- cmd="chmod 755 \"$test_dir\""
1521- eval "$cmd"
1522- TEST_EQ "$cmd" $? 0
1523-
1524- TEST_NE "HOME" "$HOME" ""
1525-}
1526-
1527-cleanup()
1528-{
1529- if [ -d "$test_dir" ]
1530- then
1531- echo "Removing test directory '$test_dir'"
1532- cmd="rmdir \"$test_dir\""
1533- eval "$cmd"
1534- TEST_EQ "$cmd" $? 0
1535- fi
1536-}
1537-
1538-ensure_job_known()
1539-{
1540- job="$1"
1541- job_name="$2"
1542-
1543- [ -z "$job" ] && die "no job"
1544- [ -z "$job_name" ] && die "no job name"
1545-
1546- TEST_FEATURE "ensure 'initctl' recognises job"
1547- initctl list|grep -q "^$job " || \
1548- TEST_FAILED "job $job_name not known to initctl"
1549-
1550- TEST_FEATURE "ensure 'status' recognises job"
1551- cmd="status ${job}"
1552- eval "$cmd" >/dev/null 2>&1
1553- rc=$?
1554- TEST_EQ "$cmd" $rc 0
1555-}
1556-
1557-# Note that if the specified job is *not* as task, it is expected to run
1558-# indefinately. This allows us to perform PID checks, etc.
1559-run_user_job_tests()
1560-{
1561- job_name="$1"
1562- job_file="$2"
1563- task="$3"
1564- env="$4"
1565-
1566- # XXX: env can be empty
1567- [ -z "$job_name" ] && die "no job name"
1568- [ -z "$job_file" ] && die "no job file"
1569- [ -z "$task" ] && die "no task value"
1570-
1571- job="${test_dir_suffix}/${job_name}"
1572-
1573- [ -f "$job_file" ] || TEST_FAILED "job file '$job_file' does not exist"
1574-
1575- ensure_job_known "$job" "$job_name"
1576-
1577- TEST_FEATURE "ensure job can be started"
1578- cmd="start ${job} ${env}"
1579- output=$(eval "$cmd")
1580- rc=$?
1581- TEST_EQ "$cmd" $rc 0
1582-
1583- if [ "$task" = no ]
1584- then
1585- TEST_FEATURE "ensure 'start' shows job pid"
1586- pid=$(echo "$output"|awk '{print $4}')
1587- TEST_NE "pid" "$pid" ""
1588-
1589- TEST_FEATURE "ensure 'initctl' shows job is running with pid"
1590- initctl list|grep -q "^$job start/running, process $pid" || \
1591- TEST_FAILED "job $job_name did not start"
1592-
1593- TEST_FEATURE "ensure 'status' shows job is running with pid"
1594- cmd="status ${job}"
1595- output=$(eval "$cmd")
1596- echo "$output"|while read job_tmp state ignored status_pid
1597- do
1598- state=$(echo $state|tr -d ',')
1599- TEST_EQ "job name" "$job_tmp" "$job"
1600- TEST_EQ "job state" "$state" "start/running"
1601- TEST_EQ "job pid" "$status_pid" "$pid"
1602- done
1603-
1604- TEST_FEATURE "ensure job pid is running with correct uids"
1605- pid_uids=$(ps --no-headers -p $pid -o euid,ruid)
1606- for pid_uid in $pid_uids
1607- do
1608- TEST_EQ "pid uid" "$pid_uid" "$uid"
1609- done
1610-
1611- TEST_FEATURE "ensure job pid is running with correct gids"
1612- pid_gids=$(ps --no-headers -p $pid -o egid,rgid)
1613- for pid_gid in $pid_gids
1614- do
1615- TEST_EQ "pid gid" "$pid_gid" "$gid"
1616- done
1617-
1618- TEST_FEATURE "ensure process is running in correct directory"
1619- cwd=$(readlink /proc/$pid/cwd)
1620- TEST_EQ "cwd" "$cwd" "$HOME"
1621-
1622- TEST_FEATURE "ensure job can be stopped"
1623- cmd="stop ${job}"
1624- output=$(eval "$cmd")
1625- rc=$?
1626- TEST_EQ "$cmd" $rc 0
1627-
1628- TEST_FEATURE "ensure job pid no longer exists"
1629- pid_ids=$(ps --no-headers -p $pid -o euid,ruid,egid,rgid)
1630- TEST_EQ "pid uids+gids" "$pid_ids" ""
1631- fi
1632-
1633- remove_job_file "$job_file"
1634- ensure_job_gone "$job" "$job_name" "$env"
1635-}
1636-
1637-remove_job_file()
1638-{
1639- job_file="$1"
1640-
1641- [ -z "$job_file" ] && die "no job file"
1642- [ ! -f "$job_file" ] && TEST_FAILED "job file '$job_file' does not exist"
1643-
1644- cmd="rm $job_file"
1645- eval "$cmd"
1646- TEST_EQ "$cmd" $? 0
1647-}
1648-
1649-ensure_job_gone()
1650-{
1651- job="$1"
1652- job_name="$2"
1653- env="$3"
1654-
1655- # XXX: no check on env since it can be empty
1656- [ -z "$job" ] && die "no job"
1657- [ -z "$job_name" ] && die "no job name"
1658-
1659- TEST_FEATURE "ensure 'initctl' no longer recognises job"
1660- initctl list|grep -q "^$job " && \
1661- TEST_FAILED "deleted job $job_name still known to initctl"
1662-
1663- TEST_FEATURE "ensure 'status' no longer recognises job"
1664- cmd="status ${job}"
1665- eval "$cmd" >/dev/null 2>&1
1666- rc=$?
1667- TEST_NE "$cmd" $rc 0
1668-}
1669-
1670-test_user_job()
1671-{
1672- test_group="$1"
1673- job_name="$2"
1674- script="$3"
1675- task="$4"
1676- env="$5"
1677-
1678- # XXX: no test on script or env since they might be empty
1679- [ -z "$test_group" ] && die "no test group"
1680- [ -z "$job_name" ] && die "no job name"
1681- [ -z "$task" ] && die "no task"
1682-
1683- TEST_GROUP "$test_group"
1684-
1685- job_file="${test_dir}/${job_name}.conf"
1686-
1687- echo "$script" > $job_file
1688-
1689- run_user_job_tests "$job_name" "$job_file" "$task" "$env"
1690-}
1691-
1692-test_user_job_binary()
1693-{
1694- group="user job running a binary"
1695- job_name="binary_test"
1696- script="exec sleep 999"
1697- test_user_job "$group" "$job_name" "$script" no ""
1698-}
1699-
1700-test_user_job_binary_task()
1701-{
1702- group="user job running a binary task"
1703- job_name="binary_task_test"
1704- OUTFILE=$(mktemp)
1705-
1706- script="\
1707-task
1708-exec /bin/true > $OUTFILE"
1709-
1710- test_user_job "$group" "$job_name" "$script" yes "OUTFILE=$OUTFILE"
1711- rm -f $OUTFILE
1712-}
1713-
1714-test_user_job_single_line_script()
1715-{
1716- group="user job running a single-line script"
1717- job_name="single_line_script_test"
1718- script="\
1719-script
1720- sleep 999
1721-end script"
1722- test_user_job "$group" "$job_name" "$script" no ""
1723-}
1724-
1725-test_user_job_single_line_script_task()
1726-{
1727- group="user job running a single-line script task"
1728- job_name="single_line_script_task_test"
1729- OUTFILE=$(mktemp)
1730-
1731- script="\
1732-task
1733-script
1734- exec /bin/true > $OUTFILE
1735-end script"
1736- test_user_job "$group" "$job_name" "$script" yes "OUTFILE=$OUTFILE"
1737- rm -f $OUTFILE
1738-}
1739-
1740-test_user_job_multi_line_script()
1741-{
1742- group="user job running a multi-line script"
1743- job_name="multi_line_script_test"
1744- script="\
1745-script
1746-
1747- /bin/true
1748- /bin/true;/bin/true
1749- sleep 999
1750-
1751-end script"
1752- test_user_job "$group" "$job_name" "$script" no ""
1753-}
1754-
1755-test_user_job_multi_line_script_task()
1756-{
1757- group="user job running a multi-line script task"
1758- job_name="multi_line_script_task_test"
1759- OUTFILE=$(mktemp)
1760-
1761- script="\
1762-task
1763-script
1764-
1765- /bin/true
1766- /bin/true
1767- /bin/true
1768-
1769-end script"
1770- test_user_job "$group" "$job_name" "$script" yes "OUTFILE=$OUTFILE"
1771- rm -f $OUTFILE
1772-}
1773-
1774-test_user_emit_events()
1775-{
1776- job_name="start_on_foo"
1777-
1778- TEST_GROUP "user emitting an event"
1779- initctl emit foo || TEST_FAILED "failed to emit event as user"
1780-
1781- TEST_GROUP "user emitting an event to start a job"
1782- script="\
1783- start on foo BAR=2
1784- stop on baz cow=moo or hello
1785- exec sleep 999"
1786-
1787- job_file="${test_dir}/${job_name}.conf"
1788- job="${test_dir_suffix}/${job_name}"
1789-
1790- echo "$script" > $job_file
1791-
1792- ensure_job_known "$job" "$job_name"
1793-
1794- initctl list|grep -q "^$job stop/waiting" || \
1795- TEST_FAILED "job $job_name not stopped"
1796-
1797- TEST_FEATURE "ensure job can be started with event"
1798- initctl emit foo BAR=2 || \
1799- TEST_FAILED "failed to emit event for user job"
1800-
1801- initctl status "$job"|grep -q "^$job start/running" || \
1802- TEST_FAILED "job $job_name failed to start"
1803-
1804- TEST_FEATURE "ensure job can be stopped with event"
1805- initctl emit baz cow=moo || \
1806- TEST_FAILED "failed to emit event for user job"
1807-
1808- initctl list|grep -q "^$job stop/waiting" || \
1809- TEST_FAILED "job $job_name not stopped"
1810-
1811- rm -f "$job_file"
1812-}
1813-
1814-test_user_job_setuid_setgid()
1815-{
1816- group="user job with setuid and setgid me"
1817- job_name="setuid_setgid_me_test"
1818- script="\
1819-setuid $(id -un)
1820-setgid $(id -gn)
1821-exec sleep 999"
1822- test_user_job "$group" "$job_name" "$script" no ""
1823-
1824- TEST_GROUP "user job with setuid and setgid root"
1825- script="\
1826-setuid root
1827-setgid root
1828-exec sleep 999"
1829-
1830- job_name="setuid_setgid_root_test"
1831- job_file="${test_dir}/${job_name}.conf"
1832- job="${test_dir_suffix}/${job_name}"
1833-
1834- echo "$script" > $job_file
1835-
1836- ensure_job_known "$job" "$job_name"
1837-
1838- TEST_FEATURE "ensure job fails to start as root"
1839- cmd="start ${job}"
1840- output=$(eval "$cmd" 2>&1)
1841- rc=$?
1842- TEST_EQ "$cmd" $rc 1
1843-
1844- TEST_FEATURE "ensure 'start' indicates job failure"
1845- error=$(echo "$output"|grep failed)
1846- TEST_NE "error" "$error" ""
1847-
1848- TEST_FEATURE "ensure 'initctl' does not list job"
1849- initctl list|grep -q "^$job stop/waiting" || \
1850- TEST_FAILED "job $job_name not listed as stopped"
1851-
1852- delete_job "$job_name"
1853-}
1854-
1855-get_job_file()
1856-{
1857- job_name="$1"
1858-
1859- [ -z "$job_name" ] && die "no job name"
1860- echo "${test_dir}/${job_name}.conf"
1861-}
1862-
1863-ensure_no_output()
1864-{
1865- job_name="$1"
1866- script="$2"
1867- instance="$3"
1868-
1869- job="${test_dir_suffix}/${job_name}"
1870-
1871- create_job "$job_name" "$script"
1872- start_job "$job" "$job_name" "$instance"
1873-
1874- check_job_output "$job_name"
1875- delete_job "$job_name"
1876-}
1877-
1878-create_job()
1879-{
1880- job_name="$1"
1881- script="$2"
1882-
1883- # XXX: script could be empty
1884- [ -z "$job_name" ] && die "no job name"
1885-
1886- debug "create_job: job_name='$job_name'"
1887- debug "create_job: script='$script'"
1888-
1889- # Not currently possible to have a user job with the
1890- # same name as a system job.
1891- #
1892- # XXX: Note that this test assumes that user has *not* specified
1893- # XXX: an alternate configuration directory using the
1894- # XXX: '--confdir' option.
1895- [ -e "${sys_job_dir}/${job_name}.conf" ] && \
1896- die "job '$job_name' already exists as a system job"
1897-
1898- job_file="${test_dir}/${job_name}.conf"
1899- job="${test_dir_suffix}/${job_name}"
1900-
1901- echo "$script" > "$job_file"
1902- sync
1903-}
1904-
1905-delete_job()
1906-{
1907- job_name="$1"
1908-
1909- [ -z "$job_name" ] && die "no job name"
1910-
1911- job_file="$(get_job_file $job_name)"
1912-
1913- rm "$job_file" || TEST_FAILED "unable to remove job file '$job_file'"
1914-}
1915-
1916-check_job_output()
1917-{
1918- job_name="$1"
1919-
1920- [ ! -z "$(ls $user_log_dir 2>/dev/null)" ] && \
1921- TEST_FAILED "job $job_name created logfile unexpectedly in '$user_log_dir'"
1922-
1923- # XXX: note that it might appear that checking in $sys_log_dir
1924- # could result in false positives, but this isn't so since
1925- # (currently) it is not possible for a user job to have the
1926- # same name as a system job. start_job() will detect this
1927- # scenario.
1928- for dir in "$user_log_dir" "$sys_log_dir"
1929- do
1930- log_file="${dir}/${job_name}.log"
1931- [ -f "$log_file" ] && \
1932- TEST_FAILED "job $job_name created logfile unexpectedly as '$log_file'"
1933- done
1934-}
1935-
1936-start_job()
1937-{
1938- job="$1"
1939- job_file="$2"
1940- instance="$3"
1941- allow_failure="$4"
1942-
1943- # XXX: instance may be blank
1944- [ -z "$job" ] && die "no job"
1945- [ -z "$job_file" ] && die "no job file"
1946-
1947- debug "start_job: job='$job'"
1948- debug "start_job: job_file='$job_file'"
1949- debug "start_job: instance='$instance'"
1950- debug "start_job: allow_failure='$allow_failure'"
1951-
1952- eval output=$(mktemp)
1953-
1954- # XXX: Don't quote instance as we don't want to pass a null instance to
1955- # start(8).
1956- cmd="start \"$job\" $instance >${output} 2>&1"
1957- debug "start_job: running '$cmd'"
1958- eval "$cmd"
1959- rc=$?
1960-
1961- if [ $rc -ne 0 -a -z "$allow_failure" ]
1962- then
1963- TEST_FAILED "job $job_file not started: $(cat $output)"
1964- fi
1965-
1966- rm -f "$output"
1967-}
1968-
1969-get_job_logfile_name()
1970-{
1971- job_name="$1"
1972- instance_value="$2"
1973-
1974- # XXX: instance may be null
1975- [ -z "$job_name" ] && die "no job name"
1976-
1977- encoded_test_dir_suffix=$(upstart_encode "${test_dir_suffix}/")
1978- file_name="${encoded_test_dir_suffix}$(make_log_name $job_name)"
1979-
1980- if [ ! -z "$instance_value" ]
1981- then
1982- log_file="${user_log_dir}/${file_name}-${instance_value}.log"
1983- else
1984- log_file="${user_log_dir}/${file_name}.log"
1985- fi
1986-
1987- echo "$log_file"
1988-}
1989-
1990-run_job()
1991-{
1992- job="$1"
1993- job_name="$2"
1994- script="$3"
1995- instance="$4"
1996-
1997- # XXX: script, instance might be blank
1998- [ -z "$job" ] && die "no job"
1999- [ -z "$job_name" ] && die "no job name"
2000-
2001- debug "run_job: job='$job'"
2002- debug "run_job: job_name='$job_name'"
2003- debug "run_job: script='$script'"
2004- debug "run_job: instance='$instance'"
2005-
2006- create_job "$job_name" "$script"
2007- start_job "$job" "$job_name" "$instance"
2008-}
2009-
2010-ensure_file_meta()
2011-{
2012- file="$1"
2013- expected_owner="$2"
2014- expected_group="$3"
2015- expected_perms="$4"
2016-
2017- [ -z "$file" ] && die "no file"
2018- [ -z "$expected_owner" ] && die "no expected owner"
2019- [ -z "$expected_group" ] && die "no expected group"
2020- [ -z "$expected_perms" ] && die "no expected perms"
2021-
2022- [ ! -f "$file" ] && die "file $file does not exist"
2023-
2024- expected_perms="640"
2025- umask_value=$(umask)
2026- umask_expected=0022
2027-
2028- if [ "$umask_value" != "$umask_expected" ]
2029- then
2030- msg="umask value is $umask_value -"
2031- msg="${msg} changing it to $umask_expected."
2032- echo "WARNING: $msg"
2033- umask "$umask_expected" || TEST_FAILED "unable to change umask"
2034- fi
2035-
2036- owner=$(ls -l "$file"|awk '{print $3}')
2037- group=$(ls -l "$file"|awk '{print $4}')
2038- perms=$(stat --printf "%a\n" "$file")
2039-
2040- [ "$owner" = "$expected_owner" ] || TEST_FAILED \
2041- "file $file has wrong owner (expected $expected_owner, got $owner)"
2042-
2043- [ "$group" = "$expected_group" ] || TEST_FAILED \
2044- "file $file has wrong group (expected $expected_group, got $group)"
2045-
2046- [ "$perms" = "$expected_perms" ] || TEST_FAILED \
2047- "file $file has wrong group (expected $expected_perms, got $perms)"
2048-}
2049-
2050-
2051-ensure_output()
2052-{
2053- job_name="$1"
2054- script="$2"
2055- expected_output="$3"
2056- instance="$4"
2057- instance_value="$5"
2058- options="$6"
2059-
2060- # XXX: remaining args could be null
2061- [ -z "$job_name" ] && die "no job name"
2062-
2063- debug "ensure_output: job_name='$job_name'"
2064- debug "ensure_output: script='$script'"
2065- debug "ensure_output: expected_ouput='$expected_ouput'"
2066- debug "ensure_output: instance='$instance'"
2067- debug "ensure_output: instance_value='$instance_value'"
2068- debug "ensure_output: options='$options'"
2069-
2070- regex=n
2071- retain=n
2072- unique=""
2073- use_od=n
2074-
2075- for opt in $options
2076- do
2077- case "$opt" in
2078- regex)
2079- regex=y
2080- ;;
2081- retain)
2082- retain=y
2083- ;;
2084- unique)
2085- unique='|sort -u'
2086- ;;
2087- use_od)
2088- use_od=y
2089- ;;
2090- esac
2091- done
2092-
2093- debug "ensure_output: regex='$regex'"
2094- debug "ensure_output: retain='$retain'"
2095- debug "ensure_output: unique='$unique'"
2096- debug "ensure_output: use_od='$use_od'"
2097-
2098- expected_owner=$(id -un)
2099- expected_group=$(id -gn)
2100- expected_perms="640"
2101-
2102- job="${test_dir_suffix}/${job_name}"
2103-
2104- run_job "$job" "$job_name" "$script" "$instance"
2105-
2106- debug "ensure_output: user_log_dir='$user_log_dir'"
2107- debug "ensure_output: test_dir='$test_dir'"
2108- debug "ensure_output: test_dir_suffix='$test_dir_suffix'"
2109-
2110- log_file=$(get_job_logfile_name "$job_name" "$instance_value")
2111-
2112- debug "ensure_output: log_file='$log_file'"
2113-
2114- # Give Upstart a chance to parse the file
2115- count=1
2116- while ! status "$job" >/dev/null 2>&1
2117- do
2118- sleep 1
2119- count=$((count+1))
2120- [ "$count" -eq 5 ] && break
2121- done
2122-
2123- # give job a chance to start
2124- count=1
2125- while [ ! -f "$log_file" ]
2126- do
2127- sleep 1
2128- count=$((count+1))
2129- [ "$count" -eq 5 ] && break
2130- done
2131-
2132- [ ! -f "$log_file" ] && \
2133- TEST_FAILED "job '$job_name' failed to create logfile"
2134-
2135- ensure_file_meta \
2136- "$log_file" \
2137- "$expected_owner" \
2138- "$expected_group" \
2139- "$expected_perms"
2140-
2141- # XXX: note we have to remove carriage returns added by the line
2142- # discipline
2143- if [ "$regex" = y ]
2144- then
2145- log=$(eval "cat $log_file|tr -d '\r' $unique")
2146- msg="job '$job_name' failed to log correct data\n"
2147- msg="${msg}\texpected regex: '$expected_output'\n"
2148- msg="${msg}\tgot : '$log'"
2149- cat "$log_file" | egrep "$expected_output" || TEST_FAILED "$msg"
2150- elif [ "$use_od" = y ]
2151- then
2152- log=$(eval "cat $log_file|tr -d '\r' $unique|od -x")
2153- msg="job '$job_name' failed to log correct data\n"
2154- msg="${msg}\texpected hex: '$expected_output'\n"
2155- msg="${msg}\tgot : '$log'"
2156- [ "$expected_output" != "$log" ] && TEST_FAILED "$msg"
2157- else
2158- log=$(eval "cat $log_file|tr -d '\r' $unique")
2159- msg="job '$job_name' failed to log correct data\n"
2160- msg="${msg}\texpected text: '$expected_output'\n"
2161- msg="${msg}\tgot : '$log'"
2162- [ "$expected_output" != "$log" ] && TEST_FAILED "$msg"
2163- fi
2164-
2165- if [ "$retain" = n ]
2166- then
2167- delete_job "$job_name"
2168- rm "$log_file" || TEST_FAILED "unable to remove log file '$log_file'"
2169- fi
2170-}
2171-
2172-test_ensure_no_unexpected_output()
2173-{
2174- #---------------------------------------------------------------------
2175- feature="ensure command job does not create log file with no console"
2176- TEST_FEATURE "$feature"
2177-
2178- job_name=$(make_job_name "$feature")
2179-
2180- script="\
2181- console none
2182- exec echo hello world"
2183-
2184- ensure_no_output "$job_name" "$script" ""
2185-
2186- #---------------------------------------------------------------------
2187- feature="ensure 1-line script job does not create log file with no console"
2188- TEST_FEATURE "$feature"
2189-
2190- job_name=$(make_job_name "$feature")
2191-
2192- script="\
2193- console none
2194- script
2195- echo hello world
2196- end script
2197- "
2198-
2199- ensure_no_output "$job_name" "$script" ""
2200-
2201- #---------------------------------------------------------------------
2202- feature="ensure multi-line script job does not create log file with no console"
2203- TEST_FEATURE "$feature"
2204-
2205- job_name=$(make_job_name "$feature")
2206-
2207- script="\
2208- console none
2209- script
2210- /bin/true
2211- echo hello world
2212- end script
2213- "
2214-
2215- ensure_no_output "$job_name" "$script" ""
2216-
2217- #---------------------------------------------------------------------
2218- feature="ensure no output if log directory does not exist"
2219- TEST_FEATURE "$feature"
2220-
2221- rmdir "${user_log_dir}" || \
2222- TEST_FAILED "unable to delete log directory '$user_log_dir'"
2223-
2224- job_name=$(make_job_name "$feature")
2225- string="hello world"
2226- script="\
2227- console log
2228- script
2229- /bin/true
2230- /bin/echo hello world
2231- end script
2232- "
2233-
2234- ensure_no_output "$job_name" "$script" ""
2235-
2236- mkdir "${user_log_dir}" || \
2237- TEST_FAILED "unable to recreate log directory '$user_log_dir'"
2238-
2239- #---------------------------------------------------------------------
2240- feature="ensure command job does not create log file with invalid command"
2241- TEST_FEATURE "$feature"
2242-
2243- job_name=$(make_job_name "$feature")
2244-
2245- script="\
2246- console log
2247- exec /this/command/does/not/exist"
2248-
2249- job="${test_dir_suffix}/${job_name}"
2250- create_job "$job_name" "$script"
2251- start_job "$job" "$job_name" "" 1
2252- check_job_output "$job_name"
2253- delete_job "$job_name"
2254-}
2255-
2256-test_output_logged()
2257-{
2258- # XXX: upstart won't create this
2259- mkdir -p "$user_log_dir"
2260-
2261- test_ensure_no_unexpected_output
2262-}
2263-
2264-test_user_jobs()
2265-{
2266- test_user_job_binary
2267- test_user_job_single_line_script
2268- test_user_job_multi_line_script
2269-
2270- test_user_job_binary_task
2271- test_user_job_single_line_script_task
2272- test_user_job_multi_line_script_task
2273-
2274- test_user_job_setuid_setgid
2275-
2276- test_user_emit_events
2277-
2278- test_output_logged
2279-}
2280-
2281-tests()
2282-{
2283- echo
2284- echo -n "Running Upstart user session tests as user '`whoami`'"
2285- echo " (uid $uid, gid $gid) in directory '$test_dir'"
2286- echo
2287-
2288- test_user_jobs
2289-
2290- echo
2291- echo "All tests completed successfully"
2292- echo
2293-}
2294-
2295-usage()
2296-{
2297-cat <<EOT
2298-USAGE: $script_name [options]
2299-
2300-OPTIONS:
2301-
2302- -a : Actually run this script.
2303- -h : Show this help.
2304- -u <user> : Specify name of test user to create.
2305-
2306-DESCRIPTION:
2307-
2308-Run simple set of Upstart user session tests.
2309-
2310-PREREQUISITE:
2311-
2312-For this test to run, non-root users must be allowed to invoke all D-Bus
2313-methods on Upstart via configuration file:
2314-
2315- /etc/dbus-1/system.d/Upstart.conf
2316-
2317-See dbus-daemon(1) for further details.
2318-
2319-WARNING: Note that this script is unavoidably invasive, so read what
2320-WARNING: follows before running!
2321-
2322-If run as a non-root user, this script will create a uniquely-named
2323-subdirectory below "\$HOME/.init/" to run its tests in. On successful
2324-completion of these tests, the unique subdirectory and its contents will
2325-be removed.
2326-
2327-If however, this script is invoked as the root user, the script will
2328-refuse to run until given the name of a test user to create via the "-u"
2329-option. If the user specified to this option already exists, this script
2330-will exit with an error. If the user does not already exist, it will be
2331-created, the script then run *as that user* and assuming successful
2332-completion of the tests, the test user and their home directory will
2333-then be deleted.
2334-
2335-EOT
2336-}
2337-
2338-#---------------------------------------------------------------------
2339-# main
2340-#---------------------------------------------------------------------
2341-
2342-while getopts "dhu:" opt
2343-do
2344- case "$opt" in
2345- d)
2346- debug_enabled=1
2347- ;;
2348-
2349- h)
2350- usage
2351- exit 0
2352- ;;
2353-
2354- u)
2355- user_to_create="$OPTARG"
2356- ;;
2357- esac
2358-done
2359-
2360-setup
2361-tests
2362-cleanup
2363-exit 0

Subscribers

People subscribed via source and target branches