github.com/hpcng/singularity@v3.1.1+incompatible/internal/pkg/util/fs/layout/layer/underlay/underlay.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 underlay
     7  
     8  import (
     9  	"fmt"
    10  	"io/ioutil"
    11  	"os"
    12  	"path/filepath"
    13  	"sort"
    14  	"strings"
    15  	"syscall"
    16  
    17  	"github.com/sylabs/singularity/internal/pkg/sylog"
    18  
    19  	"github.com/sylabs/singularity/internal/pkg/util/fs/layout"
    20  	"github.com/sylabs/singularity/internal/pkg/util/fs/mount"
    21  )
    22  
    23  const underlayDir = "/underlay"
    24  
    25  type pathLen struct {
    26  	path string
    27  	len  uint16
    28  }
    29  
    30  // Underlay layer manager
    31  type Underlay struct {
    32  	session *layout.Session
    33  }
    34  
    35  // New creates and returns an overlay layer manager
    36  func New() *Underlay {
    37  	return &Underlay{}
    38  }
    39  
    40  // Add adds required directory in session layout
    41  func (u *Underlay) Add(session *layout.Session, system *mount.System) error {
    42  	u.session = session
    43  	if err := u.session.AddDir(underlayDir); err != nil {
    44  		return err
    45  	}
    46  	return system.RunBeforeTag(mount.PreLayerTag, u.createUnderlay)
    47  }
    48  
    49  func (u *Underlay) createUnderlay(system *mount.System) error {
    50  	points := system.Points.GetByTag(mount.RootfsTag)
    51  	if len(points) <= 0 {
    52  		return fmt.Errorf("no root fs image found")
    53  	}
    54  	return u.createLayer(points[0].Destination, system)
    55  }
    56  
    57  // createLayer creates underlay layer based on content of root filesystem
    58  func (u *Underlay) createLayer(rootFsPath string, system *mount.System) error {
    59  	st := new(syscall.Stat_t)
    60  	points := system.Points
    61  	createdPath := make([]pathLen, 0)
    62  
    63  	sessionDir := u.session.Path()
    64  	for _, tag := range mount.GetTagList() {
    65  		for _, point := range points.GetByTag(tag) {
    66  			flags, _ := mount.ConvertOptions(point.Options)
    67  			if flags&syscall.MS_REMOUNT != 0 {
    68  				continue
    69  			}
    70  			if strings.HasPrefix(point.Destination, sessionDir) {
    71  				continue
    72  			}
    73  			if err := syscall.Stat(rootFsPath+point.Destination, st); err == nil {
    74  				continue
    75  			}
    76  			if err := syscall.Stat(point.Source, st); err != nil {
    77  				sylog.Warningf("skipping mount of %s: %s", point.Source, err)
    78  				continue
    79  			}
    80  			dst := underlayDir + point.Destination
    81  			if _, err := u.session.GetPath(dst); err == nil {
    82  				continue
    83  			}
    84  			switch st.Mode & syscall.S_IFMT {
    85  			case syscall.S_IFDIR:
    86  				if err := u.session.AddDir(dst); err != nil {
    87  					return err
    88  				}
    89  			default:
    90  				if err := u.session.AddFile(dst, nil); err != nil {
    91  					return err
    92  				}
    93  			}
    94  			createdPath = append(createdPath, pathLen{path: point.Destination, len: uint16(strings.Count(point.Destination, "/"))})
    95  		}
    96  	}
    97  
    98  	sort.SliceStable(createdPath, func(i, j int) bool { return createdPath[i].len < createdPath[j].len })
    99  
   100  	for _, pl := range createdPath {
   101  		splitted := strings.Split(filepath.Dir(pl.path), string(os.PathSeparator))
   102  		l := len(splitted)
   103  		p := ""
   104  		for i := 1; i < l; i++ {
   105  			s := splitted[i : i+1][0]
   106  			p += "/" + s
   107  			if s != "" {
   108  				if _, err := u.session.GetPath(p); err != nil {
   109  					if err := u.session.AddDir(p); err != nil {
   110  						return err
   111  					}
   112  				}
   113  				if err := u.duplicateDir(p, system, pl.path); err != nil {
   114  					return err
   115  				}
   116  			}
   117  		}
   118  	}
   119  
   120  	if err := u.duplicateDir("/", system, ""); err != nil {
   121  		return err
   122  	}
   123  
   124  	flags := uintptr(syscall.MS_BIND | syscall.MS_REC | syscall.MS_RDONLY)
   125  	path, _ := u.session.GetPath(underlayDir)
   126  
   127  	err := system.Points.AddBind(mount.LayerTag, path, u.session.FinalPath(), flags)
   128  	if err != nil {
   129  		return err
   130  	}
   131  	err = system.Points.AddRemount(mount.LayerTag, u.session.FinalPath(), flags)
   132  	if err != nil {
   133  		return err
   134  	}
   135  
   136  	return u.session.Update()
   137  }
   138  
   139  func (u *Underlay) duplicateDir(dir string, system *mount.System, existingPath string) error {
   140  	binds := 0
   141  	path := filepath.Clean(u.session.RootFsPath() + dir)
   142  	files, err := ioutil.ReadDir(path)
   143  	if err != nil {
   144  		// directory doesn't exists, nothing to duplicate
   145  		return nil
   146  	}
   147  	for _, file := range files {
   148  		dst := filepath.Join(underlayDir+dir, file.Name())
   149  		src := filepath.Join(path, file.Name())
   150  
   151  		// no error means entry is already created
   152  		if _, err := u.session.GetPath(dst); err == nil {
   153  			continue
   154  		}
   155  		if file.IsDir() {
   156  			if err := u.session.AddDir(dst); err != nil {
   157  				return fmt.Errorf("can't add directory %s to underlay: %s", dst, err)
   158  			}
   159  			dst, _ = u.session.GetPath(dst)
   160  			if err := system.Points.AddBind(mount.PreLayerTag, src, dst, syscall.MS_BIND); err != nil {
   161  				return fmt.Errorf("can't add bind mount point: %s", err)
   162  			}
   163  			binds++
   164  		} else if file.Mode()&os.ModeSymlink != 0 {
   165  			tgt, err := os.Readlink(src)
   166  			if err != nil {
   167  				return fmt.Errorf("can't read symlink information for %s: %s", src, err)
   168  			}
   169  			if err := u.session.AddSymlink(dst, tgt); err != nil {
   170  				return fmt.Errorf("can't add symlink: %s", err)
   171  			}
   172  		} else {
   173  			if err := u.session.AddFile(dst, nil); err != nil {
   174  				return fmt.Errorf("can't add directory %s to underlay: %s", dst, err)
   175  			}
   176  			dst, _ = u.session.GetPath(dst)
   177  			if err := system.Points.AddBind(mount.PreLayerTag, src, dst, syscall.MS_BIND); err != nil {
   178  				return fmt.Errorf("can't add bind mount point: %s", err)
   179  			}
   180  			binds++
   181  		}
   182  	}
   183  	if binds > 50 && existingPath != "" {
   184  		sylog.Warningf("underlay of %s required more than 50 (%d) bind mounts", existingPath, binds)
   185  	}
   186  	return nil
   187  }