volume_windows.go 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. package volume
  2. import (
  3. "fmt"
  4. "os"
  5. "path/filepath"
  6. "regexp"
  7. "strings"
  8. "github.com/Sirupsen/logrus"
  9. "github.com/docker/docker/pkg/system"
  10. )
  11. // read-write modes
  12. var rwModes = map[string]bool{
  13. "rw": true,
  14. }
  15. // read-only modes
  16. var roModes = map[string]bool{
  17. "ro": true,
  18. }
  19. const (
  20. // Spec should be in the format [source:]destination[:mode]
  21. //
  22. // Examples: c:\foo bar:d:rw
  23. // c:\foo:d:\bar
  24. // myname:d:
  25. // d:\
  26. //
  27. // Explanation of this regex! Thanks @thaJeztah on IRC and gist for help. See
  28. // https://gist.github.com/thaJeztah/6185659e4978789fb2b2. A good place to
  29. // test is https://regex-golang.appspot.com/assets/html/index.html
  30. //
  31. // Useful link for referencing named capturing groups:
  32. // http://stackoverflow.com/questions/20750843/using-named-matches-from-go-regex
  33. //
  34. // There are three match groups: source, destination and mode.
  35. //
  36. // RXHostDir is the first option of a source
  37. RXHostDir = `[a-z]:\\(?:[^\\/:*?"<>|\r\n]+\\?)*`
  38. // RXName is the second option of a source
  39. RXName = `[^\\/:*?"<>|\r\n]+`
  40. // RXReservedNames are reserved names not possible on Windows
  41. RXReservedNames = `(con)|(prn)|(nul)|(aux)|(com[1-9])|(lpt[1-9])`
  42. // RXSource is the combined possibilities for a source
  43. RXSource = `((?P<source>((` + RXHostDir + `)|(` + RXName + `))):)?`
  44. // Source. Can be either a host directory, a name, or omitted:
  45. // HostDir:
  46. // - Essentially using the folder solution from
  47. // https://www.safaribooksonline.com/library/view/regular-expressions-cookbook/9781449327453/ch08s18.html
  48. // but adding case insensitivity.
  49. // - Must be an absolute path such as c:\path
  50. // - Can include spaces such as `c:\program files`
  51. // - And then followed by a colon which is not in the capture group
  52. // - And can be optional
  53. // Name:
  54. // - Must not contain invalid NTFS filename characters (https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx)
  55. // - And then followed by a colon which is not in the capture group
  56. // - And can be optional
  57. // RXDestination is the regex expression for the mount destination
  58. RXDestination = `(?P<destination>([a-z]):((?:\\[^\\/:*?"<>\r\n]+)*\\?))`
  59. // Destination (aka container path):
  60. // - Variation on hostdir but can be a drive followed by colon as well
  61. // - If a path, must be absolute. Can include spaces
  62. // - Drive cannot be c: (explicitly checked in code, not RegEx)
  63. )
  64. // RXMode is the regex expression for the mode of the mount
  65. var RXMode string
  66. func init() {
  67. osv := system.GetOSVersion()
  68. // Read-only volumes supported from 14350 onwards (post Windows Server 2016 TP5)
  69. // Mode (optional):
  70. // - Hopefully self explanatory in comparison to above regex's.
  71. // - Colon is not in the capture group
  72. if osv.Build >= 14350 {
  73. RXMode = `(:(?P<mode>(?i)ro|rw))?`
  74. } else {
  75. RXMode = `(:(?P<mode>(?i)rw))?`
  76. }
  77. }
  78. // BackwardsCompatible decides whether this mount point can be
  79. // used in old versions of Docker or not.
  80. // Windows volumes are never backwards compatible.
  81. func (m *MountPoint) BackwardsCompatible() bool {
  82. return false
  83. }
  84. // ParseMountSpec validates the configuration of mount information is valid.
  85. func ParseMountSpec(spec string, volumeDriver string) (*MountPoint, error) {
  86. var specExp = regexp.MustCompile(`^` + RXSource + RXDestination + RXMode + `$`)
  87. // Ensure in platform semantics for matching. The CLI will send in Unix semantics.
  88. match := specExp.FindStringSubmatch(filepath.FromSlash(strings.ToLower(spec)))
  89. // Must have something back
  90. if len(match) == 0 {
  91. return nil, errInvalidSpec(spec)
  92. }
  93. // Pull out the sub expressions from the named capture groups
  94. matchgroups := make(map[string]string)
  95. for i, name := range specExp.SubexpNames() {
  96. matchgroups[name] = strings.ToLower(match[i])
  97. }
  98. mp := &MountPoint{
  99. Source: matchgroups["source"],
  100. Destination: matchgroups["destination"],
  101. RW: true,
  102. }
  103. if strings.ToLower(matchgroups["mode"]) == "ro" {
  104. mp.RW = false
  105. }
  106. // Volumes cannot include an explicitly supplied mode eg c:\path:rw
  107. if mp.Source == "" && mp.Destination != "" && matchgroups["mode"] != "" {
  108. return nil, errInvalidSpec(spec)
  109. }
  110. // Note: No need to check if destination is absolute as it must be by
  111. // definition of matching the regex.
  112. if filepath.VolumeName(mp.Destination) == mp.Destination {
  113. // Ensure the destination path, if a drive letter, is not the c drive
  114. if strings.ToLower(mp.Destination) == "c:" {
  115. return nil, fmt.Errorf("Destination drive letter in '%s' cannot be c:", spec)
  116. }
  117. } else {
  118. // So we know the destination is a path, not drive letter. Clean it up.
  119. mp.Destination = filepath.Clean(mp.Destination)
  120. // Ensure the destination path, if a path, is not the c root directory
  121. if strings.ToLower(mp.Destination) == `c:\` {
  122. return nil, fmt.Errorf(`Destination path in '%s' cannot be c:\`, spec)
  123. }
  124. }
  125. // See if the source is a name instead of a host directory
  126. if len(mp.Source) > 0 {
  127. validName, err := IsVolumeNameValid(mp.Source)
  128. if err != nil {
  129. return nil, err
  130. }
  131. if validName {
  132. // OK, so the source is a name.
  133. mp.Name = mp.Source
  134. mp.Source = ""
  135. // Set the driver accordingly
  136. mp.Driver = volumeDriver
  137. if len(mp.Driver) == 0 {
  138. mp.Driver = DefaultDriverName
  139. }
  140. } else {
  141. // OK, so the source must be a host directory. Make sure it's clean.
  142. mp.Source = filepath.Clean(mp.Source)
  143. }
  144. }
  145. // Ensure the host path source, if supplied, exists and is a directory
  146. if len(mp.Source) > 0 {
  147. var fi os.FileInfo
  148. var err error
  149. if fi, err = os.Stat(mp.Source); err != nil {
  150. return nil, fmt.Errorf("Source directory '%s' could not be found: %s", mp.Source, err)
  151. }
  152. if !fi.IsDir() {
  153. return nil, fmt.Errorf("Source '%s' is not a directory", mp.Source)
  154. }
  155. }
  156. logrus.Debugf("MP: Source '%s', Dest '%s', RW %t, Name '%s', Driver '%s'", mp.Source, mp.Destination, mp.RW, mp.Name, mp.Driver)
  157. return mp, nil
  158. }
  159. // IsVolumeNameValid checks a volume name in a platform specific manner.
  160. func IsVolumeNameValid(name string) (bool, error) {
  161. nameExp := regexp.MustCompile(`^` + RXName + `$`)
  162. if !nameExp.MatchString(name) {
  163. return false, nil
  164. }
  165. nameExp = regexp.MustCompile(`^` + RXReservedNames + `$`)
  166. if nameExp.MatchString(name) {
  167. return false, fmt.Errorf("Volume name %q cannot be a reserved word for Windows filenames", name)
  168. }
  169. return true, nil
  170. }
  171. // ValidMountMode will make sure the mount mode is valid.
  172. // returns if it's a valid mount mode or not.
  173. func ValidMountMode(mode string) bool {
  174. return roModes[strings.ToLower(mode)] || rwModes[strings.ToLower(mode)]
  175. }
  176. // ReadWrite tells you if a mode string is a valid read-write mode or not.
  177. func ReadWrite(mode string) bool {
  178. return rwModes[strings.ToLower(mode)]
  179. }