blob: 855698bb28280ea9a8d4205fb3d1d2066bcdc581 [file] [log] [blame]
kesavand2cde6582020-06-22 04:56:23 -04001// Copyright 2009 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5// Fork, exec, wait, etc.
6
7package windows
8
Andrea Campanella764f1ed2022-03-24 11:46:38 +01009import (
10 errorspkg "errors"
11 "unsafe"
12)
13
kesavand2cde6582020-06-22 04:56:23 -040014// EscapeArg rewrites command line argument s as prescribed
15// in http://msdn.microsoft.com/en-us/library/ms880421.
16// This function returns "" (2 double quotes) if s is empty.
17// Alternatively, these transformations are done:
18// - every back slash (\) is doubled, but only if immediately
19// followed by double quote (");
20// - every double quote (") is escaped by back slash (\);
21// - finally, s is wrapped with double quotes (arg -> "arg"),
22// but only if there is space or tab inside s.
23func EscapeArg(s string) string {
24 if len(s) == 0 {
25 return "\"\""
26 }
27 n := len(s)
28 hasSpace := false
29 for i := 0; i < len(s); i++ {
30 switch s[i] {
31 case '"', '\\':
32 n++
33 case ' ', '\t':
34 hasSpace = true
35 }
36 }
37 if hasSpace {
38 n += 2
39 }
40 if n == len(s) {
41 return s
42 }
43
44 qs := make([]byte, n)
45 j := 0
46 if hasSpace {
47 qs[j] = '"'
48 j++
49 }
50 slashes := 0
51 for i := 0; i < len(s); i++ {
52 switch s[i] {
53 default:
54 slashes = 0
55 qs[j] = s[i]
56 case '\\':
57 slashes++
58 qs[j] = s[i]
59 case '"':
60 for ; slashes > 0; slashes-- {
61 qs[j] = '\\'
62 j++
63 }
64 qs[j] = '\\'
65 j++
66 qs[j] = s[i]
67 }
68 j++
69 }
70 if hasSpace {
71 for ; slashes > 0; slashes-- {
72 qs[j] = '\\'
73 j++
74 }
75 qs[j] = '"'
76 j++
77 }
78 return string(qs[:j])
79}
80
kesavandc71914f2022-03-25 11:19:03 +053081// ComposeCommandLine escapes and joins the given arguments suitable for use as a Windows command line,
82// in CreateProcess's CommandLine argument, CreateService/ChangeServiceConfig's BinaryPathName argument,
83// or any program that uses CommandLineToArgv.
84func ComposeCommandLine(args []string) string {
85 var commandLine string
86 for i := range args {
87 if i > 0 {
88 commandLine += " "
89 }
90 commandLine += EscapeArg(args[i])
91 }
92 return commandLine
93}
94
95// DecomposeCommandLine breaks apart its argument command line into unescaped parts using CommandLineToArgv,
96// as gathered from GetCommandLine, QUERY_SERVICE_CONFIG's BinaryPathName argument, or elsewhere that
97// command lines are passed around.
98func DecomposeCommandLine(commandLine string) ([]string, error) {
99 if len(commandLine) == 0 {
100 return []string{}, nil
101 }
102 var argc int32
103 argv, err := CommandLineToArgv(StringToUTF16Ptr(commandLine), &argc)
104 if err != nil {
105 return nil, err
106 }
107 defer LocalFree(Handle(unsafe.Pointer(argv)))
108 var args []string
109 for _, v := range (*argv)[:argc] {
110 args = append(args, UTF16ToString((*v)[:]))
111 }
112 return args, nil
113}
114
kesavand2cde6582020-06-22 04:56:23 -0400115func CloseOnExec(fd Handle) {
116 SetHandleInformation(Handle(fd), HANDLE_FLAG_INHERIT, 0)
117}
118
119// FullPath retrieves the full path of the specified file.
120func FullPath(name string) (path string, err error) {
121 p, err := UTF16PtrFromString(name)
122 if err != nil {
123 return "", err
124 }
125 n := uint32(100)
126 for {
127 buf := make([]uint16, n)
128 n, err = GetFullPathName(p, uint32(len(buf)), &buf[0], nil)
129 if err != nil {
130 return "", err
131 }
132 if n <= uint32(len(buf)) {
133 return UTF16ToString(buf[:n]), nil
134 }
135 }
136}
Andrea Campanella764f1ed2022-03-24 11:46:38 +0100137
kesavandc71914f2022-03-25 11:19:03 +0530138// NewProcThreadAttributeList allocates a new ProcThreadAttributeListContainer, with the requested maximum number of attributes.
139func NewProcThreadAttributeList(maxAttrCount uint32) (*ProcThreadAttributeListContainer, error) {
Andrea Campanella764f1ed2022-03-24 11:46:38 +0100140 var size uintptr
141 err := initializeProcThreadAttributeList(nil, maxAttrCount, 0, &size)
142 if err != ERROR_INSUFFICIENT_BUFFER {
143 if err == nil {
144 return nil, errorspkg.New("unable to query buffer size from InitializeProcThreadAttributeList")
145 }
146 return nil, err
147 }
kesavandc71914f2022-03-25 11:19:03 +0530148 alloc, err := LocalAlloc(LMEM_FIXED, uint32(size))
149 if err != nil {
150 return nil, err
151 }
Andrea Campanella764f1ed2022-03-24 11:46:38 +0100152 // size is guaranteed to be ≥1 by InitializeProcThreadAttributeList.
kesavandc71914f2022-03-25 11:19:03 +0530153 al := &ProcThreadAttributeListContainer{data: (*ProcThreadAttributeList)(unsafe.Pointer(alloc))}
154 err = initializeProcThreadAttributeList(al.data, maxAttrCount, 0, &size)
Andrea Campanella764f1ed2022-03-24 11:46:38 +0100155 if err != nil {
156 return nil, err
157 }
158 return al, err
159}
160
161// Update modifies the ProcThreadAttributeList using UpdateProcThreadAttribute.
kesavandc71914f2022-03-25 11:19:03 +0530162func (al *ProcThreadAttributeListContainer) Update(attribute uintptr, value unsafe.Pointer, size uintptr) error {
163 al.pointers = append(al.pointers, value)
164 return updateProcThreadAttribute(al.data, 0, attribute, value, size, nil, nil)
Andrea Campanella764f1ed2022-03-24 11:46:38 +0100165}
166
167// Delete frees ProcThreadAttributeList's resources.
kesavandc71914f2022-03-25 11:19:03 +0530168func (al *ProcThreadAttributeListContainer) Delete() {
169 deleteProcThreadAttributeList(al.data)
170 LocalFree(Handle(unsafe.Pointer(al.data)))
171 al.data = nil
172 al.pointers = nil
173}
174
175// List returns the actual ProcThreadAttributeList to be passed to StartupInfoEx.
176func (al *ProcThreadAttributeListContainer) List() *ProcThreadAttributeList {
177 return al.data
Andrea Campanella764f1ed2022-03-24 11:46:38 +0100178}