github.com/MerlinKodo/gvisor@v0.0.0-20231110090155-957f62ecf90e/runsc/boot/mount_hints.go (about) 1 // Copyright 2022 The gVisor Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package boot 16 17 import ( 18 "fmt" 19 "strings" 20 21 "github.com/MerlinKodo/gvisor/pkg/log" 22 "github.com/MerlinKodo/gvisor/pkg/sentry/fsimpl/tmpfs" 23 "github.com/MerlinKodo/gvisor/runsc/config" 24 "github.com/MerlinKodo/gvisor/runsc/specutils" 25 specs "github.com/opencontainers/runtime-spec/specs-go" 26 ) 27 28 // MountPrefix is the annotation prefix for mount hints. 29 const MountPrefix = "dev.gvisor.spec.mount." 30 31 // ShareType indicates who can access/mutate the volume contents. 32 type ShareType int 33 34 const ( 35 invalid ShareType = iota 36 37 // container shareType indicates that the mount is used by a single 38 // container. There are no external observers. 39 container 40 41 // pod shareType indicates that the mount is used by more than one container 42 // inside the pod. There are no external observers. 43 pod 44 45 // shared shareType indicates that the mount can also be shared with a process 46 // outside the pod, e.g. NFS. 47 shared 48 ) 49 50 func (s ShareType) String() string { 51 switch s { 52 case invalid: 53 return "invalid" 54 case container: 55 return "container" 56 case pod: 57 return "pod" 58 case shared: 59 return "shared" 60 default: 61 return fmt.Sprintf("invalid share value %d", s) 62 } 63 } 64 65 // LifecycleType indicates whether creation/deletion of the volume is tied to 66 // the pod or container's lifecycle. 67 type LifecycleType int 68 69 const ( 70 // sharedLife indicates that the volume's lifecycle is not tied to the pod. 71 // The volume persists beyond the pod's life. This is the safe default. 72 sharedLife LifecycleType = iota 73 74 // podLife indicates that the volume's lifecycle is tied to the pod's 75 // lifecycle. The volume is destroyed with the pod. 76 podLife 77 78 // containerLife indicates that the volume's lifecycle is tied to the 79 // container's lifecycle. The volume is destroyed with the container. 80 containerLife 81 ) 82 83 func (o LifecycleType) String() string { 84 switch o { 85 case sharedLife: 86 return "shared" 87 case podLife: 88 return "pod" 89 case containerLife: 90 return "container" 91 default: 92 return fmt.Sprintf("invalid lifecycle value %d", o) 93 } 94 } 95 96 // PodMountHints contains a collection of mountHints for the pod. 97 type PodMountHints struct { 98 Mounts map[string]*MountHint `json:"mounts"` 99 } 100 101 // NewPodMountHints instantiates PodMountHints using spec. 102 func NewPodMountHints(spec *specs.Spec) (*PodMountHints, error) { 103 mnts := make(map[string]*MountHint) 104 for k, v := range spec.Annotations { 105 // Look for 'dev.gvisor.spec.mount' annotations and parse them. 106 if strings.HasPrefix(k, MountPrefix) { 107 // Remove the prefix and split the rest. 108 parts := strings.Split(k[len(MountPrefix):], ".") 109 if len(parts) != 2 { 110 return nil, fmt.Errorf("invalid mount annotation: %s=%s", k, v) 111 } 112 name := parts[0] 113 if len(name) == 0 { 114 return nil, fmt.Errorf("invalid mount name: %s", name) 115 } 116 mnt := mnts[name] 117 if mnt == nil { 118 mnt = &MountHint{Name: name} 119 mnts[name] = mnt 120 } 121 if err := mnt.setField(parts[1], v); err != nil { 122 log.Warningf("ignoring invalid mount annotation (name = %q, key = %q, value = %q): %v", name, parts[1], v, err) 123 } 124 } 125 } 126 127 // Validate all the parsed hints. 128 for name, m := range mnts { 129 log.Infof("Mount annotation found, name: %s, source: %q, type: %s, share: %v", name, m.Mount.Source, m.Mount.Type, m.Share) 130 if m.Share == invalid || len(m.Mount.Source) == 0 || len(m.Mount.Type) == 0 { 131 log.Warningf("ignoring mount annotations for %q because of missing required field(s)", name) 132 delete(mnts, name) 133 continue 134 } 135 136 // Check for duplicate mount sources. 137 for name2, m2 := range mnts { 138 if name != name2 && m.Mount.Source == m2.Mount.Source { 139 return nil, fmt.Errorf("mounts %q and %q have the same mount source %q", m.Name, m2.Name, m.Mount.Source) 140 } 141 } 142 } 143 144 return &PodMountHints{Mounts: mnts}, nil 145 } 146 147 // MountHint represents extra information about mounts that are provided via 148 // annotations. They can override mount type, and provide sharing information 149 // so that mounts can be correctly shared inside the pod. 150 // It is part of the sandbox.Sandbox struct, so it must be serializable. 151 type MountHint struct { 152 Name string `json:"name"` 153 Share ShareType `json:"share"` 154 Mount specs.Mount `json:"mount"` 155 Lifecycle LifecycleType `json:"lifecycle"` 156 } 157 158 func (m *MountHint) setField(key, val string) error { 159 switch key { 160 case "source": 161 if len(val) == 0 { 162 return fmt.Errorf("source cannot be empty") 163 } 164 m.Mount.Source = val 165 case "type": 166 return m.setType(val) 167 case "share": 168 return m.setShare(val) 169 case "options": 170 m.Mount.Options = specutils.FilterMountOptions(strings.Split(val, ",")) 171 case "lifecycle": 172 return m.setLifecycle(val) 173 default: 174 return fmt.Errorf("invalid mount annotation: %s=%s", key, val) 175 } 176 return nil 177 } 178 179 func (m *MountHint) setType(val string) error { 180 switch val { 181 case tmpfs.Name, Bind: 182 m.Mount.Type = val 183 default: 184 return fmt.Errorf("invalid type %q", val) 185 } 186 return nil 187 } 188 189 func (m *MountHint) setShare(val string) error { 190 switch val { 191 case container.String(): 192 m.Share = container 193 case pod.String(): 194 m.Share = pod 195 case shared.String(): 196 m.Share = shared 197 default: 198 return fmt.Errorf("invalid share value %q", val) 199 } 200 return nil 201 } 202 203 func (m *MountHint) setLifecycle(val string) error { 204 switch val { 205 case containerLife.String(): 206 m.Lifecycle = containerLife 207 case podLife.String(): 208 m.Lifecycle = podLife 209 case sharedLife.String(): 210 m.Lifecycle = sharedLife 211 default: 212 return fmt.Errorf("invalid lifecycle %q", val) 213 } 214 return nil 215 } 216 217 // shouldShareMount returns true if this mount should be configured as a shared 218 // mount that is shared among multiple containers in a pod. 219 func (m *MountHint) shouldShareMount() bool { 220 // TODO(b/142076984): Only support tmpfs for now. Bind mounts require a 221 // common gofer to mount all shared volumes. 222 return m.Mount.Type == tmpfs.Name && m.Share == pod 223 } 224 225 // ShouldOverlay returns true if this mount should be overlaid. 226 func (m *MountHint) ShouldOverlay() bool { 227 // TODO(b/142076984): Only support share=container for now. Once shared gofer 228 // support is added, we can overlay shared bind mounts too. 229 return m.Mount.Type == Bind && m.Share == container && m.Lifecycle != sharedLife 230 } 231 232 // checkCompatible verifies that shared mount is compatible with master. 233 // Master options must be the same or less restrictive than the container mount, 234 // e.g. master can be 'rw' while container mounts as 'ro'. 235 func (m *MountHint) checkCompatible(replica *specs.Mount) error { 236 masterOpts := ParseMountOptions(m.Mount.Options) 237 replicaOpts := ParseMountOptions(replica.Options) 238 239 if masterOpts.ReadOnly && !replicaOpts.ReadOnly { 240 return fmt.Errorf("cannot mount read-write shared mount because master is read-only, mount: %+v", replica) 241 } 242 if masterOpts.Flags.NoExec && !replicaOpts.Flags.NoExec { 243 return fmt.Errorf("cannot mount exec enabled shared mount because master is noexec, mount: %+v", replica) 244 } 245 if masterOpts.Flags.NoATime && !replicaOpts.Flags.NoATime { 246 return fmt.Errorf("cannot mount atime enabled shared mount because master is noatime, mount: %+v", replica) 247 } 248 return nil 249 } 250 251 // Precondition: m.mount.Type == Bind. 252 func (m *MountHint) fileAccessType() config.FileAccessType { 253 if m.Share == shared { 254 return config.FileAccessShared 255 } 256 if m.shouldShareMount() { 257 return config.FileAccessExclusive 258 } 259 if m.Share == container { 260 return config.FileAccessExclusive 261 } 262 return config.FileAccessShared 263 } 264 265 // FindMount finds the MountHint that applies to this mount. 266 func (p *PodMountHints) FindMount(mount *specs.Mount) *MountHint { 267 for _, m := range p.Mounts { 268 if m.Mount.Source == mount.Source { 269 return m 270 } 271 } 272 return nil 273 }