func NewBuilder(podRoot string, podUUID *types.UUID) (*Builder, error) { pod, err := stage1commontypes.LoadPod(podRoot, podUUID) if err != nil { logs.WithError(err).Fatal("Failed to load pod") } if len(pod.Manifest.Apps) != 1 { logs.Fatal("dgr builder support only 1 application") } fields := data.WithField("aci", manifestApp(pod).Name) aciPath, ok := manifestApp(pod).App.Environment.Get(common.EnvAciPath) if !ok || aciPath == "" { return nil, errs.WithF(fields, "Builder image require "+common.EnvAciPath+" environment variable") } aciTarget, ok := manifestApp(pod).App.Environment.Get(common.EnvAciTarget) if !ok || aciPath == "" { return nil, errs.WithF(fields, "Builder image require "+common.EnvAciTarget+" environment variable") } return &Builder{ fields: fields, aciHomePath: aciPath, aciTargetPath: aciTarget, pod: pod, stage1Rootfs: rktcommon.Stage1RootfsPath(pod.Root), stage2Rootfs: filepath.Join(rktcommon.AppPath(pod.Root, manifestApp(pod).Name), "rootfs"), }, nil }
// prepareAppImage renders and verifies the tree cache of the app image that // corresponds to the given app name. // When useOverlay is false, it attempts to render and expand the app image func prepareAppImage(cfg PrepareConfig, appName types.ACName, img types.Hash, cdir string, useOverlay bool) error { log.Println("Loading image", img.String()) if useOverlay { if err := cfg.Store.RenderTreeStore(img.String(), false); err != nil { return fmt.Errorf("error rendering tree image: %v", err) } if err := cfg.Store.CheckTreeStore(img.String()); err != nil { log.Printf("Warning: tree cache is in a bad state. Rebuilding...") if err := cfg.Store.RenderTreeStore(img.String(), true); err != nil { return fmt.Errorf("error rendering tree image: %v", err) } } } else { ad := common.AppPath(cdir, appName) err := os.MkdirAll(ad, 0755) if err != nil { return fmt.Errorf("error creating image directory: %v", err) } if err := aci.RenderACIWithImageID(img, ad, cfg.Store); err != nil { return fmt.Errorf("error rendering ACI: %v", err) } } return nil }
// prepareAppImage renders and verifies the tree cache of the app image that // corresponds to the given app name. // When useOverlay is false, it attempts to render and expand the app image func prepareAppImage(cfg PrepareConfig, appName types.ACName, img types.Hash, cdir string, useOverlay bool) error { log.Println("Loading image", img.String()) am, err := cfg.Store.GetImageManifest(img.String()) if err != nil { return fmt.Errorf("error getting the manifest: %v", err) } if _, hasOS := am.Labels.Get("os"); !hasOS { return fmt.Errorf("missing os label in the image manifest") } if _, hasArch := am.Labels.Get("arch"); !hasArch { return fmt.Errorf("missing arch label in the image manifest") } if err := types.IsValidOSArch(am.Labels.ToMap(), ValidOSArch); err != nil { return err } appInfoDir := common.AppInfoPath(cdir, appName) if err := os.MkdirAll(appInfoDir, 0755); err != nil { return fmt.Errorf("error creating apps info directory: %v", err) } if useOverlay { if cfg.PrivateUsers.Shift > 0 { return fmt.Errorf("cannot use both overlay and user namespace: not implemented yet. (Try --no-overlay)") } treeStoreID, err := cfg.Store.RenderTreeStore(img.String(), false) if err != nil { return fmt.Errorf("error rendering tree image: %v", err) } if err := cfg.Store.CheckTreeStore(treeStoreID); err != nil { log.Printf("Warning: tree cache is in a bad state: %v. Rebuilding...", err) var err error if treeStoreID, err = cfg.Store.RenderTreeStore(img.String(), true); err != nil { return fmt.Errorf("error rendering tree image: %v", err) } } if err := ioutil.WriteFile(common.AppTreeStoreIDPath(cdir, appName), []byte(treeStoreID), 0700); err != nil { return fmt.Errorf("error writing app treeStoreID: %v", err) } } else { ad := common.AppPath(cdir, appName) err := os.MkdirAll(ad, 0755) if err != nil { return fmt.Errorf("error creating image directory: %v", err) } if err := aci.RenderACIWithImageID(img, ad, cfg.Store, cfg.PrivateUsers); err != nil { return fmt.Errorf("error rendering ACI: %v", err) } } if err := writeManifest(cfg.CommonConfig, img, appInfoDir); err != nil { return err } return nil }
// deletePod cleans up files and resource associated with the pod // pod must be under exclusive lock and be in either ExitedGarbage // or Garbage state func deletePod(p *pod) { if !p.isExitedGarbage && !p.isGarbage { panic(fmt.Sprintf("logic error: deletePod called with non-garbage pod %q (status %q)", p.uuid, p.getState())) } if p.isExitedGarbage { s, err := store.NewStore(getDataDir()) if err != nil { stderr("Cannot open store: %v", err) return } defer s.Close() stage1TreeStoreID, err := p.getStage1TreeStoreID() if err != nil { stderr("Error getting stage1 treeStoreID: %v", err) return } stage1RootFS := s.GetTreeStoreRootFS(stage1TreeStoreID) // execute stage1's GC if err := stage0.GC(p.path(), p.uuid, stage1RootFS, globalFlags.Debug); err != nil { stderr("Stage1 GC of pod %q failed: %v", p.uuid, err) return } if p.usesOverlay() { apps, err := p.getApps() if err != nil { stderr("Error retrieving app hashes from pod manifest: %v", err) return } for _, a := range apps { dest := filepath.Join(common.AppPath(p.path(), a.Name), "rootfs") if err := syscall.Unmount(dest, 0); err != nil { // machine could have been rebooted and mounts lost. // ignore "does not exist" and "not a mount point" errors if err != syscall.ENOENT && err != syscall.EINVAL { stderr("Error unmounting app at %v: %v", dest, err) } } } s1 := filepath.Join(common.Stage1ImagePath(p.path()), "rootfs") if err := syscall.Unmount(s1, 0); err != nil { // machine could have been rebooted and mounts lost. // ignore "does not exist" and "not a mount point" errors if err != syscall.ENOENT && err != syscall.EINVAL { stderr("Error unmounting stage1 at %v: %v", s1, err) return } } } } if err := os.RemoveAll(p.path()); err != nil { stderr("Unable to remove pod %q: %v", p.uuid, err) } }
// setupAppImage mounts the overlay filesystem for the app image that // corresponds to the given hash. Then, it creates the tmp directory. // When useOverlay is false it just creates the tmp directory for this app. func setupAppImage(cfg RunConfig, appName types.ACName, img types.Hash, cdir string, useOverlay bool) error { ad := common.AppPath(cdir, appName) if useOverlay { err := os.MkdirAll(ad, 0776) if err != nil { return fmt.Errorf("error creating image directory: %v", err) } if err := overlayRender(cfg, img, cdir, ad, appName.String()); err != nil { return fmt.Errorf("error rendering overlay filesystem: %v", err) } } return nil }
// setupAppImage mounts the overlay filesystem for the app image that // corresponds to the given hash if useOverlay is true. // It also creates an mtab file in the application's rootfs if one is not // present. func setupAppImage(cfg RunConfig, appName types.ACName, img types.Hash, cdir string, useOverlay bool) error { ad := common.AppPath(cdir, appName) if useOverlay { err := os.MkdirAll(ad, common.DefaultRegularDirPerm) if err != nil { return errwrap.Wrap(errors.New("error creating image directory"), err) } treeStoreID, err := ioutil.ReadFile(common.AppTreeStoreIDPath(cdir, appName)) if err != nil { return err } if err := copyAppManifest(cdir, appName, ad); err != nil { return err } if err := overlayRender(cfg, string(treeStoreID), cdir, ad, appName.String()); err != nil { return errwrap.Wrap(errors.New("error rendering overlay filesystem"), err) } } return ensureMtabExists(filepath.Join(ad, "rootfs")) }
// setupAppImage mounts the overlay filesystem for the app image that // corresponds to the given hash. Then, it creates the tmp directory. // When useOverlay is false it just creates the tmp directory for this app. func setupAppImage(cfg RunConfig, appName types.ACName, img types.Hash, cdir string, useOverlay bool) error { ad := common.AppPath(cdir, appName) if useOverlay { err := os.MkdirAll(ad, defaultRegularDirPerm) if err != nil { return fmt.Errorf("error creating image directory: %v", err) } treeStoreID, err := ioutil.ReadFile(common.AppTreeStoreIDPath(cdir, appName)) if err != nil { return err } if err := copyAppManifest(cdir, appName, ad); err != nil { return err } if err := overlayRender(cfg, string(treeStoreID), cdir, ad, appName.String()); err != nil { return fmt.Errorf("error rendering overlay filesystem: %v", err) } } return nil }
func stage1(rp *stage1commontypes.RuntimePod) int { uuid, err := types.NewUUID(flag.Arg(0)) if err != nil { log.Print("UUID is missing or malformed\n") return 254 } root := "." p, err := stage1commontypes.LoadPod(root, uuid, rp) if err != nil { log.PrintE("can't load pod", err) return 254 } if err := p.SaveRuntime(); err != nil { log.FatalE("failed to save runtime parameters", err) } // Sanity checks if len(p.Manifest.Apps) != 1 { log.Printf("flavor %q only supports 1 application per Pod for now", flavor) return 254 } ra := p.Manifest.Apps[0] imgName := p.AppNameToImageName(ra.Name) args := ra.App.Exec if len(args) == 0 { log.Printf(`image %q has an empty "exec" (try --exec=BINARY)`, imgName) return 254 } lfd, err := common.GetRktLockFD() if err != nil { log.PrintE("can't get rkt lock fd", err) return 254 } // set close-on-exec flag on RKT_LOCK_FD so it gets correctly closed after execution is finished if err := sys.CloseOnExec(lfd, true); err != nil { log.PrintE("can't set FD_CLOEXEC on rkt lock", err) return 254 } workDir := "/" if ra.App.WorkingDirectory != "" { workDir = ra.App.WorkingDirectory } env := []string{"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"} for _, e := range ra.App.Environment { env = append(env, e.Name+"="+e.Value) } rfs := filepath.Join(common.AppPath(p.Root, ra.Name), "rootfs") argFlyMounts, err := evaluateMounts(rfs, string(ra.Name), p) if err != nil { log.PrintE("can't evaluate mounts", err) return 254 } effectiveMounts := append( []flyMount{ {"", "", "/dev", "none", syscall.MS_REC | syscall.MS_SHARED}, {"/dev", rfs, "/dev", "none", syscall.MS_BIND | syscall.MS_REC}, {"", "", "/proc", "none", syscall.MS_REC | syscall.MS_SHARED}, {"/proc", rfs, "/proc", "none", syscall.MS_BIND | syscall.MS_REC}, {"", "", "/sys", "none", syscall.MS_REC | syscall.MS_SHARED}, {"/sys", rfs, "/sys", "none", syscall.MS_BIND | syscall.MS_REC}, {"tmpfs", rfs, "/tmp", "tmpfs", 0}, }, argFlyMounts..., ) /* Process DNS config files * * /etc/resolv.conf: four modes * 'host' - bind-mount host's file * 'stage0' - bind-mount the file created by stage0 * 'default' - do nothing (we would respect CNI if fly had networking) * 'none' - do nothing */ switch p.ResolvConfMode { case "host": effectiveMounts = append(effectiveMounts, flyMount{"/etc/resolv.conf", rfs, "/etc/resolv.conf", "none", syscall.MS_BIND | syscall.MS_RDONLY}) case "stage0": if err := copyResolv(p); err != nil { log.PrintE("can't copy /etc/resolv.conf", err) return 254 } } /* * /etc/hosts: three modes: * 'host' - bind-mount hosts's file * 'stage0' - bind mount the file created by stage1 * 'default' - create a stub /etc/hosts if needed */ switch p.EtcHostsMode { case "host": effectiveMounts = append(effectiveMounts, flyMount{"/etc/hosts", rfs, "/etc/hosts", "none", syscall.MS_BIND | syscall.MS_RDONLY}) case "stage0": effectiveMounts = append(effectiveMounts, flyMount{ filepath.Join(common.Stage1RootfsPath(p.Root), "etc", "rkt-hosts"), rfs, "/etc/hosts", "none", syscall.MS_BIND | syscall.MS_RDONLY}) case "default": stage2HostsPath := filepath.Join(common.AppRootfsPath(p.Root, ra.Name), "etc", "hosts") if _, err := os.Stat(stage2HostsPath); err != nil && os.IsNotExist(err) { fallbackHosts := []byte("127.0.0.1 localhost localdomain\n") ioutil.WriteFile(stage2HostsPath, fallbackHosts, 0644) } } for _, mount := range effectiveMounts { diag.Printf("Processing %+v", mount) var ( err error hostPathInfo os.FileInfo targetPathInfo os.FileInfo ) if strings.HasPrefix(mount.HostPath, "/") { if hostPathInfo, err = os.Stat(mount.HostPath); err != nil { log.PrintE(fmt.Sprintf("stat of host path %s", mount.HostPath), err) return 254 } } else { hostPathInfo = nil } absTargetPath := filepath.Join(mount.TargetPrefixPath, mount.RelTargetPath) if targetPathInfo, err = os.Stat(absTargetPath); err != nil && !os.IsNotExist(err) { log.PrintE(fmt.Sprintf("stat of target path %s", absTargetPath), err) return 254 } switch { case (mount.Flags & syscall.MS_REMOUNT) != 0: { diag.Printf("don't attempt to create files for remount of %q", absTargetPath) } case targetPathInfo == nil: absTargetPathParent, _ := filepath.Split(absTargetPath) if err := os.MkdirAll(absTargetPathParent, 0755); err != nil { log.PrintE(fmt.Sprintf("can't create directory %q", absTargetPath), err) return 254 } switch { case hostPathInfo == nil || hostPathInfo.IsDir(): if err := os.Mkdir(absTargetPath, 0755); err != nil { log.PrintE(fmt.Sprintf("can't create directory %q", absTargetPath), err) return 254 } case !hostPathInfo.IsDir(): file, err := os.OpenFile(absTargetPath, os.O_CREATE, 0700) if err != nil { log.PrintE(fmt.Sprintf("can't create file %q", absTargetPath), err) return 254 } file.Close() } case hostPathInfo != nil: switch { case hostPathInfo.IsDir() && !targetPathInfo.IsDir(): log.Printf("can't mount because %q is a directory while %q is not", mount.HostPath, absTargetPath) return 254 case !hostPathInfo.IsDir() && targetPathInfo.IsDir(): log.Printf("can't mount because %q is not a directory while %q is", mount.HostPath, absTargetPath) return 254 } } if err := syscall.Mount(mount.HostPath, absTargetPath, mount.Fs, mount.Flags, ""); err != nil { log.PrintE(fmt.Sprintf("can't mount %q on %q with flags %v", mount.HostPath, absTargetPath, mount.Flags), err) return 254 } } if err = stage1common.WritePid(os.Getpid(), "pid"); err != nil { log.Error(err) return 254 } var uidResolver, gidResolver user.Resolver var uid, gid int uidResolver, err = user.NumericIDs(ra.App.User) if err != nil { uidResolver, err = user.IDsFromStat(rfs, ra.App.User, nil) } if err != nil { // give up log.PrintE(fmt.Sprintf("invalid user %q", ra.App.User), err) return 254 } if uid, _, err = uidResolver.IDs(); err != nil { log.PrintE(fmt.Sprintf("failed to configure user %q", ra.App.User), err) return 254 } gidResolver, err = user.NumericIDs(ra.App.Group) if err != nil { gidResolver, err = user.IDsFromStat(rfs, ra.App.Group, nil) } if err != nil { // give up log.PrintE(fmt.Sprintf("invalid group %q", ra.App.Group), err) return 254 } if _, gid, err = gidResolver.IDs(); err != nil { log.PrintE(fmt.Sprintf("failed to configure group %q", ra.App.Group), err) return 254 } diag.Printf("chroot to %q", rfs) if err := syscall.Chroot(rfs); err != nil { log.PrintE("can't chroot", err) return 254 } if err := os.Chdir(workDir); err != nil { log.PrintE(fmt.Sprintf("can't change to working directory %q", workDir), err) return 254 } // lock the current goroutine to its current OS thread. // This will force the subsequent syscalls to be executed in the same OS thread as Setresuid, and Setresgid, // see https://github.com/golang/go/issues/1435#issuecomment-66054163. runtime.LockOSThread() diag.Printf("setting uid %d gid %d", uid, gid) if err := syscall.Setresgid(gid, gid, gid); err != nil { log.PrintE(fmt.Sprintf("can't set gid %d", gid), err) return 254 } if err := syscall.Setresuid(uid, uid, uid); err != nil { log.PrintE(fmt.Sprintf("can't set uid %d", uid), err) return 254 } diag.Printf("execing %q in %q", args, rfs) err = stage1common.WithClearedCloExec(lfd, func() error { return syscall.Exec(args[0], args, env) }) if err != nil { log.PrintE(fmt.Sprintf("can't execute %q", args[0]), err) return 254 } return 0 }
func stage1() int { uuid, err := types.NewUUID(flag.Arg(0)) if err != nil { log.Print("UUID is missing or malformed\n") return 1 } root := "." p, err := stage1commontypes.LoadPod(root, uuid) if err != nil { log.PrintE("can't load pod", err) return 1 } // Sanity checks if len(p.Manifest.Apps) != 1 { log.Printf("flavor %q only supports 1 application per Pod for now", flavor) return 1 } ra := p.Manifest.Apps[0] imgName := p.AppNameToImageName(ra.Name) args := ra.App.Exec if len(args) == 0 { log.Printf(`image %q has an empty "exec" (try --exec=BINARY)`, imgName) return 1 } lfd, err := common.GetRktLockFD() if err != nil { log.PrintE("can't get rkt lock fd", err) return 1 } // set close-on-exec flag on RKT_LOCK_FD so it gets correctly closed after execution is finished if err := sys.CloseOnExec(lfd, true); err != nil { log.PrintE("can't set FD_CLOEXEC on rkt lock", err) return 1 } workDir := "/" if ra.App.WorkingDirectory != "" { workDir = ra.App.WorkingDirectory } env := []string{"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"} for _, e := range ra.App.Environment { env = append(env, e.Name+"="+e.Value) } rfs := filepath.Join(common.AppPath(p.Root, ra.Name), "rootfs") if err := copyResolv(p); err != nil { log.PrintE("can't copy /etc/resolv.conf", err) return 1 } argFlyMounts, err := evaluateMounts(rfs, string(ra.Name), p) if err != nil { log.PrintE("can't evaluate mounts", err) return 1 } effectiveMounts := append( []flyMount{ {"", "", "/dev", "none", syscall.MS_REC | syscall.MS_SHARED}, {"/dev", rfs, "/dev", "none", syscall.MS_BIND | syscall.MS_REC}, {"", "", "/proc", "none", syscall.MS_REC | syscall.MS_SHARED}, {"/proc", rfs, "/proc", "none", syscall.MS_BIND | syscall.MS_REC}, {"", "", "/sys", "none", syscall.MS_REC | syscall.MS_SHARED}, {"/sys", rfs, "/sys", "none", syscall.MS_BIND | syscall.MS_REC}, {"tmpfs", rfs, "/tmp", "tmpfs", 0}, }, argFlyMounts..., ) for _, mount := range effectiveMounts { var ( err error hostPathInfo os.FileInfo targetPathInfo os.FileInfo ) if strings.HasPrefix(mount.HostPath, "/") { if hostPathInfo, err = os.Stat(mount.HostPath); err != nil { log.PrintE(fmt.Sprintf("stat of host path %s", mount.HostPath), err) return 1 } } else { hostPathInfo = nil } absTargetPath := filepath.Join(mount.TargetPrefixPath, mount.RelTargetPath) if targetPathInfo, err = os.Stat(absTargetPath); err != nil && !os.IsNotExist(err) { log.PrintE(fmt.Sprintf("stat of target path %s", absTargetPath), err) return 1 } switch { case targetPathInfo == nil: absTargetPathParent, _ := filepath.Split(absTargetPath) if err := os.MkdirAll(absTargetPathParent, 0755); err != nil { log.PrintE(fmt.Sprintf("can't create directory %q", absTargetPath), err) return 1 } switch { case hostPathInfo == nil || hostPathInfo.IsDir(): if err := os.Mkdir(absTargetPath, 0755); err != nil { log.PrintE(fmt.Sprintf("can't create directory %q", absTargetPath), err) return 1 } case !hostPathInfo.IsDir(): file, err := os.OpenFile(absTargetPath, os.O_CREATE, 0700) if err != nil { log.PrintE(fmt.Sprintf("can't create file %q", absTargetPath), err) return 1 } file.Close() } case hostPathInfo != nil: switch { case hostPathInfo.IsDir() && !targetPathInfo.IsDir(): log.Printf("can't mount because %q is a directory while %q is not", mount.HostPath, absTargetPath) return 1 case !hostPathInfo.IsDir() && targetPathInfo.IsDir(): log.Printf("can't mount because %q is not a directory while %q is", mount.HostPath, absTargetPath) return 1 } } if err := syscall.Mount(mount.HostPath, absTargetPath, mount.Fs, mount.Flags, ""); err != nil { log.PrintE(fmt.Sprintf("can't mount %q on %q with flags %v", mount.HostPath, absTargetPath, mount.Flags), err) return 1 } } if err = stage1common.WritePid(os.Getpid(), "pid"); err != nil { log.Error(err) return 1 } var uidResolver, gidResolver user.Resolver var uid, gid int uidResolver, err = user.NumericIDs(ra.App.User) if err != nil { uidResolver, err = user.IDsFromStat(rfs, ra.App.User, nil) } if err != nil { // give up log.PrintE(fmt.Sprintf("invalid user %q", ra.App.User), err) return 1 } if uid, _, err = uidResolver.IDs(); err != nil { log.PrintE(fmt.Sprintf("failed to configure user %q", ra.App.User), err) return 1 } gidResolver, err = user.NumericIDs(ra.App.Group) if err != nil { gidResolver, err = user.IDsFromStat(rfs, ra.App.Group, nil) } if err != nil { // give up log.PrintE(fmt.Sprintf("invalid group %q", ra.App.Group), err) return 1 } if _, gid, err = gidResolver.IDs(); err != nil { log.PrintE(fmt.Sprintf("failed to configure group %q", ra.App.Group), err) return 1 } diag.Printf("chroot to %q", rfs) if err := syscall.Chroot(rfs); err != nil { log.PrintE("can't chroot", err) return 1 } if err := os.Chdir(workDir); err != nil { log.PrintE(fmt.Sprintf("can't change to working directory %q", workDir), err) return 1 } // lock the current goroutine to its current OS thread. // This will force the subsequent syscalls to be executed in the same OS thread as Setresuid, and Setresgid, // see https://github.com/golang/go/issues/1435#issuecomment-66054163. runtime.LockOSThread() diag.Printf("setting uid %d gid %d", uid, gid) if err := syscall.Setresgid(gid, gid, gid); err != nil { log.PrintE(fmt.Sprintf("can't set gid %d", gid), err) return 1 } if err := syscall.Setresuid(uid, uid, uid); err != nil { log.PrintE(fmt.Sprintf("can't set uid %d", uid), err) return 1 } diag.Printf("execing %q in %q", args, rfs) err = stage1common.WithClearedCloExec(lfd, func() error { return syscall.Exec(args[0], args, env) }) if err != nil { log.PrintE(fmt.Sprintf("can't execute %q", args[0]), err) return 1 } return 0 }
// prepareAppImage renders and verifies the tree cache of the app image that // corresponds to the given app name. // When useOverlay is false, it attempts to render and expand the app image func prepareAppImage(cfg PrepareConfig, appName types.ACName, img types.Hash, cdir string, useOverlay bool) error { debug("Loading image %s", img.String()) am, err := cfg.Store.GetImageManifest(img.String()) if err != nil { return fmt.Errorf("error getting the manifest: %v", err) } if _, hasOS := am.Labels.Get("os"); !hasOS { return fmt.Errorf("missing os label in the image manifest") } if _, hasArch := am.Labels.Get("arch"); !hasArch { return fmt.Errorf("missing arch label in the image manifest") } if err := types.IsValidOSArch(am.Labels.ToMap(), ValidOSArch); err != nil { return err } appInfoDir := common.AppInfoPath(cdir, appName) if err := os.MkdirAll(appInfoDir, defaultRegularDirPerm); err != nil { return fmt.Errorf("error creating apps info directory: %v", err) } if useOverlay { if cfg.PrivateUsers.Shift > 0 { return fmt.Errorf("cannot use both overlay and user namespace: not implemented yet. (Try --no-overlay)") } treeStoreID, _, err := cfg.Store.RenderTreeStore(img.String(), false) if err != nil { return fmt.Errorf("error rendering tree image: %v", err) } if !cfg.SkipTreeStoreCheck { hash, err := cfg.Store.CheckTreeStore(treeStoreID) if err != nil { log.Printf("Warning: tree cache is in a bad state: %v. Rebuilding...", err) var err error treeStoreID, hash, err = cfg.Store.RenderTreeStore(img.String(), true) if err != nil { return fmt.Errorf("error rendering tree image: %v", err) } } cfg.CommonConfig.RootHash = hash } if err := ioutil.WriteFile(common.AppTreeStoreIDPath(cdir, appName), []byte(treeStoreID), defaultRegularFilePerm); err != nil { return fmt.Errorf("error writing app treeStoreID: %v", err) } } else { ad := common.AppPath(cdir, appName) err := os.MkdirAll(ad, defaultRegularDirPerm) if err != nil { return fmt.Errorf("error creating image directory: %v", err) } shiftedUid, shiftedGid, err := cfg.PrivateUsers.ShiftRange(uint32(os.Getuid()), uint32(os.Getgid())) if err != nil { return fmt.Errorf("error getting uid, gid: %v", err) } if err := os.Chown(ad, int(shiftedUid), int(shiftedGid)); err != nil { return fmt.Errorf("error shifting app %q's stage2 dir: %v", appName, err) } if err := aci.RenderACIWithImageID(img, ad, cfg.Store, cfg.PrivateUsers); err != nil { return fmt.Errorf("error rendering ACI: %v", err) } } if err := writeManifest(*cfg.CommonConfig, img, appInfoDir); err != nil { return err } return nil }
func RmApp(cfg RmConfig) error { pod, err := pkgPod.PodFromUUIDString(cfg.DataDir, cfg.UUID.String()) if err != nil { return errwrap.Wrap(errors.New("error loading pod"), err) } defer pod.Close() debug("locking sandbox manifest") if err := pod.ExclusiveLockManifest(); err != nil { return errwrap.Wrap(errors.New("failed to lock sandbox manifest"), err) } defer pod.UnlockManifest() pm, err := pod.SandboxManifest() if err != nil { return errwrap.Wrap(errors.New("cannot remove application, sandbox validation failed"), err) } app := pm.Apps.Get(*cfg.AppName) if app == nil { return fmt.Errorf("error: nonexistent app %q", *cfg.AppName) } if cfg.PodPID > 0 { // Call app-stop and app-rm entrypoint only if the pod is still running. // Otherwise, there's not much we can do about it except unmounting/removing // the file system. args := []string{ fmt.Sprintf("--debug=%t", cfg.Debug), fmt.Sprintf("--app=%s", cfg.AppName), } ce := CrossingEntrypoint{ PodPath: cfg.PodPath, PodPID: cfg.PodPID, AppName: cfg.AppName.String(), EntrypointName: appStopEntrypoint, EntrypointArgs: args, Interactive: false, } if err := ce.Run(); err != nil { status, err := common.GetExitStatus(err) // ignore nonexistent units failing to stop. Exit status 5 // comes from systemctl and means the unit doesn't exist if err != nil { return err } else if status != 5 { return fmt.Errorf("exit status %d", status) } } ce.EntrypointName = appRmEntrypoint if err := ce.Run(); err != nil { return err } } if cfg.UsesOverlay { treeStoreID, err := ioutil.ReadFile(common.AppTreeStoreIDPath(cfg.PodPath, *cfg.AppName)) if err != nil { return err } appRootfs := common.AppRootfsPath(cfg.PodPath, *cfg.AppName) if err := syscall.Unmount(appRootfs, 0); err != nil { return err } ts := filepath.Join(cfg.PodPath, "overlay", string(treeStoreID)) if err := os.RemoveAll(ts); err != nil { return errwrap.Wrap(errors.New("error removing app info directory"), err) } } appInfoDir := common.AppInfoPath(cfg.PodPath, *cfg.AppName) if err := os.RemoveAll(appInfoDir); err != nil { return errwrap.Wrap(errors.New("error removing app info directory"), err) } if err := os.RemoveAll(common.AppPath(cfg.PodPath, *cfg.AppName)); err != nil { return err } appStatusPath := filepath.Join(common.Stage1RootfsPath(cfg.PodPath), "rkt", "status", cfg.AppName.String()) if err := os.Remove(appStatusPath); err != nil && !os.IsNotExist(err) { return err } envPath := filepath.Join(common.Stage1RootfsPath(cfg.PodPath), "rkt", "env", cfg.AppName.String()) if err := os.Remove(envPath); err != nil && !os.IsNotExist(err) { return err } for i, app := range pm.Apps { if app.Name == *cfg.AppName { pm.Apps = append(pm.Apps[:i], pm.Apps[i+1:]...) break } } return pod.UpdateManifest(pm, cfg.PodPath) }
func RmApp(cfg RmConfig) error { pod, err := pkgPod.PodFromUUIDString(cfg.DataDir, cfg.UUID.String()) if err != nil { return errwrap.Wrap(errors.New("error loading pod"), err) } defer pod.Close() debug("locking pod manifest") if err := pod.ExclusiveManifestLock(); err != nil { return errwrap.Wrap(errors.New("failed to lock pod manifest"), err) } defer pod.ManifestUnlock() _, pm, err := pod.PodManifest() if err != nil { return errwrap.Wrap(errors.New("error loading pod manifest"), err) } var mutable bool ms, ok := pm.Annotations.Get("coreos.com/rkt/stage1/mutable") if ok { mutable, err = strconv.ParseBool(ms) if err != nil { return errwrap.Wrap(errors.New("error parsing mutable annotation"), err) } } if !mutable { return errors.New("immutable pod: cannot remove application") } app := pm.Apps.Get(*cfg.AppName) if app == nil { return fmt.Errorf("error: nonexistent app %q", *cfg.AppName) } if cfg.PodPID > 0 { // Call app-stop and app-rm entrypoint only if the pod is still running. // Otherwise, there's not much we can do about it except unmounting/removing // the file system. args := []string{ fmt.Sprintf("--app=%s", cfg.AppName), } ce := CrossingEntrypoint{ PodPath: cfg.PodPath, PodPID: cfg.PodPID, AppName: cfg.AppName.String(), EntrypointName: appStopEntrypoint, EntrypointArgs: args, Interactive: false, } if err := ce.Run(); err != nil { status, err := common.GetExitStatus(err) // ignore nonexistent units failing to stop. Exit status 5 // comes from systemctl and means the unit doesn't exist if err != nil { return err } else if status != 5 { return fmt.Errorf("exit status %d", status) } } ce.EntrypointName = appRmEntrypoint if err := ce.Run(); err != nil { return err } } if cfg.UsesOverlay { treeStoreID, err := ioutil.ReadFile(common.AppTreeStoreIDPath(cfg.PodPath, *cfg.AppName)) if err != nil { return err } appRootfs := common.AppRootfsPath(cfg.PodPath, *cfg.AppName) if err := syscall.Unmount(appRootfs, 0); err != nil { return err } ts := filepath.Join(cfg.PodPath, "overlay", string(treeStoreID)) if err := os.RemoveAll(ts); err != nil { return errwrap.Wrap(errors.New("error removing app info directory"), err) } } appInfoDir := common.AppInfoPath(cfg.PodPath, *cfg.AppName) if err := os.RemoveAll(appInfoDir); err != nil { return errwrap.Wrap(errors.New("error removing app info directory"), err) } if err := os.RemoveAll(common.AppPath(cfg.PodPath, *cfg.AppName)); err != nil { return err } appStatusPath := filepath.Join(common.Stage1RootfsPath(cfg.PodPath), "rkt", "status", cfg.AppName.String()) if err := os.Remove(appStatusPath); err != nil && !os.IsNotExist(err) { return err } envPath := filepath.Join(common.Stage1RootfsPath(cfg.PodPath), "rkt", "env", cfg.AppName.String()) if err := os.Remove(envPath); err != nil && !os.IsNotExist(err) { return err } removeAppFromPodManifest(pm, cfg.AppName) if err := updatePodManifest(cfg.PodPath, pm); err != nil { return err } return nil }
func stage1() int { uuid, err := types.NewUUID(flag.Arg(0)) if err != nil { log.Print("UUID is missing or malformed\n") return 1 } root := "." p, err := stage1commontypes.LoadPod(root, uuid) if err != nil { log.PrintE("can't load pod", err) return 1 } if len(p.Manifest.Apps) != 1 { log.Printf("flavor %q only supports 1 application per Pod for now", flavor) return 1 } lfd, err := common.GetRktLockFD() if err != nil { log.PrintE("can't get rkt lock fd", err) return 1 } // set close-on-exec flag on RKT_LOCK_FD so it gets correctly closed after execution is finished if err := sys.CloseOnExec(lfd, true); err != nil { log.PrintE("can't set FD_CLOEXEC on rkt lock", err) return 1 } env := []string{"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"} for _, e := range p.Manifest.Apps[0].App.Environment { env = append(env, e.Name+"="+e.Value) } args := p.Manifest.Apps[0].App.Exec rfs := filepath.Join(common.AppPath(p.Root, p.Manifest.Apps[0].Name), "rootfs") argFlyMounts, err := evaluateMounts(rfs, string(p.Manifest.Apps[0].Name), p) if err != nil { log.PrintE("can't evaluate mounts", err) return 1 } effectiveMounts := append( []flyMount{ {"", "", "/dev", "none", syscall.MS_REC | syscall.MS_SHARED}, {"/dev", rfs, "/dev", "none", syscall.MS_BIND | syscall.MS_REC}, {"", "", "/proc", "none", syscall.MS_REC | syscall.MS_SHARED}, {"/proc", rfs, "/proc", "none", syscall.MS_BIND | syscall.MS_REC}, {"", "", "/sys", "none", syscall.MS_REC | syscall.MS_SHARED}, {"/sys", rfs, "/sys", "none", syscall.MS_BIND | syscall.MS_REC}, {"tmpfs", rfs, "/tmp", "tmpfs", 0}, }, argFlyMounts..., ) for _, mount := range effectiveMounts { var ( err error hostPathInfo os.FileInfo targetPathInfo os.FileInfo ) if strings.HasPrefix(mount.HostPath, "/") { if hostPathInfo, err = os.Stat(mount.HostPath); err != nil { log.PrintE(fmt.Sprintf("stat of host directory %s", mount.HostPath), err) return 1 } } else { hostPathInfo = nil } absTargetPath := filepath.Join(mount.TargetPrefixPath, mount.RelTargetPath) if targetPathInfo, err = os.Stat(absTargetPath); err != nil && !os.IsNotExist(err) { log.PrintE(fmt.Sprintf("stat of target directory %s", absTargetPath), err) return 1 } switch { case targetPathInfo == nil: absTargetPathParent, _ := filepath.Split(absTargetPath) if err := os.MkdirAll(absTargetPathParent, 0700); err != nil { log.PrintE(fmt.Sprintf("can't create directory %q", absTargetPath), err) return 1 } switch { case hostPathInfo == nil || hostPathInfo.IsDir(): if err := os.Mkdir(absTargetPath, 0700); err != nil { log.PrintE(fmt.Sprintf("can't create directory %q", absTargetPath), err) return 1 } case !hostPathInfo.IsDir(): file, err := os.OpenFile(absTargetPath, os.O_CREATE, 0700) if err != nil { log.PrintE(fmt.Sprintf("can't create file %q", absTargetPath), err) return 1 } file.Close() } case hostPathInfo != nil: switch { case hostPathInfo.IsDir() && !targetPathInfo.IsDir(): log.Printf("can't mount because %q is a directory while %q is not", mount.HostPath, absTargetPath) return 1 case !hostPathInfo.IsDir() && targetPathInfo.IsDir(): log.Printf("can't mount because %q is not a directory while %q is", mount.HostPath, absTargetPath) return 1 } } if err := syscall.Mount(mount.HostPath, absTargetPath, mount.Fs, mount.Flags, ""); err != nil { log.PrintE(fmt.Sprintf("can't mount %q on %q with flags %v", mount.HostPath, absTargetPath, mount.Flags), err) return 1 } } if err = stage1common.WritePpid(os.Getpid()); err != nil { log.Error(err) return 4 } diag.Printf("chroot to %q", rfs) if err := syscall.Chroot(rfs); err != nil { log.PrintE("can't chroot", err) return 1 } if err := os.Chdir("/"); err != nil { log.PrintE("can't change to root new directory", err) return 1 } diag.Printf("execing %q in %q", args, rfs) err = stage1common.WithClearedCloExec(lfd, func() error { return syscall.Exec(args[0], args, env) }) if err != nil { log.PrintE(fmt.Sprintf("can't execute %q", args[0]), err) return 7 } return 0 }
func runExport(cmd *cobra.Command, args []string) (exit int) { if len(args) != 2 { cmd.Usage() return 1 } outACI := args[1] ext := filepath.Ext(outACI) if ext != schema.ACIExtension { stderr.Printf("extension must be %s (given %s)", schema.ACIExtension, outACI) return 1 } p, err := getPodFromUUIDString(args[0]) if err != nil { stderr.PrintE("problem retrieving pod", err) return 1 } defer p.Close() if !p.isExited { stderr.Print("pod is not exited. Only exited pods can be exported") return 1 } app, err := getApp(p) if err != nil { stderr.PrintE("unable to find app", err) return 1 } root := common.AppPath(p.path(), app.Name) manifestPath := filepath.Join(common.AppInfoPath(p.path(), app.Name), aci.ManifestFile) if p.usesOverlay() { tmpDir := filepath.Join(getDataDir(), "tmp") if err := os.MkdirAll(tmpDir, common.DefaultRegularDirPerm); err != nil { stderr.PrintE("unable to create temp directory", err) return 1 } podDir, err := ioutil.TempDir(tmpDir, fmt.Sprintf("rkt-export-%s", p.uuid)) if err != nil { stderr.PrintE("unable to create export temp directory", err) return 1 } defer func() { if err := os.RemoveAll(podDir); err != nil { stderr.PrintE("problem removing temp directory", err) exit = 1 } }() mntDir := filepath.Join(podDir, "rootfs") if err := os.Mkdir(mntDir, common.DefaultRegularDirPerm); err != nil { stderr.PrintE("unable to create rootfs directory inside temp directory", err) return 1 } if err := mountOverlay(p, app, mntDir); err != nil { stderr.PrintE(fmt.Sprintf("couldn't mount directory at %s", mntDir), err) return 1 } defer func() { if err := syscall.Unmount(mntDir, 0); err != nil { stderr.PrintE(fmt.Sprintf("error unmounting directory %s", mntDir), err) exit = 1 } }() root = podDir } else { hasMPs, err := appHasMountpoints(p.path(), app.Name) if err != nil { stderr.PrintE("error parsing mountpoints", err) return 1 } if hasMPs { stderr.Printf("pod has remaining mountpoints. Only pods using overlayfs or with no mountpoints can be exported") return 1 } } // Check for user namespace (--private-user), if in use get uidRange var uidRange *user.UidRange privUserFile := filepath.Join(p.path(), common.PrivateUsersPreparedFilename) privUserContent, err := ioutil.ReadFile(privUserFile) if err == nil { uidRange = user.NewBlankUidRange() // The file was found, save uid & gid shift and count if err := uidRange.Deserialize(privUserContent); err != nil { stderr.PrintE(fmt.Sprintf("problem deserializing the content of %s", common.PrivateUsersPreparedFilename), err) return 1 } } if err = buildAci(root, manifestPath, outACI, uidRange); err != nil { stderr.PrintE("error building aci", err) return 1 } return 0 }
// TODO(iaguis): RmConfig? func RmApp(dir string, uuid *types.UUID, usesOverlay bool, appName *types.ACName, podPID int) error { p, err := stage1types.LoadPod(dir, uuid) if err != nil { return errwrap.Wrap(errors.New("error loading pod manifest"), err) } pm := p.Manifest var mutable bool ms, ok := pm.Annotations.Get("coreos.com/rkt/stage1/mutable") if ok { mutable, err = strconv.ParseBool(ms) if err != nil { return errwrap.Wrap(errors.New("error parsing mutable annotation"), err) } } if !mutable { return errors.New("immutable pod: cannot remove application") } app := pm.Apps.Get(*appName) if app == nil { return fmt.Errorf("error: nonexistent app %q", *appName) } treeStoreID, err := ioutil.ReadFile(common.AppTreeStoreIDPath(dir, *appName)) if err != nil { return err } eep, err := getStage1Entrypoint(dir, enterEntrypoint) if err != nil { return errwrap.Wrap(errors.New("error determining 'enter' entrypoint"), err) } args := []string{ uuid.String(), appName.String(), filepath.Join(common.Stage1RootfsPath(dir), eep), strconv.Itoa(podPID), } if err := callEntrypoint(dir, appStopEntrypoint, args); err != nil { return err } if err := callEntrypoint(dir, appRmEntrypoint, args); err != nil { return err } appInfoDir := common.AppInfoPath(dir, *appName) if err := os.RemoveAll(appInfoDir); err != nil { return errwrap.Wrap(errors.New("error removing app info directory"), err) } if usesOverlay { appRootfs := common.AppRootfsPath(dir, *appName) if err := syscall.Unmount(appRootfs, 0); err != nil { return err } ts := filepath.Join(dir, "overlay", string(treeStoreID)) if err := os.RemoveAll(ts); err != nil { return errwrap.Wrap(errors.New("error removing app info directory"), err) } } if err := os.RemoveAll(common.AppPath(dir, *appName)); err != nil { return err } appStatusPath := filepath.Join(common.Stage1RootfsPath(dir), "rkt", "status", appName.String()) if err := os.Remove(appStatusPath); err != nil && !os.IsNotExist(err) { return err } envPath := filepath.Join(common.Stage1RootfsPath(dir), "rkt", "env", appName.String()) if err := os.Remove(envPath); err != nil && !os.IsNotExist(err) { return err } removeAppFromPodManifest(pm, appName) if err := updatePodManifest(dir, pm); err != nil { return err } return nil }
// emptyExitedGarbage discards sufficiently aged pods from exitedGarbageDir() func emptyExitedGarbage(gracePeriod time.Duration) error { if err := walkPods(includeExitedGarbageDir, func(p *pod) { gp := p.path() st := &syscall.Stat_t{} if err := syscall.Lstat(gp, st); err != nil { if err != syscall.ENOENT { stderr("Unable to stat %q, ignoring: %v", gp, err) } return } if expiration := time.Unix(st.Ctim.Unix()).Add(gracePeriod); time.Now().After(expiration) { if err := p.ExclusiveLock(); err != nil { return } stdout("Garbage collecting pod %q", p.uuid) s, err := store.NewStore(globalFlags.Dir) if err != nil { stderr("Cannot open store: %v", err) return } stage1ID, err := p.getStage1Hash() if err != nil { stderr("Error getting stage1 hash") return } stage1RootFS := s.GetTreeStoreRootFS(stage1ID.String()) // execute stage1's GC if err := stage0.GC(p.path(), p.uuid, stage1RootFS, globalFlags.Debug); err != nil { stderr("Stage1 GC of pod %q failed: %v", p.uuid, err) return } if p.usesOverlay() { apps, err := p.getApps() if err != nil { stderr("Error retrieving app list from pod manifest: %v", err) return } for _, a := range apps { dest := filepath.Join(common.AppPath(p.path(), a.Name), "rootfs") if err := syscall.Unmount(dest, 0); err != nil { // machine could have been rebooted and mounts lost. // ignore "does not exist" and "not a mount point" errors if err != syscall.ENOENT && err != syscall.EINVAL { stderr("Error unmounting app at %v: %v", dest, err) } } } s1 := filepath.Join(common.Stage1ImagePath(p.path()), "rootfs") if err := syscall.Unmount(s1, 0); err != nil { // machine could have been rebooted and mounts lost. // ignore "does not exist" and "not a mount point" errors if err != syscall.ENOENT && err != syscall.EINVAL { stderr("Error unmounting stage1 at %v: %v", s1, err) return } } } if err := os.RemoveAll(gp); err != nil { stderr("Unable to remove pod %q: %v", p.uuid, err) } } }); err != nil { return err } return nil }
// TODO(iaguis): add override options for Exec, Environment (à la patch-manifest) func AddApp(cfg RunConfig, dir string, img *types.Hash) error { im, err := cfg.Store.GetImageManifest(img.String()) if err != nil { return err } appName, err := imageNameToAppName(im.Name) if err != nil { return err } p, err := stage1types.LoadPod(dir, cfg.UUID) if err != nil { return errwrap.Wrap(errors.New("error loading pod manifest"), err) } pm := p.Manifest var mutable bool ms, ok := pm.Annotations.Get("coreos.com/rkt/stage1/mutable") if ok { mutable, err = strconv.ParseBool(ms) if err != nil { return errwrap.Wrap(errors.New("error parsing mutable annotation"), err) } } if !mutable { return errors.New("immutable pod: cannot add application") } if pm.Apps.Get(*appName) != nil { return fmt.Errorf("error: multiple apps with name %s", *appName) } if im.App == nil { return fmt.Errorf("error: image %s has no app section)", img) } appInfoDir := common.AppInfoPath(dir, *appName) if err := os.MkdirAll(appInfoDir, common.DefaultRegularDirPerm); err != nil { return errwrap.Wrap(errors.New("error creating apps info directory"), err) } uidRange := user.NewBlankUidRange() // TODO(iaguis): DRY: refactor this var treeStoreID string if cfg.UseOverlay { treeStoreID, _, err := cfg.TreeStore.Render(img.String(), false) if err != nil { return errwrap.Wrap(errors.New("error rendering tree image"), err) } hash, err := cfg.TreeStore.Check(treeStoreID) if err != nil { log.PrintE("warning: tree cache is in a bad state. Rebuilding...", err) var err error treeStoreID, hash, err = cfg.TreeStore.Render(img.String(), true) if err != nil { return errwrap.Wrap(errors.New("error rendering tree image"), err) } } cfg.RootHash = hash if err := ioutil.WriteFile(common.AppTreeStoreIDPath(dir, *appName), []byte(treeStoreID), common.DefaultRegularFilePerm); err != nil { return errwrap.Wrap(errors.New("error writing app treeStoreID"), err) } } else { ad := common.AppPath(dir, *appName) err := os.MkdirAll(ad, common.DefaultRegularDirPerm) if err != nil { return errwrap.Wrap(errors.New("error creating image directory"), err) } privateUsers, err := preparedWithPrivateUsers(dir) if err != nil { log.FatalE("error reading user namespace information", err) } if err := uidRange.Deserialize([]byte(privateUsers)); err != nil { return err } shiftedUid, shiftedGid, err := uidRange.ShiftRange(uint32(os.Getuid()), uint32(os.Getgid())) if err != nil { return errwrap.Wrap(errors.New("error getting uid, gid"), err) } if err := os.Chown(ad, int(shiftedUid), int(shiftedGid)); err != nil { return errwrap.Wrap(fmt.Errorf("error shifting app %q's stage2 dir", *appName), err) } if err := aci.RenderACIWithImageID(*img, ad, cfg.Store, uidRange); err != nil { return errwrap.Wrap(errors.New("error rendering ACI"), err) } } if err := writeManifest(*cfg.CommonConfig, *img, appInfoDir); err != nil { return errwrap.Wrap(errors.New("error writing manifest"), err) } if err := setupAppImage(cfg, *appName, *img, dir, cfg.UseOverlay); err != nil { return fmt.Errorf("error setting up app image: %v", err) } if cfg.UseOverlay { imgDir := filepath.Join(dir, "overlay", treeStoreID) if err := os.Chown(imgDir, -1, cfg.RktGid); err != nil { return err } } ra := schema.RuntimeApp{ Name: *appName, App: im.App, Image: schema.RuntimeImage{ Name: &im.Name, ID: *img, Labels: im.Labels, }, // TODO(iaguis): default isolators } env := ra.App.Environment env.Set("AC_APP_NAME", appName.String()) envFilePath := filepath.Join(common.Stage1RootfsPath(dir), "rkt", "env", appName.String()) if err := common.WriteEnvFile(env, uidRange, envFilePath); err != nil { return err } apps := append(p.Manifest.Apps, ra) p.Manifest.Apps = apps if err := updatePodManifest(dir, p.Manifest); err != nil { return err } if _, err := os.Create(common.AppCreatedPath(p.Root, appName.String())); err != nil { return err } return nil }