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  }