github.com/blixtra/rkt@v0.8.1-0.20160204105720-ab0d1add1a43/stage1/prepare-app/prepare-app.c (about)

     1  // Copyright 2015 The rkt Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  #include <errno.h>
    16  #include <string.h>
    17  #include <stdio.h>
    18  #include <stdlib.h>
    19  #include <sys/mount.h>
    20  #include <sys/stat.h>
    21  #include <sys/types.h>
    22  #include <unistd.h>
    23  #include <fcntl.h>
    24  
    25  #define err_out(_fmt, _args...)						\
    26  		fprintf(stderr, "Error: " _fmt "\n", ##_args);
    27  static int exit_err;
    28  #define exit_if(_cond, _fmt, _args...)					\
    29  	exit_err++;							\
    30  	if(_cond) {							\
    31  		err_out(_fmt, ##_args);					\
    32  		exit(exit_err);						\
    33  	}
    34  #define pexit_if(_cond, _fmt, _args...)					\
    35  	exit_if(_cond, _fmt ": %s", ##_args, strerror(errno))
    36  
    37  #define goto_if(_cond, _lbl, _fmt, _args...)				\
    38  	if(_cond) {							\
    39  		err_out(_fmt, ##_args);					\
    40  		goto _lbl;						\
    41  	}
    42  #define pgoto_if(_cond, _lbl, _fmt, _args...)				\
    43  	goto_if(_cond, _lbl, _fmt ": %s", ##_args, strerror(errno));
    44  
    45  #define nelems(_array) \
    46  	(sizeof(_array) / sizeof(_array[0]))
    47  #define lenof(_str) \
    48  	(sizeof(_str) - 1)
    49  
    50  #define MACHINE_ID_LEN		lenof("0123456789abcdef0123456789ab")
    51  #define MACHINE_NAME_LEN	lenof("rkt-01234567-89ab-cdef-0123-456789ab")
    52  
    53  typedef struct _dir_op_t {
    54  	const char	*name;
    55  	mode_t		mode;
    56  } dir_op_t;
    57  
    58  typedef struct _mount_point_t {
    59  	const char	*source;
    60  	const char	*target;
    61  	const char	*type;
    62  	const char	*options;
    63  	unsigned long	flags;
    64  } mount_point;
    65  
    66  #define dir(_name, _mode) \
    67  	{ .name = _name, .mode = _mode }
    68  
    69  static int get_machine_name(char *out, int out_len) {
    70  	int	fd;
    71  	char	buf[MACHINE_ID_LEN + 1];
    72  
    73  	pgoto_if((fd = open("/etc/machine-id", O_RDONLY)) == -1,
    74  		_fail, "Error opening \"/etc/machine-id\"");
    75  	pgoto_if(read(fd, buf, MACHINE_ID_LEN) == -1,
    76  		_fail_fd, "Error reading \"/etc/machine-id\"");
    77  	pgoto_if(close(fd) != 0,
    78  		_fail, "Error closing \"/etc/machine-id\"");
    79  	goto_if(snprintf(out, out_len,
    80  			"rkt-%.8s-%.4s-%.4s-%.4s-%.8s",
    81  			buf, buf+8, buf+12, buf+16, buf+20) >= out_len,
    82  		_fail, "Error constructing machine name");
    83  
    84  	return 1;
    85  
    86  _fail_fd:
    87  	close(fd);
    88  _fail:
    89  	return 0;
    90  }
    91  
    92  static int ensure_etc_hosts_exists(const char *root, int rootfd) {
    93  	char	name[MACHINE_NAME_LEN + 1];
    94  	char	hosts[128];
    95  	int	fd, len;
    96  
    97  	if(faccessat(rootfd, "etc/hosts", F_OK, AT_EACCESS) == 0)
    98  		return 1;
    99  
   100  	goto_if(!get_machine_name(name, sizeof(name)),
   101  		_fail, "Failed to get machine name");
   102  	goto_if((len = snprintf(hosts, sizeof(hosts),
   103  			"%s\t%s\t%s\t%s\n",
   104  			"127.0.0.1", name,
   105  			"localhost", "localhost.localdomain")) >= sizeof(hosts),
   106  		_fail, "/etc/hosts line too long: \"%s\"", hosts);
   107  	pgoto_if((fd = openat(rootfd, "etc/hosts", O_WRONLY|O_CREAT, 0644)) == -1,
   108  		_fail, "Failed to create \"%s/etc/hosts\"", root);
   109  	pgoto_if(write(fd, hosts, len) != len,
   110  		_fail_fd, "Failed to write \"%s/etc/hosts\"", root);
   111  	pgoto_if(close(fd) != 0,
   112  		_fail, "Failed to close \"%s/etc/hosts\"", root);
   113  
   114  	return 1;
   115  
   116  _fail_fd:
   117  	close(fd);
   118  _fail:
   119  	return 0;
   120  }
   121  
   122  int main(int argc, char *argv[])
   123  {
   124  	static const char *unlink_paths[] = {
   125  		"dev/shm",
   126  		"dev/ptmx",
   127  		NULL
   128  	};
   129  	static const dir_op_t dirs[] = {
   130  		dir("dev",	0755),
   131  		dir("dev/net",	0755),
   132  		dir("dev/shm",	0755),
   133  		dir("etc",	0755),
   134  		dir("proc",	0755),
   135  		dir("sys",	0755),
   136  		dir("tmp",	01777),
   137  		dir("dev/pts",	0755),
   138  	};
   139  	static const char *devnodes[] = {
   140  		"/dev/null",
   141  		"/dev/zero",
   142  		"/dev/full",
   143  		"/dev/random",
   144  		"/dev/urandom",
   145  		"/dev/tty",
   146  		"/dev/net/tun",
   147  		"/dev/console",
   148  		NULL
   149  	};
   150  	static const mount_point dirs_mount_table[] = {
   151  		{ "/proc", "/proc", "bind", NULL, MS_BIND|MS_REC },
   152  		{ "/sys", "/sys", "bind", NULL, MS_BIND|MS_REC },
   153  		{ "/dev/shm", "/dev/shm", "bind", NULL, MS_BIND },
   154  		{ "/dev/pts", "/dev/pts", "bind", NULL, MS_BIND },
   155  	};
   156  	static const mount_point files_mount_table[] = {
   157  		{ "/etc/rkt-resolv.conf", "/etc/resolv.conf", "bind", NULL, MS_BIND },
   158  	};
   159  	const char *root;
   160  	int rootfd;
   161  	char to[4096];
   162  	int i;
   163  
   164  	exit_if(argc < 2,
   165  		"Usage: %s /path/to/root", argv[0]);
   166  
   167  	root = argv[1];
   168  
   169  	/* Make stage2's root a mount point. Chrooting an application in a
   170  	 * directory which is not a mount point is not nice because the
   171  	 * application would not be able to remount "/" it as private mount.
   172  	 * This allows Docker to run inside rkt.
   173  	 * The recursive flag is to preserve volumes mounted previously by
   174  	 * systemd-nspawn via "rkt run -volume".
   175  	 * */
   176  	pexit_if(mount(root, root, "bind", MS_BIND | MS_REC, NULL) == -1,
   177  			"Make / a mount point failed");
   178  
   179  	rootfd = open(root, O_DIRECTORY | O_CLOEXEC);
   180  	pexit_if(rootfd < 0,
   181  		"Failed to open directory \"%s\"", root);
   182  
   183  	/* Some images have annoying symlinks that are resolved as dangling
   184  	 * links before the chroot in stage1. E.g. "/dev/shm" -> "/run/shm"
   185  	 * Just remove the symlinks.
   186           */
   187  	for (i = 0; unlink_paths[i]; i++) {
   188  		pexit_if(unlinkat(rootfd, unlink_paths[i], 0) != 0
   189  			 && errno != ENOENT && errno != EISDIR,
   190  			 "Failed to unlink \"%s\"", unlink_paths[i])
   191  	}
   192  
   193  	/* Create the directories */
   194  	umask(0);
   195  	for (i = 0; i < nelems(dirs); i++) {
   196  		const dir_op_t *d = &dirs[i];
   197  		pexit_if(mkdirat(rootfd, d->name, d->mode) == -1 &&
   198  			 errno != EEXIST,
   199  			"Failed to create directory \"%s/%s\"", root, d->name);
   200  	}
   201  
   202  	exit_if(!ensure_etc_hosts_exists(root, rootfd),
   203  		"Failed to ensure \"%s/etc/hosts\" exists", root);
   204  
   205  	close(rootfd);
   206  
   207  	/* systemd-nspawn already creates few /dev entries in the container
   208  	 * namespace: copy_devnodes()
   209  	 * http://cgit.freedesktop.org/systemd/systemd/tree/src/nspawn/nspawn.c?h=v219#n1345
   210  	 *
   211  	 * But they are not visible by the apps because they are "protected" by
   212  	 * the chroot.
   213  	 *
   214  	 * Bind mount them individually over the chroot border.
   215  	 *
   216  	 * Do NOT bind mount the whole directory /dev because it would shadow
   217  	 * potential individual bind mount by stage0 ("rkt run --volume...").
   218  	 *
   219  	 * Do NOT use mknod, it would not work for /dev/console because it is
   220  	 * a bind mount to a pts and pts device nodes only work when they live
   221  	 * on a devpts filesystem.
   222  	 */
   223  	for (i = 0; devnodes[i]; i++) {
   224  		const char *from = devnodes[i];
   225  		int fd;
   226  
   227  		/* If the file does not exist, skip it. It might be because
   228  		 * the kernel does not provide it (e.g. kernel compiled without
   229  		 * CONFIG_TUN) or because systemd-nspawn does not provide it
   230  		 * (/dev/net/tun is not available with systemd-nspawn < v217
   231  		 */
   232  		if (access(from, F_OK) != 0)
   233  			continue;
   234  
   235  		exit_if(snprintf(to, sizeof(to), "%s%s", root, from) >= sizeof(to),
   236  			"Path too long: \"%s\"", to);
   237  
   238  		/* The mode does not matter: it will be bind-mounted over.
   239  		 */
   240  		fd = open(to, O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY, 0644);
   241  		if (fd != -1)
   242  			close(fd);
   243  
   244  		pexit_if(mount(from, to, "bind", MS_BIND, NULL) == -1,
   245  				"Mounting \"%s\" on \"%s\" failed", from, to);
   246  	}
   247  
   248  	/* Bind mount directories */
   249  	for (i = 0; i < nelems(dirs_mount_table); i++) {
   250  		const mount_point *mnt = &dirs_mount_table[i];
   251  
   252  		exit_if(snprintf(to, sizeof(to), "%s/%s", root, mnt->target) >= sizeof(to),
   253  			"Path too long: \"%s\"", to);
   254  		pexit_if(mount(mnt->source, to, mnt->type,
   255  			       mnt->flags, mnt->options) == -1,
   256  				"Mounting \"%s\" on \"%s\" failed", mnt->source, to);
   257  	}
   258  
   259  	/* Bind mount files, if the source exists */
   260  	for (i = 0; i < nelems(files_mount_table); i++) {
   261  		const mount_point *mnt = &files_mount_table[i];
   262  		int fd;
   263  
   264  		exit_if(snprintf(to, sizeof(to), "%s/%s", root, mnt->target) >= sizeof(to),
   265  			"Path too long: \"%s\"", to);
   266  		if (access(mnt->source, F_OK) != 0)
   267  			continue;
   268  		if (access(to, F_OK) != 0) {
   269  			pexit_if((fd = creat(to, 0644)) == -1,
   270  				"Cannot create file: \"%s\"", to);
   271  			pexit_if(close(fd) == -1,
   272  				"Cannot close file: \"%s\"", to);
   273  		}
   274  		pexit_if(mount(mnt->source, to, mnt->type,
   275  			       mnt->flags, mnt->options) == -1,
   276  				"Mounting \"%s\" on \"%s\" failed", mnt->source, to);
   277  	}
   278  
   279  	/* /dev/ptmx -> /dev/pts/ptmx */
   280  	exit_if(snprintf(to, sizeof(to), "%s/dev/ptmx", root) >= sizeof(to),
   281  		"Path too long: \"%s\"", to);
   282  	pexit_if(symlink("/dev/pts/ptmx", to) == -1,
   283  		"Failed to create /dev/ptmx symlink");
   284  
   285  	return EXIT_SUCCESS;
   286  }