@@ -22,6 +22,35 @@ namespace {
2222
2323constexpr okw_uptr kCommandLine = 32768 ; // this environment's own bound
2424
25+ // ⚠️ THE NAMES A STARTED PROGRAM IS GIVEN, IN THE FORM EVERY PROGRAM READS.
26+ //
27+ // GetFinalPathNameByHandleW answers with the `\\?\' prefix, which tells this
28+ // system to take the rest verbatim. CreateProcessW accepts it, and the started
29+ // program inherits it as its current directory --- where the command interpreter
30+ // refuses it ("UNC paths are not supported") and runs in the Windows directory
31+ // instead. A batch file, which is how many tools are installed on this system,
32+ // therefore ran somewhere other than where it was started. So the prefix is
33+ // removed wherever the name means the same without it: a drive path within the
34+ // classic bound, and a network share in its `\\server\share' form. A longer name
35+ // keeps the prefix, because without it the name would not be accepted at all.
36+ okw_uptr plain_name (wchar_t * s, okw_uptr n) {
37+ // MAX_PATH, less the separator and the terminator a current directory takes.
38+ constexpr okw_uptr kClassic = 258 ;
39+ const bool verbatim = n >= 4 && s[0 ] == L' \\ ' && s[1 ] == L' \\ ' && s[2 ] == L' ?' && s[3 ] == L' \\ ' ;
40+ if (!verbatim) return n;
41+ const bool drive = n >= 7 && ((s[4 ] >= L' A' && s[4 ] <= L' Z' ) || (s[4 ] >= L' a' && s[4 ] <= L' z' ))
42+ && s[5 ] == L' :' && s[6 ] == L' \\ ' ;
43+ const bool share = n >= 8 && (s[4 ] == L' U' || s[4 ] == L' u' ) && (s[5 ] == L' N' || s[5 ] == L' n' )
44+ && (s[6 ] == L' C' || s[6 ] == L' c' ) && s[7 ] == L' \\ ' ;
45+ okw_uptr drop = 0 ;
46+ if (drive && n - 4 <= kClassic ) drop = 4 ; // \\?\C:\dir -> C:\dir
47+ else if (share && n - 6 <= kClassic ) drop = 6 ; // \\?\UNC\host\x -> \\host\x
48+ if (drop == 0 ) return n;
49+ for (okw_uptr i = drop; i <= n; ++i) s[i - drop] = s[i]; // the terminator too
50+ if (drop == 6 ) s[0 ] = L' \\ ' ;
51+ return n - drop;
52+ }
53+
2554// ⚠️ THE HANDLES A START PLACES ARE INHERITABLE FOR THE LENGTH OF THE START AND NO
2655// LONGER, AND ONE START AT A TIME DOES THIS.
2756//
@@ -100,24 +129,35 @@ bool append_utf8(wchar_t* out, okw_uptr cap, okw_uptr& at, const char* s, okw_up
100129 return true ;
101130}
102131
132+ // ⚠️⚠️ UNTIL 0.7.3 EVERY SEPARATOR WAS DROPPED, AND EVERY ELEMENT WAS QUOTED.
133+ //
134+ // A run of backslashes was counted and never written, so `C:\dir\file' arrived as
135+ // `C:dirfile' and a trailing one ended the quoting early and joined the elements
136+ // after it. And an element that needs no quoting was quoted anyway, which the
137+ // splitting accepts and the command interpreter does not: it reads `"/c"' as a
138+ // command rather than as its switch, and a batch file --- run through the
139+ // interpreter --- received a line it called incorrect. Now an element is written
140+ // as it is unless the splitting would alter it, and otherwise quoted by the rule
141+ // the splitting inverts: backslashes before a quote are doubled and the quote
142+ // escaped, backslashes before the closing quote are doubled, and every other
143+ // backslash is literal.
103144bool append_quoted (wchar_t * out, okw_uptr cap, okw_uptr& at, const wchar_t * s, okw_uptr n) {
145+ bool needs = n == 0 ;
146+ for (okw_uptr i = 0 ; i < n && !needs; ++i)
147+ needs = s[i] == L' ' || s[i] == L' \t ' || s[i] == L' \n ' || s[i] == L' \v ' || s[i] == L' "' ;
148+ if (!needs) return append_wide (out, cap, at, s, n);
149+
104150 if (!append_wide (out, cap, at, L" \" " , 1 )) return false ;
105151 okw_uptr backslashes = 0 ;
106152 for (okw_uptr i = 0 ; i < n; ++i) {
107153 if (s[i] == L' \\ ' ) { ++backslashes; continue ; }
108- if (s[i] == L' "' ) {
109- for (okw_uptr k = 0 ; k <= backslashes; ++k)
110- if (!append_wide (out, cap, at, L" \\ " , 1 )) return false ;
111- backslashes = 0 ;
112- } else {
113- backslashes = 0 ;
114- }
115- // The run of separators preceding this character is emitted with it.
116- if (at + 1 >= cap) return false ;
117- if (s[i] == L' "' ) { out[at++] = L' "' ; continue ; }
118- out[at++] = s[i];
154+ const okw_uptr written = s[i] == L' "' ? 2 * backslashes + 1 : backslashes;
155+ for (okw_uptr k = 0 ; k < written; ++k)
156+ if (!append_wide (out, cap, at, L" \\ " , 1 )) return false ;
157+ backslashes = 0 ;
158+ if (!append_wide (out, cap, at, s + i, 1 )) return false ;
119159 }
120- for (okw_uptr k = 0 ; k < backslashes; ++k)
160+ for (okw_uptr k = 0 ; k < 2 * backslashes; ++k)
121161 if (!append_wide (out, cap, at, L" \\ " , 1 )) return false ;
122162 return append_wide (out, cap, at, L" \" " , 1 );
123163}
@@ -208,6 +248,11 @@ int kal_process_spawn(const kal_spawn* how,
208248 // was missing until 0.11 was a caller able to say which.
209249 wchar_t cwd[okw::kMaxName ];
210250 wchar_t line[kCommandLine ];
251+ // One element, converted before it is quoted into `line'. Its own
252+ // buffer: quoting lengthens an element, so converting it in place just
253+ // beyond what `line' holds had the quoting overwrite what it had not
254+ // yet read.
255+ wchar_t one[kCommandLine ];
211256 wchar_t block[kCommandLine ];
212257 };
213258 auto * work = static_cast <scratch*>(kal_alloc (sizeof (scratch), alignof (scratch)));
@@ -229,13 +274,15 @@ int kal_process_spawn(const kal_spawn* how,
229274 image[at++] = relative.buffer [i];
230275 }
231276 image[at] = 0 ;
277+ (void )plain_name (image, at);
232278
233279 // The same enquiry the image path comes from, upon the other directory.
234280 wchar_t * cwd = work->cwd ;
235281 const DWORD cn = GetFinalPathNameByHandleW (run, cwd, okw::kMaxName - 1 ,
236282 FILE_NAME_NORMALIZED | VOLUME_NAME_DOS );
237283 if (cn == 0 || cn >= okw::kMaxName - 1 ) return okw::translate_win32 (GetLastError ());
238284 cwd[cn] = 0 ;
285+ (void )plain_name (cwd, cn);
239286
240287 // The vector, unaltered, including its first element.
241288 wchar_t * line = work->line ;
@@ -247,8 +294,8 @@ int kal_process_spawn(const kal_spawn* how,
247294 // started program split it. Clause 7.6 requires the vector to arrive
248295 // unaltered, and the quoting is the inverse of that splitting.
249296 okw_uptr produced = 0 ;
250- wchar_t * one = line + used + 1 ; // beyond what is written
251- const okw_uptr room = kCommandLine - used - 2 ;
297+ wchar_t * one = work-> one ;
298+ const okw_uptr room = kCommandLine - 1 ;
252299 if (!append_utf8 (one, room, produced, argv[i], argv_lens[i]))
253300 return argv_lens[i] >= room ? kal_err_no_space : kal_err_invalid;
254301 if (!append_quoted (line, kCommandLine , used, one, produced))
0 commit comments