github.com/hms58/moby@v1.13.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  
    11  // read-write modes
    12  var rwModes = map[string]bool{
    13  	"rw": true,
    14  }
    15  
    16  // read-only modes
    17  var roModes = map[string]bool{
    18  	"ro": true,
    19  }
    20  
    21  var platformRawValidationOpts = []func(*validateOpts){
    22  	// filepath.IsAbs is weird on Windows:
    23  	//	`c:` is not considered an absolute path
    24  	//	`c:\` is considered an absolute path
    25  	// In any case, the regex matching below ensures absolute paths
    26  	// TODO: consider this a bug with filepath.IsAbs (?)
    27  	func(o *validateOpts) { o.skipAbsolutePathCheck = true },
    28  }
    29  
    30  const (
    31  	// Spec should be in the format [source:]destination[:mode]
    32  	//
    33  	// Examples: c:\foo bar:d:rw
    34  	//           c:\foo:d:\bar
    35  	//           myname:d:
    36  	//           d:\
    37  	//
    38  	// Explanation of this regex! Thanks @thaJeztah on IRC and gist for help. See
    39  	// https://gist.github.com/thaJeztah/6185659e4978789fb2b2. A good place to
    40  	// test is https://regex-golang.appspot.com/assets/html/index.html
    41  	//
    42  	// Useful link for referencing named capturing groups:
    43  	// http://stackoverflow.com/questions/20750843/using-named-matches-from-go-regex
    44  	//
    45  	// There are three match groups: source, destination and mode.
    46  	//
    47  
    48  	// RXHostDir is the first option of a source
    49  	RXHostDir = `[a-z]:\\(?:[^\\/:*?"<>|\r\n]+\\?)*`
    50  	// RXName is the second option of a source
    51  	RXName = `[^\\/:*?"<>|\r\n]+`
    52  	// RXReservedNames are reserved names not possible on Windows
    53  	RXReservedNames = `(con)|(prn)|(nul)|(aux)|(com[1-9])|(lpt[1-9])`
    54  
    55  	// RXSource is the combined possibilities for a source
    56  	RXSource = `((?P<source>((` + RXHostDir + `)|(` + RXName + `))):)?`
    57  
    58  	// Source. Can be either a host directory, a name, or omitted:
    59  	//  HostDir:
    60  	//    -  Essentially using the folder solution from
    61  	//       https://www.safaribooksonline.com/library/view/regular-expressions-cookbook/9781449327453/ch08s18.html
    62  	//       but adding case insensitivity.
    63  	//    -  Must be an absolute path such as c:\path
    64  	//    -  Can include spaces such as `c:\program files`
    65  	//    -  And then followed by a colon which is not in the capture group
    66  	//    -  And can be optional
    67  	//  Name:
    68  	//    -  Must not contain invalid NTFS filename characters (https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx)
    69  	//    -  And then followed by a colon which is not in the capture group
    70  	//    -  And can be optional
    71  
    72  	// RXDestination is the regex expression for the mount destination
    73  	RXDestination = `(?P<destination>([a-z]):((?:\\[^\\/:*?"<>\r\n]+)*\\?))`
    74  	// Destination (aka container path):
    75  	//    -  Variation on hostdir but can be a drive followed by colon as well
    76  	//    -  If a path, must be absolute. Can include spaces
    77  	//    -  Drive cannot be c: (explicitly checked in code, not RegEx)
    78  
    79  	// RXMode is the regex expression for the mode of the mount
    80  	// Mode (optional):
    81  	//    -  Hopefully self explanatory in comparison to above regex's.
    82  	//    -  Colon is not in the capture group
    83  	RXMode = `(:(?P<mode>(?i)ro|rw))?`
    84  )
    85  
    86  // BackwardsCompatible decides whether this mount point can be
    87  // used in old versions of Docker or not.
    88  // Windows volumes are never backwards compatible.
    89  func (m *MountPoint) BackwardsCompatible() bool {
    90  	return false
    91  }
    92  
    93  func splitRawSpec(raw string) ([]string, error) {
    94  	specExp := regexp.MustCompile(`^` + RXSource + RXDestination + RXMode + `$`)
    95  	match := specExp.FindStringSubmatch(strings.ToLower(raw))
    96  
    97  	// Must have something back
    98  	if len(match) == 0 {
    99  		return nil, errInvalidSpec(raw)
   100  	}
   101  
   102  	var split []string
   103  	matchgroups := make(map[string]string)
   104  	// Pull out the sub expressions from the named capture groups
   105  	for i, name := range specExp.SubexpNames() {
   106  		matchgroups[name] = strings.ToLower(match[i])
   107  	}
   108  	if source, exists := matchgroups["source"]; exists {
   109  		if source != "" {
   110  			split = append(split, source)
   111  		}
   112  	}
   113  	if destination, exists := matchgroups["destination"]; exists {
   114  		if destination != "" {
   115  			split = append(split, destination)
   116  		}
   117  	}
   118  	if mode, exists := matchgroups["mode"]; exists {
   119  		if mode != "" {
   120  			split = append(split, mode)
   121  		}
   122  	}
   123  	// Fix #26329. If the destination appears to be a file, and the source is null,
   124  	// it may be because we've fallen through the possible naming regex and hit a
   125  	// situation where the user intention was to map a file into a container through
   126  	// a local volume, but this is not supported by the platform.
   127  	if matchgroups["source"] == "" && matchgroups["destination"] != "" {
   128  		validName, err := IsVolumeNameValid(matchgroups["destination"])
   129  		if err != nil {
   130  			return nil, err
   131  		}
   132  		if !validName {
   133  			if fi, err := os.Stat(matchgroups["destination"]); err == nil {
   134  				if !fi.IsDir() {
   135  					return nil, fmt.Errorf("file '%s' cannot be mapped. Only directories can be mapped on this platform", matchgroups["destination"])
   136  				}
   137  			}
   138  		}
   139  	}
   140  	return split, nil
   141  }
   142  
   143  // IsVolumeNameValid checks a volume name in a platform specific manner.
   144  func IsVolumeNameValid(name string) (bool, error) {
   145  	nameExp := regexp.MustCompile(`^` + RXName + `$`)
   146  	if !nameExp.MatchString(name) {
   147  		return false, nil
   148  	}
   149  	nameExp = regexp.MustCompile(`^` + RXReservedNames + `$`)
   150  	if nameExp.MatchString(name) {
   151  		return false, fmt.Errorf("volume name %q cannot be a reserved word for Windows filenames", name)
   152  	}
   153  	return true, nil
   154  }
   155  
   156  // ValidMountMode will make sure the mount mode is valid.
   157  // returns if it's a valid mount mode or not.
   158  func ValidMountMode(mode string) bool {
   159  	if mode == "" {
   160  		return true
   161  	}
   162  	return roModes[strings.ToLower(mode)] || rwModes[strings.ToLower(mode)]
   163  }
   164  
   165  // ReadWrite tells you if a mode string is a valid read-write mode or not.
   166  func ReadWrite(mode string) bool {
   167  	return rwModes[strings.ToLower(mode)] || mode == ""
   168  }
   169  
   170  func validateNotRoot(p string) error {
   171  	p = strings.ToLower(convertSlash(p))
   172  	if p == "c:" || p == `c:\` {
   173  		return fmt.Errorf("destination path cannot be `c:` or `c:\\`: %v", p)
   174  	}
   175  	return nil
   176  }
   177  
   178  func validateCopyMode(mode bool) error {
   179  	if mode {
   180  		return fmt.Errorf("Windows does not support copying image path content")
   181  	}
   182  	return nil
   183  }
   184  
   185  func convertSlash(p string) string {
   186  	return filepath.FromSlash(p)
   187  }
   188  
   189  func clean(p string) string {
   190  	if match, _ := regexp.MatchString("^[a-z]:$", p); match {
   191  		return p
   192  	}
   193  	return filepath.Clean(p)
   194  }
   195  
   196  func validateStat(fi os.FileInfo) error {
   197  	if !fi.IsDir() {
   198  		return fmt.Errorf("source path must be a directory")
   199  	}
   200  	return nil
   201  }