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