github.com/hpcng/singularity@v3.1.1+incompatible/pkg/image/image.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 image 7 8 import ( 9 "fmt" 10 "os" 11 "path/filepath" 12 "strings" 13 "syscall" 14 15 "github.com/sylabs/singularity/internal/pkg/sylog" 16 "github.com/sylabs/singularity/internal/pkg/util/user" 17 ) 18 19 const ( 20 // SQUASHFS constant for squashfs format 21 SQUASHFS = iota + 1 22 // EXT3 constant for ext3 format 23 EXT3 24 // SANDBOX constant for directory format 25 SANDBOX 26 // SIF constant for sif format 27 SIF 28 ) 29 30 const ( 31 // RootFs partition name 32 RootFs = "rootfs" 33 launchString = " run-singularity" 34 bufferSize = 2048 35 ) 36 37 var registeredFormats = []struct { 38 name string 39 format format 40 }{ 41 {"sif", &sifFormat{}}, 42 {"sandbox", &sandboxFormat{}}, 43 {"squashfs", &squashfsFormat{}}, 44 {"ext3", &ext3Format{}}, 45 } 46 47 // format describes the interface that an image format type must implement. 48 type format interface { 49 openMode(bool) int 50 initializer(*Image, os.FileInfo) error 51 } 52 53 // Section identifies and locates a data section in image object. 54 type Section struct { 55 Size uint64 `json:"size"` 56 Offset uint64 `json:"offset"` 57 Type uint32 `json:"type"` 58 Name string `json:"name"` 59 } 60 61 // Image describes an image object, an image is composed of one 62 // or more partitions (eg: container root filesystem, overlay), 63 // image format like SIF contains descriptors pointing to chunk of 64 // data, chunks position and size are stored as image sections. 65 type Image struct { 66 Path string `json:"path"` 67 Name string `json:"name"` 68 Type int `json:"type"` 69 File *os.File `json:"-"` 70 Fd uintptr `json:"fd"` 71 Source string `json:"source"` 72 Writable bool `json:"writable"` 73 Partitions []Section `json:"partitions"` 74 Sections []Section `json:"sections"` 75 } 76 77 // AuthorizedPath checks if image is in a path supplied in paths 78 func (i *Image) AuthorizedPath(paths []string) (bool, error) { 79 authorized := false 80 dirname := i.Path 81 82 for _, path := range paths { 83 match, err := filepath.EvalSymlinks(filepath.Clean(path)) 84 if err != nil { 85 return authorized, fmt.Errorf("failed to resolve path %s: %s", path, err) 86 } 87 if strings.HasPrefix(dirname, match) { 88 authorized = true 89 break 90 } 91 } 92 return authorized, nil 93 } 94 95 // AuthorizedOwner checks if image is owned by user supplied in users list 96 func (i *Image) AuthorizedOwner(owners []string) (bool, error) { 97 authorized := false 98 fileinfo, err := i.File.Stat() 99 if err != nil { 100 return authorized, fmt.Errorf("failed to get stat for %s", i.Path) 101 } 102 uid := fileinfo.Sys().(*syscall.Stat_t).Uid 103 for _, owner := range owners { 104 pw, err := user.GetPwNam(owner) 105 if err != nil { 106 return authorized, fmt.Errorf("failed to retrieve user information for %s: %s", owner, err) 107 } 108 if pw.UID == uid { 109 authorized = true 110 break 111 } 112 } 113 return authorized, nil 114 } 115 116 // AuthorizedGroup checks if image is owned by group supplied in groups list 117 func (i *Image) AuthorizedGroup(groups []string) (bool, error) { 118 authorized := false 119 fileinfo, err := i.File.Stat() 120 if err != nil { 121 return authorized, fmt.Errorf("failed to get stat for %s", i.Path) 122 } 123 gid := fileinfo.Sys().(*syscall.Stat_t).Gid 124 for _, group := range groups { 125 gr, err := user.GetGrNam(group) 126 if err != nil { 127 return authorized, fmt.Errorf("failed to retrieve group information for %s: %s", group, err) 128 } 129 if gr.GID == gid { 130 authorized = true 131 break 132 } 133 } 134 return authorized, nil 135 } 136 137 // ResolvePath returns a resolved absolute path 138 func ResolvePath(path string) (string, error) { 139 abspath, err := filepath.Abs(path) 140 if err != nil { 141 return "", fmt.Errorf("failed to get absolute path: %s", err) 142 } 143 resolvedPath, err := filepath.EvalSymlinks(abspath) 144 if err != nil { 145 return "", fmt.Errorf("failed to retrieved path for %s: %s", path, err) 146 } 147 return resolvedPath, nil 148 } 149 150 // Init initializes an image object based on given path 151 func Init(path string, writable bool) (*Image, error) { 152 sylog.Debugf("Entering image format intializer") 153 154 resolvedPath, err := ResolvePath(path) 155 if err != nil { 156 return nil, err 157 } 158 159 img := &Image{ 160 Path: resolvedPath, 161 Name: filepath.Base(resolvedPath), 162 Partitions: make([]Section, 1), 163 } 164 165 for _, rf := range registeredFormats { 166 sylog.Debugf("Check for image format %s", rf.name) 167 168 img.Writable = writable 169 170 mode := rf.format.openMode(writable) 171 172 if mode&os.O_RDWR != 0 { 173 if err := syscall.Access(resolvedPath, 2); err != nil { 174 sylog.Debugf("Opening %s in read-only mode: no write permissions", path) 175 mode = os.O_RDONLY 176 img.Writable = false 177 } 178 } 179 180 img.File, err = os.OpenFile(resolvedPath, mode, 0) 181 if err != nil { 182 continue 183 } 184 fileinfo, err := img.File.Stat() 185 if err != nil { 186 img.File.Close() 187 return nil, err 188 } 189 190 err = rf.format.initializer(img, fileinfo) 191 if err != nil { 192 sylog.Debugf("%s format initializer returns: %s", rf.name, err) 193 img.File.Close() 194 continue 195 } 196 if _, _, err := syscall.Syscall(syscall.SYS_FCNTL, img.File.Fd(), syscall.F_SETFD, syscall.O_CLOEXEC); err != 0 { 197 sylog.Warningf("failed to set O_CLOEXEC flags on image") 198 } 199 200 img.Source = fmt.Sprintf("/proc/self/fd/%d", img.File.Fd()) 201 img.Fd = img.File.Fd() 202 203 return img, nil 204 } 205 return nil, fmt.Errorf("image format not recognized") 206 }