...

Source file src/github.com/Microsoft/hcsshim/internal/guest/storage/overlay/overlay.go

Documentation: github.com/Microsoft/hcsshim/internal/guest/storage/overlay

     1  //go:build linux
     2  // +build linux
     3  
     4  package overlay
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"os"
    10  	"path/filepath"
    11  	"strings"
    12  
    13  	"github.com/Microsoft/hcsshim/internal/log"
    14  	"github.com/Microsoft/hcsshim/internal/memory"
    15  	"github.com/Microsoft/hcsshim/internal/oc"
    16  	"github.com/pkg/errors"
    17  	"github.com/sirupsen/logrus"
    18  	"go.opencensus.io/trace"
    19  	"golang.org/x/sys/unix"
    20  )
    21  
    22  // Test dependencies.
    23  var (
    24  	osMkdirAll  = os.MkdirAll
    25  	osRemoveAll = os.RemoveAll
    26  	unixMount   = unix.Mount
    27  )
    28  
    29  // processErrNoSpace logs disk space and inode information for `path` that we encountered the ENOSPC error on.
    30  // This can be used to get a better view of whats going on on the disk at the time of the error.
    31  func processErrNoSpace(ctx context.Context, path string, err error) {
    32  	st := &unix.Statfs_t{}
    33  	// Pass in filepath.Dir() of the path as if we got an error while creating the directory it definitely doesn't exist.
    34  	// Take its parent, which should be on the same drive.
    35  	if statErr := unix.Statfs(filepath.Dir(path), st); statErr != nil {
    36  		log.G(ctx).WithError(statErr).WithField("path", filepath.Dir(path)).Warn("failed to get disk information for ENOSPC error")
    37  		return
    38  	}
    39  
    40  	all := st.Blocks * uint64(st.Bsize)
    41  	available := st.Bavail * uint64(st.Bsize)
    42  	free := st.Bfree * uint64(st.Bsize)
    43  	used := all - free
    44  
    45  	toGigabyteStr := func(val uint64) string {
    46  		return fmt.Sprintf("%.1f", float64(val)/float64(memory.GiB))
    47  	}
    48  
    49  	log.G(ctx).WithFields(logrus.Fields{
    50  		"available-disk-space-GiB": toGigabyteStr(available),
    51  		"free-disk-space-GiB":      toGigabyteStr(free),
    52  		"used-disk-space-GiB":      toGigabyteStr(used),
    53  		"total-inodes":             st.Files,
    54  		"free-inodes":              st.Ffree,
    55  		"path":                     path,
    56  	}).WithError(err).Warn("got ENOSPC, gathering diagnostics")
    57  }
    58  
    59  // MountLayer first enforces the security policy for the container's layer paths
    60  // and then calls Mount to mount the layer paths as an overlayfs.
    61  func MountLayer(
    62  	ctx context.Context,
    63  	layerPaths []string,
    64  	upperdirPath, workdirPath, rootfsPath string,
    65  	readonly bool,
    66  ) (err error) {
    67  	_, span := oc.StartSpan(ctx, "overlay::MountLayer")
    68  	defer span.End()
    69  	defer func() { oc.SetSpanStatus(span, err) }()
    70  
    71  	return Mount(ctx, layerPaths, upperdirPath, workdirPath, rootfsPath, readonly)
    72  }
    73  
    74  // Mount creates an overlay mount with `basePaths` at `target`.
    75  //
    76  // If `upperdirPath != ""` the path will be created. On mount failure the
    77  // created `upperdirPath` will be automatically cleaned up.
    78  //
    79  // If `workdirPath != ""` the path will be created. On mount failure the created
    80  // `workdirPath` will be automatically cleaned up.
    81  //
    82  // Always creates `target`. On mount failure the created `target` will
    83  // be automatically cleaned up.
    84  func Mount(ctx context.Context, basePaths []string, upperdirPath, workdirPath, target string, readonly bool) (err error) {
    85  	_, span := oc.StartSpan(ctx, "overlay::Mount")
    86  	defer span.End()
    87  	defer func() { oc.SetSpanStatus(span, err) }()
    88  
    89  	lowerdir := strings.Join(basePaths, ":")
    90  	span.AddAttributes(
    91  		trace.StringAttribute("lowerdir", lowerdir),
    92  		trace.StringAttribute("upperdirPath", upperdirPath),
    93  		trace.StringAttribute("workdirPath", workdirPath),
    94  		trace.StringAttribute("target", target),
    95  		trace.BoolAttribute("readonly", readonly))
    96  
    97  	// If we got an ENOSPC error on creating any directories, log disk space and inode info for
    98  	//  the mount that the directory belongs to get a better view of the where the problem lies.
    99  	defer func() {
   100  		var perr *os.PathError
   101  		if errors.As(err, &perr) && errors.Is(perr.Err, unix.ENOSPC) {
   102  			processErrNoSpace(ctx, perr.Path, err)
   103  		}
   104  	}()
   105  
   106  	if target == "" {
   107  		return errors.New("cannot have empty target")
   108  	}
   109  
   110  	if readonly && (upperdirPath != "" || workdirPath != "") {
   111  		return errors.Errorf("upperdirPath: %q, and workdirPath: %q must be empty when readonly==true", upperdirPath, workdirPath)
   112  	}
   113  
   114  	options := []string{"lowerdir=" + lowerdir}
   115  	if upperdirPath != "" {
   116  		if err := osMkdirAll(upperdirPath, 0755); err != nil {
   117  			return errors.Wrap(err, "failed to create upper directory in scratch space")
   118  		}
   119  		defer func() {
   120  			if err != nil {
   121  				_ = osRemoveAll(upperdirPath)
   122  			}
   123  		}()
   124  		options = append(options, "upperdir="+upperdirPath)
   125  	}
   126  	if workdirPath != "" {
   127  		if err := osMkdirAll(workdirPath, 0755); err != nil {
   128  			return errors.Wrap(err, "failed to create workdir in scratch space")
   129  		}
   130  		defer func() {
   131  			if err != nil {
   132  				_ = osRemoveAll(workdirPath)
   133  			}
   134  		}()
   135  		options = append(options, "workdir="+workdirPath)
   136  	}
   137  	if err := osMkdirAll(target, 0755); err != nil {
   138  		return errors.Wrapf(err, "failed to create directory for container root filesystem %s", target)
   139  	}
   140  	defer func() {
   141  		if err != nil {
   142  			_ = osRemoveAll(target)
   143  		}
   144  	}()
   145  	var flags uintptr
   146  	if readonly {
   147  		flags |= unix.MS_RDONLY
   148  	}
   149  	if err := unixMount("overlay", target, "overlay", flags, strings.Join(options, ",")); err != nil {
   150  		return errors.Wrapf(err, "failed to mount overlayfs at %s", target)
   151  	}
   152  	return nil
   153  }
   154  

View as plain text