github.com/rumpl/bof@v23.0.0-rc.2+incompatible/daemon/archive.go (about) 1 package daemon // import "github.com/docker/docker/daemon" 2 3 import ( 4 "io" 5 "os" 6 "strings" 7 8 "github.com/docker/docker/api/types" 9 "github.com/docker/docker/container" 10 "github.com/docker/docker/errdefs" 11 "github.com/docker/docker/pkg/archive" 12 "github.com/docker/docker/pkg/chrootarchive" 13 "github.com/docker/docker/pkg/ioutils" 14 "github.com/docker/docker/pkg/system" 15 "github.com/pkg/errors" 16 ) 17 18 // ErrExtractPointNotDirectory is used to convey that the operation to extract 19 // a tar archive to a directory in a container has failed because the specified 20 // path does not refer to a directory. 21 var ErrExtractPointNotDirectory = errors.New("extraction point is not a directory") 22 23 // The daemon will use the following interfaces if the container fs implements 24 // these for optimized copies to and from the container. 25 type extractor interface { 26 ExtractArchive(src io.Reader, dst string, opts *archive.TarOptions) error 27 } 28 29 type archiver interface { 30 ArchivePath(src string, opts *archive.TarOptions) (io.ReadCloser, error) 31 } 32 33 // helper functions to extract or archive 34 func extractArchive(i interface{}, src io.Reader, dst string, opts *archive.TarOptions, root string) error { 35 if ea, ok := i.(extractor); ok { 36 return ea.ExtractArchive(src, dst, opts) 37 } 38 39 return chrootarchive.UntarWithRoot(src, dst, opts, root) 40 } 41 42 func archivePath(i interface{}, src string, opts *archive.TarOptions, root string) (io.ReadCloser, error) { 43 if ap, ok := i.(archiver); ok { 44 return ap.ArchivePath(src, opts) 45 } 46 return chrootarchive.Tar(src, opts, root) 47 } 48 49 // ContainerCopy performs a deprecated operation of archiving the resource at 50 // the specified path in the container identified by the given name. 51 func (daemon *Daemon) ContainerCopy(name string, res string) (io.ReadCloser, error) { 52 ctr, err := daemon.GetContainer(name) 53 if err != nil { 54 return nil, err 55 } 56 57 // Make sure an online file-system operation is permitted. 58 if err := daemon.isOnlineFSOperationPermitted(ctr); err != nil { 59 return nil, errdefs.System(err) 60 } 61 62 data, err := daemon.containerCopy(ctr, res) 63 if err == nil { 64 return data, nil 65 } 66 67 if os.IsNotExist(err) { 68 return nil, containerFileNotFound{res, name} 69 } 70 return nil, errdefs.System(err) 71 } 72 73 // ContainerStatPath stats the filesystem resource at the specified path in the 74 // container identified by the given name. 75 func (daemon *Daemon) ContainerStatPath(name string, path string) (stat *types.ContainerPathStat, err error) { 76 ctr, err := daemon.GetContainer(name) 77 if err != nil { 78 return nil, err 79 } 80 81 // Make sure an online file-system operation is permitted. 82 if err := daemon.isOnlineFSOperationPermitted(ctr); err != nil { 83 return nil, errdefs.System(err) 84 } 85 86 stat, err = daemon.containerStatPath(ctr, path) 87 if err == nil { 88 return stat, nil 89 } 90 91 if os.IsNotExist(err) { 92 return nil, containerFileNotFound{path, name} 93 } 94 return nil, errdefs.System(err) 95 } 96 97 // ContainerArchivePath creates an archive of the filesystem resource at the 98 // specified path in the container identified by the given name. Returns a 99 // tar archive of the resource and whether it was a directory or a single file. 100 func (daemon *Daemon) ContainerArchivePath(name string, path string) (content io.ReadCloser, stat *types.ContainerPathStat, err error) { 101 ctr, err := daemon.GetContainer(name) 102 if err != nil { 103 return nil, nil, err 104 } 105 106 // Make sure an online file-system operation is permitted. 107 if err := daemon.isOnlineFSOperationPermitted(ctr); err != nil { 108 return nil, nil, errdefs.System(err) 109 } 110 111 content, stat, err = daemon.containerArchivePath(ctr, path) 112 if err == nil { 113 return content, stat, nil 114 } 115 116 if os.IsNotExist(err) { 117 return nil, nil, containerFileNotFound{path, name} 118 } 119 return nil, nil, errdefs.System(err) 120 } 121 122 // ContainerExtractToDir extracts the given archive to the specified location 123 // in the filesystem of the container identified by the given name. The given 124 // path must be of a directory in the container. If it is not, the error will 125 // be ErrExtractPointNotDirectory. If noOverwriteDirNonDir is true then it will 126 // be an error if unpacking the given content would cause an existing directory 127 // to be replaced with a non-directory and vice versa. 128 func (daemon *Daemon) ContainerExtractToDir(name, path string, copyUIDGID, noOverwriteDirNonDir bool, content io.Reader) error { 129 ctr, err := daemon.GetContainer(name) 130 if err != nil { 131 return err 132 } 133 134 // Make sure an online file-system operation is permitted. 135 if err := daemon.isOnlineFSOperationPermitted(ctr); err != nil { 136 return errdefs.System(err) 137 } 138 139 err = daemon.containerExtractToDir(ctr, path, copyUIDGID, noOverwriteDirNonDir, content) 140 if err == nil { 141 return nil 142 } 143 144 if os.IsNotExist(err) { 145 return containerFileNotFound{path, name} 146 } 147 return errdefs.System(err) 148 } 149 150 // containerStatPath stats the filesystem resource at the specified path in this 151 // container. Returns stat info about the resource. 152 func (daemon *Daemon) containerStatPath(container *container.Container, path string) (stat *types.ContainerPathStat, err error) { 153 container.Lock() 154 defer container.Unlock() 155 156 if err = daemon.Mount(container); err != nil { 157 return nil, err 158 } 159 defer daemon.Unmount(container) 160 161 err = daemon.mountVolumes(container) 162 defer container.DetachAndUnmount(daemon.LogVolumeEvent) 163 if err != nil { 164 return nil, err 165 } 166 167 // Normalize path before sending to rootfs 168 path = container.BaseFS.FromSlash(path) 169 170 resolvedPath, absPath, err := container.ResolvePath(path) 171 if err != nil { 172 return nil, err 173 } 174 175 return container.StatPath(resolvedPath, absPath) 176 } 177 178 // containerArchivePath creates an archive of the filesystem resource at the specified 179 // path in this container. Returns a tar archive of the resource and stat info 180 // about the resource. 181 func (daemon *Daemon) containerArchivePath(container *container.Container, path string) (content io.ReadCloser, stat *types.ContainerPathStat, err error) { 182 container.Lock() 183 184 defer func() { 185 if err != nil { 186 // Wait to unlock the container until the archive is fully read 187 // (see the ReadCloseWrapper func below) or if there is an error 188 // before that occurs. 189 container.Unlock() 190 } 191 }() 192 193 if err = daemon.Mount(container); err != nil { 194 return nil, nil, err 195 } 196 197 defer func() { 198 if err != nil { 199 // unmount any volumes 200 container.DetachAndUnmount(daemon.LogVolumeEvent) 201 // unmount the container's rootfs 202 daemon.Unmount(container) 203 } 204 }() 205 206 if err = daemon.mountVolumes(container); err != nil { 207 return nil, nil, err 208 } 209 210 // Normalize path before sending to rootfs 211 path = container.BaseFS.FromSlash(path) 212 213 resolvedPath, absPath, err := container.ResolvePath(path) 214 if err != nil { 215 return nil, nil, err 216 } 217 218 stat, err = container.StatPath(resolvedPath, absPath) 219 if err != nil { 220 return nil, nil, err 221 } 222 223 // We need to rebase the archive entries if the last element of the 224 // resolved path was a symlink that was evaluated and is now different 225 // than the requested path. For example, if the given path was "/foo/bar/", 226 // but it resolved to "/var/lib/docker/containers/{id}/foo/baz/", we want 227 // to ensure that the archive entries start with "bar" and not "baz". This 228 // also catches the case when the root directory of the container is 229 // requested: we want the archive entries to start with "/" and not the 230 // container ID. 231 driver := container.BaseFS 232 233 // Get the source and the base paths of the container resolved path in order 234 // to get the proper tar options for the rebase tar. 235 resolvedPath = driver.Clean(resolvedPath) 236 if driver.Base(resolvedPath) == "." { 237 resolvedPath += string(driver.Separator()) + "." 238 } 239 240 sourceDir := resolvedPath 241 sourceBase := "." 242 243 if stat.Mode&os.ModeDir == 0 { // not dir 244 sourceDir, sourceBase = driver.Split(resolvedPath) 245 } 246 opts := archive.TarResourceRebaseOpts(sourceBase, driver.Base(absPath)) 247 248 data, err := archivePath(driver, sourceDir, opts, container.BaseFS.Path()) 249 if err != nil { 250 return nil, nil, err 251 } 252 253 content = ioutils.NewReadCloserWrapper(data, func() error { 254 err := data.Close() 255 container.DetachAndUnmount(daemon.LogVolumeEvent) 256 daemon.Unmount(container) 257 container.Unlock() 258 return err 259 }) 260 261 daemon.LogContainerEvent(container, "archive-path") 262 263 return content, stat, nil 264 } 265 266 // containerExtractToDir extracts the given tar archive to the specified location in the 267 // filesystem of this container. The given path must be of a directory in the 268 // container. If it is not, the error will be ErrExtractPointNotDirectory. If 269 // noOverwriteDirNonDir is true then it will be an error if unpacking the 270 // given content would cause an existing directory to be replaced with a non- 271 // directory and vice versa. 272 func (daemon *Daemon) containerExtractToDir(container *container.Container, path string, copyUIDGID, noOverwriteDirNonDir bool, content io.Reader) (err error) { 273 container.Lock() 274 defer container.Unlock() 275 276 if err = daemon.Mount(container); err != nil { 277 return err 278 } 279 defer daemon.Unmount(container) 280 281 err = daemon.mountVolumes(container) 282 defer container.DetachAndUnmount(daemon.LogVolumeEvent) 283 if err != nil { 284 return err 285 } 286 287 // Normalize path before sending to rootfs' 288 path = container.BaseFS.FromSlash(path) 289 driver := container.BaseFS 290 291 // Check if a drive letter supplied, it must be the system drive. No-op except on Windows 292 path, err = system.CheckSystemDriveAndRemoveDriveLetter(path, driver) 293 if err != nil { 294 return err 295 } 296 297 // The destination path needs to be resolved to a host path, with all 298 // symbolic links followed in the scope of the container's rootfs. Note 299 // that we do not use `container.ResolvePath(path)` here because we need 300 // to also evaluate the last path element if it is a symlink. This is so 301 // that you can extract an archive to a symlink that points to a directory. 302 303 // Consider the given path as an absolute path in the container. 304 absPath := archive.PreserveTrailingDotOrSeparator( 305 driver.Join(string(driver.Separator()), path), 306 path, 307 driver.Separator()) 308 309 // This will evaluate the last path element if it is a symlink. 310 resolvedPath, err := container.GetResourcePath(absPath) 311 if err != nil { 312 return err 313 } 314 315 stat, err := driver.Lstat(resolvedPath) 316 if err != nil { 317 return err 318 } 319 320 if !stat.IsDir() { 321 return ErrExtractPointNotDirectory 322 } 323 324 // Need to check if the path is in a volume. If it is, it cannot be in a 325 // read-only volume. If it is not in a volume, the container cannot be 326 // configured with a read-only rootfs. 327 328 // Use the resolved path relative to the container rootfs as the new 329 // absPath. This way we fully follow any symlinks in a volume that may 330 // lead back outside the volume. 331 // 332 // The Windows implementation of filepath.Rel in golang 1.4 does not 333 // support volume style file path semantics. On Windows when using the 334 // filter driver, we are guaranteed that the path will always be 335 // a volume file path. 336 var baseRel string 337 if strings.HasPrefix(resolvedPath, `\\?\Volume{`) { 338 if strings.HasPrefix(resolvedPath, driver.Path()) { 339 baseRel = resolvedPath[len(driver.Path()):] 340 if baseRel[:1] == `\` { 341 baseRel = baseRel[1:] 342 } 343 } 344 } else { 345 baseRel, err = driver.Rel(driver.Path(), resolvedPath) 346 } 347 if err != nil { 348 return err 349 } 350 // Make it an absolute path. 351 absPath = driver.Join(string(driver.Separator()), baseRel) 352 353 // @ TODO: gupta-ak: Technically, this works since it no-ops 354 // on Windows and the file system is local anyway on linux. 355 // But eventually, it should be made driver aware. 356 toVolume, err := checkIfPathIsInAVolume(container, absPath) 357 if err != nil { 358 return err 359 } 360 361 if !toVolume && container.HostConfig.ReadonlyRootfs { 362 return ErrRootFSReadOnly 363 } 364 365 options := daemon.defaultTarCopyOptions(noOverwriteDirNonDir) 366 367 if copyUIDGID { 368 var err error 369 // tarCopyOptions will appropriately pull in the right uid/gid for the 370 // user/group and will set the options. 371 options, err = daemon.tarCopyOptions(container, noOverwriteDirNonDir) 372 if err != nil { 373 return err 374 } 375 } 376 377 if err := extractArchive(driver, content, resolvedPath, options, container.BaseFS.Path()); err != nil { 378 return err 379 } 380 381 daemon.LogContainerEvent(container, "extract-to-dir") 382 383 return nil 384 } 385 386 func (daemon *Daemon) containerCopy(container *container.Container, resource string) (rc io.ReadCloser, err error) { 387 if resource[0] == '/' || resource[0] == '\\' { 388 resource = resource[1:] 389 } 390 container.Lock() 391 392 defer func() { 393 if err != nil { 394 // Wait to unlock the container until the archive is fully read 395 // (see the ReadCloseWrapper func below) or if there is an error 396 // before that occurs. 397 container.Unlock() 398 } 399 }() 400 401 if err := daemon.Mount(container); err != nil { 402 return nil, err 403 } 404 405 defer func() { 406 if err != nil { 407 // unmount any volumes 408 container.DetachAndUnmount(daemon.LogVolumeEvent) 409 // unmount the container's rootfs 410 daemon.Unmount(container) 411 } 412 }() 413 414 if err := daemon.mountVolumes(container); err != nil { 415 return nil, err 416 } 417 418 // Normalize path before sending to rootfs 419 resource = container.BaseFS.FromSlash(resource) 420 driver := container.BaseFS 421 422 basePath, err := container.GetResourcePath(resource) 423 if err != nil { 424 return nil, err 425 } 426 stat, err := driver.Stat(basePath) 427 if err != nil { 428 return nil, err 429 } 430 var filter []string 431 if !stat.IsDir() { 432 d, f := driver.Split(basePath) 433 basePath = d 434 filter = []string{f} 435 } 436 archv, err := archivePath(driver, basePath, &archive.TarOptions{ 437 Compression: archive.Uncompressed, 438 IncludeFiles: filter, 439 }, container.BaseFS.Path()) 440 if err != nil { 441 return nil, err 442 } 443 444 reader := ioutils.NewReadCloserWrapper(archv, func() error { 445 err := archv.Close() 446 container.DetachAndUnmount(daemon.LogVolumeEvent) 447 daemon.Unmount(container) 448 container.Unlock() 449 return err 450 }) 451 daemon.LogContainerEvent(container, "copy") 452 return reader, nil 453 }