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