1
2
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
23 var (
24 osMkdirAll = os.MkdirAll
25 osRemoveAll = os.RemoveAll
26 unixMount = unix.Mount
27 )
28
29
30
31 func processErrNoSpace(ctx context.Context, path string, err error) {
32 st := &unix.Statfs_t{}
33
34
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
60
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
75
76
77
78
79
80
81
82
83
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
98
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