local_unix.go 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. //go:build linux || freebsd
  2. // +build linux freebsd
  3. // Package local provides the default implementation for volumes. It
  4. // is used to mount data volume containers and directories local to
  5. // the host server.
  6. package local // import "github.com/docker/docker/volume/local"
  7. import (
  8. "fmt"
  9. "net"
  10. "os"
  11. "path/filepath"
  12. "strings"
  13. "syscall"
  14. "time"
  15. "github.com/docker/docker/errdefs"
  16. "github.com/docker/docker/quota"
  17. units "github.com/docker/go-units"
  18. "github.com/moby/sys/mount"
  19. "github.com/moby/sys/mountinfo"
  20. "github.com/pkg/errors"
  21. )
  22. var (
  23. oldVfsDir = filepath.Join("vfs", "dir")
  24. validOpts = map[string]struct{}{
  25. "type": {}, // specify the filesystem type for mount, e.g. nfs
  26. "o": {}, // generic mount options
  27. "device": {}, // device to mount from
  28. "size": {}, // quota size limit
  29. }
  30. mandatoryOpts = map[string][]string{
  31. "device": {"type"},
  32. "type": {"device"},
  33. "o": {"device", "type"},
  34. }
  35. )
  36. type optsConfig struct {
  37. MountType string
  38. MountOpts string
  39. MountDevice string
  40. Quota quota.Quota
  41. }
  42. func (o *optsConfig) String() string {
  43. return fmt.Sprintf("type='%s' device='%s' o='%s' size='%d'", o.MountType, o.MountDevice, o.MountOpts, o.Quota.Size)
  44. }
  45. // scopedPath verifies that the path where the volume is located
  46. // is under Docker's root and the valid local paths.
  47. func (r *Root) scopedPath(realPath string) bool {
  48. // Volumes path for Docker version >= 1.7
  49. if strings.HasPrefix(realPath, filepath.Join(r.scope, volumesPathName)) && realPath != filepath.Join(r.scope, volumesPathName) {
  50. return true
  51. }
  52. // Volumes path for Docker version < 1.7
  53. if strings.HasPrefix(realPath, filepath.Join(r.scope, oldVfsDir)) {
  54. return true
  55. }
  56. return false
  57. }
  58. func setOpts(v *localVolume, opts map[string]string) error {
  59. if len(opts) == 0 {
  60. return nil
  61. }
  62. err := validateOpts(opts)
  63. if err != nil {
  64. return err
  65. }
  66. v.opts = &optsConfig{
  67. MountType: opts["type"],
  68. MountOpts: opts["o"],
  69. MountDevice: opts["device"],
  70. }
  71. if val, ok := opts["size"]; ok {
  72. size, err := units.RAMInBytes(val)
  73. if err != nil {
  74. return err
  75. }
  76. if size > 0 && v.quotaCtl == nil {
  77. return errdefs.InvalidParameter(errors.Errorf("quota size requested but no quota support"))
  78. }
  79. v.opts.Quota.Size = uint64(size)
  80. }
  81. return nil
  82. }
  83. func validateOpts(opts map[string]string) error {
  84. if len(opts) == 0 {
  85. return nil
  86. }
  87. for opt := range opts {
  88. if _, ok := validOpts[opt]; !ok {
  89. return errdefs.InvalidParameter(errors.Errorf("invalid option: %q", opt))
  90. }
  91. }
  92. for opt, reqopts := range mandatoryOpts {
  93. if _, ok := opts[opt]; ok {
  94. for _, reqopt := range reqopts {
  95. if _, ok := opts[reqopt]; !ok {
  96. return errdefs.InvalidParameter(errors.Errorf("missing required option: %q", reqopt))
  97. }
  98. }
  99. }
  100. }
  101. return nil
  102. }
  103. func unmount(path string) {
  104. _ = mount.Unmount(path)
  105. }
  106. func (v *localVolume) needsMount() bool {
  107. if v.opts == nil {
  108. return false
  109. }
  110. if v.opts.MountDevice != "" || v.opts.MountType != "" {
  111. return true
  112. }
  113. return false
  114. }
  115. func (v *localVolume) mount() error {
  116. if v.opts.MountDevice == "" {
  117. return fmt.Errorf("missing device in volume options")
  118. }
  119. mountOpts := v.opts.MountOpts
  120. switch v.opts.MountType {
  121. case "nfs", "cifs":
  122. if addrValue := getAddress(v.opts.MountOpts); addrValue != "" && net.ParseIP(addrValue).To4() == nil {
  123. ipAddr, err := net.ResolveIPAddr("ip", addrValue)
  124. if err != nil {
  125. return errors.Wrapf(err, "error resolving passed in network volume address")
  126. }
  127. mountOpts = strings.Replace(mountOpts, "addr="+addrValue, "addr="+ipAddr.String(), 1)
  128. }
  129. }
  130. err := mount.Mount(v.opts.MountDevice, v.path, v.opts.MountType, mountOpts)
  131. if err != nil {
  132. if password := getPassword(v.opts.MountOpts); password != "" {
  133. err = errors.New(strings.Replace(err.Error(), "password="+password, "password=********", 1))
  134. }
  135. }
  136. return errors.Wrap(err, "failed to mount local volume")
  137. }
  138. func (v *localVolume) postMount() error {
  139. if v.opts == nil {
  140. return nil
  141. }
  142. if v.opts.Quota.Size > 0 {
  143. if v.quotaCtl != nil {
  144. err := v.quotaCtl.SetQuota(v.path, v.opts.Quota)
  145. if err != nil {
  146. return err
  147. }
  148. } else {
  149. return fmt.Errorf("size quota requested for volume but no quota support")
  150. }
  151. }
  152. return nil
  153. }
  154. func (v *localVolume) unmount() error {
  155. if v.needsMount() {
  156. if err := mount.Unmount(v.path); err != nil {
  157. if mounted, mErr := mountinfo.Mounted(v.path); mounted || mErr != nil {
  158. return errdefs.System(err)
  159. }
  160. }
  161. v.active.mounted = false
  162. }
  163. return nil
  164. }
  165. func (v *localVolume) CreatedAt() (time.Time, error) {
  166. fileInfo, err := os.Stat(v.path)
  167. if err != nil {
  168. return time.Time{}, err
  169. }
  170. sec, nsec := fileInfo.Sys().(*syscall.Stat_t).Ctim.Unix()
  171. return time.Unix(sec, nsec), nil
  172. }