archive.go 13 KB

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