archive.go 13 KB

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