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  }