github.com/portworx/docker@v1.12.1/volume/volume_windows.go (about)

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