github.com/hanks177/podman/v4@v4.1.3-0.20220613032544-16d90015bc83/libpod/container_stat_linux.go (about) 1 //go:build linux 2 // +build linux 3 4 package libpod 5 6 import ( 7 "os" 8 "path/filepath" 9 "strings" 10 11 "github.com/containers/buildah/copier" 12 "github.com/hanks177/podman/v4/libpod/define" 13 "github.com/hanks177/podman/v4/pkg/copy" 14 "github.com/pkg/errors" 15 ) 16 17 // statInsideMount stats the specified path *inside* the container's mount and PID 18 // namespace. It returns the file info along with the resolved root ("/") and 19 // the resolved path (relative to the root). 20 func (c *Container) statInsideMount(containerPath string) (*copier.StatForItem, string, string, error) { 21 resolvedRoot := "/" 22 resolvedPath := c.pathAbs(containerPath) 23 var statInfo *copier.StatForItem 24 25 err := c.joinMountAndExec( 26 func() error { 27 var statErr error 28 statInfo, statErr = secureStat(resolvedRoot, resolvedPath) 29 return statErr 30 }, 31 ) 32 33 return statInfo, resolvedRoot, resolvedPath, err 34 } 35 36 // statOnHost stats the specified path *on the host*. It returns the file info 37 // along with the resolved root and the resolved path. Both paths are absolute 38 // to the host's root. Note that the paths may resolved outside the 39 // container's mount point (e.g., to a volume or bind mount). 40 func (c *Container) statOnHost(mountPoint string, containerPath string) (*copier.StatForItem, string, string, error) { 41 // Now resolve the container's path. It may hit a volume, it may hit a 42 // bind mount, it may be relative. 43 resolvedRoot, resolvedPath, err := c.resolvePath(mountPoint, containerPath) 44 if err != nil { 45 return nil, "", "", err 46 } 47 48 statInfo, err := secureStat(resolvedRoot, resolvedPath) 49 return statInfo, resolvedRoot, resolvedPath, err 50 } 51 52 func (c *Container) stat(containerMountPoint string, containerPath string) (*define.FileInfo, string, string, error) { 53 var ( 54 resolvedRoot string 55 resolvedPath string 56 absContainerPath string 57 statInfo *copier.StatForItem 58 statErr error 59 ) 60 61 // Make sure that "/" copies the *contents* of the mount point and not 62 // the directory. 63 if containerPath == "/" { 64 containerPath = "/." 65 } 66 67 // Wildcards are not allowed. 68 // TODO: it's now technically possible wildcards. 69 // We may consider enabling support in the future. 70 if strings.Contains(containerPath, "*") { 71 return nil, "", "", copy.ErrENOENT 72 } 73 74 if c.state.State == define.ContainerStateRunning { 75 // If the container is running, we need to join it's mount namespace 76 // and stat there. 77 statInfo, resolvedRoot, resolvedPath, statErr = c.statInsideMount(containerPath) 78 } else { 79 // If the container is NOT running, we need to resolve the path 80 // on the host. 81 statInfo, resolvedRoot, resolvedPath, statErr = c.statOnHost(containerMountPoint, containerPath) 82 } 83 84 if statErr != nil { 85 if statInfo == nil { 86 return nil, "", "", statErr 87 } 88 // Not all errors from secureStat map to ErrNotExist, so we 89 // have to look into the error string. Turning it into an 90 // ENOENT let's the API handlers return the correct status code 91 // which is crucial for the remote client. 92 if os.IsNotExist(statErr) || strings.Contains(statErr.Error(), "o such file or directory") { 93 statErr = copy.ErrENOENT 94 } 95 } 96 97 switch { 98 case statInfo.IsSymlink: 99 // Symlinks are already evaluated and always relative to the 100 // container's mount point. 101 absContainerPath = statInfo.ImmediateTarget 102 case strings.HasPrefix(resolvedPath, containerMountPoint): 103 // If the path is on the container's mount point, strip it off. 104 absContainerPath = strings.TrimPrefix(resolvedPath, containerMountPoint) 105 absContainerPath = filepath.Join("/", absContainerPath) 106 default: 107 // No symlink and not on the container's mount point, so let's 108 // move it back to the original input. It must have evaluated 109 // to a volume or bind mount but we cannot return host paths. 110 absContainerPath = containerPath 111 } 112 113 // Preserve the base path as specified by the user. The `filepath` 114 // packages likes to remove trailing slashes and dots that are crucial 115 // to the copy logic. 116 absContainerPath = copy.PreserveBasePath(containerPath, absContainerPath) 117 resolvedPath = copy.PreserveBasePath(containerPath, resolvedPath) 118 119 info := &define.FileInfo{ 120 IsDir: statInfo.IsDir, 121 Name: filepath.Base(absContainerPath), 122 Size: statInfo.Size, 123 Mode: statInfo.Mode, 124 ModTime: statInfo.ModTime, 125 LinkTarget: absContainerPath, 126 } 127 128 return info, resolvedRoot, resolvedPath, statErr 129 } 130 131 // secureStat extracts file info for path in a chroot'ed environment in root. 132 func secureStat(root string, path string) (*copier.StatForItem, error) { 133 var glob string 134 var err error 135 136 // If root and path are equal, then dir must be empty and the glob must 137 // be ".". 138 if filepath.Clean(root) == filepath.Clean(path) { 139 glob = "." 140 } else { 141 glob, err = filepath.Rel(root, path) 142 if err != nil { 143 return nil, err 144 } 145 } 146 147 globStats, err := copier.Stat(root, "", copier.StatOptions{}, []string{glob}) 148 if err != nil { 149 return nil, err 150 } 151 152 if len(globStats) != 1 { 153 return nil, errors.Errorf("internal error: secureStat: expected 1 item but got %d", len(globStats)) 154 } 155 if len(globStats) != 1 { 156 return nil, errors.Errorf("internal error: secureStat: expected 1 result but got %d", len(globStats[0].Results)) 157 } 158 159 // NOTE: the key in the map differ from `glob` when hitting symlink. 160 // Hence, we just take the first (and only) key/value pair. 161 for _, stat := range globStats[0].Results { 162 var statErr error 163 if stat.Error != "" { 164 statErr = errors.New(stat.Error) 165 } 166 // If necessary evaluate the symlink 167 if stat.IsSymlink { 168 target, err := copier.Eval(root, path, copier.EvalOptions{}) 169 if err != nil { 170 return nil, errors.Wrap(err, "error evaluating symlink in container") 171 } 172 // Need to make sure the symlink is relative to the root! 173 target = strings.TrimPrefix(target, root) 174 target = filepath.Join("/", target) 175 stat.ImmediateTarget = target 176 } 177 return stat, statErr 178 } 179 180 // Nothing found! 181 return nil, copy.ErrENOENT 182 }