github.com/ilhicas/nomad@v0.11.8/drivers/docker/win32_volume_parse.go (about)

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