Skip to content

Commit fc89f29

Browse files
authored
0.7.3 --- a started program's arguments and directory arrive as they were given (#22)
The command line dropped every backslash: a run of them was counted and never written, and an element was converted in place just beyond the line, so quoting overwrote what it had not read. Every element was also quoted, which the command interpreter reads as a command rather than a switch. Elements are now written as they are unless the splitting would alter them, and quoted by the rule it inverts otherwise. A started program read its arguments through the name-narrowing that turns backslashes into slashes; arguments are now narrowed as given (clause 7.6). And the program and its directory were handed over in the \\?\ form, which the command interpreter refuses as a current directory; the prefix is removed wherever the name means the same without it.
1 parent 752b471 commit fc89f29

5 files changed

Lines changed: 76 additions & 22 deletions

File tree

mcpp.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
22
namespace = "mcpplibs"
33
name = "openkal-windows"
4-
version = "0.7.2"
4+
version = "0.7.3"
55
description = "An implementation of openkal for Windows, written on the Win32 interfaces and the object manager beneath them, using no C runtime symbol."
66
license = "Apache-2.0"
77

src/env.cpp

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,10 @@ int g_varc = 0;
3333

3434
bool g_ready = false;
3535

36-
const char* store(const wchar_t* w, int wlen, okw_uptr& out_len) {
36+
const char* store(const wchar_t* w, int wlen, okw_uptr& out_len, bool names) {
3737
if (g_used + 4 >= kText) { out_len = 0; return ""; }
3838
char* at = g_text + g_used;
39-
const okw_uptr n = okw::narrow(w, static_cast<okw_uptr>(wlen), at, kText - g_used);
39+
const okw_uptr n = okw::narrow(w, static_cast<okw_uptr>(wlen), at, kText - g_used, names);
4040
g_used += n + 1;
4141
out_len = n;
4242
return at;
@@ -53,7 +53,10 @@ void prepare() {
5353
if (parts) {
5454
for (int i = 0; i < count && g_argc < kMaxArgs - 1; ++i) {
5555
okw_uptr len = 0;
56-
g_argv[g_argc] = store(parts[i], wide_length(parts[i]), len);
56+
// ⚠️ AS GIVEN, AND UNTIL 0.7.3 EVERY BACKSLASH CAME OUT AS A SLASH.
57+
// An argument is not a name: `C:\dir' and a pattern's `\d' are what
58+
// the caller wrote, and clause 7.6 requires the vector unaltered.
59+
g_argv[g_argc] = store(parts[i], wide_length(parts[i]), len, false);
5760
g_argv_len[g_argc] = len;
5861
++g_argc;
5962
}
@@ -74,7 +77,7 @@ void prepare() {
7477
// bookkeeping and is not a variable a program set.
7578
if (p[0] != L'=') {
7679
okw_uptr total = 0;
77-
const char* entry = store(p, len, total);
80+
const char* entry = store(p, len, total, true);
7881
okw_uptr split = 0;
7982
while (split < total && entry[split] != '=') ++split;
8083
g_entry[g_varc] = entry;

src/process.cpp

Lines changed: 61 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,35 @@ namespace {
2222

2323
constexpr 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.
103144
bool 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))

src/win.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,14 +112,15 @@ wide_name::wide_name(const char* utf8, okw_uptr len) : ok(false) {
112112
ok = true;
113113
}
114114

115-
okw_uptr narrow(const wchar_t* wide, okw_uptr wide_len, char* out, okw_uptr cap) {
115+
okw_uptr narrow(const wchar_t* wide, okw_uptr wide_len, char* out, okw_uptr cap, bool names) {
116116
if (wide_len == 0 || cap == 0) { if (cap) out[0] = 0; return 0; }
117117
const int produced = WideCharToMultiByte(CP_UTF8, 0, wide, static_cast<int>(wide_len),
118118
out, static_cast<int>(cap - 1), nullptr, nullptr);
119119
if (produced <= 0) { out[0] = 0; return 0; }
120120
// Reported the way openkal spells it, which is the reverse of the
121121
// substitution above and is the only place the difference appears.
122-
for (int i = 0; i < produced; ++i) if (out[i] == '\\') out[i] = '/';
122+
if (names)
123+
for (int i = 0; i < produced; ++i) if (out[i] == '\\') out[i] = '/';
123124
out[produced] = 0;
124125
return static_cast<okw_uptr>(produced);
125126
}

src/win.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,10 @@ struct wide_name {
277277
};
278278

279279
// The reverse, for reporting a name this environment produced.
280-
okw_uptr narrow(const wchar_t* wide, okw_uptr wide_len, char* out, okw_uptr cap);
280+
// `names' substitutes openkal's separator for this environment's, which is right
281+
// for a name and wrong for an argument: an argument is not a name, and clause
282+
// 7.6 requires it to arrive as it was given.
283+
okw_uptr narrow(const wchar_t* wide, okw_uptr wide_len, char* out, okw_uptr cap, bool names = true);
281284

282285
// A name is a single component or a sequence separated by a forward slash. It
283286
// shall not begin with a separator and shall not contain a component that

0 commit comments

Comments
 (0)