github.com/demonoid81/moby@v0.0.0-20200517203328-62dd8e17c460/builder/dockerfile/dispatchers_windows.go (about) 1 package dockerfile // import "github.com/demonoid81/moby/builder/dockerfile" 2 3 import ( 4 "errors" 5 "fmt" 6 "os" 7 "path" 8 "path/filepath" 9 "regexp" 10 "strings" 11 12 "github.com/demonoid81/moby/api/types/container" 13 "github.com/demonoid81/moby/pkg/system" 14 "github.com/moby/buildkit/frontend/dockerfile/instructions" 15 ) 16 17 var pattern = regexp.MustCompile(`^[a-zA-Z]:\.$`) 18 19 // normalizeWorkdir normalizes a user requested working directory in a 20 // platform semantically consistent way. 21 func normalizeWorkdir(platform string, current string, requested string) (string, error) { 22 if platform == "" { 23 platform = "windows" 24 } 25 if platform == "windows" { 26 return normalizeWorkdirWindows(current, requested) 27 } 28 return normalizeWorkdirUnix(current, requested) 29 } 30 31 // normalizeWorkdirUnix normalizes a user requested working directory in a 32 // platform semantically consistent way. 33 func normalizeWorkdirUnix(current string, requested string) (string, error) { 34 if requested == "" { 35 return "", errors.New("cannot normalize nothing") 36 } 37 current = strings.Replace(current, string(os.PathSeparator), "/", -1) 38 requested = strings.Replace(requested, string(os.PathSeparator), "/", -1) 39 if !path.IsAbs(requested) { 40 return path.Join(`/`, current, requested), nil 41 } 42 return requested, nil 43 } 44 45 // normalizeWorkdirWindows normalizes a user requested working directory in a 46 // platform semantically consistent way. 47 func normalizeWorkdirWindows(current string, requested string) (string, error) { 48 if requested == "" { 49 return "", errors.New("cannot normalize nothing") 50 } 51 52 // `filepath.Clean` will replace "" with "." so skip in that case 53 if current != "" { 54 current = filepath.Clean(current) 55 } 56 if requested != "" { 57 requested = filepath.Clean(requested) 58 } 59 60 // If either current or requested in Windows is: 61 // C: 62 // C:. 63 // then an error will be thrown as the definition for the above 64 // refers to `current directory on drive C:` 65 // Since filepath.Clean() will automatically normalize the above 66 // to `C:.`, we only need to check the last format 67 if pattern.MatchString(current) { 68 return "", fmt.Errorf("%s is not a directory. If you are specifying a drive letter, please add a trailing '\\'", current) 69 } 70 if pattern.MatchString(requested) { 71 return "", fmt.Errorf("%s is not a directory. If you are specifying a drive letter, please add a trailing '\\'", requested) 72 } 73 74 // Target semantics is C:\somefolder, specifically in the format: 75 // UPPERCASEDriveLetter-Colon-Backslash-FolderName. We are already 76 // guaranteed that `current`, if set, is consistent. This allows us to 77 // cope correctly with any of the following in a Dockerfile: 78 // WORKDIR a --> C:\a 79 // WORKDIR c:\\foo --> C:\foo 80 // WORKDIR \\foo --> C:\foo 81 // WORKDIR /foo --> C:\foo 82 // WORKDIR c:\\foo \ WORKDIR bar --> C:\foo --> C:\foo\bar 83 // WORKDIR C:/foo \ WORKDIR bar --> C:\foo --> C:\foo\bar 84 // WORKDIR C:/foo \ WORKDIR \\bar --> C:\foo --> C:\bar 85 // WORKDIR /foo \ WORKDIR c:/bar --> C:\foo --> C:\bar 86 if len(current) == 0 || system.IsAbs(requested) { 87 if (requested[0] == os.PathSeparator) || 88 (len(requested) > 1 && string(requested[1]) != ":") || 89 (len(requested) == 1) { 90 requested = filepath.Join(`C:\`, requested) 91 } 92 } else { 93 requested = filepath.Join(current, requested) 94 } 95 // Upper-case drive letter 96 return (strings.ToUpper(string(requested[0])) + requested[1:]), nil 97 } 98 99 // resolveCmdLine takes a command line arg set and optionally prepends a platform-specific 100 // shell in front of it. It returns either an array of arguments and an indication that 101 // the arguments are not yet escaped; Or, an array containing a single command line element 102 // along with an indication that the arguments are escaped so the runtime shouldn't escape. 103 // 104 // A better solution could be made, but it would be exceptionally invasive throughout 105 // many parts of the daemon which are coded assuming Linux args array only only, not taking 106 // account of Windows-natural command line semantics and it's argv handling. Put another way, 107 // while what is here is good-enough, it could be improved, but would be highly invasive. 108 // 109 // The commands when this function is called are RUN, ENTRYPOINT and CMD. 110 func resolveCmdLine(cmd instructions.ShellDependantCmdLine, runConfig *container.Config, os, command, original string) ([]string, bool) { 111 112 // Make sure we return an empty array if there is no cmd.CmdLine 113 if len(cmd.CmdLine) == 0 { 114 return []string{}, runConfig.ArgsEscaped 115 } 116 117 if os == "windows" { // ie WCOW 118 if cmd.PrependShell { 119 // WCOW shell-form. Return a single-element array containing the original command line prepended with the shell. 120 // Also indicate that it has not been escaped (so will be passed through directly to HCS). Note that 121 // we go back to the original un-parsed command line in the dockerfile line, strip off both the command part of 122 // it (RUN/ENTRYPOINT/CMD), and also strip any leading white space. IOW, we deliberately ignore any prior parsing 123 // so as to ensure it is treated exactly as a command line. For those interested, `RUN mkdir "c:/foo"` is a particularly 124 // good example of why this is necessary if you fancy debugging how cmd.exe and its builtin mkdir works. (Windows 125 // doesn't have a mkdir.exe, and I'm guessing cmd.exe has some very long unavoidable and unchangeable historical 126 // design decisions over how both its built-in echo and mkdir are coded. Probably more too.) 127 original = original[len(command):] // Strip off the command 128 original = strings.TrimLeft(original, " \t\v\n") // Strip of leading whitespace 129 return []string{strings.Join(getShell(runConfig, os), " ") + " " + original}, true 130 } 131 132 // WCOW JSON/"exec" form. 133 return cmd.CmdLine, false 134 } 135 136 // LCOW - use args as an array, same as LCOL. 137 if cmd.PrependShell && cmd.CmdLine != nil { 138 return append(getShell(runConfig, os), cmd.CmdLine...), false 139 } 140 return cmd.CmdLine, false 141 }