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