archive_unix.go 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  1. //go:build !windows
  2. // +build !windows
  3. package daemon // import "github.com/docker/docker/daemon"
  4. import (
  5. "io"
  6. "os"
  7. "path/filepath"
  8. "github.com/docker/docker/api/types"
  9. "github.com/docker/docker/container"
  10. "github.com/docker/docker/errdefs"
  11. "github.com/docker/docker/pkg/archive"
  12. "github.com/docker/docker/pkg/chrootarchive"
  13. "github.com/docker/docker/pkg/ioutils"
  14. volumemounts "github.com/docker/docker/volume/mounts"
  15. "github.com/pkg/errors"
  16. )
  17. // containerStatPath stats the filesystem resource at the specified path in this
  18. // container. Returns stat info about the resource.
  19. func (daemon *Daemon) containerStatPath(container *container.Container, path string) (stat *types.ContainerPathStat, err error) {
  20. container.Lock()
  21. defer container.Unlock()
  22. if err = daemon.Mount(container); err != nil {
  23. return nil, err
  24. }
  25. defer daemon.Unmount(container)
  26. err = daemon.mountVolumes(container)
  27. defer container.DetachAndUnmount(daemon.LogVolumeEvent)
  28. if err != nil {
  29. return nil, err
  30. }
  31. resolvedPath, absPath, err := container.ResolvePath(path)
  32. if err != nil {
  33. return nil, err
  34. }
  35. return container.StatPath(resolvedPath, absPath)
  36. }
  37. // containerArchivePath creates an archive of the filesystem resource at the specified
  38. // path in this container. Returns a tar archive of the resource and stat info
  39. // about the resource.
  40. func (daemon *Daemon) containerArchivePath(container *container.Container, path string) (content io.ReadCloser, stat *types.ContainerPathStat, err error) {
  41. container.Lock()
  42. defer func() {
  43. if err != nil {
  44. // Wait to unlock the container until the archive is fully read
  45. // (see the ReadCloseWrapper func below) or if there is an error
  46. // before that occurs.
  47. container.Unlock()
  48. }
  49. }()
  50. if err = daemon.Mount(container); err != nil {
  51. return nil, nil, err
  52. }
  53. defer func() {
  54. if err != nil {
  55. // unmount any volumes
  56. container.DetachAndUnmount(daemon.LogVolumeEvent)
  57. // unmount the container's rootfs
  58. daemon.Unmount(container)
  59. }
  60. }()
  61. if err = daemon.mountVolumes(container); err != nil {
  62. return nil, nil, err
  63. }
  64. resolvedPath, absPath, err := container.ResolvePath(path)
  65. if err != nil {
  66. return nil, nil, err
  67. }
  68. stat, err = container.StatPath(resolvedPath, absPath)
  69. if err != nil {
  70. return nil, nil, err
  71. }
  72. // We need to rebase the archive entries if the last element of the
  73. // resolved path was a symlink that was evaluated and is now different
  74. // than the requested path. For example, if the given path was "/foo/bar/",
  75. // but it resolved to "/var/lib/docker/containers/{id}/foo/baz/", we want
  76. // to ensure that the archive entries start with "bar" and not "baz". This
  77. // also catches the case when the root directory of the container is
  78. // requested: we want the archive entries to start with "/" and not the
  79. // container ID.
  80. // Get the source and the base paths of the container resolved path in order
  81. // to get the proper tar options for the rebase tar.
  82. resolvedPath = filepath.Clean(resolvedPath)
  83. if filepath.Base(resolvedPath) == "." {
  84. resolvedPath += string(filepath.Separator) + "."
  85. }
  86. sourceDir := resolvedPath
  87. sourceBase := "."
  88. if stat.Mode&os.ModeDir == 0 { // not dir
  89. sourceDir, sourceBase = filepath.Split(resolvedPath)
  90. }
  91. opts := archive.TarResourceRebaseOpts(sourceBase, filepath.Base(absPath))
  92. data, err := chrootarchive.Tar(sourceDir, opts, container.BaseFS)
  93. if err != nil {
  94. return nil, nil, err
  95. }
  96. content = ioutils.NewReadCloserWrapper(data, func() error {
  97. err := data.Close()
  98. container.DetachAndUnmount(daemon.LogVolumeEvent)
  99. daemon.Unmount(container)
  100. container.Unlock()
  101. return err
  102. })
  103. daemon.LogContainerEvent(container, "archive-path")
  104. return content, stat, nil
  105. }
  106. // containerExtractToDir extracts the given tar archive to the specified location in the
  107. // filesystem of this container. The given path must be of a directory in the
  108. // container. If it is not, the error will be an errdefs.InvalidParameter. If
  109. // noOverwriteDirNonDir is true then it will be an error if unpacking the
  110. // given content would cause an existing directory to be replaced with a non-
  111. // directory and vice versa.
  112. func (daemon *Daemon) containerExtractToDir(container *container.Container, path string, copyUIDGID, noOverwriteDirNonDir bool, content io.Reader) (err error) {
  113. container.Lock()
  114. defer container.Unlock()
  115. if err = daemon.Mount(container); err != nil {
  116. return err
  117. }
  118. defer daemon.Unmount(container)
  119. err = daemon.mountVolumes(container)
  120. defer container.DetachAndUnmount(daemon.LogVolumeEvent)
  121. if err != nil {
  122. return err
  123. }
  124. // The destination path needs to be resolved to a host path, with all
  125. // symbolic links followed in the scope of the container's rootfs. Note
  126. // that we do not use `container.ResolvePath(path)` here because we need
  127. // to also evaluate the last path element if it is a symlink. This is so
  128. // that you can extract an archive to a symlink that points to a directory.
  129. // Consider the given path as an absolute path in the container.
  130. absPath := archive.PreserveTrailingDotOrSeparator(filepath.Join(string(filepath.Separator), path), path)
  131. // This will evaluate the last path element if it is a symlink.
  132. resolvedPath, err := container.GetResourcePath(absPath)
  133. if err != nil {
  134. return err
  135. }
  136. stat, err := os.Lstat(resolvedPath)
  137. if err != nil {
  138. return err
  139. }
  140. if !stat.IsDir() {
  141. return errdefs.InvalidParameter(errors.New("extraction point is not a directory"))
  142. }
  143. // Need to check if the path is in a volume. If it is, it cannot be in a
  144. // read-only volume. If it is not in a volume, the container cannot be
  145. // configured with a read-only rootfs.
  146. // Use the resolved path relative to the container rootfs as the new
  147. // absPath. This way we fully follow any symlinks in a volume that may
  148. // lead back outside the volume.
  149. baseRel, err := filepath.Rel(container.BaseFS, resolvedPath)
  150. if err != nil {
  151. return err
  152. }
  153. // Make it an absolute path.
  154. absPath = filepath.Join(string(filepath.Separator), baseRel)
  155. toVolume, err := checkIfPathIsInAVolume(container, absPath)
  156. if err != nil {
  157. return err
  158. }
  159. if !toVolume && container.HostConfig.ReadonlyRootfs {
  160. return errdefs.InvalidParameter(errors.New("container rootfs is marked read-only"))
  161. }
  162. options := daemon.defaultTarCopyOptions(noOverwriteDirNonDir)
  163. if copyUIDGID {
  164. var err error
  165. // tarCopyOptions will appropriately pull in the right uid/gid for the
  166. // user/group and will set the options.
  167. options, err = daemon.tarCopyOptions(container, noOverwriteDirNonDir)
  168. if err != nil {
  169. return err
  170. }
  171. }
  172. if err := chrootarchive.UntarWithRoot(content, resolvedPath, options, container.BaseFS); err != nil {
  173. return err
  174. }
  175. daemon.LogContainerEvent(container, "extract-to-dir")
  176. return nil
  177. }
  178. func (daemon *Daemon) containerCopy(container *container.Container, resource string) (rc io.ReadCloser, err error) {
  179. if resource[0] == '/' || resource[0] == '\\' {
  180. resource = resource[1:]
  181. }
  182. container.Lock()
  183. defer func() {
  184. if err != nil {
  185. // Wait to unlock the container until the archive is fully read
  186. // (see the ReadCloseWrapper func below) or if there is an error
  187. // before that occurs.
  188. container.Unlock()
  189. }
  190. }()
  191. if err := daemon.Mount(container); err != nil {
  192. return nil, err
  193. }
  194. defer func() {
  195. if err != nil {
  196. // unmount any volumes
  197. container.DetachAndUnmount(daemon.LogVolumeEvent)
  198. // unmount the container's rootfs
  199. daemon.Unmount(container)
  200. }
  201. }()
  202. if err := daemon.mountVolumes(container); err != nil {
  203. return nil, err
  204. }
  205. basePath, err := container.GetResourcePath(resource)
  206. if err != nil {
  207. return nil, err
  208. }
  209. stat, err := os.Stat(basePath)
  210. if err != nil {
  211. return nil, err
  212. }
  213. var filter []string
  214. if !stat.IsDir() {
  215. d, f := filepath.Split(basePath)
  216. basePath = d
  217. filter = []string{f}
  218. }
  219. archv, err := chrootarchive.Tar(basePath, &archive.TarOptions{
  220. Compression: archive.Uncompressed,
  221. IncludeFiles: filter,
  222. }, container.BaseFS)
  223. if err != nil {
  224. return nil, err
  225. }
  226. reader := ioutils.NewReadCloserWrapper(archv, func() error {
  227. err := archv.Close()
  228. container.DetachAndUnmount(daemon.LogVolumeEvent)
  229. daemon.Unmount(container)
  230. container.Unlock()
  231. return err
  232. })
  233. daemon.LogContainerEvent(container, "copy")
  234. return reader, nil
  235. }
  236. // checkIfPathIsInAVolume checks if the path is in a volume. If it is, it
  237. // cannot be in a read-only volume. If it is not in a volume, the container
  238. // cannot be configured with a read-only rootfs.
  239. func checkIfPathIsInAVolume(container *container.Container, absPath string) (bool, error) {
  240. var toVolume bool
  241. parser := volumemounts.NewParser()
  242. for _, mnt := range container.MountPoints {
  243. if toVolume = parser.HasResource(mnt, absPath); toVolume {
  244. if mnt.RW {
  245. break
  246. }
  247. return false, errdefs.InvalidParameter(errors.New("mounted volume is marked read-only"))
  248. }
  249. }
  250. return toVolume, nil
  251. }