Merge lp:~csurbhi/upstart/upstart-add-pivot-handling into lp:upstart

Proposed by Surbhi Palande on 2011-06-15
Status: Work in progress
Proposed branch: lp:~csurbhi/upstart/upstart-add-pivot-handling
Merge into: lp:upstart
Diff against target: 1254 lines (+972/-15)
12 files modified
dbus/com.ubuntu.Upstart.xml (+7/-1)
init/Makefile.am (+12/-11)
init/conf.c (+35/-0)
init/conf.h (+1/-0)
init/control.c (+35/-0)
init/control.h (+3/-0)
init/events.h (+8/-1)
init/paths.h (+8/-0)
init/pivot.c (+616/-0)
init/pivot.h (+66/-0)
init/tests/test_control.c (+106/-1)
util/initctl.c (+75/-1)
To merge this branch: bzr merge lp:~csurbhi/upstart/upstart-add-pivot-handling
Reviewer Review Type Date Requested Status
Scott James Remnant 2011-06-15 Pending
Review via email: mp+64723@code.launchpad.net

Description of the change

The pivot command is used for changing the root filesystem in the initramfs from the memory based "/" to the disk based real root filesystem. This command can be issued as follows:

initctl pivot <ROOTFS> <INIT>

where ROOTFS is the root filesystem that we want to move to while in the initramfs. INIT is the first program that we wish to execute once this move to the real root filesystem is made.

This command is intended to be used when upstart is executed in initramfs for making the initramfs event driven.

It is assumed that a user can specify a different ROOTFS, INIT or arguments to this new INIT at the grub command prompt. The console used for logging the messages is /dev/console and is a not a boot argument which can be changed.

This command has no effect when it is executed from a non memory based root filesystem.

This change shall enable upstart to be used as init in the initramfs for making the initramfs event driven. Please do let me know your thoughts on this change.

Thanks!

To post a comment you must log in.
1316. By Surbhi Palande on 2011-06-27

Initial support to handle a new initctl command: pivot.
Usage: initctl pivot <ROOTFS> <INIT>" "<args>"

This command can be used to change the root filesystem from a initramfs
based rootfs to the new requested <ROOTFS>. On successfully changing the root
filesystem the new <INIT> shall be executed. On failure in handling this
command a pivot-failed event is emitted. This command works as intended
only when fired from initramfs. When fired from any other filesystem, this
command has no effect other than the emission of the "pivot-failed" event.

1317. By Surbhi Palande on 2011-07-15

Some cleanup related fixes. Expecting one or two more!

1318. By Surbhi Palande on 2011-07-15

One more typo fix

Unmerged revisions

1318. By Surbhi Palande on 2011-07-15

One more typo fix

1317. By Surbhi Palande on 2011-07-15

Some cleanup related fixes. Expecting one or two more!

1316. By Surbhi Palande on 2011-06-27

Initial support to handle a new initctl command: pivot.
Usage: initctl pivot <ROOTFS> <INIT>" "<args>"

This command can be used to change the root filesystem from a initramfs
based rootfs to the new requested <ROOTFS>. On successfully changing the root
filesystem the new <INIT> shall be executed. On failure in handling this
command a pivot-failed event is emitted. This command works as intended
only when fired from initramfs. When fired from any other filesystem, this
command has no effect other than the emission of the "pivot-failed" event.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'dbus/com.ubuntu.Upstart.xml'
2--- dbus/com.ubuntu.Upstart.xml 2010-12-10 07:18:34 +0000
3+++ dbus/com.ubuntu.Upstart.xml 2011-07-15 17:51:29 +0000
4@@ -3,7 +3,7 @@
5
6 com.ubuntu.Upstart.xml - interface definition for manager object
7
8- Copyright © 2009 Canonical Ltd.
9+ Copyright © 2009-2011 Canonical Ltd.
10 Author: Scott James Remnant <scott@netsplit.com>.
11
12 This file is free software; Canonical Ltd gives unlimited permission
13@@ -57,6 +57,12 @@
14 <arg name="file" type="h" direction="in" />
15 </method>
16
17+ <!-- Pivot to new rootfs and execute new init -->
18+ <method name="PivotToNewRootfs">
19+ <arg name="rootfs" type="s" direction="in" />
20+ <arg name="init" type="s" direction="in" />
21+ </method>
22+
23 <!-- Basic information about Upstart -->
24 <property name="version" type="s" access="read" />
25 <property name="log_priority" type="s" access="readwrite" />
26
27=== modified file 'init/Makefile.am'
28--- init/Makefile.am 2011-05-15 12:53:17 +0000
29+++ init/Makefile.am 2011-07-15 17:51:29 +0000
30@@ -51,6 +51,7 @@
31 parse_conf.c parse_conf.h \
32 conf.c conf.h \
33 control.c control.h \
34+ pivot.c pivot.h \
35 errors.h
36 nodist_init_SOURCES = \
37 $(com_ubuntu_Upstart_OUTPUTS) \
38@@ -159,7 +160,7 @@
39 system.o environ.o process.o \
40 job_class.o job_process.o job.o event.o event_operator.o blocked.o \
41 parse_job.o parse_conf.o conf.o control.o \
42- session.o \
43+ session.o pivot.o \
44 com.ubuntu.Upstart.o \
45 com.ubuntu.Upstart.Job.o com.ubuntu.Upstart.Instance.o \
46 $(NIH_LIBS) \
47@@ -171,7 +172,7 @@
48 system.o environ.o process.o \
49 job_class.o job_process.o job.o event.o event_operator.o blocked.o \
50 parse_job.o parse_conf.o conf.o control.o \
51- session.o \
52+ session.o pivot.o \
53 com.ubuntu.Upstart.o \
54 com.ubuntu.Upstart.Job.o com.ubuntu.Upstart.Instance.o \
55 $(NIH_LIBS) \
56@@ -183,7 +184,7 @@
57 system.o environ.o process.o \
58 job_class.o job_process.o job.o event.o event_operator.o blocked.o \
59 parse_job.o parse_conf.o conf.o control.o \
60- session.o \
61+ session.o pivot.o \
62 com.ubuntu.Upstart.o \
63 com.ubuntu.Upstart.Job.o com.ubuntu.Upstart.Instance.o \
64 $(NIH_LIBS) \
65@@ -195,7 +196,7 @@
66 system.o environ.o process.o \
67 job_class.o job_process.o job.o event.o event_operator.o blocked.o \
68 parse_job.o parse_conf.o conf.o control.o \
69- session.o \
70+ session.o pivot.o \
71 com.ubuntu.Upstart.o \
72 com.ubuntu.Upstart.Job.o com.ubuntu.Upstart.Instance.o \
73 $(NIH_LIBS) \
74@@ -207,7 +208,7 @@
75 system.o environ.o process.o \
76 job_class.o job_process.o job.o event.o event_operator.o blocked.o \
77 parse_job.o parse_conf.o conf.o control.o \
78- session.o \
79+ session.o pivot.o \
80 com.ubuntu.Upstart.o \
81 com.ubuntu.Upstart.Job.o com.ubuntu.Upstart.Instance.o \
82 $(NIH_LIBS) \
83@@ -219,7 +220,7 @@
84 system.o environ.o process.o \
85 job_class.o job_process.o job.o event.o event_operator.o blocked.o \
86 parse_job.o parse_conf.o conf.o control.o \
87- session.o \
88+ session.o pivot.o \
89 com.ubuntu.Upstart.o \
90 com.ubuntu.Upstart.Job.o com.ubuntu.Upstart.Instance.o \
91 $(NIH_LIBS) \
92@@ -231,7 +232,7 @@
93 system.o environ.o process.o \
94 job_class.o job_process.o job.o event.o event_operator.o blocked.o \
95 parse_job.o parse_conf.o conf.o control.o \
96- session.o \
97+ session.o pivot.o \
98 com.ubuntu.Upstart.o \
99 com.ubuntu.Upstart.Job.o com.ubuntu.Upstart.Instance.o \
100 $(NIH_LIBS) \
101@@ -243,7 +244,7 @@
102 system.o environ.o process.o \
103 job_class.o job_process.o job.o event.o event_operator.o blocked.o \
104 parse_job.o parse_conf.o conf.o control.o \
105- session.o \
106+ session.o pivot.o \
107 com.ubuntu.Upstart.o \
108 com.ubuntu.Upstart.Job.o com.ubuntu.Upstart.Instance.o \
109 $(NIH_LIBS) \
110@@ -255,7 +256,7 @@
111 system.o environ.o process.o \
112 job_class.o job_process.o job.o event.o event_operator.o blocked.o \
113 parse_job.o parse_conf.o conf.o control.o \
114- session.o \
115+ session.o pivot.o \
116 com.ubuntu.Upstart.o \
117 com.ubuntu.Upstart.Job.o com.ubuntu.Upstart.Instance.o \
118 $(NIH_LIBS) \
119@@ -267,7 +268,7 @@
120 system.o environ.o process.o \
121 job_class.o job_process.o job.o event.o event_operator.o blocked.o \
122 parse_job.o parse_conf.o conf.o control.o \
123- session.o \
124+ session.o pivot.o \
125 com.ubuntu.Upstart.o \
126 com.ubuntu.Upstart.Job.o com.ubuntu.Upstart.Instance.o \
127 $(NIH_LIBS) \
128@@ -279,7 +280,7 @@
129 system.o environ.o process.o \
130 job_class.o job_process.o job.o event.o event_operator.o blocked.o \
131 parse_job.o parse_conf.o conf.o control.o \
132- session.o \
133+ session.o pivot.o \
134 com.ubuntu.Upstart.o \
135 com.ubuntu.Upstart.Job.o com.ubuntu.Upstart.Instance.o \
136 $(NIH_LIBS) \
137
138=== modified file 'init/conf.c'
139--- init/conf.c 2011-06-06 17:05:11 +0000
140+++ init/conf.c 2011-07-15 17:51:29 +0000
141@@ -1186,6 +1186,41 @@
142 return NULL;
143 }
144
145+/*
146+ * delete_conf_watches:
147+ *
148+ * This function is called to delete the notification handlers registered
149+ * against the configuration directories/files.
150+ */
151+void delete_conf_watches ()
152+{
153+ NihList * watches;
154+ NihWatch * watch;
155+ NihWatchHandle *handle;
156+ NIH_LIST_FOREACH (conf_sources, iter) {
157+ ConfSource *source = (ConfSource *) iter;
158+ if (! source)
159+ return;
160+ if(source->path) {
161+ nih_debug ("freeing source for path: %s", source->path);
162+ }
163+ /* remove the notification watches */
164+ watch = source->watch;
165+ if (watch) {
166+ watches = & (watch->watches);
167+ if (watches) {
168+ NIH_LIST_FOREACH (watches, wh) {
169+ handle = (NihWatchHandle *)wh;
170+ inotify_rm_watch (watch->fd, handle->wd);
171+ }
172+ }
173+ close (watch->fd);
174+ nih_free (source);
175+ }
176+ }
177+}
178+
179+
180 #ifdef DEBUG
181
182 size_t
183
184=== modified file 'init/conf.h'
185--- init/conf.h 2011-06-06 12:52:08 +0000
186+++ init/conf.h 2011-07-15 17:51:29 +0000
187@@ -129,6 +129,7 @@
188
189 char *toggle_conf_name (const void *parent, const char *path)
190 __attribute__ ((warn_unused_result, malloc));
191+void delete_conf_watches (void);
192
193 #ifdef DEBUG
194
195
196=== modified file 'init/control.c'
197--- init/control.c 2011-06-15 13:20:41 +0000
198+++ init/control.c 2011-07-15 17:51:29 +0000
199@@ -54,6 +54,7 @@
200 #include "conf.h"
201 #include "control.h"
202 #include "errors.h"
203+#include "pivot.h"
204
205 #include "com.ubuntu.Upstart.h"
206
207@@ -613,6 +614,40 @@
208 return 0;
209 }
210
211+/**
212+ * control_pivot_to_new_rootfs:
213+ * @data: not used,
214+ * @message: D-Bus connection and message received,
215+ * @rootfs: dir to chroot to
216+ * @init: new init to spawn on chrooting to rootfs
217+ * This shall be filled in by pivot_to_new_rootfs ()
218+ *
219+ * Called to change the root filesystem to a requested @rootfs, after which the
220+ * requested @init should be executed. This function changes the root
221+ * filesystem only when "/" is on initramfs and the @rootfs is the real rootfs
222+ * to be switched to. On changing the root filesystem as requested, this
223+ * function releases memory by deleting the contents of the memory based
224+ * initramfs before executing @init. If @rootfs or @init are not valid, the
225+ * org.freedesktop.DBus.Error.InvalidArg error will be returned immediately.
226+ * If this function fails before freeing the contents of the initramfs then
227+ * the pivot_failed event is generated. If this function fails after the
228+ * contents of the initramfs have been freed then failure handling is no more
229+ * possible.
230+ *
231+ * Returns: does not return on success, negative value on raised error.
232+ **/
233+int
234+control_pivot_to_new_rootfs (void *data,
235+ NihDBusMessage *message,
236+ const char *rootfs,
237+ const char *init)
238+{
239+ nih_assert (message != NULL);
240+ nih_assert (rootfs != NULL);
241+ nih_assert (init != NULL);
242+
243+ return pivot_to_new_rootfs (message, rootfs, init);
244+}
245
246 /**
247 * control_get_version:
248
249=== modified file 'init/control.h'
250--- init/control.h 2011-06-06 17:05:11 +0000
251+++ init/control.h 2011-07-15 17:51:29 +0000
252@@ -72,6 +72,9 @@
253 const char *name, char * const *env,
254 int wait)
255 __attribute__ ((warn_unused_result));
256+int control_pivot_to_new_rootfs (void *data, NihDBusMessage *message,
257+ const char *rootfs, const char *init)
258+ __attribute__ ((warn_unused_result));
259 int control_emit_event_with_file (void *data, NihDBusMessage *message,
260 const char *name, char * const *env,
261 int wait, int file)
262
263=== modified file 'init/events.h'
264--- init/events.h 2009-06-23 09:29:35 +0000
265+++ init/events.h 2011-07-15 17:51:29 +0000
266@@ -1,6 +1,6 @@
267 /* upstart
268 *
269- * Copyright © 2009 Canonical Ltd.
270+ * Copyright © 2009-2011 Canonical Ltd.
271 * Author: Scott James Remnant <scott@netsplit.com>.
272 *
273 * This program is free software; you can redistribute it and/or modify
274@@ -93,5 +93,12 @@
275 **/
276 #define JOB_STOPPED_EVENT "stopped"
277
278+/**
279+ * PIVOT_FAILED_EVENT:
280+ *
281+ * Name of the event we generate once the pivot event fails to change the
282+ * root filesystem.
283+ **/
284+#define PIVOT_FAILED_EVENT "pivot-failed"
285
286 #endif /* INIT_EVENTS_H */
287
288=== modified file 'init/paths.h'
289--- init/paths.h 2011-06-06 12:52:08 +0000
290+++ init/paths.h 2011-07-15 17:51:29 +0000
291@@ -177,4 +177,12 @@
292 (IS_CONF_EXT_STD(period) || \
293 IS_CONF_EXT_OVERRIDE(period))
294
295+/**
296+ * Kernel command line arguments path
297+ *
298+ * This file stores the kernel command line arguments which are read for
299+ * executing a new requested init using the pivot command from initramfs.
300+ **/
301+#define PROC_CMDLINE "/proc/cmdline"
302+
303 #endif /* INIT_PATHS_H */
304
305=== added file 'init/pivot.c'
306--- init/pivot.c 1970-01-01 00:00:00 +0000
307+++ init/pivot.c 2011-07-15 17:51:29 +0000
308@@ -0,0 +1,616 @@
309+/* upstart
310+ *
311+ * pivot.c - Pivot command handling
312+ *
313+ * Copyright © 2011 Canonical Ltd.
314+ * Author: Surbhi A. Palande <surbhi.palande@ubuntu.com>.
315+ *
316+ * This program is free software; you can redistribute it and/or modify
317+ * it under the terms of the GNU General Public License version 2, as
318+ * published by the Free Software Foundation.
319+ *
320+ * This program is distributed in the hope that it will be useful,
321+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
322+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
323+ * GNU General Public License for more details.
324+ *
325+ * You should have received a copy of the GNU General Public License along
326+ * with this program; if not, write to the Free Software Foundation, Inc.,
327+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
328+ */
329+
330+#include "pivot.h"
331+#include "conf.h"
332+
333+/*
334+ * @get_complete_path:
335+ *
336+ * @dirname: absolute path of the parent directory.
337+ * @dirent_name: name of the directory entry whose full path is constructed by
338+ * using the parent directories absolute path i.e @dirname
339+ *
340+ * This function is called for getting an absolute path of a directory entry given
341+ * the parent directories absolute path.
342+ *
343+ * Returns: the absolute path on success. On failure returns NULL.
344+ */
345+static char *
346+get_complete_path (const char * dirname, const char * dirent_name)
347+{
348+ char * full_path = NULL;
349+
350+ nih_assert (dirname != NULL);
351+ nih_assert (dirent_name != NULL);
352+
353+ full_path = nih_strdup (NULL, dirname);
354+ if (full_path) {
355+ if (dirname [strlen(dirname) - 1] != '/') {
356+ full_path = nih_strcat (&full_path, NULL, "/");
357+ }
358+ if (full_path)
359+ full_path = nih_strcat (&full_path, NULL, dirent_name);
360+ }
361+ return full_path;
362+}
363+
364+/*
365+ * @del_dir:
366+ *
367+ * @dirname: directory whose contents need to be deleted.
368+ *
369+ * This function is called to free the contents of memory by deleting the
370+ * memory based members of @dirname. It does not delete the contents of a
371+ * member directory in case it is a mount point for other file systems which
372+ * are not memory based. This function assumes that "/" is a memory based
373+ * filesystem and uses this information to find out whether the underlying
374+ * device of a member directory is the same as that of "/" or not.
375+ *
376+ *
377+ * Returns: On success returns 0 while on failure it raises the
378+ * org.freedesktop.DBus.Error.InvalidArg error and exits out of upstart as
379+ * recovery of a pivot command is not possible.
380+ */
381+static int
382+del_dir (const char * dirname)
383+{
384+ struct dirent * dirent;
385+ struct stat rst, cst;
386+ DIR * dirp;
387+ char * path = NULL;
388+ int ret = 0;
389+
390+ nih_assert (dirname != NULL);
391+
392+ ret = stat ("/", &rst);
393+ if (ret) {
394+ nih_debug ("Could not stat /");
395+ goto error;
396+ }
397+ dirp = opendir (dirname);
398+ if (!dirp) {
399+ ret = -1;
400+ goto error;
401+ }
402+ while ((dirent = readdir (dirp)) != NULL) {
403+ if (!strcmp (dirent->d_name, ".") ||
404+ (!strcmp (dirent->d_name, ".."))) {
405+ continue;
406+ }
407+ path = get_complete_path (dirname, dirent->d_name);
408+ if (!path) {
409+ ret = -1;
410+ goto cleanup;
411+ }
412+ switch (dirent->d_type) {
413+ case DT_DIR:
414+ ret = stat (path, &cst);
415+ /* Free the contents of only the memory based
416+ * file systems but not of any other mounted
417+ * file systems if any.
418+ */
419+ if (!ret && dev_match (rst.st_dev, cst.st_dev)) {
420+ ret = del_dir (path);
421+ if (!ret) {
422+ nih_debug ("Removing dir: %s", path);
423+ ret = rmdir (path);
424+ }
425+ }
426+ break;
427+ default:
428+ nih_debug ("Removing file: %s", path);
429+ ret = unlink (path);
430+ break;
431+ }
432+ nih_free (path);
433+ if (ret)
434+ break;
435+ }
436+cleanup:
437+ closedir (dirp);
438+error:
439+ if (ret) {
440+ nih_assert (errno != 0);
441+ switch (errno) {
442+ case ENOMEM:
443+ nih_error_raise_no_memory ();
444+ break;
445+ default:
446+ nih_error_raise (errno, strerror (errno));
447+ break;
448+ }
449+ nih_fatal (_("Could not delete the contents of initramfs: %s"), strerror (errno));
450+ exit (1);
451+ }
452+ return 0;
453+}
454+
455+/**
456+ * pivot_emit_failed_event:
457+ *
458+ * @argv: The arguments to be passed to the new requested init for execution.
459+ *
460+ * This function generates a new event "pivot_failed". It is used to indicate
461+ * that the initcl pivot command failed to chroot to the new rootfs.
462+ *
463+ * Returns: on success returns the PIVOT_FAILED_EVENT and on failure returns
464+ * NULL.
465+ **/
466+Event *
467+pivot_emit_failed_event (char ** argv)
468+{
469+ Event *event;
470+
471+ event = NIH_MUST (event_new (NULL, PIVOT_FAILED_EVENT, argv));
472+ if (!event) {
473+ nih_debug ("Could not emit %s", PIVOT_FAILED_EVENT);
474+ nih_free (argv);
475+ return NULL;
476+ }
477+ return event;
478+}
479+
480+/**
481+ * @are_we_on_ramfs():
482+ *
483+ * This function is called to check if the current root filesystem is based on
484+ * a memory based filesystem.
485+ *
486+ * Returns: TRUE if the root filesystem is memory based, else returns FALSE.
487+ **/
488+static int
489+are_we_on_ramfs ()
490+{
491+ int ret = 0;
492+ struct stat st;
493+ struct statfs sfs;
494+
495+ /* Make sure we're on a ramfs */
496+ /* 1. The initramfs should have /init */
497+ ret = stat ("/init", &st);
498+ if (ret) {
499+ if (errno == ENOMEM)
500+ nih_error_raise_no_memory ();
501+ else
502+ nih_dbus_error_raise_printf (DBUS_ERROR_INVALID_ARGS,
503+ _("/ may not be on initramfs"));
504+ return FALSE;
505+ }
506+ if (!S_ISREG (st.st_mode)) {
507+ nih_dbus_error_raise_printf (DBUS_ERROR_INVALID_ARGS,
508+ _("/ may not be on initramfs"));
509+ return FALSE;
510+ }
511+ /* Make sure that the statistics of / indicate that its a memory based
512+ * filesystem and not a disk based one */
513+ ret = statfs ("/", &sfs);
514+ if (ret) {
515+ switch(errno) {
516+ case ENOMEM:
517+ nih_error_raise_no_memory ();
518+ break;
519+ default:
520+ nih_dbus_error_raise_printf (DBUS_ERROR_FAILED,
521+ _("Cannot verify if we are in the initramfs"));
522+ break;
523+ }
524+ return FALSE;
525+ }
526+ if (sfs.f_type != (long) RAMFS_MAGIC && sfs.f_type != (long) TMPFS_MAGIC) {
527+ nih_dbus_error_raise_printf (DBUS_ERROR_INVALID_ARGS,
528+ _("/ is not a memory based filesystem"));
529+ return FALSE;
530+ }
531+ return TRUE;
532+}
533+
534+/**
535+ * is_rootfs_ok:
536+ *
537+ * @rootfs: specifies a path to a filesytem.
538+ *
539+ * This function checks if the @rootfs exists, if its on based on a different
540+ * device/filesystem than that of the "/" and also if its a directory.
541+ *
542+ * Returns: TRUE if @rootfs is on a different filesystem than /. Else returns
543+ * FALSE.
544+ **/
545+static int
546+is_rootfs_ok (const char * rootfs)
547+{
548+ int ret = 0;
549+ struct stat rst, cst;
550+
551+ nih_assert (rootfs != NULL);
552+ /* Make sure the requested rootfs is not the same filesystem
553+ as the current("/") root filesystem */
554+ ret = stat ("/", &rst);
555+ if (ret) {
556+ if (errno == ENOMEM)
557+ nih_error_raise_no_memory ();
558+ else
559+ nih_dbus_error_raise_printf (DBUS_ERROR_INVALID_ARGS,
560+ _("Could not stat /"));
561+ return FALSE;
562+ }
563+ ret = stat (rootfs, &cst);
564+ if (ret) {
565+ if (errno == ENOMEM)
566+ nih_error_raise_no_memory ();
567+ else
568+ nih_dbus_error_raise_printf (DBUS_ERROR_INVALID_ARGS,
569+ _("Could not stat requested rootfs:%s dir"),
570+ rootfs);
571+ return FALSE;
572+ }
573+ if (dev_match (rst.st_dev, cst.st_dev)) {
574+ nih_dbus_error_raise_printf (DBUS_ERROR_INVALID_ARGS,
575+ _("new rootfs: %s requested is not a different "
576+ "filesystem than /"), rootfs);
577+ return FALSE;
578+ }
579+ if (!S_ISDIR (cst.st_mode)) {
580+ nih_dbus_error_raise_printf (DBUS_ERROR_INVALID_ARGS,
581+ _("new rootfs: %s requested is not a directory"),
582+ rootfs);
583+ return FALSE;
584+ }
585+ return TRUE;
586+}
587+
588+/**
589+ * @is_init_ok:
590+ *
591+ * @init: process that shall be perhaps executed.
592+ *
593+ * This function checks if the requested @init exists and if so then checks
594+ * if it is a regular file which could be executed. This is called before the
595+ * execution of init is attempted.
596+ *
597+ * Returns: TRUE when @init is a regular file else returns FALSE
598+ **/
599+static int
600+is_init_ok (const char * init)
601+{
602+ struct stat st;
603+ int ret = 0;
604+
605+ nih_assert (init != NULL);
606+ /* Check if the requested init exists */
607+ ret = stat (init, &st);
608+ if (ret) {
609+ switch (errno) {
610+ case ENOMEM:
611+ nih_error_raise_no_memory ();
612+ break;
613+ default:
614+ nih_dbus_error_raise_printf (DBUS_ERROR_INVALID_ARGS,
615+ _("The requested init: %s may not be correct"),
616+ init);
617+ break;
618+ }
619+ ret = FALSE;
620+ }
621+ else if (!S_ISREG (st.st_mode)) {
622+ nih_dbus_error_raise_printf (DBUS_ERROR_INVALID_ARGS,
623+ _("The requested init: %s is not a regular "
624+ "file"), init);
625+ ret = FALSE;
626+ } else {
627+ if ((st.st_mode & S_IXUSR) || (st.st_mode & S_IXGRP) || (st.st_mode & S_IXOTH)) {
628+ ret = TRUE;
629+ } else {
630+ nih_dbus_error_raise_printf (DBUS_ERROR_INVALID_ARGS,
631+ _("The requested init: %s is not an "
632+ "executable"), init);
633+ }
634+ }
635+ return ret;
636+}
637+
638+/*
639+ * @get_init_args:
640+ *
641+ * @init: The init that will be executed by calling an execvp later.
642+ *
643+ * This function is called to get the command line arguments for executing
644+ * @init. @init is the first argument of this command line array. The rest of
645+ * the arguments are collected from PROC_CMDLINE which stores the arguments
646+ * specified at boot time.
647+ *
648+ * Returns: the address of the array with the command line arguments on success.
649+ * On failure returns NULL
650+ */
651+static char **
652+get_init_args(const char * root, const char * init)
653+{
654+
655+ char ** env = NULL;
656+ FILE * cmdline = NULL;
657+ char ** tokens = NULL;
658+ int i=0;
659+ char line[PAGESZ];
660+
661+ nih_assert (root != NULL);
662+ nih_assert (init != NULL);
663+
664+ /* Create the argv for execv(). The first argument is the "init" you
665+ * want to execute. The command line arguments to be passed to this
666+ * init are copied from PROC_CMDLINE
667+ */
668+ env = nih_str_array_new (NULL);
669+ if (!env)
670+ goto error;
671+ if (! nih_str_array_add (&env, NULL, NULL, init))
672+ goto cleanup;
673+ /* If the procfs is moved before this call then the /proc/cmdline will
674+ * be found in @root/PROC_CMDLINE
675+ */
676+ cmdline = fopen (PROC_CMDLINE, "r");
677+ if (!cmdline)
678+ goto cleanup;
679+ if (!fgets (line, sizeof line, cmdline))
680+ goto cleanup;
681+ tokens = nih_str_split (env, line, " ", 1);
682+ if (!tokens)
683+ goto cleanup;
684+ for ( i=0 ; tokens [i] != NULL; i++) {
685+ if ((tokens [i][0] == '-') && (tokens [i][1]='-')) {
686+ /* argument to init */
687+ if (!nih_str_array_add (&env, NULL, NULL, tokens[i]))
688+ goto cleanup;
689+ }
690+ }
691+ fclose (cmdline);
692+ return env;
693+cleanup:
694+ if (cmdline)
695+ fclose (cmdline);
696+ nih_free (env);
697+error:
698+ switch (errno) {
699+ case ENOMEM:
700+ nih_error_raise_no_memory ();
701+ break;
702+ default:
703+ nih_dbus_error_raise_printf (DBUS_ERROR_FAILED,
704+ "Failed to open %s", PROC_CMDLINE);
705+ break;
706+ }
707+ return NULL;
708+}
709+
710+
711+/*
712+ * @move_mounts:
713+ *
714+ * @origin: virtual fs to be moved from "/"
715+ * @rootfs: root filesystem where the @origin should be moved to.
716+ *
717+ * This function is intended to move the virtual filesystem @origin from "/"
718+ * to the real @rootfs. This is a necessary step before we move root from "/"
719+ * to the real @rootfs. This function should be called to move /dev, /sys and
720+ * /proc to a corresponding mountpoint in @rootfs
721+ *
722+ * Returns: On success returns 0 and on failure returns -1
723+ *
724+ */
725+static int
726+move_mounts(const char * origin, const char * rootfs)
727+{
728+ char * path = NULL;
729+ int ret = 0;
730+
731+ path = nih_strdup (NULL, rootfs);
732+ if (path) {
733+ if (origin[0] != '/')
734+ path = nih_strcat (&path, NULL, "/");
735+ if (path) {
736+ path = nih_strcat (&path, NULL, origin);
737+ /* Move the devfs to the new root filesystem */
738+ if (path)
739+ ret = mount (origin, path, NULL, MS_MOVE, NULL);
740+ }
741+ }
742+ if (ret || !path) {
743+ if (errno == ENOMEM)
744+ nih_error_raise_no_memory ();
745+ nih_dbus_error_raise_printf (DBUS_ERROR_FAILED,
746+ _("Cannot move %s to %s"), origin, rootfs);
747+ ret = -1;
748+ }
749+ if (path)
750+ nih_free (path);
751+ return ret;
752+}
753+
754+/*
755+ * send_reply:
756+ * @message: Dbus connection and message received. We use this for sending a
757+ * reply.
758+ *
759+ * This function is used to send a reply back to the initctl command before we
760+ * execvp the requested init. We do not want to return to the middleware but
761+ * instead execvp the requested init. By not returning to the middleware we
762+ * need to ourself send a reply explicitly to initctl so that it can exit.
763+ */
764+static void
765+send_reply (NihDBusMessage *message)
766+{
767+ DBusMessageIter iter;
768+ DBusMessage * reply;
769+
770+ nih_assert (message != NULL);
771+
772+ do {
773+ __label__ enomem;
774+
775+ /* Construct the reply message. */
776+ reply = dbus_message_new_method_return (message->message);
777+ if (! reply)
778+ goto enomem;
779+ dbus_message_iter_init_append (reply, &iter);
780+ enomem: __attribute__ ((unused));
781+ } while (! reply);
782+ /* Send the reply, appending it to the outgoing queue. */
783+ NIH_MUST (dbus_connection_send (message->connection, reply, NULL));
784+ dbus_message_unref (reply);
785+ return;
786+}
787+
788+/**
789+ * pivot_to_new_rootfs:
790+ * @message: D-Bus connection and message received,
791+ * @rootfs: dir to chroot to
792+ * @init: new init to spawn on chrooting to rootfs
793+ *
794+ * Called to change the root filesystem to a requested @rootfs, after which the
795+ * requested @init should be executed. This function changes the root
796+ * filesystem only when "/" is on initramfs and the @rootfs is the real rootfs
797+ * to be switched to. On changing the root filesystem as requested, this
798+ * function releases memory by deleting the contents of the memory based
799+ * initramfs before executing @init. If @rootfs or @init are not valid, the
800+ * org.freedesktop.DBus.Error.InvalidArg error will be returned immediately.
801+ * If this function fails before freeing the contents of the initramfs then
802+ * the pivot_failed event is generated. If this function fails after the
803+ * contents of the initramfs have been freed then failure handling is no more
804+ * possible.
805+ *
806+ * Returns: does not return on success, negative value on raised error.
807+ **/
808+int
809+pivot_to_new_rootfs (NihDBusMessage *message,
810+ const char *rootfs,
811+ const char *init)
812+{
813+ char ** env;
814+ int ret = 0;
815+
816+ nih_assert (message != NULL);
817+ nih_assert (rootfs != NULL);
818+ nih_assert (init != NULL);
819+
820+ /* Verify that the rootfs dir name is valid */
821+ if (! strlen (rootfs)) {
822+ nih_dbus_error_raise_printf (DBUS_ERROR_INVALID_ARGS,
823+ _("Rootfs dir name may not be empty string"));
824+ pivot_emit_failed_event (NULL);
825+ return -1;
826+ }
827+ /* Verify that the init name is valid */
828+ if (! strlen (init)) {
829+ nih_dbus_error_raise_printf (DBUS_ERROR_INVALID_ARGS,
830+ _("Name of init may not be empty string"));
831+ pivot_emit_failed_event (NULL);
832+ return -1;
833+ }
834+ /* Before you free up the initramfs memory by deleting files in the
835+ * initramfs, make sure:
836+ * 1. That the current root filesystem is memory based.
837+ * 2. That you are trying to pivot to a different than the current filesystem.
838+ */
839+ if (!are_we_on_ramfs ()) {
840+ pivot_emit_failed_event (NULL);
841+ return -1;
842+ }
843+ /* Make sure the requested rootfs is not the same filesystem
844+ as the current("/") root filesystem */
845+ if (!is_rootfs_ok (rootfs)) {
846+ pivot_emit_failed_event (NULL);
847+ return -1;
848+ }
849+ /* Create the argv for execv(). The first argument is the "init" you
850+ * want to execute. The command line arguments to be passed to this
851+ * init are copied from /proc/cmdline.
852+ */
853+ env = get_init_args (rootfs, init);
854+ if (!env) {
855+ pivot_emit_failed_event (NULL);
856+ return -1;
857+ }
858+ delete_conf_watches ();
859+ /* Move the virtual filesystem to the new rootfs. This is needed for
860+ * making @rootfs as the new root filesystem
861+ */
862+ /* 1. Move the sysfs to the new root filesystem */
863+ if (move_mounts ("/sys", rootfs)) {
864+ pivot_emit_failed_event (env);
865+ nih_free (env);
866+ return -1;
867+ }
868+ /* 2. Move the procfs to the new root filesystem */
869+ if (move_mounts ("/proc", rootfs)) {
870+ pivot_emit_failed_event (env);
871+ nih_free (env);
872+ return -1;
873+ }
874+
875+ /* First, change to the new root directory */
876+ ret = chdir (rootfs);
877+ if (ret) {
878+ nih_dbus_error_raise_printf (DBUS_ERROR_FAILED,
879+ _("Cannot change to newly requested rootfs dir"));
880+ pivot_emit_failed_event (env);
881+ nih_free (env);
882+ return -1;
883+ }
884+ /* Check if the requested init exists */
885+ if (!is_init_ok (init)) {
886+ ret = chdir ("/");
887+ pivot_emit_failed_event (env);
888+ nih_free (env);
889+ return -1;
890+ }
891+ send_reply (message);
892+ nih_signal_reset ();
893+ /* After this point you cannot recover from a initramfs console */
894+ /* Hopefully we are sure we are in the initramfs and so safe to free
895+ * the memory now by freeing up the rambased filesystem's contents*/
896+ del_dir ("/");
897+ /* Overmount the root with the request root filesystem*/
898+ ret = mount (".", "/", NULL, MS_MOVE, NULL);
899+ if (ret) {
900+ if(errno == ENOMEM) {
901+ nih_error_raise_no_memory();
902+ }
903+ nih_fatal (_("Cannot mount the requested rootfs"));
904+ exit (1);
905+
906+ }
907+ nih_debug ("Mounted the new root filesystem ");
908+ /* chroot, chdir to the new root filesystem*/
909+ if (chroot (".")) {
910+ nih_fatal (_("Could not chroot to the new requested root filesystem"));
911+ exit (1);
912+
913+ }
914+ if (chdir ("/")) {
915+ nih_fatal (_("Could not chdir to the new requested root filesystem"));
916+ exit (1);
917+ }
918+ /* Spawn init */
919+ nih_debug ("About to execute new requested init: %s", init);
920+ execvp (init, env);
921+ nih_fatal (_("Could not execute the new init in the requested rootfs"));
922+ exit (1);
923+}
924+
925
926=== added file 'init/pivot.h'
927--- init/pivot.h 1970-01-01 00:00:00 +0000
928+++ init/pivot.h 2011-07-15 17:51:29 +0000
929@@ -0,0 +1,66 @@
930+/* upstart
931+ *
932+ * pivot.h - Pivot command handling definitions
933+ *
934+ * Copyright © 2011 Canonical Ltd.
935+ * Author: Surbhi A. Palande <surbhi.palande@ubuntu.com>.
936+ *
937+ * This program is free software; you can redistribute it and/or modify
938+ * it under the terms of the GNU General Public License version 2, as
939+ * published by the Free Software Foundation.
940+ *
941+ * This program is distributed in the hope that it will be useful,
942+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
943+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
944+ * GNU General Public License for more details.
945+ *
946+ * You should have received a copy of the GNU General Public License along
947+ * with this program; if not, write to the Free Software Foundation, Inc.,
948+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
949+ */
950+
951+
952+#include <sys/mount.h>
953+#include <sys/stat.h>
954+#include <sys/types.h>
955+#include <dirent.h>
956+#include <sys/vfs.h>
957+#include <unistd.h>
958+#include <fcntl.h>
959+#include <stdio.h>
960+#include <string.h>
961+#include <sys/types.h>
962+#include <sys/wait.h>
963+
964+#include <nih/macros.h>
965+#include <nih/alloc.h>
966+#include <nih/string.h>
967+#include <nih/main.h>
968+#include <nih/logging.h>
969+#include <nih/error.h>
970+#include <nih/errors.h>
971+#include <nih-dbus/dbus_error.h>
972+
973+#include <linux/magic.h>
974+#include <linux/fs.h>
975+
976+#include "system.h"
977+#include "paths.h"
978+#include "events.h"
979+
980+/*
981+ * This macro is used for checking if the major and minor numbers of two
982+ * devices are the same.
983+ *
984+ * @devid1: is a dev_t type id of a device
985+ * @devid2: is a dev_t type id of another device
986+ *
987+ * Returns TRUE if the device ids match, else FALSE
988+ */
989+#define dev_match(devid1, devid2) ((major (devid1) == major (devid2)) && (minor (devid1) == minor (devid2)))
990+
991+
992+#define PAGESZ getpagesize()
993+
994+Event * pivot_emit_failed_event (char ** argv);
995+int pivot_to_new_rootfs (NihDBusMessage *message, const char *rootfs, const char *init);
996
997=== modified file 'init/tests/test_control.c'
998--- init/tests/test_control.c 2011-06-06 17:05:11 +0000
999+++ init/tests/test_control.c 2011-07-15 17:51:29 +0000
1000@@ -2,7 +2,7 @@
1001 *
1002 * test_dbus.c - test suite for init/dbus.c
1003 *
1004- * Copyright © 2010 Canonical Ltd.
1005+ * Copyright © 2010-2011 Canonical Ltd.
1006 * Author: Scott James Remnant <scott@netsplit.com>.
1007 *
1008 * This program is free software; you can redistribute it and/or modify
1009@@ -60,6 +60,7 @@
1010 #include "conf.h"
1011 #include "control.h"
1012 #include "errors.h"
1013+#include "events.h"
1014
1015
1016 extern const char *control_server_address;
1017@@ -2176,6 +2177,108 @@
1018 nih_log_priority = NIH_LOG_UNKNOWN;
1019 }
1020
1021+void
1022+test_pivot_root (void)
1023+{
1024+ NihDBusMessage *message = NULL;
1025+ char *rootfs = NULL;
1026+ char *init = NULL;
1027+ NihError *err;
1028+ NihDBusError *dbus_err;
1029+ int ret = 0;
1030+ JobClass *class = NULL;
1031+ Job *job;
1032+
1033+ /* Check that the function returns the package string as a newly
1034+ * allocated child of the message structure.
1035+ */
1036+ TEST_FUNCTION ("control_pivot_to_new_rootfs");
1037+ nih_error_init ();
1038+ job_class_init ();
1039+
1040+ TEST_ALLOC_FAIL {
1041+ TEST_ALLOC_SAFE {
1042+ message = nih_new (NULL, NihDBusMessage);
1043+ message->connection = NULL;
1044+ message->message = NULL;
1045+
1046+ /* Case 1: Invalid ROOTFS and Invalid INIT */
1047+ rootfs = nih_strdup (NULL, "/tea");
1048+ init = nih_strdup (NULL, "/biscuit");
1049+ class = job_class_new (NULL, "test", NULL);
1050+ class->task = TRUE;
1051+ class->process[PROCESS_MAIN] = process_new (class->process);
1052+ class->process[PROCESS_MAIN]->command = "echo";
1053+ class->start_on = event_operator_new (
1054+ class, EVENT_MATCH, PIVOT_FAILED_EVENT, NULL);
1055+ nih_hash_add (job_classes, &class->entry);
1056+ }
1057+
1058+
1059+ ret = control_pivot_to_new_rootfs (NULL, message, rootfs,
1060+ init);
1061+ if (ret < 0) {
1062+ TEST_EQ (ret, -1);
1063+ err = nih_error_get ();
1064+ TEST_ALLOC_SIZE (err, sizeof (NihDBusError));
1065+
1066+ TEST_EQ (err->number, NIH_DBUS_ERROR);
1067+ dbus_err = (NihDBusError *)err;
1068+ TEST_EQ_STR (dbus_err->name, DBUS_ERROR_INVALID_ARGS);
1069+ nih_free (dbus_err);
1070+
1071+ }
1072+
1073+ event_poll();
1074+ /* a pivot_failed event should be created by the dummy init
1075+ * and dummy rootfs. We try to start a job on that*/
1076+ TEST_ALLOC_SAFE {
1077+ TEST_HASH_NOT_EMPTY (class->instances);
1078+ job = (Job *)nih_hash_lookup (class->instances, "");
1079+
1080+ TEST_EQ (job->goal, JOB_START);
1081+
1082+ waitpid (job->pid[PROCESS_MAIN], NULL, 0);
1083+ nih_free (rootfs);
1084+ nih_free (init);
1085+
1086+ /* Case 2: valid ROOTFS and valid INIT. Rootfs not different
1087+ * than current FS */
1088+ rootfs = nih_strdup (NULL, "/");
1089+ init = nih_strdup (NULL, "/sbin/init");
1090+ }
1091+
1092+ ret = control_pivot_to_new_rootfs (NULL, message, rootfs,
1093+ init);
1094+ if (ret < 0) {
1095+ TEST_EQ (ret, -1);
1096+ err = nih_error_get ();
1097+ TEST_ALLOC_SIZE (err, sizeof (NihDBusError));
1098+
1099+ TEST_EQ (err->number, NIH_DBUS_ERROR);
1100+ dbus_err = (NihDBusError *)err;
1101+ TEST_EQ_STR (dbus_err->name, DBUS_ERROR_INVALID_ARGS);
1102+ nih_free (dbus_err);
1103+
1104+ }
1105+
1106+ event_poll();
1107+ /* a pivot_failed event should be created by the dummy init
1108+ * and dummy rootfs. We try to start a job on that*/
1109+ TEST_ALLOC_SAFE {
1110+ TEST_HASH_NOT_EMPTY (class->instances);
1111+ job = (Job *)nih_hash_lookup (class->instances, "");
1112+
1113+ TEST_EQ (job->goal, JOB_START);
1114+
1115+ waitpid (job->pid[PROCESS_MAIN], NULL, 0);
1116+ nih_free (rootfs);
1117+ nih_free (init);
1118+ nih_free (class);
1119+ }
1120+ nih_free (message);
1121+ }
1122+}
1123
1124 int
1125 main (int argc,
1126@@ -2205,5 +2308,7 @@
1127 test_get_log_priority ();
1128 test_set_log_priority ();
1129
1130+ test_pivot_root();
1131+
1132 return 0;
1133 }
1134
1135=== modified file 'util/initctl.c'
1136--- util/initctl.c 2011-06-03 09:37:25 +0000
1137+++ util/initctl.c 2011-07-15 17:51:29 +0000
1138@@ -1,6 +1,6 @@
1139 /* upstart
1140 *
1141- * Copyright © 2010 Canonical Ltd.
1142+ * Copyright © 2010-2011 Canonical Ltd.
1143 * Author: Scott James Remnant <scott@netsplit.com>.
1144 *
1145 * This program is free software; you can redistribute it and/or modify
1146@@ -121,6 +121,7 @@
1147 int log_priority_action (NihCommand *command, char * const *args);
1148 int show_config_action (NihCommand *command, char * const *args);
1149 int check_config_action (NihCommand *command, char * const *args);
1150+int pivot_action (NihCommand *command, char * const *args);
1151
1152
1153 /**
1154@@ -1202,6 +1203,54 @@
1155 }
1156
1157 /**
1158+ * pivot_action:
1159+ * @command: NihCommand invoked,
1160+ * @args: command-line arguments.
1161+ *
1162+ * This function is called for the "pivot" command.
1163+ *
1164+ * Returns: 0 on success and 1 on error
1165+ **/
1166+int
1167+pivot_action (NihCommand * command,
1168+ char * const *args)
1169+{
1170+ nih_local NihDBusProxy *upstart = NULL;
1171+ int ret = 1;
1172+ NihError * err;
1173+
1174+ nih_assert (command != NULL);
1175+ nih_assert (args != NULL);
1176+
1177+ if (!args[0]) {
1178+ fprintf (stderr, _("%s: missing rootfs \n"), program_name);
1179+ nih_main_suggest_help ();
1180+ return 1;
1181+ }
1182+ nih_debug("rootfs: %s", args[0]);
1183+ if (!args[1]) {
1184+ fprintf (stderr, _("%s: missing init \n"), program_name);
1185+ nih_main_suggest_help ();
1186+ return 1;
1187+ }
1188+ nih_debug("init: %s", args[1]);
1189+
1190+ upstart = upstart_open (NULL);
1191+ if (! upstart)
1192+ return 1;
1193+
1194+ ret = upstart_pivot_to_new_rootfs_sync (NULL, upstart, args[0],
1195+ args[1]);
1196+ if (ret < 0) {
1197+ err = nih_error_get ();
1198+ nih_error ("Error: %s", err->message);
1199+ nih_free (err);
1200+ ret = 1;
1201+ }
1202+ return ret;
1203+}
1204+
1205+/**
1206 * show_config_action:
1207 * @command: NihCommand invoked,
1208 * @args: command-line arguments.
1209@@ -2297,6 +2346,15 @@
1210 };
1211
1212 /**
1213+ * pivot_options:
1214+ *
1215+ * Command-line options accepted for the pivot command.
1216+ **/
1217+NihOption pivot_options[] = {
1218+ NIH_OPTION_LAST
1219+};
1220+
1221+/**
1222 * reload_configuration_options:
1223 *
1224 * Command-line options accepted for the reload-configuration command.
1225@@ -2365,6 +2423,14 @@
1226 static NihCommandGroup event_commands = { N_("Event") };
1227
1228 /**
1229+ * pivot_group:
1230+ *
1231+ * Group of commands related to new rootfs and new init.
1232+ **/
1233+static NihCommandGroup pivot_commands = { N_("Rootfs") };
1234+
1235+
1236+/**
1237 * commands:
1238 *
1239 * Commands accepts as the first non-option argument, or program name.
1240@@ -2430,6 +2496,14 @@
1241 "to be included in the event.\n"),
1242 &event_commands, emit_options, emit_action },
1243
1244+ { "pivot", N_("ROOTFS INIT"),
1245+ N_("Execute a new init process after changing from a ram based root"
1246+ " filesystem to a new root filesystem" ),
1247+ N_("ROOTFS is the path of the new root filestem to be changed to"
1248+ "INIT is the new init process in the new root filesystem that should be "
1249+ "spawned after successfully changing the root filesystem to ROOTFS \n"),
1250+ &pivot_commands, pivot_options, pivot_action },
1251+
1252 { "reload-configuration", NULL,
1253 N_("Reload the configuration of the init daemon."),
1254 NULL,

Subscribers

People subscribed via source and target branches