github.com/apptainer/singularity@v3.1.1+incompatible/internal/pkg/util/fs/layout/manager.go (about) 1 // Copyright (c) 2018, 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 layout 7 8 import ( 9 "fmt" 10 "os" 11 "path/filepath" 12 "strings" 13 "syscall" 14 15 "github.com/sylabs/singularity/internal/pkg/util/fs" 16 ) 17 18 const ( 19 dirMode os.FileMode = 0755 20 fileMode = 0644 21 ) 22 23 type file struct { 24 mode os.FileMode 25 uid int 26 gid int 27 content []byte 28 created bool 29 } 30 31 type dir struct { 32 mode os.FileMode 33 uid int 34 gid int 35 created bool 36 } 37 38 type symlink struct { 39 uid int 40 gid int 41 target string 42 created bool 43 } 44 45 // Manager manages a filesystem layout in a given path 46 type Manager struct { 47 DirMode os.FileMode 48 FileMode os.FileMode 49 rootPath string 50 entries map[string]interface{} 51 dirs []*dir 52 } 53 54 func (m *Manager) checkPath(path string, checkExist bool) (string, error) { 55 if m.entries == nil { 56 return "", fmt.Errorf("root path is not set") 57 } 58 p := filepath.Clean(path) 59 if !filepath.IsAbs(p) { 60 return "", fmt.Errorf("path %s is not an absolute path", p) 61 } 62 if checkExist { 63 if _, ok := m.entries[p]; ok { 64 return "", fmt.Errorf("%s already exists in layout", p) 65 } 66 } else { 67 if _, ok := m.entries[p]; !ok { 68 return "", fmt.Errorf("%s doesn't exist in layout", p) 69 } 70 } 71 return p, nil 72 } 73 74 func (m *Manager) createParentDir(path string) { 75 uid := os.Getuid() 76 gid := os.Getgid() 77 78 splitted := strings.Split(path, string(os.PathSeparator)) 79 l := len(splitted) 80 p := "" 81 for i := 1; i < l; i++ { 82 s := splitted[i : i+1][0] 83 p += "/" + s 84 if s != "" { 85 if _, ok := m.entries[p]; !ok { 86 d := &dir{mode: m.DirMode, uid: uid, gid: gid} 87 m.entries[p] = d 88 m.dirs = append(m.dirs, d) 89 } 90 } 91 } 92 } 93 94 // SetRootPath sets layout root path 95 func (m *Manager) SetRootPath(path string) error { 96 if !fs.IsDir(path) { 97 return fmt.Errorf("%s is not a directory or doesn't exists", path) 98 } 99 m.rootPath = filepath.Clean(path) 100 if m.entries == nil { 101 m.entries = make(map[string]interface{}) 102 } else { 103 return fmt.Errorf("root path is already set") 104 } 105 if m.dirs == nil { 106 m.dirs = make([]*dir, 0) 107 } 108 if m.DirMode == 0000 { 109 m.DirMode = dirMode 110 } 111 if m.FileMode == 0000 { 112 m.FileMode = fileMode 113 } 114 d := &dir{mode: m.DirMode, uid: os.Getuid(), gid: os.Getgid()} 115 m.entries["/"] = d 116 m.dirs = append(m.dirs, d) 117 return nil 118 } 119 120 // AddDir adds a directory in layout, will recursively add parent 121 // directories if they don't exist 122 func (m *Manager) AddDir(path string) error { 123 p, err := m.checkPath(path, true) 124 if err != nil { 125 return err 126 } 127 m.createParentDir(p) 128 return nil 129 } 130 131 // AddFile adds a file in layout, will recursively add parent 132 // directories if they don't exist 133 func (m *Manager) AddFile(path string, content []byte) error { 134 p, err := m.checkPath(path, true) 135 if err != nil { 136 return err 137 } 138 m.createParentDir(filepath.Dir(p)) 139 m.entries[p] = &file{mode: m.FileMode, uid: os.Getuid(), gid: os.Getgid(), content: content} 140 return nil 141 } 142 143 // AddSymlink adds a symlink in layout, will recursively add parent 144 // directories if they don't exist 145 func (m *Manager) AddSymlink(path string, target string) error { 146 p, err := m.checkPath(path, true) 147 if err != nil { 148 return err 149 } 150 m.createParentDir(filepath.Dir(p)) 151 m.entries[p] = &symlink{uid: os.Getuid(), gid: os.Getgid(), target: target} 152 return nil 153 } 154 155 // GetPath returns the full path of layout path 156 func (m *Manager) GetPath(path string) (string, error) { 157 _, err := m.checkPath(path, false) 158 if err != nil { 159 return "", err 160 } 161 return filepath.Join(m.rootPath, path), nil 162 } 163 164 // Chmod sets permission mode for path 165 func (m *Manager) Chmod(path string, mode os.FileMode) error { 166 _, err := m.checkPath(path, false) 167 if err != nil { 168 return err 169 } 170 switch m.entries[path].(type) { 171 case *file: 172 m.entries[path].(*file).mode = mode 173 case *dir: 174 m.entries[path].(*dir).mode = mode 175 } 176 return nil 177 } 178 179 // Chown sets ownership for path 180 func (m *Manager) Chown(path string, uid, gid int) error { 181 _, err := m.checkPath(path, false) 182 if err != nil { 183 return err 184 } 185 switch m.entries[path].(type) { 186 case *file: 187 m.entries[path].(*file).uid = uid 188 m.entries[path].(*file).gid = gid 189 case *dir: 190 m.entries[path].(*dir).uid = uid 191 m.entries[path].(*dir).gid = gid 192 case *symlink: 193 m.entries[path].(*symlink).uid = uid 194 m.entries[path].(*symlink).gid = gid 195 } 196 return nil 197 } 198 199 // Create creates the filesystem layout 200 func (m *Manager) Create() error { 201 return m.sync() 202 } 203 204 // Update updates the filesystem layout 205 func (m *Manager) Update() error { 206 return m.sync() 207 } 208 209 func (m *Manager) sync() error { 210 uid := os.Getuid() 211 gid := os.Getgid() 212 213 if m.entries == nil { 214 return fmt.Errorf("root path is not set") 215 } 216 217 oldmask := syscall.Umask(0) 218 defer syscall.Umask(oldmask) 219 220 for _, d := range m.dirs[1:] { 221 if d.created { 222 continue 223 } 224 path := "" 225 for p, e := range m.entries { 226 if e == d { 227 path = m.rootPath + p 228 break 229 } 230 } 231 if d.mode != m.DirMode { 232 if err := os.Mkdir(path, d.mode); err != nil { 233 return fmt.Errorf("failed to create %s directory: %s", path, err) 234 } 235 } else { 236 if err := os.Mkdir(path, m.DirMode); err != nil { 237 return fmt.Errorf("failed to create %s directory: %s", path, err) 238 } 239 } 240 if d.uid != uid || d.gid != gid { 241 if err := os.Chown(path, d.uid, d.gid); err != nil { 242 return fmt.Errorf("failed to change owner of %s: %s", path, err) 243 } 244 } 245 d.created = true 246 } 247 248 for p, e := range m.entries { 249 path := m.rootPath + p 250 switch e.(type) { 251 case *file: 252 entry := e.(*file) 253 if entry.created { 254 continue 255 } 256 f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, entry.mode) 257 if err != nil { 258 return fmt.Errorf("failed to create file %s: %s", path, err) 259 } 260 l := len(entry.content) 261 if l > 0 { 262 if n, err := f.Write(entry.content); err != nil || n != l { 263 return fmt.Errorf("failed to write file %s content: %s", path, err) 264 } 265 } 266 if err := f.Close(); err != nil { 267 return fmt.Errorf("error while closing file: %s", err) 268 } 269 if entry.uid != uid || entry.gid != gid { 270 if err := os.Chown(path, entry.uid, entry.gid); err != nil { 271 return fmt.Errorf("failed to change %s ownership: %s", path, err) 272 } 273 } 274 entry.created = true 275 case *symlink: 276 entry := e.(*symlink) 277 if entry.created { 278 continue 279 } 280 if err := os.Symlink(entry.target, path); err != nil { 281 return fmt.Errorf("failed to create symlink %s: %s", path, err) 282 } 283 if entry.uid != uid || entry.gid != gid { 284 if err := os.Lchown(path, entry.uid, entry.gid); err != nil { 285 return fmt.Errorf("failed to change %s ownership: %s", path, err) 286 } 287 } 288 entry.created = true 289 } 290 } 291 return nil 292 }