github.com/rkt/rkt@v1.30.1-0.20200224141603-171c416fac02/stage1/init/common/mount.go (about) 1 // Copyright 2015 The rkt 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 common 16 17 import ( 18 "errors" 19 "fmt" 20 "os" 21 "path/filepath" 22 "strconv" 23 "syscall" 24 25 "github.com/rkt/rkt/common" 26 "github.com/rkt/rkt/pkg/fileutil" 27 "github.com/rkt/rkt/pkg/fs" 28 "github.com/rkt/rkt/pkg/user" 29 30 "github.com/appc/spec/schema" 31 "github.com/appc/spec/schema/types" 32 "github.com/hashicorp/errwrap" 33 ) 34 35 /* 36 * Some common stage1 mount tasks 37 * 38 * TODO(cdc) De-duplicate code from stage0/gc.go 39 */ 40 41 // Mount extends schema.Mount with additional rkt specific fields. 42 type Mount struct { 43 schema.Mount 44 45 Volume types.Volume 46 DockerImplicit bool 47 ReadOnly bool 48 } 49 50 // ConvertedFromDocker determines if an app's image has been converted 51 // from docker. This is needed because implicit docker empty volumes have 52 // different behavior from AppC 53 func ConvertedFromDocker(im *schema.ImageManifest) bool { 54 if im == nil { // nil sometimes sneaks in here due to unit tests 55 return false 56 } 57 ann := im.Annotations 58 _, ok := ann.Get("appc.io/docker/repository") 59 return ok 60 } 61 62 // Source computes the real volume source for a volume. 63 // Volumes of type 'empty' use a workdir relative to podRoot 64 func (m *Mount) Source(podRoot string) string { 65 switch m.Volume.Kind { 66 case "host": 67 return m.Volume.Source 68 case "empty": 69 return filepath.Join(common.SharedVolumesPath(podRoot), m.Volume.Name.String()) 70 } 71 return "" // We validate in GenerateMounts that it's valid 72 } 73 74 // GenerateMounts maps MountPoint paths to volumes, returning a list of mounts, 75 // each with a parameter indicating if it's an implicit empty volume from a 76 // Docker image. 77 func GenerateMounts(ra *schema.RuntimeApp, podVolumes []types.Volume, convertedFromDocker bool) ([]Mount, error) { 78 app := ra.App 79 80 var genMnts []Mount 81 82 vols := make(map[types.ACName]types.Volume) 83 for _, v := range podVolumes { 84 vols[v.Name] = v 85 } 86 87 // RuntimeApps have mounts, whereas Apps have mountPoints. mountPoints are partially for 88 // Docker compat; since apps can declare mountpoints. However, if we just run with rkt run, 89 // then we'll only have a Mount and no corresponding MountPoint. 90 // Furthermore, Mounts can have embedded volumes in the case of the CRI. 91 // So, we generate a pile of Mounts and their corresponding Volume 92 93 // Map of hostpath -> Mount 94 mnts := make(map[string]schema.Mount) 95 96 // Check runtimeApp's Mounts 97 for _, m := range ra.Mounts { 98 mnts[m.Path] = m 99 100 vol := m.AppVolume // Mounts can supply a volume 101 if vol == nil { 102 vv, ok := vols[m.Volume] 103 if !ok { 104 return nil, fmt.Errorf("could not find volume %s", m.Volume) 105 } 106 vol = &vv 107 } 108 109 // Find a corresponding MountPoint, which is optional 110 ro := false 111 for _, mp := range ra.App.MountPoints { 112 if mp.Name == m.Volume { 113 ro = mp.ReadOnly 114 break 115 } 116 } 117 if vol.ReadOnly != nil { 118 ro = *vol.ReadOnly 119 } 120 121 switch vol.Kind { 122 case "host": 123 case "empty": 124 default: 125 return nil, fmt.Errorf("Volume %s has invalid kind %s", vol.Name, vol.Kind) 126 } 127 genMnts = append(genMnts, 128 Mount{ 129 Mount: m, 130 DockerImplicit: false, 131 ReadOnly: ro, 132 Volume: *vol, 133 }) 134 } 135 136 // Now, match up MountPoints with Mounts or Volumes 137 // If there's no Mount and no Volume, generate an empty volume 138 for _, mp := range app.MountPoints { 139 // there's already a Mount for this MountPoint, stop 140 if _, ok := mnts[mp.Path]; ok { 141 continue 142 } 143 144 // No Mount, try to match based on volume name 145 vol, ok := vols[mp.Name] 146 // there is no volume for this mount point, creating an "empty" volume 147 // implicitly 148 if !ok { 149 defaultMode := "0755" 150 defaultUID := 0 151 defaultGID := 0 152 uniqName := ra.Name + "-" + mp.Name 153 emptyVol := types.Volume{ 154 Name: uniqName, 155 Kind: "empty", 156 Mode: &defaultMode, 157 UID: &defaultUID, 158 GID: &defaultGID, 159 } 160 161 log.Printf("warning: no volume specified for mount point %q, implicitly creating an \"empty\" volume. This volume will be removed when the pod is garbage-collected.", mp.Name) 162 if convertedFromDocker { 163 log.Printf("Docker converted image, initializing implicit volume with data contained at the mount point %q.", mp.Name) 164 } 165 166 vols[uniqName] = emptyVol 167 genMnts = append(genMnts, 168 Mount{ 169 Mount: schema.Mount{ 170 Volume: uniqName, 171 Path: mp.Path, 172 }, 173 Volume: emptyVol, 174 ReadOnly: mp.ReadOnly, 175 DockerImplicit: convertedFromDocker, 176 }) 177 } else { 178 ro := mp.ReadOnly 179 if vol.ReadOnly != nil { 180 ro = *vol.ReadOnly 181 } 182 genMnts = append(genMnts, 183 Mount{ 184 Mount: schema.Mount{ 185 Volume: vol.Name, 186 Path: mp.Path, 187 }, 188 Volume: vol, 189 ReadOnly: ro, 190 DockerImplicit: false, 191 }) 192 } 193 } 194 195 return genMnts, nil 196 } 197 198 // PrepareMountpoints creates and sets permissions for empty volumes. 199 // If the mountpoint comes from a Docker image and it is an implicit empty 200 // volume, we copy files from the image to the volume, see 201 // https://docs.docker.com/engine/userguide/containers/dockervolumes/#data-volumes 202 func PrepareMountpoints(volPath string, targetPath string, vol *types.Volume, dockerImplicit bool) error { 203 if vol.Kind != "empty" { 204 return nil 205 } 206 207 diag.Printf("creating an empty volume folder for sharing: %q", volPath) 208 m, err := strconv.ParseUint(*vol.Mode, 8, 32) 209 if err != nil { 210 return errwrap.Wrap(fmt.Errorf("invalid mode %q for volume %q", *vol.Mode, vol.Name), err) 211 } 212 mode := os.FileMode(m) 213 Uid := *vol.UID 214 Gid := *vol.GID 215 216 if dockerImplicit { 217 fi, err := os.Stat(targetPath) 218 if err == nil { 219 // the directory exists in the image, let's set the same 220 // permissions and copy files from there to the empty volume 221 mode = fi.Mode() 222 Uid = int(fi.Sys().(*syscall.Stat_t).Uid) 223 Gid = int(fi.Sys().(*syscall.Stat_t).Gid) 224 225 if err := fileutil.CopyTree(targetPath, volPath, user.NewBlankUidRange()); err != nil { 226 return errwrap.Wrap(fmt.Errorf("error copying image files to empty volume %q", volPath), err) 227 } 228 } 229 } 230 231 if err := os.MkdirAll(volPath, 0770); err != nil { 232 return errwrap.Wrap(fmt.Errorf("error creating %q", volPath), err) 233 } 234 if err := os.Chown(volPath, Uid, Gid); err != nil { 235 return errwrap.Wrap(fmt.Errorf("could not change owner of %q", volPath), err) 236 } 237 if err := os.Chmod(volPath, mode); err != nil { 238 return errwrap.Wrap(fmt.Errorf("could not change permissions of %q", volPath), err) 239 } 240 241 return nil 242 } 243 244 // BindMount, well, bind mounts a source in to a destination. This will 245 // do some bookkeeping: 246 // * evaluate all symlinks 247 // * ensure the source exists 248 // * recursively create the destination 249 func BindMount(mnt fs.MountUnmounter, source, destination string, readOnly bool) error { 250 absSource, err := filepath.EvalSymlinks(source) 251 if err != nil { 252 return errwrap.Wrap(fmt.Errorf("Could not resolve symlink for source %v", source), err) 253 } 254 255 if err := EnsureTargetExists(absSource, destination); err != nil { 256 return errwrap.Wrap(fmt.Errorf("Could not create destination mount point: %v", destination), err) 257 } else if err := mnt.Mount(absSource, destination, "bind", syscall.MS_BIND, ""); err != nil { 258 return errwrap.Wrap(fmt.Errorf("Could not bind mount %v to %v", absSource, destination), err) 259 } 260 if readOnly { 261 err := mnt.Mount(source, destination, "bind", syscall.MS_REMOUNT|syscall.MS_RDONLY|syscall.MS_BIND, "") 262 263 // If we failed to remount ro, unmount 264 if err != nil { 265 mnt.Unmount(destination, 0) // if this fails, oh well 266 return errwrap.Wrap(fmt.Errorf("Could not remount %v read-only", destination), err) 267 } 268 } 269 return nil 270 } 271 272 // EnsureTargetExists will recursively create a given mountpoint. If directories 273 // are created, their permissions are initialized to common.SharedVolumePerm 274 func EnsureTargetExists(source, destination string) error { 275 fileInfo, err := os.Stat(source) 276 if err != nil { 277 return errwrap.Wrap(fmt.Errorf("could not stat source location: %v", source), err) 278 } 279 280 targetPathParent, _ := filepath.Split(destination) 281 if err := os.MkdirAll(targetPathParent, common.SharedVolumePerm); err != nil { 282 return errwrap.Wrap(fmt.Errorf("could not create parent directory: %v", targetPathParent), err) 283 } 284 285 if fileInfo.IsDir() { 286 if err := os.Mkdir(destination, common.SharedVolumePerm); err != nil && !os.IsExist(err) { 287 return errwrap.Wrap(errors.New("could not create destination directory "+destination), err) 288 } 289 } else { 290 if file, err := os.OpenFile(destination, os.O_CREATE, common.SharedVolumePerm); err != nil { 291 return errwrap.Wrap(errors.New("could not create destination file"), err) 292 } else { 293 file.Close() 294 } 295 } 296 return nil 297 }