github.com/AbhinandanKurakure/podman/v3@v3.4.10/libpod/container_path_resolution.go (about) 1 // +linux 2 package libpod 3 4 import ( 5 "path/filepath" 6 "strings" 7 8 securejoin "github.com/cyphar/filepath-securejoin" 9 "github.com/opencontainers/runtime-spec/specs-go" 10 "github.com/pkg/errors" 11 "github.com/sirupsen/logrus" 12 ) 13 14 // pathAbs returns an absolute path. If the specified path is 15 // relative, it will be resolved relative to the container's working dir. 16 func (c *Container) pathAbs(path string) string { 17 if !filepath.IsAbs(path) { 18 // If the containerPath is not absolute, it's relative to the 19 // container's working dir. To be extra careful, let's first 20 // join the working dir with "/", and the add the containerPath 21 // to it. 22 path = filepath.Join(filepath.Join("/", c.WorkingDir()), path) 23 } 24 return path 25 } 26 27 // resolveContainerPaths resolves the container's mount point and the container 28 // path as specified by the user. Both may resolve to paths outside of the 29 // container's mount point when the container path hits a volume or bind mount. 30 // 31 // It returns a bool, indicating whether containerPath resolves outside of 32 // mountPoint (e.g., via a mount or volume), the resolved root (e.g., container 33 // mount, bind mount or volume) and the resolved path on the root (absolute to 34 // the host). 35 func (c *Container) resolvePath(mountPoint string, containerPath string) (string, string, error) { 36 // Let's first make sure we have a path relative to the mount point. 37 pathRelativeToContainerMountPoint := c.pathAbs(containerPath) 38 resolvedPathOnTheContainerMountPoint := filepath.Join(mountPoint, pathRelativeToContainerMountPoint) 39 pathRelativeToContainerMountPoint = strings.TrimPrefix(pathRelativeToContainerMountPoint, mountPoint) 40 pathRelativeToContainerMountPoint = filepath.Join("/", pathRelativeToContainerMountPoint) 41 42 // Now we have an "absolute container Path" but not yet resolved on the 43 // host (e.g., "/foo/bar/file.txt"). As mentioned above, we need to 44 // check if "/foo/bar/file.txt" is on a volume or bind mount. To do 45 // that, we need to walk *down* the paths to the root. Assuming 46 // volume-1 is mounted to "/foo" and volume-2 is mounted to "/foo/bar", 47 // we must select "/foo/bar". Once selected, we need to rebase the 48 // remainder (i.e, "/file.txt") on the volume's mount point on the 49 // host. Same applies to bind mounts. 50 51 searchPath := pathRelativeToContainerMountPoint 52 for { 53 volume, err := findVolume(c, searchPath) 54 if err != nil { 55 return "", "", err 56 } 57 if volume != nil { 58 logrus.Debugf("Container path %q resolved to volume %q on path %q", containerPath, volume.Name(), searchPath) 59 60 // TODO: We really need to force the volume to mount 61 // before doing this, but that API is not exposed 62 // externally right now and doing so is beyond the scope 63 // of this commit. 64 mountPoint, err := volume.MountPoint() 65 if err != nil { 66 return "", "", err 67 } 68 if mountPoint == "" { 69 return "", "", errors.Errorf("volume %s is not mounted, cannot copy into it", volume.Name()) 70 } 71 72 // We found a matching volume for searchPath. We now 73 // need to first find the relative path of our input 74 // path to the searchPath, and then join it with the 75 // volume's mount point. 76 pathRelativeToVolume := strings.TrimPrefix(pathRelativeToContainerMountPoint, searchPath) 77 absolutePathOnTheVolumeMount, err := securejoin.SecureJoin(mountPoint, pathRelativeToVolume) 78 if err != nil { 79 return "", "", err 80 } 81 return mountPoint, absolutePathOnTheVolumeMount, nil 82 } 83 84 if mount := findBindMount(c, searchPath); mount != nil { 85 logrus.Debugf("Container path %q resolved to bind mount %q:%q on path %q", containerPath, mount.Source, mount.Destination, searchPath) 86 // We found a matching bind mount for searchPath. We 87 // now need to first find the relative path of our 88 // input path to the searchPath, and then join it with 89 // the source of the bind mount. 90 pathRelativeToBindMount := strings.TrimPrefix(pathRelativeToContainerMountPoint, searchPath) 91 absolutePathOnTheBindMount, err := securejoin.SecureJoin(mount.Source, pathRelativeToBindMount) 92 if err != nil { 93 return "", "", err 94 } 95 return mount.Source, absolutePathOnTheBindMount, nil 96 } 97 98 if searchPath == "/" { 99 // Cannot go beyond "/", so we're done. 100 break 101 } 102 // Walk *down* the path (e.g., "/foo/bar/x" -> "/foo/bar"). 103 searchPath = filepath.Dir(searchPath) 104 } 105 106 // No volume, no bind mount but just a normal path on the container. 107 return mountPoint, resolvedPathOnTheContainerMountPoint, nil 108 } 109 110 // findVolume checks if the specified containerPath matches the destination 111 // path of a Volume. Returns a matching Volume or nil. 112 func findVolume(c *Container, containerPath string) (*Volume, error) { 113 runtime := c.Runtime() 114 cleanedContainerPath := filepath.Clean(containerPath) 115 for _, vol := range c.config.NamedVolumes { 116 if cleanedContainerPath == filepath.Clean(vol.Dest) { 117 return runtime.GetVolume(vol.Name) 118 } 119 } 120 return nil, nil 121 } 122 123 // isPathOnVolume returns true if the specified containerPath is a subdir of any 124 // Volume's destination. 125 func isPathOnVolume(c *Container, containerPath string) bool { 126 cleanedContainerPath := filepath.Clean(containerPath) 127 for _, vol := range c.config.NamedVolumes { 128 if cleanedContainerPath == filepath.Clean(vol.Dest) { 129 return true 130 } 131 for dest := vol.Dest; dest != "/" && dest != "."; dest = filepath.Dir(dest) { 132 if cleanedContainerPath == dest { 133 return true 134 } 135 } 136 } 137 return false 138 } 139 140 // findBindMounts checks if the specified containerPath matches the destination 141 // path of a Mount. Returns a matching Mount or nil. 142 func findBindMount(c *Container, containerPath string) *specs.Mount { 143 cleanedPath := filepath.Clean(containerPath) 144 for _, m := range c.config.Spec.Mounts { 145 if m.Type != "bind" { 146 continue 147 } 148 if cleanedPath == filepath.Clean(m.Destination) { 149 mount := m 150 return &mount 151 } 152 } 153 return nil 154 } 155 156 /// isPathOnBindMount returns true if the specified containerPath is a subdir of any 157 // Mount's destination. 158 func isPathOnBindMount(c *Container, containerPath string) bool { 159 cleanedContainerPath := filepath.Clean(containerPath) 160 for _, m := range c.config.Spec.Mounts { 161 if cleanedContainerPath == filepath.Clean(m.Destination) { 162 return true 163 } 164 for dest := m.Destination; dest != "/" && dest != "."; dest = filepath.Dir(dest) { 165 if cleanedContainerPath == dest { 166 return true 167 } 168 } 169 } 170 return false 171 }