github.com/singularityware/singularity@v3.1.1+incompatible/internal/pkg/build/assemblers/assembler_sif.go (about)

     1  // Copyright (c) 2018-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 assemblers
     7  
     8  import (
     9  	"encoding/binary"
    10  	"fmt"
    11  	"io/ioutil"
    12  	"os"
    13  	"os/exec"
    14  	"path/filepath"
    15  	"regexp"
    16  	"runtime"
    17  	"strconv"
    18  	"strings"
    19  	"syscall"
    20  
    21  	uuid "github.com/satori/go.uuid"
    22  	"github.com/sylabs/sif/pkg/sif"
    23  	"github.com/sylabs/singularity/internal/pkg/buildcfg"
    24  	"github.com/sylabs/singularity/internal/pkg/runtime/engines/config"
    25  	singularityConfig "github.com/sylabs/singularity/internal/pkg/runtime/engines/singularity/config"
    26  	"github.com/sylabs/singularity/internal/pkg/sylog"
    27  	"github.com/sylabs/singularity/pkg/build/types"
    28  )
    29  
    30  // SIFAssembler doesnt store anything
    31  type SIFAssembler struct {
    32  }
    33  
    34  func createSIF(path string, definition, ociConf []byte, squashfile string) (err error) {
    35  	// general info for the new SIF file creation
    36  	cinfo := sif.CreateInfo{
    37  		Pathname:   path,
    38  		Launchstr:  sif.HdrLaunch,
    39  		Sifversion: sif.HdrVersion,
    40  		ID:         uuid.NewV4(),
    41  	}
    42  
    43  	// data we need to create a definition file descriptor
    44  	definput := sif.DescriptorInput{
    45  		Datatype: sif.DataDeffile,
    46  		Groupid:  sif.DescrDefaultGroup,
    47  		Link:     sif.DescrUnusedLink,
    48  		Data:     definition,
    49  	}
    50  	definput.Size = int64(binary.Size(definput.Data))
    51  
    52  	// add this descriptor input element to creation descriptor slice
    53  	cinfo.InputDescr = append(cinfo.InputDescr, definput)
    54  
    55  	if len(ociConf) > 0 {
    56  		// data we need to create a definition file descriptor
    57  		ociInput := sif.DescriptorInput{
    58  			Datatype: sif.DataGenericJSON,
    59  			Groupid:  sif.DescrDefaultGroup,
    60  			Link:     sif.DescrUnusedLink,
    61  			Data:     ociConf,
    62  			Fname:    "oci-config.json",
    63  		}
    64  		ociInput.Size = int64(binary.Size(ociInput.Data))
    65  
    66  		// add this descriptor input element to creation descriptor slice
    67  		cinfo.InputDescr = append(cinfo.InputDescr, ociInput)
    68  	}
    69  
    70  	// data we need to create a system partition descriptor
    71  	parinput := sif.DescriptorInput{
    72  		Datatype: sif.DataPartition,
    73  		Groupid:  sif.DescrDefaultGroup,
    74  		Link:     sif.DescrUnusedLink,
    75  		Fname:    squashfile,
    76  	}
    77  	// open up the data object file for this descriptor
    78  	if parinput.Fp, err = os.Open(parinput.Fname); err != nil {
    79  		return fmt.Errorf("while opening partition file: %s", err)
    80  	}
    81  	defer parinput.Fp.Close()
    82  	fi, err := parinput.Fp.Stat()
    83  	if err != nil {
    84  		return fmt.Errorf("while calling start on partition file: %s", err)
    85  	}
    86  	parinput.Size = fi.Size()
    87  
    88  	err = parinput.SetPartExtra(sif.FsSquash, sif.PartPrimSys, sif.GetSIFArch(runtime.GOARCH))
    89  	if err != nil {
    90  		return
    91  	}
    92  
    93  	// add this descriptor input element to the list
    94  	cinfo.InputDescr = append(cinfo.InputDescr, parinput)
    95  
    96  	// remove anything that may exist at the build destination at last moment
    97  	os.RemoveAll(path)
    98  
    99  	// test container creation with two partition input descriptors
   100  	if _, err := sif.CreateContainer(cinfo); err != nil {
   101  		return fmt.Errorf("while creating container: %s", err)
   102  	}
   103  
   104  	// chown the sif file to the calling user
   105  	if uid, gid, ok := changeOwner(); ok {
   106  		if err := os.Chown(path, uid, gid); err != nil {
   107  			return fmt.Errorf("while changing image ownership: %s", err)
   108  		}
   109  	}
   110  
   111  	return nil
   112  }
   113  
   114  func getMksquashfsPath() (string, error) {
   115  	// Parse singularity configuration file
   116  	c := &singularityConfig.FileConfig{}
   117  	if err := config.Parser(buildcfg.SYSCONFDIR+"/singularity/singularity.conf", c); err != nil {
   118  		return "", fmt.Errorf("Unable to parse singularity.conf file: %s", err)
   119  	}
   120  
   121  	// p is either "" or the string value in the conf file
   122  	p := c.MksquashfsPath
   123  
   124  	// If the path contains the binary name use it as is, otherwise add mksquashfs via filepath.Join
   125  	if !strings.HasSuffix(c.MksquashfsPath, "mksquashfs") {
   126  		p = filepath.Join(c.MksquashfsPath, "mksquashfs")
   127  	}
   128  
   129  	// exec.LookPath functions on absolute paths (ignoring $PATH) as well
   130  	return exec.LookPath(p)
   131  }
   132  
   133  // Assemble creates a SIF image from a Bundle
   134  func (a *SIFAssembler) Assemble(b *types.Bundle, path string) (err error) {
   135  	sylog.Infof("Creating SIF file...")
   136  
   137  	mksquashfs, err := getMksquashfsPath()
   138  	if err != nil {
   139  		return fmt.Errorf("While searching for mksquashfs: %v", err)
   140  	}
   141  
   142  	f, err := ioutil.TempFile(b.Path, "squashfs-")
   143  	squashfsPath := f.Name() + ".img"
   144  	f.Close()
   145  	os.Remove(f.Name())
   146  	os.Remove(squashfsPath)
   147  	defer os.Remove(squashfsPath)
   148  
   149  	args := []string{b.Rootfs(), squashfsPath, "-noappend"}
   150  
   151  	// build squashfs with all-root flag when building as a user
   152  	if syscall.Getuid() != 0 {
   153  		args = append(args, "-all-root")
   154  	}
   155  
   156  	mksquashfsCmd := exec.Command(mksquashfs, args...)
   157  	stderr, err := mksquashfsCmd.StderrPipe()
   158  	if err != nil {
   159  		return fmt.Errorf("While setting up stderr pipe: %v", err)
   160  	}
   161  
   162  	if err := mksquashfsCmd.Start(); err != nil {
   163  		return fmt.Errorf("While starting mksquashfs: %v", err)
   164  	}
   165  
   166  	errOut, err := ioutil.ReadAll(stderr)
   167  	if err != nil {
   168  		return fmt.Errorf("While reading mksquashfs stderr: %v", err)
   169  	}
   170  
   171  	if err := mksquashfsCmd.Wait(); err != nil {
   172  		return fmt.Errorf("While running mksquashfs: %v: %s", err, strings.Replace(string(errOut), "\n", " ", -1))
   173  	}
   174  
   175  	err = createSIF(path, b.Recipe.Raw, b.JSONObjects["oci-config"], squashfsPath)
   176  	if err != nil {
   177  		return fmt.Errorf("While creating SIF: %v", err)
   178  	}
   179  
   180  	return
   181  }
   182  
   183  // changeOwner check the command being called with sudo with the environment
   184  // variable SUDO_COMMAND. Pattern match that for the singularity bin
   185  func changeOwner() (int, int, bool) {
   186  	r := regexp.MustCompile("(singularity)")
   187  	sudoCmd := os.Getenv("SUDO_COMMAND")
   188  	if !r.MatchString(sudoCmd) {
   189  		return 0, 0, false
   190  	}
   191  
   192  	if os.Getenv("SUDO_USER") == "" || syscall.Getuid() != 0 {
   193  		return 0, 0, false
   194  	}
   195  
   196  	_uid := os.Getenv("SUDO_UID")
   197  	_gid := os.Getenv("SUDO_GID")
   198  	if _uid == "" || _gid == "" {
   199  		sylog.Warningf("Env vars SUDO_UID or SUDO_GID are not set, won't call chown over built SIF")
   200  
   201  		return 0, 0, false
   202  	}
   203  
   204  	uid, err := strconv.Atoi(_uid)
   205  	if err != nil {
   206  		sylog.Warningf("Error while calling strconv: %v", err)
   207  
   208  		return 0, 0, false
   209  	}
   210  	gid, err := strconv.Atoi(_gid)
   211  	if err != nil {
   212  		sylog.Warningf("Error while calling strconv : %v", err)
   213  
   214  		return 0, 0, false
   215  	}
   216  
   217  	return uid, gid, true
   218  }