github.com/apptainer/singularity@v3.1.1+incompatible/internal/pkg/runtime/engines/imgbuild/create.go (about) 1 // Copyright (c) 2019, Sylabs Inc. All rights reserved. 2 // This software is licensed under a 3-clause BSD license. Please consult the 3 // LICENSE.md file distributed with the sources of this project regarding your 4 // rights to use or distribute this software. 5 6 package imgbuild 7 8 import ( 9 "fmt" 10 "net" 11 "net/rpc" 12 "os" 13 "os/exec" 14 "path/filepath" 15 "syscall" 16 17 "github.com/sylabs/singularity/internal/pkg/buildcfg" 18 imgbuildConfig "github.com/sylabs/singularity/internal/pkg/runtime/engines/imgbuild/config" 19 "github.com/sylabs/singularity/internal/pkg/runtime/engines/singularity/rpc/client" 20 "github.com/sylabs/singularity/internal/pkg/sylog" 21 ) 22 23 // CreateContainer creates a container 24 func (engine *EngineOperations) CreateContainer(pid int, rpcConn net.Conn) error { 25 if engine.CommonConfig.EngineName != imgbuildConfig.Name { 26 return fmt.Errorf("engineName configuration doesn't match runtime name") 27 } 28 29 rpcOps := &client.RPC{ 30 Client: rpc.NewClient(rpcConn), 31 Name: engine.CommonConfig.EngineName, 32 } 33 if rpcOps.Client == nil { 34 return fmt.Errorf("failed to initialiaze RPC client") 35 } 36 37 rootfs := engine.EngineConfig.Rootfs() 38 39 st, err := os.Stat(rootfs) 40 if err != nil { 41 return fmt.Errorf("stat on %s failed", rootfs) 42 } 43 44 if st.IsDir() == false { 45 return fmt.Errorf("%s is not a directory", rootfs) 46 } 47 48 sessionPath, err := filepath.EvalSymlinks(buildcfg.SESSIONDIR) 49 if err != nil { 50 return fmt.Errorf("failed to resolved session directory %s: %s", buildcfg.SESSIONDIR, err) 51 } 52 53 // sensible mount point options to avoid accidental system settings override 54 flags := uintptr(syscall.MS_BIND | syscall.MS_NOSUID | syscall.MS_NOEXEC | syscall.MS_NODEV | syscall.MS_RDONLY) 55 56 sylog.Debugf("Mounting image directory %s\n", rootfs) 57 _, err = rpcOps.Mount(rootfs, sessionPath, "", syscall.MS_BIND, "errors=remount-ro") 58 if err != nil { 59 return fmt.Errorf("failed to mount directory filesystem %s: %s", rootfs, err) 60 } 61 62 dest := filepath.Join(sessionPath, "tmp") 63 sylog.Debugf("Mounting /tmp at %s\n", dest) 64 _, err = rpcOps.Mount("/tmp", dest, "", syscall.MS_BIND, "") 65 if err != nil { 66 return fmt.Errorf("mount /tmp failed: %s", err) 67 } 68 69 dest = filepath.Join(sessionPath, "var", "tmp") 70 sylog.Debugf("Mounting /var/tmp at %s\n", dest) 71 _, err = rpcOps.Mount("/var/tmp", dest, "", syscall.MS_BIND, "") 72 if err != nil { 73 return fmt.Errorf("mount /var/tmp failed: %s", err) 74 } 75 76 // run setup/files sections here to allow injection of custom /etc/hosts or /etc/resolv.conf 77 if engine.EngineConfig.RunSection("setup") && engine.EngineConfig.Recipe.BuildData.Setup != "" { 78 // Run %setup script here 79 setup := exec.Command("/bin/sh", "-cex", engine.EngineConfig.Recipe.BuildData.Setup) 80 setup.Env = engine.EngineConfig.OciConfig.Process.Env 81 setup.Stdout = os.Stdout 82 setup.Stderr = os.Stderr 83 84 sylog.Infof("Running setup scriptlet\n") 85 if err := setup.Start(); err != nil { 86 sylog.Fatalf("failed to start %%setup proc: %v\n", err) 87 } 88 if err := setup.Wait(); err != nil { 89 sylog.Fatalf("setup proc: %v\n", err) 90 } 91 } 92 93 if engine.EngineConfig.RunSection("files") { 94 sylog.Debugf("Copying files from host") 95 if err := engine.copyFiles(); err != nil { 96 return fmt.Errorf("unable to copy files to container fs: %v", err) 97 } 98 } 99 100 dest = filepath.Join(sessionPath, "proc") 101 sylog.Debugf("Mounting /proc at %s\n", dest) 102 _, err = rpcOps.Mount("/proc", dest, "", flags, "") 103 if err != nil { 104 return fmt.Errorf("mount proc failed: %s", err) 105 } 106 _, err = rpcOps.Mount("", dest, "", syscall.MS_REMOUNT|flags, "") 107 if err != nil { 108 return fmt.Errorf("remount proc failed: %s", err) 109 } 110 111 dest = filepath.Join(sessionPath, "sys") 112 sylog.Debugf("Mounting /sys at %s\n", dest) 113 _, err = rpcOps.Mount("/sys", dest, "", flags, "") 114 if err != nil { 115 return fmt.Errorf("mount sys failed: %s", err) 116 } 117 _, err = rpcOps.Mount("", dest, "", syscall.MS_REMOUNT|flags, "") 118 if err != nil { 119 return fmt.Errorf("remount sys failed: %s", err) 120 } 121 122 dest = filepath.Join(sessionPath, "dev") 123 sylog.Debugf("Mounting /dev at %s\n", dest) 124 _, err = rpcOps.Mount("/dev", dest, "", syscall.MS_BIND|syscall.MS_REC, "") 125 if err != nil { 126 return fmt.Errorf("mount /dev failed: %s", err) 127 } 128 129 dest = filepath.Join(sessionPath, "etc", "resolv.conf") 130 sylog.Debugf("Mounting /etc/resolv.conf at %s\n", dest) 131 _, err = rpcOps.Mount("/etc/resolv.conf", dest, "", flags, "") 132 if err != nil { 133 return fmt.Errorf("mount /etc/resolv.conf failed: %s", err) 134 } 135 _, err = rpcOps.Mount("", dest, "", syscall.MS_REMOUNT|flags, "") 136 if err != nil { 137 return fmt.Errorf("remount /etc/resolv.conf failed: %s", err) 138 } 139 140 dest = filepath.Join(sessionPath, "etc", "hosts") 141 sylog.Debugf("Mounting /etc/hosts at %s\n", dest) 142 _, err = rpcOps.Mount("/etc/hosts", dest, "", flags, "") 143 if err != nil { 144 return fmt.Errorf("mount /etc/hosts failed: %s", err) 145 } 146 _, err = rpcOps.Mount("", dest, "", syscall.MS_REMOUNT|flags, "") 147 if err != nil { 148 return fmt.Errorf("remount /etc/hosts failed: %s", err) 149 } 150 151 sylog.Debugf("Chdir into %s\n", sessionPath) 152 err = syscall.Chdir(sessionPath) 153 if err != nil { 154 return fmt.Errorf("change directory failed: %s", err) 155 } 156 157 sylog.Debugf("Set RPC mount propagation flag to PRIVATE") 158 _, err = rpcOps.Mount("", "/", "", syscall.MS_PRIVATE|syscall.MS_REC, "") 159 if err != nil { 160 return err 161 } 162 163 sylog.Debugf("Chroot into %s\n", buildcfg.SESSIONDIR) 164 _, err = rpcOps.Chroot(buildcfg.SESSIONDIR, "pivot") 165 if err != nil { 166 sylog.Debugf("Fallback to move/chroot") 167 _, err = rpcOps.Chroot(buildcfg.SESSIONDIR, "move") 168 if err != nil { 169 return fmt.Errorf("chroot failed: %s", err) 170 } 171 } 172 173 sylog.Debugf("Chdir into / to avoid errors\n") 174 err = syscall.Chdir("/") 175 if err != nil { 176 return fmt.Errorf("change directory failed: %s", err) 177 } 178 if err := rpcOps.Client.Close(); err != nil { 179 return fmt.Errorf("can't close connection with rpc server: %s", err) 180 } 181 182 return nil 183 } 184 185 func (engine *EngineOperations) copyFiles() error { 186 // iterate through filetransfers 187 for _, transfer := range engine.EngineConfig.Recipe.BuildData.Files { 188 // sanity 189 if transfer.Src == "" { 190 sylog.Warningf("Attempt to copy file with no name...") 191 continue 192 } 193 // dest = source if not specified 194 if transfer.Dst == "" { 195 transfer.Dst = transfer.Src 196 } 197 // copy each file into bundle rootfs 198 transfer.Dst = filepath.Join(engine.EngineConfig.Rootfs(), transfer.Dst) 199 sylog.Infof("Copying %v to %v", transfer.Src, transfer.Dst) 200 copy := exec.Command("/bin/cp", "-fLr", transfer.Src, transfer.Dst) 201 if err := copy.Run(); err != nil { 202 return fmt.Errorf("While copying %v to %v: %v", transfer.Src, transfer.Dst, err) 203 } 204 } 205 206 return nil 207 }