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  }