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  }