123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206 |
- package volume
- import (
- "fmt"
- "os"
- "path/filepath"
- "regexp"
- "strings"
- "github.com/Sirupsen/logrus"
- "github.com/docker/docker/pkg/system"
- )
- // read-write modes
- var rwModes = map[string]bool{
- "rw": true,
- }
- // read-only modes
- var roModes = map[string]bool{
- "ro": true,
- }
- 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])`
- // RXSource is the combined possibilities for a source
- RXSource = `((?P<source>((` + RXHostDir + `)|(` + RXName + `))):)?`
- // 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]+)*\\?))`
- // 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
- var RXMode string
- func init() {
- osv := system.GetOSVersion()
- // Read-only volumes supported from 14350 onwards (post Windows Server 2016 TP5)
- // Mode (optional):
- // - Hopefully self explanatory in comparison to above regex's.
- // - Colon is not in the capture group
- if osv.Build >= 14350 {
- RXMode = `(:(?P<mode>(?i)ro|rw))?`
- } else {
- RXMode = `(:(?P<mode>(?i)rw))?`
- }
- }
- // BackwardsCompatible decides whether this mount point can be
- // used in old versions of Docker or not.
- // Windows volumes are never backwards compatible.
- func (m *MountPoint) BackwardsCompatible() bool {
- return false
- }
- // ParseMountSpec validates the configuration of mount information is valid.
- func ParseMountSpec(spec string, volumeDriver string) (*MountPoint, error) {
- var specExp = regexp.MustCompile(`^` + RXSource + RXDestination + RXMode + `$`)
- // Ensure in platform semantics for matching. The CLI will send in Unix semantics.
- match := specExp.FindStringSubmatch(filepath.FromSlash(strings.ToLower(spec)))
- // Must have something back
- if len(match) == 0 {
- return nil, errInvalidSpec(spec)
- }
- // Pull out the sub expressions from the named capture groups
- matchgroups := make(map[string]string)
- for i, name := range specExp.SubexpNames() {
- matchgroups[name] = strings.ToLower(match[i])
- }
- mp := &MountPoint{
- Source: matchgroups["source"],
- Destination: matchgroups["destination"],
- RW: true,
- }
- if strings.ToLower(matchgroups["mode"]) == "ro" {
- mp.RW = false
- }
- // Volumes cannot include an explicitly supplied mode eg c:\path:rw
- if mp.Source == "" && mp.Destination != "" && matchgroups["mode"] != "" {
- return nil, errInvalidSpec(spec)
- }
- // Note: No need to check if destination is absolute as it must be by
- // definition of matching the regex.
- if filepath.VolumeName(mp.Destination) == mp.Destination {
- // Ensure the destination path, if a drive letter, is not the c drive
- if strings.ToLower(mp.Destination) == "c:" {
- return nil, fmt.Errorf("Destination drive letter in '%s' cannot be c:", spec)
- }
- } else {
- // So we know the destination is a path, not drive letter. Clean it up.
- mp.Destination = filepath.Clean(mp.Destination)
- // Ensure the destination path, if a path, is not the c root directory
- if strings.ToLower(mp.Destination) == `c:\` {
- return nil, fmt.Errorf(`Destination path in '%s' cannot be c:\`, spec)
- }
- }
- // See if the source is a name instead of a host directory
- if len(mp.Source) > 0 {
- validName, err := IsVolumeNameValid(mp.Source)
- if err != nil {
- return nil, err
- }
- if validName {
- // OK, so the source is a name.
- mp.Name = mp.Source
- mp.Source = ""
- // Set the driver accordingly
- mp.Driver = volumeDriver
- if len(mp.Driver) == 0 {
- mp.Driver = DefaultDriverName
- }
- } else {
- // OK, so the source must be a host directory. Make sure it's clean.
- mp.Source = filepath.Clean(mp.Source)
- }
- }
- // Ensure the host path source, if supplied, exists and is a directory
- if len(mp.Source) > 0 {
- var fi os.FileInfo
- var err error
- if fi, err = os.Stat(mp.Source); err != nil {
- return nil, fmt.Errorf("Source directory '%s' could not be found: %s", mp.Source, err)
- }
- if !fi.IsDir() {
- return nil, fmt.Errorf("Source '%s' is not a directory", mp.Source)
- }
- }
- logrus.Debugf("MP: Source '%s', Dest '%s', RW %t, Name '%s', Driver '%s'", mp.Source, mp.Destination, mp.RW, mp.Name, mp.Driver)
- return mp, nil
- }
- // IsVolumeNameValid checks a volume name in a platform specific manner.
- func IsVolumeNameValid(name string) (bool, error) {
- nameExp := regexp.MustCompile(`^` + RXName + `$`)
- if !nameExp.MatchString(name) {
- return false, nil
- }
- nameExp = regexp.MustCompile(`^` + RXReservedNames + `$`)
- if nameExp.MatchString(name) {
- return false, fmt.Errorf("Volume name %q cannot be a reserved word for Windows filenames", name)
- }
- return true, nil
- }
- // ValidMountMode will make sure the mount mode is valid.
- // returns if it's a valid mount mode or not.
- func ValidMountMode(mode string) bool {
- return roModes[strings.ToLower(mode)] || rwModes[strings.ToLower(mode)]
- }
- // ReadWrite tells you if a mode string is a valid read-write mode or not.
- func ReadWrite(mode string) bool {
- return rwModes[strings.ToLower(mode)]
- }
|