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