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