github.com/clintkitson/docker@v1.9.1/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/api/types" 11 "github.com/docker/docker/pkg/archive" 12 "github.com/docker/docker/pkg/chrootarchive" 13 "github.com/docker/docker/pkg/ioutils" 14 ) 15 16 // ErrExtractPointNotDirectory is used to convey that the operation to extract 17 // a tar archive to a directory in a container has failed because the specified 18 // path does not refer to a directory. 19 var ErrExtractPointNotDirectory = errors.New("extraction point is not a directory") 20 21 // ContainerCopy performs a deprecated operation of archiving the resource at 22 // the specified path in the conatiner identified by the given name. 23 func (daemon *Daemon) ContainerCopy(name string, res string) (io.ReadCloser, error) { 24 container, err := daemon.Get(name) 25 if err != nil { 26 return nil, err 27 } 28 29 if res[0] == '/' || res[0] == '\\' { 30 res = res[1:] 31 } 32 33 return container.copy(res) 34 } 35 36 // ContainerStatPath stats the filesystem resource at the specified path in the 37 // container identified by the given name. 38 func (daemon *Daemon) ContainerStatPath(name string, path string) (stat *types.ContainerPathStat, err error) { 39 container, err := daemon.Get(name) 40 if err != nil { 41 return nil, err 42 } 43 44 return container.StatPath(path) 45 } 46 47 // ContainerArchivePath creates an archive of the filesystem resource at the 48 // specified path in the container identified by the given name. Returns a 49 // tar archive of the resource and whether it was a directory or a single file. 50 func (daemon *Daemon) ContainerArchivePath(name string, path string) (content io.ReadCloser, stat *types.ContainerPathStat, err error) { 51 container, err := daemon.Get(name) 52 if err != nil { 53 return nil, nil, err 54 } 55 56 return container.ArchivePath(path) 57 } 58 59 // ContainerExtractToDir extracts the given archive to the specified location 60 // in the filesystem of the container identified by the given name. The given 61 // path must be of a directory in the container. If it is not, the error will 62 // be ErrExtractPointNotDirectory. If noOverwriteDirNonDir is true then it will 63 // be an error if unpacking the given content would cause an existing directory 64 // to be replaced with a non-directory and vice versa. 65 func (daemon *Daemon) ContainerExtractToDir(name, path string, noOverwriteDirNonDir bool, content io.Reader) error { 66 container, err := daemon.Get(name) 67 if err != nil { 68 return err 69 } 70 71 return container.ExtractToDir(path, noOverwriteDirNonDir, content) 72 } 73 74 // resolvePath resolves the given path in the container to a resource on the 75 // host. Returns a resolved path (absolute path to the resource on the host), 76 // the absolute path to the resource relative to the container's rootfs, and 77 // a error if the path points to outside the container's rootfs. 78 func (container *Container) resolvePath(path string) (resolvedPath, absPath string, err error) { 79 // Consider the given path as an absolute path in the container. 80 absPath = archive.PreserveTrailingDotOrSeparator(filepath.Join(string(filepath.Separator), path), path) 81 82 // Split the absPath into its Directory and Base components. We will 83 // resolve the dir in the scope of the container then append the base. 84 dirPath, basePath := filepath.Split(absPath) 85 86 resolvedDirPath, err := container.GetResourcePath(dirPath) 87 if err != nil { 88 return "", "", err 89 } 90 91 // resolvedDirPath will have been cleaned (no trailing path separators) so 92 // we can manually join it with the base path element. 93 resolvedPath = resolvedDirPath + string(filepath.Separator) + basePath 94 95 return resolvedPath, absPath, nil 96 } 97 98 // statPath is the unexported version of StatPath. Locks and mounts should 99 // be acquired before calling this method and the given path should be fully 100 // resolved to a path on the host corresponding to the given absolute path 101 // inside the container. 102 func (container *Container) statPath(resolvedPath, absPath string) (stat *types.ContainerPathStat, err error) { 103 lstat, err := os.Lstat(resolvedPath) 104 if err != nil { 105 return nil, err 106 } 107 108 var linkTarget string 109 if lstat.Mode()&os.ModeSymlink != 0 { 110 // Fully evaluate the symlink in the scope of the container rootfs. 111 hostPath, err := container.GetResourcePath(absPath) 112 if err != nil { 113 return nil, err 114 } 115 116 linkTarget, err = filepath.Rel(container.basefs, hostPath) 117 if err != nil { 118 return nil, err 119 } 120 121 // Make it an absolute path. 122 linkTarget = filepath.Join(string(filepath.Separator), linkTarget) 123 } 124 125 return &types.ContainerPathStat{ 126 Name: filepath.Base(absPath), 127 Size: lstat.Size(), 128 Mode: lstat.Mode(), 129 Mtime: lstat.ModTime(), 130 LinkTarget: linkTarget, 131 }, nil 132 } 133 134 // StatPath stats the filesystem resource at the specified path in this 135 // container. Returns stat info about the resource. 136 func (container *Container) StatPath(path string) (stat *types.ContainerPathStat, err error) { 137 container.Lock() 138 defer container.Unlock() 139 140 if err = container.Mount(); err != nil { 141 return nil, err 142 } 143 defer container.Unmount() 144 145 err = container.mountVolumes() 146 defer container.unmountVolumes(true) 147 if err != nil { 148 return nil, err 149 } 150 151 resolvedPath, absPath, err := container.resolvePath(path) 152 if err != nil { 153 return nil, err 154 } 155 156 return container.statPath(resolvedPath, absPath) 157 } 158 159 // ArchivePath creates an archive of the filesystem resource at the specified 160 // path in this container. Returns a tar archive of the resource and stat info 161 // about the resource. 162 func (container *Container) ArchivePath(path string) (content io.ReadCloser, stat *types.ContainerPathStat, err error) { 163 container.Lock() 164 165 defer func() { 166 if err != nil { 167 // Wait to unlock the container until the archive is fully read 168 // (see the ReadCloseWrapper func below) or if there is an error 169 // before that occurs. 170 container.Unlock() 171 } 172 }() 173 174 if err = container.Mount(); err != nil { 175 return nil, nil, err 176 } 177 178 defer func() { 179 if err != nil { 180 // unmount any volumes 181 container.unmountVolumes(true) 182 // unmount the container's rootfs 183 container.Unmount() 184 } 185 }() 186 187 if err = container.mountVolumes(); err != nil { 188 return nil, nil, err 189 } 190 191 resolvedPath, absPath, err := container.resolvePath(path) 192 if err != nil { 193 return nil, nil, err 194 } 195 196 stat, err = container.statPath(resolvedPath, absPath) 197 if err != nil { 198 return nil, nil, err 199 } 200 201 // We need to rebase the archive entries if the last element of the 202 // resolved path was a symlink that was evaluated and is now different 203 // than the requested path. For example, if the given path was "/foo/bar/", 204 // but it resolved to "/var/lib/docker/containers/{id}/foo/baz/", we want 205 // to ensure that the archive entries start with "bar" and not "baz". This 206 // also catches the case when the root directory of the container is 207 // requested: we want the archive entries to start with "/" and not the 208 // container ID. 209 data, err := archive.TarResourceRebase(resolvedPath, filepath.Base(absPath)) 210 if err != nil { 211 return nil, nil, err 212 } 213 214 content = ioutils.NewReadCloserWrapper(data, func() error { 215 err := data.Close() 216 container.unmountVolumes(true) 217 container.Unmount() 218 container.Unlock() 219 return err 220 }) 221 222 container.logEvent("archive-path") 223 224 return content, stat, nil 225 } 226 227 // ExtractToDir extracts the given tar archive to the specified location in the 228 // filesystem of this container. The given path must be of a directory in the 229 // container. If it is not, the error will be ErrExtractPointNotDirectory. If 230 // noOverwriteDirNonDir is true then it will be an error if unpacking the 231 // given content would cause an existing directory to be replaced with a non- 232 // directory and vice versa. 233 func (container *Container) ExtractToDir(path string, noOverwriteDirNonDir bool, content io.Reader) (err error) { 234 container.Lock() 235 defer container.Unlock() 236 237 if err = container.Mount(); err != nil { 238 return err 239 } 240 defer container.Unmount() 241 242 err = container.mountVolumes() 243 defer container.unmountVolumes(true) 244 if err != nil { 245 return err 246 } 247 248 // The destination path needs to be resolved to a host path, with all 249 // symbolic links followed in the scope of the container's rootfs. Note 250 // that we do not use `container.resolvePath(path)` here because we need 251 // to also evaluate the last path element if it is a symlink. This is so 252 // that you can extract an archive to a symlink that points to a directory. 253 254 // Consider the given path as an absolute path in the container. 255 absPath := archive.PreserveTrailingDotOrSeparator(filepath.Join(string(filepath.Separator), path), path) 256 257 // This will evaluate the last path element if it is a symlink. 258 resolvedPath, err := container.GetResourcePath(absPath) 259 if err != nil { 260 return err 261 } 262 263 stat, err := os.Lstat(resolvedPath) 264 if err != nil { 265 return err 266 } 267 268 if !stat.IsDir() { 269 return ErrExtractPointNotDirectory 270 } 271 272 // Need to check if the path is in a volume. If it is, it cannot be in a 273 // read-only volume. If it is not in a volume, the container cannot be 274 // configured with a read-only rootfs. 275 276 // Use the resolved path relative to the container rootfs as the new 277 // absPath. This way we fully follow any symlinks in a volume that may 278 // lead back outside the volume. 279 // 280 // The Windows implementation of filepath.Rel in golang 1.4 does not 281 // support volume style file path semantics. On Windows when using the 282 // filter driver, we are guaranteed that the path will always be 283 // a volume file path. 284 var baseRel string 285 if strings.HasPrefix(resolvedPath, `\\?\Volume{`) { 286 if strings.HasPrefix(resolvedPath, container.basefs) { 287 baseRel = resolvedPath[len(container.basefs):] 288 if baseRel[:1] == `\` { 289 baseRel = baseRel[1:] 290 } 291 } 292 } else { 293 baseRel, err = filepath.Rel(container.basefs, resolvedPath) 294 } 295 if err != nil { 296 return err 297 } 298 // Make it an absolute path. 299 absPath = filepath.Join(string(filepath.Separator), baseRel) 300 301 toVolume, err := checkIfPathIsInAVolume(container, absPath) 302 if err != nil { 303 return err 304 } 305 306 if !toVolume && container.hostConfig.ReadonlyRootfs { 307 return ErrRootFSReadOnly 308 } 309 310 options := &archive.TarOptions{ 311 ChownOpts: &archive.TarChownOptions{ 312 UID: 0, GID: 0, // TODO: use config.User? Remap to userns root? 313 }, 314 NoOverwriteDirNonDir: noOverwriteDirNonDir, 315 } 316 317 if err := chrootarchive.Untar(content, resolvedPath, options); err != nil { 318 return err 319 } 320 321 container.logEvent("extract-to-dir") 322 323 return nil 324 }