github.com/apptainer/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 }