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