github.com/Heebron/moby@v0.0.0-20221111184709-6eab4f55faf7/daemon/archive_windows.go (about) 1 package daemon // import "github.com/docker/docker/daemon" 2 3 import ( 4 "errors" 5 "io" 6 "os" 7 "path/filepath" 8 "strings" 9 10 "github.com/docker/docker/api/types" 11 containertypes "github.com/docker/docker/api/types/container" 12 "github.com/docker/docker/container" 13 "github.com/docker/docker/errdefs" 14 "github.com/docker/docker/pkg/archive" 15 "github.com/docker/docker/pkg/chrootarchive" 16 "github.com/docker/docker/pkg/ioutils" 17 "github.com/docker/docker/pkg/system" 18 ) 19 20 // containerStatPath stats the filesystem resource at the specified path in this 21 // container. Returns stat info about the resource. 22 func (daemon *Daemon) containerStatPath(container *container.Container, path string) (stat *types.ContainerPathStat, err error) { 23 container.Lock() 24 defer container.Unlock() 25 26 // Make sure an online file-system operation is permitted. 27 if err := daemon.isOnlineFSOperationPermitted(container); err != nil { 28 return nil, err 29 } 30 31 if err = daemon.Mount(container); err != nil { 32 return nil, err 33 } 34 defer daemon.Unmount(container) 35 36 err = daemon.mountVolumes(container) 37 defer container.DetachAndUnmount(daemon.LogVolumeEvent) 38 if err != nil { 39 return nil, err 40 } 41 42 // Normalize path before sending to rootfs 43 path = filepath.FromSlash(path) 44 45 resolvedPath, absPath, err := container.ResolvePath(path) 46 if err != nil { 47 return nil, err 48 } 49 50 return container.StatPath(resolvedPath, absPath) 51 } 52 53 // containerArchivePath creates an archive of the filesystem resource at the specified 54 // path in this container. Returns a tar archive of the resource and stat info 55 // about the resource. 56 func (daemon *Daemon) containerArchivePath(container *container.Container, path string) (content io.ReadCloser, stat *types.ContainerPathStat, err error) { 57 container.Lock() 58 59 defer func() { 60 if err != nil { 61 // Wait to unlock the container until the archive is fully read 62 // (see the ReadCloseWrapper func below) or if there is an error 63 // before that occurs. 64 container.Unlock() 65 } 66 }() 67 68 // Make sure an online file-system operation is permitted. 69 if err := daemon.isOnlineFSOperationPermitted(container); err != nil { 70 return nil, nil, err 71 } 72 73 if err = daemon.Mount(container); err != nil { 74 return nil, nil, err 75 } 76 77 defer func() { 78 if err != nil { 79 // unmount any volumes 80 container.DetachAndUnmount(daemon.LogVolumeEvent) 81 // unmount the container's rootfs 82 daemon.Unmount(container) 83 } 84 }() 85 86 if err = daemon.mountVolumes(container); err != nil { 87 return nil, nil, err 88 } 89 90 // Normalize path before sending to rootfs 91 path = filepath.FromSlash(path) 92 93 resolvedPath, absPath, err := container.ResolvePath(path) 94 if err != nil { 95 return nil, nil, err 96 } 97 98 stat, err = container.StatPath(resolvedPath, absPath) 99 if err != nil { 100 return nil, nil, err 101 } 102 103 // We need to rebase the archive entries if the last element of the 104 // resolved path was a symlink that was evaluated and is now different 105 // than the requested path. For example, if the given path was "/foo/bar/", 106 // but it resolved to "/var/lib/docker/containers/{id}/foo/baz/", we want 107 // to ensure that the archive entries start with "bar" and not "baz". This 108 // also catches the case when the root directory of the container is 109 // requested: we want the archive entries to start with "/" and not the 110 // container ID. 111 112 // Get the source and the base paths of the container resolved path in order 113 // to get the proper tar options for the rebase tar. 114 resolvedPath = filepath.Clean(resolvedPath) 115 if filepath.Base(resolvedPath) == "." { 116 resolvedPath += string(filepath.Separator) + "." 117 } 118 119 sourceDir := resolvedPath 120 sourceBase := "." 121 122 if stat.Mode&os.ModeDir == 0 { // not dir 123 sourceDir, sourceBase = filepath.Split(resolvedPath) 124 } 125 opts := archive.TarResourceRebaseOpts(sourceBase, filepath.Base(absPath)) 126 127 data, err := chrootarchive.Tar(sourceDir, opts, container.BaseFS) 128 if err != nil { 129 return nil, nil, err 130 } 131 132 content = ioutils.NewReadCloserWrapper(data, func() error { 133 err := data.Close() 134 container.DetachAndUnmount(daemon.LogVolumeEvent) 135 daemon.Unmount(container) 136 container.Unlock() 137 return err 138 }) 139 140 daemon.LogContainerEvent(container, "archive-path") 141 142 return content, stat, nil 143 } 144 145 // containerExtractToDir extracts the given tar archive to the specified location in the 146 // filesystem of this container. The given path must be of a directory in the 147 // container. If it is not, the error will be an errdefs.InvalidParameter. If 148 // noOverwriteDirNonDir is true then it will be an error if unpacking the 149 // given content would cause an existing directory to be replaced with a non- 150 // directory and vice versa. 151 func (daemon *Daemon) containerExtractToDir(container *container.Container, path string, copyUIDGID, noOverwriteDirNonDir bool, content io.Reader) (err error) { 152 container.Lock() 153 defer container.Unlock() 154 155 // Make sure an online file-system operation is permitted. 156 if err := daemon.isOnlineFSOperationPermitted(container); err != nil { 157 return err 158 } 159 160 if err = daemon.Mount(container); err != nil { 161 return err 162 } 163 defer daemon.Unmount(container) 164 165 err = daemon.mountVolumes(container) 166 defer container.DetachAndUnmount(daemon.LogVolumeEvent) 167 if err != nil { 168 return err 169 } 170 171 // Normalize path before sending to rootfs' 172 path = filepath.FromSlash(path) 173 174 // Check if a drive letter supplied, it must be the system drive. No-op except on Windows 175 path, err = system.CheckSystemDriveAndRemoveDriveLetter(path) 176 if err != nil { 177 return err 178 } 179 180 // The destination path needs to be resolved to a host path, with all 181 // symbolic links followed in the scope of the container's rootfs. Note 182 // that we do not use `container.ResolvePath(path)` here because we need 183 // to also evaluate the last path element if it is a symlink. This is so 184 // that you can extract an archive to a symlink that points to a directory. 185 186 // Consider the given path as an absolute path in the container. 187 absPath := archive.PreserveTrailingDotOrSeparator(filepath.Join(string(filepath.Separator), path), path) 188 189 // This will evaluate the last path element if it is a symlink. 190 resolvedPath, err := container.GetResourcePath(absPath) 191 if err != nil { 192 return err 193 } 194 195 stat, err := os.Lstat(resolvedPath) 196 if err != nil { 197 return err 198 } 199 200 if !stat.IsDir() { 201 return errdefs.InvalidParameter(errors.New("extraction point is not a directory")) 202 } 203 204 // Need to check if the path is in a volume. If it is, it cannot be in a 205 // read-only volume. If it is not in a volume, the container cannot be 206 // configured with a read-only rootfs. 207 208 // Use the resolved path relative to the container rootfs as the new 209 // absPath. This way we fully follow any symlinks in a volume that may 210 // lead back outside the volume. 211 // 212 // The Windows implementation of filepath.Rel in golang 1.4 does not 213 // support volume style file path semantics. On Windows when using the 214 // filter driver, we are guaranteed that the path will always be 215 // a volume file path. 216 var baseRel string 217 if strings.HasPrefix(resolvedPath, `\\?\Volume{`) { 218 if strings.HasPrefix(resolvedPath, container.BaseFS) { 219 baseRel = resolvedPath[len(container.BaseFS):] 220 if baseRel[:1] == `\` { 221 baseRel = baseRel[1:] 222 } 223 } 224 } else { 225 baseRel, err = filepath.Rel(container.BaseFS, resolvedPath) 226 } 227 if err != nil { 228 return err 229 } 230 // Make it an absolute path. 231 absPath = filepath.Join(string(filepath.Separator), baseRel) 232 233 toVolume, err := checkIfPathIsInAVolume(container, absPath) 234 if err != nil { 235 return err 236 } 237 238 if !toVolume && container.HostConfig.ReadonlyRootfs { 239 return errdefs.InvalidParameter(errors.New("container rootfs is marked read-only")) 240 } 241 242 options := daemon.defaultTarCopyOptions(noOverwriteDirNonDir) 243 244 if copyUIDGID { 245 var err error 246 // tarCopyOptions will appropriately pull in the right uid/gid for the 247 // user/group and will set the options. 248 options, err = daemon.tarCopyOptions(container, noOverwriteDirNonDir) 249 if err != nil { 250 return err 251 } 252 } 253 254 if err := chrootarchive.UntarWithRoot(content, resolvedPath, options, container.BaseFS); err != nil { 255 return err 256 } 257 258 daemon.LogContainerEvent(container, "extract-to-dir") 259 260 return nil 261 } 262 263 func (daemon *Daemon) containerCopy(container *container.Container, resource string) (rc io.ReadCloser, err error) { 264 if resource[0] == '/' || resource[0] == '\\' { 265 resource = resource[1:] 266 } 267 container.Lock() 268 269 defer func() { 270 if err != nil { 271 // Wait to unlock the container until the archive is fully read 272 // (see the ReadCloseWrapper func below) or if there is an error 273 // before that occurs. 274 container.Unlock() 275 } 276 }() 277 278 // Make sure an online file-system operation is permitted. 279 if err := daemon.isOnlineFSOperationPermitted(container); err != nil { 280 return nil, err 281 } 282 283 if err := daemon.Mount(container); err != nil { 284 return nil, err 285 } 286 287 defer func() { 288 if err != nil { 289 // unmount any volumes 290 container.DetachAndUnmount(daemon.LogVolumeEvent) 291 // unmount the container's rootfs 292 daemon.Unmount(container) 293 } 294 }() 295 296 if err := daemon.mountVolumes(container); err != nil { 297 return nil, err 298 } 299 300 // Normalize path before sending to rootfs 301 resource = filepath.FromSlash(resource) 302 303 basePath, err := container.GetResourcePath(resource) 304 if err != nil { 305 return nil, err 306 } 307 stat, err := os.Stat(basePath) 308 if err != nil { 309 return nil, err 310 } 311 var filter []string 312 if !stat.IsDir() { 313 d, f := filepath.Split(basePath) 314 basePath = d 315 filter = []string{f} 316 } 317 archv, err := chrootarchive.Tar(basePath, &archive.TarOptions{ 318 Compression: archive.Uncompressed, 319 IncludeFiles: filter, 320 }, container.BaseFS) 321 if err != nil { 322 return nil, err 323 } 324 325 reader := ioutils.NewReadCloserWrapper(archv, func() error { 326 err := archv.Close() 327 container.DetachAndUnmount(daemon.LogVolumeEvent) 328 daemon.Unmount(container) 329 container.Unlock() 330 return err 331 }) 332 daemon.LogContainerEvent(container, "copy") 333 return reader, nil 334 } 335 336 // checkIfPathIsInAVolume checks if the path is in a volume. If it is, it 337 // cannot be in a read-only volume. If it is not in a volume, the container 338 // cannot be configured with a read-only rootfs. 339 // 340 // This is a no-op on Windows which does not support read-only volumes, or 341 // extracting to a mount point inside a volume. TODO Windows: FIXME Post-TP5 342 func checkIfPathIsInAVolume(container *container.Container, absPath string) (bool, error) { 343 return false, nil 344 } 345 346 // isOnlineFSOperationPermitted returns an error if an online filesystem operation 347 // is not permitted (such as stat or for copying). Running Hyper-V containers 348 // cannot have their file-system interrogated from the host as the filter is 349 // loaded inside the utility VM, not the host. 350 // IMPORTANT: The container lock MUST be held when calling this function. 351 func (daemon *Daemon) isOnlineFSOperationPermitted(container *container.Container) error { 352 if !container.Running { 353 return nil 354 } 355 356 // Determine isolation. If not specified in the hostconfig, use daemon default. 357 actualIsolation := container.HostConfig.Isolation 358 if containertypes.Isolation.IsDefault(containertypes.Isolation(actualIsolation)) { 359 actualIsolation = daemon.defaultIsolation 360 } 361 if containertypes.Isolation.IsHyperV(actualIsolation) { 362 return errors.New("filesystem operations against a running Hyper-V container are not supported") 363 } 364 return nil 365 }