fuseoverlayfs.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520
  1. //go:build linux
  2. package fuseoverlayfs // import "github.com/docker/docker/daemon/graphdriver/fuse-overlayfs"
  3. import (
  4. "bytes"
  5. "context"
  6. "fmt"
  7. "io"
  8. "os"
  9. "os/exec"
  10. "path"
  11. "path/filepath"
  12. "strings"
  13. "github.com/containerd/containerd/pkg/userns"
  14. "github.com/containerd/log"
  15. "github.com/docker/docker/daemon/graphdriver"
  16. "github.com/docker/docker/daemon/graphdriver/overlayutils"
  17. "github.com/docker/docker/pkg/archive"
  18. "github.com/docker/docker/pkg/chrootarchive"
  19. "github.com/docker/docker/pkg/containerfs"
  20. "github.com/docker/docker/pkg/directory"
  21. "github.com/docker/docker/pkg/idtools"
  22. "github.com/docker/docker/pkg/parsers/kernel"
  23. "github.com/moby/locker"
  24. "github.com/moby/sys/mount"
  25. "github.com/opencontainers/selinux/go-selinux/label"
  26. "github.com/pkg/errors"
  27. "golang.org/x/sys/unix"
  28. )
  29. // untar defines the untar method
  30. var untar = chrootarchive.UntarUncompressed
  31. const (
  32. driverName = "fuse-overlayfs"
  33. binary = "fuse-overlayfs"
  34. linkDir = "l"
  35. diffDirName = "diff"
  36. workDirName = "work"
  37. mergedDirName = "merged"
  38. lowerFile = "lower"
  39. maxDepth = 128
  40. // idLength represents the number of random characters
  41. // which can be used to create the unique link identifier
  42. // for every layer. If this value is too long then the
  43. // page size limit for the mount command may be exceeded.
  44. // The idLength should be selected such that following equation
  45. // is true (512 is a buffer for label metadata).
  46. // ((idLength + len(linkDir) + 1) * maxDepth) <= (pageSize - 512)
  47. idLength = 26
  48. )
  49. // Driver contains information about the home directory and the list of active
  50. // mounts that are created using this driver.
  51. type Driver struct {
  52. home string
  53. idMap idtools.IdentityMapping
  54. ctr *graphdriver.RefCounter
  55. naiveDiff graphdriver.DiffDriver
  56. locker *locker.Locker
  57. }
  58. var logger = log.G(context.TODO()).WithField("storage-driver", driverName)
  59. func init() {
  60. graphdriver.Register(driverName, Init)
  61. }
  62. // Init returns the naive diff driver for fuse-overlayfs.
  63. // If fuse-overlayfs is not supported on the host, the error
  64. // graphdriver.ErrNotSupported is returned.
  65. func Init(home string, options []string, idMap idtools.IdentityMapping) (graphdriver.Driver, error) {
  66. if _, err := exec.LookPath(binary); err != nil {
  67. logger.Error(err)
  68. return nil, graphdriver.ErrNotSupported
  69. }
  70. if !kernel.CheckKernelVersion(4, 18, 0) {
  71. return nil, graphdriver.ErrNotSupported
  72. }
  73. currentID := idtools.CurrentIdentity()
  74. dirID := idtools.Identity{
  75. UID: currentID.UID,
  76. GID: idMap.RootPair().GID,
  77. }
  78. if err := idtools.MkdirAllAndChown(home, 0o710, dirID); err != nil {
  79. return nil, err
  80. }
  81. if err := idtools.MkdirAllAndChown(path.Join(home, linkDir), 0o700, currentID); err != nil {
  82. return nil, err
  83. }
  84. d := &Driver{
  85. home: home,
  86. idMap: idMap,
  87. ctr: graphdriver.NewRefCounter(graphdriver.NewFsChecker(graphdriver.FsMagicFUSE)),
  88. locker: locker.New(),
  89. }
  90. d.naiveDiff = graphdriver.NewNaiveDiffDriver(d, idMap)
  91. return d, nil
  92. }
  93. func (d *Driver) String() string {
  94. return driverName
  95. }
  96. // Status returns current driver information in a two dimensional string array.
  97. func (d *Driver) Status() [][2]string {
  98. return [][2]string{}
  99. }
  100. // GetMetadata returns metadata about the overlay driver such as the LowerDir,
  101. // UpperDir, WorkDir, and MergeDir used to store data.
  102. func (d *Driver) GetMetadata(id string) (map[string]string, error) {
  103. dir := d.dir(id)
  104. if _, err := os.Stat(dir); err != nil {
  105. return nil, err
  106. }
  107. metadata := map[string]string{
  108. "WorkDir": path.Join(dir, workDirName),
  109. "MergedDir": path.Join(dir, mergedDirName),
  110. "UpperDir": path.Join(dir, diffDirName),
  111. }
  112. lowerDirs, err := d.getLowerDirs(id)
  113. if err != nil {
  114. return nil, err
  115. }
  116. if len(lowerDirs) > 0 {
  117. metadata["LowerDir"] = strings.Join(lowerDirs, ":")
  118. }
  119. return metadata, nil
  120. }
  121. // Cleanup any state created by overlay which should be cleaned when daemon
  122. // is being shutdown. For now, we just have to unmount the bind mounted
  123. // we had created.
  124. func (d *Driver) Cleanup() error {
  125. return mount.RecursiveUnmount(d.home)
  126. }
  127. // CreateReadWrite creates a layer that is writable for use as a container
  128. // file system.
  129. func (d *Driver) CreateReadWrite(id, parent string, opts *graphdriver.CreateOpts) error {
  130. if opts != nil && len(opts.StorageOpt) != 0 {
  131. return fmt.Errorf("--storage-opt is not supported")
  132. }
  133. return d.create(id, parent, opts)
  134. }
  135. // Create is used to create the upper, lower, and merge directories required for overlay fs for a given id.
  136. // The parent filesystem is used to configure these directories for the overlay.
  137. func (d *Driver) Create(id, parent string, opts *graphdriver.CreateOpts) (retErr error) {
  138. if opts != nil && len(opts.StorageOpt) != 0 {
  139. return fmt.Errorf("--storage-opt is not supported")
  140. }
  141. return d.create(id, parent, opts)
  142. }
  143. func (d *Driver) create(id, parent string, opts *graphdriver.CreateOpts) (retErr error) {
  144. dir := d.dir(id)
  145. root := d.idMap.RootPair()
  146. if err := idtools.MkdirAllAndChown(path.Dir(dir), 0o710, root); err != nil {
  147. return err
  148. }
  149. if err := idtools.MkdirAndChown(dir, 0o710, root); err != nil {
  150. return err
  151. }
  152. defer func() {
  153. // Clean up on failure
  154. if retErr != nil {
  155. os.RemoveAll(dir)
  156. }
  157. }()
  158. if opts != nil && len(opts.StorageOpt) > 0 {
  159. return fmt.Errorf("--storage-opt is not supported")
  160. }
  161. if err := idtools.MkdirAndChown(path.Join(dir, diffDirName), 0o755, root); err != nil {
  162. return err
  163. }
  164. lid := overlayutils.GenerateID(idLength, logger)
  165. if err := os.Symlink(path.Join("..", id, diffDirName), path.Join(d.home, linkDir, lid)); err != nil {
  166. return err
  167. }
  168. // Write link id to link file
  169. if err := os.WriteFile(path.Join(dir, "link"), []byte(lid), 0o644); err != nil {
  170. return err
  171. }
  172. // if no parent directory, done
  173. if parent == "" {
  174. return nil
  175. }
  176. if err := idtools.MkdirAndChown(path.Join(dir, workDirName), 0o710, root); err != nil {
  177. return err
  178. }
  179. if err := os.WriteFile(path.Join(d.dir(parent), "committed"), []byte{}, 0o600); err != nil {
  180. return err
  181. }
  182. lower, err := d.getLower(parent)
  183. if err != nil {
  184. return err
  185. }
  186. if lower != "" {
  187. if err := os.WriteFile(path.Join(dir, lowerFile), []byte(lower), 0o666); err != nil {
  188. return err
  189. }
  190. }
  191. return nil
  192. }
  193. func (d *Driver) getLower(parent string) (string, error) {
  194. parentDir := d.dir(parent)
  195. // Ensure parent exists
  196. if _, err := os.Lstat(parentDir); err != nil {
  197. return "", err
  198. }
  199. // Read Parent link fileA
  200. parentLink, err := os.ReadFile(path.Join(parentDir, "link"))
  201. if err != nil {
  202. return "", err
  203. }
  204. lowers := []string{path.Join(linkDir, string(parentLink))}
  205. parentLower, err := os.ReadFile(path.Join(parentDir, lowerFile))
  206. if err == nil {
  207. parentLowers := strings.Split(string(parentLower), ":")
  208. lowers = append(lowers, parentLowers...)
  209. }
  210. if len(lowers) > maxDepth {
  211. return "", errors.New("max depth exceeded")
  212. }
  213. return strings.Join(lowers, ":"), nil
  214. }
  215. func (d *Driver) dir(id string) string {
  216. return path.Join(d.home, id)
  217. }
  218. func (d *Driver) getLowerDirs(id string) ([]string, error) {
  219. var lowersArray []string
  220. lowers, err := os.ReadFile(path.Join(d.dir(id), lowerFile))
  221. if err == nil {
  222. for _, s := range strings.Split(string(lowers), ":") {
  223. lp, err := os.Readlink(path.Join(d.home, s))
  224. if err != nil {
  225. return nil, err
  226. }
  227. lowersArray = append(lowersArray, path.Clean(path.Join(d.home, linkDir, lp)))
  228. }
  229. } else if !os.IsNotExist(err) {
  230. return nil, err
  231. }
  232. return lowersArray, nil
  233. }
  234. // Remove cleans the directories that are created for this id.
  235. func (d *Driver) Remove(id string) error {
  236. if id == "" {
  237. return fmt.Errorf("refusing to remove the directories: id is empty")
  238. }
  239. d.locker.Lock(id)
  240. defer d.locker.Unlock(id)
  241. dir := d.dir(id)
  242. lid, err := os.ReadFile(path.Join(dir, "link"))
  243. if err == nil {
  244. if len(lid) == 0 {
  245. logger.Errorf("refusing to remove empty link for layer %v", id)
  246. } else if err := os.RemoveAll(path.Join(d.home, linkDir, string(lid))); err != nil {
  247. logger.Debugf("Failed to remove link: %v", err)
  248. }
  249. }
  250. if err := containerfs.EnsureRemoveAll(dir); err != nil && !os.IsNotExist(err) {
  251. return err
  252. }
  253. return nil
  254. }
  255. // Get creates and mounts the required file system for the given id and returns the mount path.
  256. func (d *Driver) Get(id, mountLabel string) (_ string, retErr error) {
  257. d.locker.Lock(id)
  258. defer d.locker.Unlock(id)
  259. dir := d.dir(id)
  260. if _, err := os.Stat(dir); err != nil {
  261. return "", err
  262. }
  263. diffDir := path.Join(dir, diffDirName)
  264. lowers, err := os.ReadFile(path.Join(dir, lowerFile))
  265. if err != nil {
  266. // If no lower, just return diff directory
  267. if os.IsNotExist(err) {
  268. return diffDir, nil
  269. }
  270. return "", err
  271. }
  272. mergedDir := path.Join(dir, mergedDirName)
  273. if count := d.ctr.Increment(mergedDir); count > 1 {
  274. return mergedDir, nil
  275. }
  276. defer func() {
  277. if retErr != nil {
  278. if c := d.ctr.Decrement(mergedDir); c <= 0 {
  279. if unmounted := fusermountU(mergedDir); !unmounted {
  280. if mntErr := unix.Unmount(mergedDir, 0); mntErr != nil {
  281. logger.Errorf("error unmounting %v: %v", mergedDir, mntErr)
  282. }
  283. }
  284. // Cleanup the created merged directory; see the comment in Put's rmdir
  285. if rmErr := unix.Rmdir(mergedDir); rmErr != nil && !os.IsNotExist(rmErr) {
  286. logger.Debugf("Failed to remove %s: %v: %v", id, rmErr, err)
  287. }
  288. }
  289. }
  290. }()
  291. workDir := path.Join(dir, workDirName)
  292. splitLowers := strings.Split(string(lowers), ":")
  293. absLowers := make([]string, len(splitLowers))
  294. for i, s := range splitLowers {
  295. absLowers[i] = path.Join(d.home, s)
  296. }
  297. var readonly bool
  298. if _, err := os.Stat(path.Join(dir, "committed")); err == nil {
  299. readonly = true
  300. } else if !os.IsNotExist(err) {
  301. return "", err
  302. }
  303. var opts string
  304. if readonly {
  305. opts = "lowerdir=" + diffDir + ":" + strings.Join(absLowers, ":")
  306. } else {
  307. opts = "lowerdir=" + strings.Join(absLowers, ":") + ",upperdir=" + diffDir + ",workdir=" + workDir
  308. }
  309. mountData := label.FormatMountLabel(opts, mountLabel)
  310. mountTarget := mergedDir
  311. if err := idtools.MkdirAndChown(mergedDir, 0o700, d.idMap.RootPair()); err != nil {
  312. return "", err
  313. }
  314. mountProgram := exec.Command(binary, "-o", mountData, mountTarget)
  315. mountProgram.Dir = d.home
  316. var b bytes.Buffer
  317. mountProgram.Stderr = &b
  318. if err = mountProgram.Run(); err != nil {
  319. output := b.String()
  320. if output == "" {
  321. output = "<stderr empty>"
  322. }
  323. return "", errors.Wrapf(err, "using mount program %s: %s", binary, output)
  324. }
  325. return mergedDir, nil
  326. }
  327. // Put unmounts the mount path created for the give id.
  328. // It also removes the 'merged' directory to force the kernel to unmount the
  329. // overlay mount in other namespaces.
  330. func (d *Driver) Put(id string) error {
  331. d.locker.Lock(id)
  332. defer d.locker.Unlock(id)
  333. dir := d.dir(id)
  334. _, err := os.ReadFile(path.Join(dir, lowerFile))
  335. if err != nil {
  336. // If no lower, no mount happened and just return directly
  337. if os.IsNotExist(err) {
  338. return nil
  339. }
  340. return err
  341. }
  342. mountpoint := path.Join(dir, mergedDirName)
  343. if count := d.ctr.Decrement(mountpoint); count > 0 {
  344. return nil
  345. }
  346. if unmounted := fusermountU(mountpoint); !unmounted {
  347. if err := unix.Unmount(mountpoint, unix.MNT_DETACH); err != nil {
  348. logger.Debugf("Failed to unmount %s overlay: %s - %v", id, mountpoint, err)
  349. }
  350. }
  351. // Remove the mountpoint here. Removing the mountpoint (in newer kernels)
  352. // will cause all other instances of this mount in other mount namespaces
  353. // to be unmounted. This is necessary to avoid cases where an overlay mount
  354. // that is present in another namespace will cause subsequent mounts
  355. // operations to fail with ebusy. We ignore any errors here because this may
  356. // fail on older kernels which don't have
  357. // torvalds/linux@8ed936b5671bfb33d89bc60bdcc7cf0470ba52fe applied.
  358. if err := unix.Rmdir(mountpoint); err != nil && !os.IsNotExist(err) {
  359. logger.Debugf("Failed to remove %s overlay: %v", id, err)
  360. }
  361. return nil
  362. }
  363. // Exists checks to see if the id is already mounted.
  364. func (d *Driver) Exists(id string) bool {
  365. _, err := os.Stat(d.dir(id))
  366. return err == nil
  367. }
  368. // isParent determines whether the given parent is the direct parent of the
  369. // given layer id
  370. func (d *Driver) isParent(id, parent string) bool {
  371. lowers, err := d.getLowerDirs(id)
  372. if err != nil {
  373. return false
  374. }
  375. if parent == "" && len(lowers) > 0 {
  376. return false
  377. }
  378. parentDir := d.dir(parent)
  379. var ld string
  380. if len(lowers) > 0 {
  381. ld = filepath.Dir(lowers[0])
  382. }
  383. if ld == "" && parent == "" {
  384. return true
  385. }
  386. return ld == parentDir
  387. }
  388. // ApplyDiff applies the new layer into a root
  389. func (d *Driver) ApplyDiff(id string, parent string, diff io.Reader) (size int64, err error) {
  390. if !d.isParent(id, parent) {
  391. return d.naiveDiff.ApplyDiff(id, parent, diff)
  392. }
  393. applyDir := d.getDiffPath(id)
  394. logger.Debugf("Applying tar in %s", applyDir)
  395. // Overlay doesn't need the parent id to apply the diff
  396. if err := untar(diff, applyDir, &archive.TarOptions{
  397. IDMap: d.idMap,
  398. // Use AUFS whiteout format: https://github.com/containers/storage/blob/39a8d5ed9843844eafb5d2ba6e6a7510e0126f40/drivers/overlay/overlay.go#L1084-L1089
  399. WhiteoutFormat: archive.AUFSWhiteoutFormat,
  400. InUserNS: userns.RunningInUserNS(),
  401. }); err != nil {
  402. return 0, err
  403. }
  404. return directory.Size(context.TODO(), applyDir)
  405. }
  406. func (d *Driver) getDiffPath(id string) string {
  407. dir := d.dir(id)
  408. return path.Join(dir, diffDirName)
  409. }
  410. // DiffSize calculates the changes between the specified id
  411. // and its parent and returns the size in bytes of the changes
  412. // relative to its base filesystem directory.
  413. func (d *Driver) DiffSize(id, parent string) (size int64, err error) {
  414. return d.naiveDiff.DiffSize(id, parent)
  415. }
  416. // Diff produces an archive of the changes between the specified
  417. // layer and its parent layer which may be "".
  418. func (d *Driver) Diff(id, parent string) (io.ReadCloser, error) {
  419. return d.naiveDiff.Diff(id, parent)
  420. }
  421. // Changes produces a list of changes between the specified layer and its
  422. // parent layer. If parent is "", then all changes will be ADD changes.
  423. func (d *Driver) Changes(id, parent string) ([]archive.Change, error) {
  424. return d.naiveDiff.Changes(id, parent)
  425. }
  426. // fusermountU is from https://github.com/containers/storage/blob/39a8d5ed9843844eafb5d2ba6e6a7510e0126f40/drivers/overlay/overlay.go#L1016-L1040
  427. func fusermountU(mountpoint string) (unmounted bool) {
  428. // Attempt to unmount the FUSE mount using either fusermount or fusermount3.
  429. // If they fail, fallback to unix.Unmount
  430. for _, v := range []string{"fusermount3", "fusermount"} {
  431. err := exec.Command(v, "-u", mountpoint).Run()
  432. if err != nil && !os.IsNotExist(err) {
  433. log.G(context.TODO()).Debugf("Error unmounting %s with %s - %v", mountpoint, v, err)
  434. }
  435. if err == nil {
  436. unmounted = true
  437. break
  438. }
  439. }
  440. // If fusermount|fusermount3 failed to unmount the FUSE file system, make sure all
  441. // pending changes are propagated to the file system
  442. if !unmounted {
  443. fd, err := unix.Open(mountpoint, unix.O_DIRECTORY, 0)
  444. if err == nil {
  445. if err := unix.Syncfs(fd); err != nil {
  446. log.G(context.TODO()).Debugf("Error Syncfs(%s) - %v", mountpoint, err)
  447. }
  448. unix.Close(fd)
  449. }
  450. }
  451. return
  452. }