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