func main() { root := "." debug := len(os.Args) > 1 && os.Args[1] == "debug" c, err := LoadContainer(root) if err != nil { fmt.Fprintf(os.Stderr, "Failed to load container: %v\n", err) os.Exit(1) } mirrorLocalZoneInfo(c.Root) if err = c.ContainerToSystemd(); err != nil { fmt.Fprintf(os.Stderr, "Failed to configure systemd: %v\n", err) os.Exit(2) } args := []string{ filepath.Join(path.Stage1RootfsPath(c.Root), interpBin), filepath.Join(path.Stage1RootfsPath(c.Root), nspawnBin), "--boot", // Launch systemd in the container "--register", "false", // We cannot assume the host system is running systemd } if !debug { args = append(args, "--quiet") // silence most nspawn output (log_warning is currently not covered by this) } nsargs, err := c.ContainerToNspawnArgs() if err != nil { fmt.Fprintf(os.Stderr, "Failed to generate nspawn args: %v\n", err) os.Exit(4) } args = append(args, nsargs...) // Arguments to systemd args = append(args, "--") args = append(args, "--default-standard-output=tty") // redirect all service logs straight to tty if !debug { args = append(args, "--log-target=null") // silence systemd output inside container args = append(args, "--show-status=0") // silence systemd initialization status output } env := os.Environ() env = append(env, "LD_PRELOAD="+filepath.Join(path.Stage1RootfsPath(c.Root), "fakesdboot.so")) env = append(env, "LD_LIBRARY_PATH="+filepath.Join(path.Stage1RootfsPath(c.Root), "usr/lib")) if err := syscall.Exec(args[0], args, env); err != nil { fmt.Fprintf(os.Stderr, "Failed to execute nspawn: %v\n", err) os.Exit(5) } }
// mirrorLocalZoneInfo tries to reproduce the /etc/localtime target in stage1/ to satisfy systemd-nspawn func mirrorLocalZoneInfo(root string) { zif, err := os.Readlink("/etc/localtime") if err != nil { return } src, err := os.Open(zif) if err != nil { return } defer src.Close() destp := filepath.Join(path.Stage1RootfsPath(root), zif) if err = os.MkdirAll(filepath.Dir(destp), 0755); err != nil { return } dest, err := os.OpenFile(destp, os.O_CREATE|os.O_WRONLY, 0644) if err != nil { return } defer dest.Close() _, _ = io.Copy(dest, src) }
// ContainerToNspawnArgs renders a prepared Container as a systemd-nspawn // argument list ready to be executed func (c *Container) ContainerToNspawnArgs() ([]string, error) { args := []string{ "--uuid=" + c.Manifest.UUID.String(), "--directory=" + rktpath.Stage1RootfsPath(c.Root), } for _, am := range c.Apps { a := c.Manifest.Apps.Get(am.Name) if a == nil { panic("could not find app in container manifest!") } aa, err := c.appToNspawnArgs(am, a.ImageID) if err != nil { return nil, fmt.Errorf("failed to construct args for app %q: %v", am.Name, err) } args = append(args, aa...) } return args, nil }
// Setup sets up a filesystem for a container based on the given config. // The directory containing the filesystem is returned, and any error encountered. func Setup(cfg Config) (string, error) { if cfg.Debug { log.SetOutput(os.Stderr) } cuuid, err := types.NewUUID(uuid.New()) if err != nil { return "", fmt.Errorf("error creating UID: %v", err) } // TODO(jonboulle): collision detection/mitigation // Create a directory for this container dir := filepath.Join(cfg.ContainersDir, cuuid.String()) if err := os.MkdirAll(dir, 0700); err != nil { return "", fmt.Errorf("error creating directory: %v", err) } log.Printf("Unpacking stage1 rootfs") if cfg.Stage1Rootfs != "" { err = unpackRootfs(cfg.Stage1Rootfs, rktpath.Stage1RootfsPath(dir)) } else { err = unpackBuiltinRootfs(rktpath.Stage1RootfsPath(dir)) } if err != nil { return "", fmt.Errorf("error unpacking rootfs: %v", err) } log.Printf("Writing stage1 init") var in io.Reader if cfg.Stage1Init != "" { in, err = os.Open(cfg.Stage1Init) if err != nil { return "", fmt.Errorf("error loading stage1 init binary: %v", err) } } else { init_bin, err := stage1_init.Asset("s1init") if err != nil { return "", fmt.Errorf("error accessing stage1 init bindata: %v", err) } in = bytes.NewBuffer(init_bin) } fn := filepath.Join(dir, initPath) out, err := os.OpenFile(fn, os.O_CREATE|os.O_WRONLY, 0555) if err != nil { return "", fmt.Errorf("error opening stage1 init for writing: %v", err) } if _, err := io.Copy(out, in); err != nil { return "", fmt.Errorf("error writing stage1 init: %v", err) } if err := out.Close(); err != nil { return "", fmt.Errorf("error closing stage1 init: %v", err) } log.Printf("Wrote filesystem to %s\n", dir) cm := schema.ContainerRuntimeManifest{ ACKind: "ContainerRuntimeManifest", UUID: *cuuid, Apps: make(schema.AppList, 0), } v, err := types.NewSemVer(version.Version) if err != nil { return "", fmt.Errorf("error creating version: %v", err) } cm.ACVersion = *v for _, img := range cfg.Images { am, err := setupImage(cfg, img, dir) if err != nil { return "", fmt.Errorf("error setting up image %s: %v", img, err) } if cm.Apps.Get(am.Name) != nil { return "", fmt.Errorf("error: multiple apps with name %s", am.Name) } a := schema.App{ Name: am.Name, ImageID: img, Isolators: am.Isolators, Annotations: am.Annotations, } cm.Apps = append(cm.Apps, a) } var sVols []types.Volume for key, path := range cfg.Volumes { v := types.Volume{ Kind: "host", Source: path, ReadOnly: true, Fulfills: []types.ACName{ types.ACName(key), }, } sVols = append(sVols, v) } // TODO(jonboulle): check that app mountpoint expectations are // satisfied here, rather than waiting for stage1 cm.Volumes = sVols cdoc, err := json.Marshal(cm) if err != nil { return "", fmt.Errorf("error marshalling container manifest: %v", err) } log.Printf("Writing container manifest") fn = rktpath.ContainerManifestPath(dir) if err := ioutil.WriteFile(fn, cdoc, 0700); err != nil { return "", fmt.Errorf("error writing container manifest: %v", err) } return dir, nil }