10
"github.com/containers/podman/v5/libpod/define"
11
securejoin "github.com/cyphar/filepath-securejoin"
12
"github.com/opencontainers/runtime-spec/specs-go"
13
"github.com/sirupsen/logrus"
16
// pathAbs returns an absolute path. If the specified path is
17
// relative, it will be resolved relative to the container's working dir.
18
func (c *Container) pathAbs(path string) string {
19
if !filepath.IsAbs(path) {
20
// If the containerPath is not absolute, it's relative to the
21
// container's working dir. To be extra careful, let's first
22
// join the working dir with "/", and the add the containerPath
24
path = filepath.Join(filepath.Join("/", c.WorkingDir()), path)
29
// resolvePath resolves the container's mount point and the container
30
// path as specified by the user. Both may resolve to paths outside of the
31
// container's mount point when the container path hits a volume or bind mount.
33
// It returns a bool, indicating whether containerPath resolves outside of
34
// mountPoint (e.g., via a mount or volume), the resolved root (e.g., container
35
// mount, bind mount or volume) and the resolved path on the root (absolute to
37
func (c *Container) resolvePath(mountPoint string, containerPath string) (string, string, error) {
38
// Let's first make sure we have a path relative to the mount point.
39
pathRelativeToContainerMountPoint := c.pathAbs(containerPath)
40
resolvedPathOnTheContainerMountPoint := filepath.Join(mountPoint, pathRelativeToContainerMountPoint)
41
pathRelativeToContainerMountPoint = strings.TrimPrefix(pathRelativeToContainerMountPoint, mountPoint)
42
pathRelativeToContainerMountPoint = filepath.Join("/", pathRelativeToContainerMountPoint)
44
// Now we have an "absolute container Path" but not yet resolved on the
45
// host (e.g., "/foo/bar/file.txt"). As mentioned above, we need to
46
// check if "/foo/bar/file.txt" is on a volume or bind mount. To do
47
// that, we need to walk *down* the paths to the root. Assuming
48
// volume-1 is mounted to "/foo" and volume-2 is mounted to "/foo/bar",
49
// we must select "/foo/bar". Once selected, we need to rebase the
50
// remainder (i.e, "/file.txt") on the volume's mount point on the
51
// host. Same applies to bind mounts.
53
searchPath := pathRelativeToContainerMountPoint
55
volume, err := findVolume(c, searchPath)
60
logrus.Debugf("Container path %q resolved to volume %q on path %q", containerPath, volume.Name(), searchPath)
62
// TODO: We really need to force the volume to mount
63
// before doing this, but that API is not exposed
64
// externally right now and doing so is beyond the scope
66
mountPoint, err := volume.MountPoint()
71
return "", "", fmt.Errorf("volume %s is not mounted, cannot copy into it", volume.Name())
74
// We found a matching volume for searchPath. We now
75
// need to first find the relative path of our input
76
// path to the searchPath, and then join it with the
77
// volume's mount point.
78
pathRelativeToVolume := strings.TrimPrefix(pathRelativeToContainerMountPoint, searchPath)
79
absolutePathOnTheVolumeMount, err := securejoin.SecureJoin(mountPoint, pathRelativeToVolume)
83
return mountPoint, absolutePathOnTheVolumeMount, nil
86
if mount := findBindMount(c, searchPath); mount != nil {
87
logrus.Debugf("Container path %q resolved to bind mount %q:%q on path %q", containerPath, mount.Source, mount.Destination, searchPath)
88
// We found a matching bind mount for searchPath. We
89
// now need to first find the relative path of our
90
// input path to the searchPath, and then join it with
91
// the source of the bind mount.
92
pathRelativeToBindMount := strings.TrimPrefix(pathRelativeToContainerMountPoint, searchPath)
93
absolutePathOnTheBindMount, err := securejoin.SecureJoin(mount.Source, pathRelativeToBindMount)
97
return mount.Source, absolutePathOnTheBindMount, nil
100
if searchPath == "/" {
101
// Cannot go beyond "/", so we're done.
104
// Walk *down* the path (e.g., "/foo/bar/x" -> "/foo/bar").
105
searchPath = filepath.Dir(searchPath)
108
// No volume, no bind mount but just a normal path on the container.
109
return mountPoint, resolvedPathOnTheContainerMountPoint, nil
112
// findVolume checks if the specified containerPath matches the destination
113
// path of a Volume. Returns a matching Volume or nil.
114
func findVolume(c *Container, containerPath string) (*Volume, error) {
115
runtime := c.Runtime()
116
cleanedContainerPath := filepath.Clean(containerPath)
117
for _, vol := range c.config.NamedVolumes {
118
if cleanedContainerPath == filepath.Clean(vol.Dest) {
119
return runtime.GetVolume(vol.Name)
125
// isSubDir checks whether path is a subdirectory of root.
126
func isSubDir(path, root string) bool {
127
// check if the specified container path is below a bind mount.
128
rel, err := filepath.Rel(root, path)
132
return rel != ".." && !strings.HasPrefix(rel, "../")
135
// isPathOnVolume returns true if the specified containerPath is a subdir of any
136
// Volume's destination.
137
func isPathOnVolume(c *Container, containerPath string) bool {
138
cleanedContainerPath := filepath.Clean(containerPath)
139
for _, vol := range c.config.NamedVolumes {
140
cleanedDestination := filepath.Clean(vol.Dest)
141
if cleanedContainerPath == cleanedDestination {
144
if isSubDir(cleanedContainerPath, cleanedDestination) {
147
for dest := cleanedDestination; dest != "/" && dest != "."; dest = filepath.Dir(dest) {
148
if cleanedContainerPath == dest {
156
// findBindMount checks if the specified containerPath matches the destination
157
// path of a Mount. Returns a matching Mount or nil.
158
func findBindMount(c *Container, containerPath string) *specs.Mount {
159
cleanedPath := filepath.Clean(containerPath)
160
for _, m := range c.config.Spec.Mounts {
161
if m.Type != define.TypeBind {
164
if cleanedPath == filepath.Clean(m.Destination) {
172
// / isPathOnMount returns true if the specified containerPath is a subdir of any
173
// Mount's destination.
174
func isPathOnMount(c *Container, containerPath string) bool {
175
cleanedContainerPath := filepath.Clean(containerPath)
176
for _, m := range c.config.Spec.Mounts {
177
cleanedDestination := filepath.Clean(m.Destination)
178
if cleanedContainerPath == cleanedDestination {
181
if isSubDir(cleanedContainerPath, cleanedDestination) {
184
for dest := cleanedDestination; dest != "/" && dest != "."; dest = filepath.Dir(dest) {
185
if cleanedContainerPath == dest {