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