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