github.com/ilhicas/nomad@v0.11.8/drivers/docker/win32_volume_parse.go (about) 1 package docker 2 3 import ( 4 "fmt" 5 "os" 6 "regexp" 7 "strings" 8 9 "github.com/pkg/errors" 10 ) 11 12 // This code is taken from github.com/docker/volume/mounts/windows_parser.go 13 // See https://github.com/moby/moby/blob/master/LICENSE for the license, Apache License 2.0 at this time. 14 15 const ( 16 // Spec should be in the format [source:]destination[:mode] 17 // 18 // Examples: c:\foo bar:d:rw 19 // c:\foo:d:\bar 20 // myname:d: 21 // d:\ 22 // 23 // Explanation of this regex! Thanks @thaJeztah on IRC and gist for help. See 24 // https://gist.github.com/thaJeztah/6185659e4978789fb2b2. A good place to 25 // test is https://regex-golang.appspot.com/assets/html/index.html 26 // 27 // Useful link for referencing named capturing groups: 28 // http://stackoverflow.com/questions/20750843/using-named-matches-from-go-regex 29 // 30 // There are three match groups: source, destination and mode. 31 // 32 33 // rxHostDir is the first option of a source 34 rxHostDir = `(?:\\\\\?\\)?[a-z]:[\\/](?:[^\\/:*?"<>|\r\n]+[\\/]?)*` 35 // rxName is the second option of a source 36 rxName = `[^\\/:*?"<>|\r\n]+\/?.*` 37 38 // RXReservedNames are reserved names not possible on Windows 39 rxReservedNames = `(con)|(prn)|(nul)|(aux)|(com[1-9])|(lpt[1-9])` 40 41 // rxPipe is a named path pipe (starts with `\\.\pipe\`, possibly with / instead of \) 42 rxPipe = `[/\\]{2}.[/\\]pipe[/\\][^:*?"<>|\r\n]+` 43 // rxSource is the combined possibilities for a source 44 rxSource = `((?P<source>((` + rxHostDir + `)|(` + rxName + `)|(` + rxPipe + `))):)?` 45 46 // Source. Can be either a host directory, a name, or omitted: 47 // HostDir: 48 // - Essentially using the folder solution from 49 // https://www.safaribooksonline.com/library/view/regular-expressions-cookbook/9781449327453/ch08s18.html 50 // but adding case insensitivity. 51 // - Must be an absolute path such as c:\path 52 // - Can include spaces such as `c:\program files` 53 // - And then followed by a colon which is not in the capture group 54 // - And can be optional 55 // Name: 56 // - Must not contain invalid NTFS filename characters (https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx) 57 // - And then followed by a colon which is not in the capture group 58 // - And can be optional 59 60 // rxDestination is the regex expression for the mount destination 61 rxDestination = `(?P<destination>((?:\\\\\?\\)?([a-z]):((?:[\\/][^\\/:*?"<>\r\n]+)*[\\/]?))|(` + rxPipe + `)|([/].*))` 62 63 // Destination (aka container path): 64 // - Variation on hostdir but can be a drive followed by colon as well 65 // - If a path, must be absolute. Can include spaces 66 // - Drive cannot be c: (explicitly checked in code, not RegEx) 67 68 // rxMode is the regex expression for the mode of the mount 69 // Mode (optional): 70 // - Hopefully self explanatory in comparison to above regex's. 71 // - Colon is not in the capture group 72 rxMode = `(:(?P<mode>(?i)ro|rw))?` 73 ) 74 75 func errInvalidSpec(spec string) error { 76 return errors.Errorf("invalid volume specification: '%s'", spec) 77 } 78 79 type fileInfoProvider interface { 80 fileInfo(path string) (exist, isDir bool, err error) 81 } 82 83 type defaultFileInfoProvider struct { 84 } 85 86 func (defaultFileInfoProvider) fileInfo(path string) (exist, isDir bool, err error) { 87 fi, err := os.Stat(path) 88 if err != nil { 89 if !os.IsNotExist(err) { 90 return false, false, err 91 } 92 return false, false, nil 93 } 94 return true, fi.IsDir(), nil 95 } 96 97 var currentFileInfoProvider fileInfoProvider = defaultFileInfoProvider{} 98 99 func windowsSplitRawSpec(raw, destRegex string) ([]string, error) { 100 specExp := regexp.MustCompile(`^` + rxSource + destRegex + rxMode + `$`) 101 match := specExp.FindStringSubmatch(strings.ToLower(raw)) 102 103 // Must have something back 104 if len(match) == 0 { 105 return nil, errInvalidSpec(raw) 106 } 107 108 var split []string 109 matchgroups := make(map[string]string) 110 // Pull out the sub expressions from the named capture groups 111 for i, name := range specExp.SubexpNames() { 112 matchgroups[name] = strings.ToLower(match[i]) 113 } 114 if source, exists := matchgroups["source"]; exists { 115 if source != "" { 116 split = append(split, source) 117 } 118 } 119 if destination, exists := matchgroups["destination"]; exists { 120 if destination != "" { 121 split = append(split, destination) 122 } 123 } 124 if mode, exists := matchgroups["mode"]; exists { 125 if mode != "" { 126 split = append(split, mode) 127 } 128 } 129 // Fix #26329. If the destination appears to be a file, and the source is null, 130 // it may be because we've fallen through the possible naming regex and hit a 131 // situation where the user intention was to map a file into a container through 132 // a local volume, but this is not supported by the platform. 133 if matchgroups["source"] == "" && matchgroups["destination"] != "" { 134 volExp := regexp.MustCompile(`^` + rxName + `$`) 135 reservedNameExp := regexp.MustCompile(`^` + rxReservedNames + `$`) 136 137 if volExp.MatchString(matchgroups["destination"]) { 138 if reservedNameExp.MatchString(matchgroups["destination"]) { 139 return nil, fmt.Errorf("volume name %q cannot be a reserved word for Windows filenames", matchgroups["destination"]) 140 } 141 } else { 142 143 exists, isDir, _ := currentFileInfoProvider.fileInfo(matchgroups["destination"]) 144 if exists && !isDir { 145 return nil, fmt.Errorf("file '%s' cannot be mapped. Only directories can be mapped on this platform", matchgroups["destination"]) 146 147 } 148 } 149 } 150 return split, nil 151 }