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