volume_windows.go 6.2 KB

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