Skip to content

Commit 1e7c1a3

Browse files
committed
Bug#35983164 NdbProcess treats space, backslash, double quote different on Windows and posix [#5]
A test program NdbProcess-t for testing argument quoting. Change-Id: I37842d1109f76f684636eb7c763d2d5a46114aae
1 parent 1d536b3 commit 1e7c1a3

File tree

2 files changed

+296
-0
lines changed

2 files changed

+296
-0
lines changed

storage/ndb/src/common/portlib/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,3 +83,4 @@ NDB_ADD_TEST(NdbHW-t NdbHW.cpp LIBS ndbmgmapi ndbgeneral ndbportlib)
8383
NDB_ADD_TEST(NdbDir-t NdbDir.cpp LIBS ndbmgmapi ndbgeneral ndbportlib)
8484

8585
NDB_ADD_TEST(NdbGetInAddr-t NdbTCP.cpp LIBS ndbgeneral ${LIBSOCKET} ${LIBNSL})
86+
NDB_ADD_TEST(NdbProcess-t NdbProcess.cpp LIBS ndbmgmapi ndbgeneral ndbportlib)
Lines changed: 295 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,295 @@
1+
/*
2+
Copyright (c) 2023, Oracle and/or its affiliates.
3+
4+
5+
This program is free software; you can redistribute it and/or modify
6+
it under the terms of the GNU General Public License, version 2.0,
7+
as published by the Free Software Foundation.
8+
9+
This program is also distributed with certain software (including
10+
but not limited to OpenSSL) that is licensed under separate terms,
11+
as designated in a particular file or component or in included license
12+
documentation. The authors of MySQL hereby grant you an additional
13+
permission to link the program and your derivative works with the
14+
separately licensed software that they have included with MySQL.
15+
16+
This program is distributed in the hope that it will be useful,
17+
but WITHOUT ANY WARRANTY; without even the implied warranty of
18+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19+
GNU General Public License, version 2.0, for more details.
20+
21+
You should have received a copy of the GNU General Public License
22+
along with this program; if not, write to the Free Software
23+
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
24+
*/
25+
26+
#ifdef TEST_NDBPROCESS
27+
28+
#include "portlib/NdbProcess.hpp"
29+
#include <stdlib.h>
30+
#include <string.h>
31+
#include <climits>
32+
#include <cstdio>
33+
#include <cstdlib>
34+
#include <filesystem>
35+
#include <string>
36+
#include <vector>
37+
#include "unittest/mytap/tap.h"
38+
39+
using std::fprintf;
40+
using std::strcmp;
41+
using std::strlen;
42+
using std::filesystem::canonical;
43+
using std::filesystem::path;
44+
45+
#ifdef _WIN32
46+
#define strcasecmp _stricmp
47+
#endif
48+
49+
bool check_call_output(const NdbProcess::Args &args, FILE *rfile) {
50+
size_t max_length = 0;
51+
for (unsigned i = 0; i < args.args().size(); i++)
52+
if (max_length < args.args()[i].length())
53+
max_length = args.args()[i].length();
54+
// Allow longer arguments to detect some failure
55+
max_length = max_length + 1 + 2;
56+
57+
auto arg_buf = std::make_unique<char[]>(max_length);
58+
59+
int argi = 0;
60+
int argc = args.args().size();
61+
for (; argi < argc; argi++) {
62+
int len = -1;
63+
char buf[19 + 1 + 1];
64+
if (fgets(buf, sizeof(buf), rfile) == nullptr) {
65+
fprintf(stderr, "ERROR: %s: %u: expected length got EOF\n", __func__,
66+
__LINE__);
67+
return false;
68+
}
69+
long llen = -1;
70+
char *endp = nullptr;
71+
llen = strtol(buf, &endp, 10);
72+
if (endp == buf) {
73+
fprintf(stderr, "ERROR: %s: %u: expected length got nothing\n", __func__,
74+
__LINE__);
75+
return false;
76+
}
77+
if (llen < INT_MIN || llen > INT_MAX) {
78+
fprintf(stderr, "ERROR: %s: %u: invalid length %ld\n", __func__, __LINE__,
79+
llen);
80+
return false;
81+
}
82+
if (*endp != '\n') {
83+
if (*endp != '\r' || endp[1] != '\n') {
84+
fprintf(stderr,
85+
"ERROR: %s: %u: expected newline after length got %d %d\n",
86+
__func__, __LINE__, endp[0], endp[1]);
87+
return false;
88+
}
89+
endp++;
90+
}
91+
if (endp + 1 != buf + strlen(buf)) {
92+
fprintf(stderr,
93+
"ERROR: %s: %u: expected newline after length got more %d\n",
94+
__func__, __LINE__, endp[1]);
95+
return false;
96+
}
97+
len = int(llen);
98+
if (len < 0) {
99+
fprintf(stderr, "ERROR: %s: %u: Bad argument length %d.\n", __func__,
100+
__LINE__, len);
101+
return false;
102+
}
103+
if (size_t(len + 1) > max_length) {
104+
fprintf(stderr, "ERROR: %s: %u: Bad argument length %d.\n", __func__,
105+
__LINE__, len);
106+
return false;
107+
}
108+
size_t n = fread(arg_buf.get(), 1, len, rfile);
109+
if (n != size_t(len)) {
110+
fprintf(stderr, "ERROR: %s: %u: Got partial argument (%zu of %d) %*s.\n",
111+
__func__, __LINE__, n, len, int(n), arg_buf.get());
112+
return false;
113+
}
114+
arg_buf[n] = 0;
115+
int ch = fgetc(rfile);
116+
if (ch != '\n') {
117+
if (ch == '\r') {
118+
// Also allow CR+NL as newline.
119+
ch = fgetc(rfile);
120+
}
121+
if (ch != '\n') {
122+
fprintf(stderr, "ERROR: %s: %u: Expected <newline> got char(%d).\n",
123+
__func__, __LINE__, ch);
124+
return false;
125+
}
126+
}
127+
if (strcmp(arg_buf.get(), args.args()[argi].c_str()) != 0) {
128+
fprintf(stderr, "ERROR: %s: %u: GOT: %s: EXPECTED: %s.\n", __func__,
129+
__LINE__, arg_buf.get(), args.args()[argi].c_str());
130+
return false;
131+
}
132+
}
133+
134+
if (argi != argc) {
135+
fprintf(stderr, "ERROR: %s: %u: to few arg: got %d expected %d\n", __func__,
136+
__LINE__, argi, argc);
137+
return false;
138+
}
139+
if (fgets(arg_buf.get(), max_length, rfile) != nullptr) {
140+
fprintf(stderr, "ERROR: %s: %u: to many arg\n", __func__, __LINE__);
141+
return false;
142+
}
143+
return true;
144+
}
145+
146+
bool test_call_arg_passing(const char *host, const path &prog,
147+
NdbProcess::Args cmdargs,
148+
const NdbProcess::Args &args) {
149+
cmdargs.add(args);
150+
151+
NdbProcess::Pipes pipes;
152+
if (!pipes.connected()) {
153+
perror("pipes");
154+
fprintf(stderr, "ERROR: %s: %u: !pipes.connected()\n", __func__, __LINE__);
155+
return false;
156+
}
157+
158+
std::unique_ptr<NdbProcess> proc;
159+
if (!host)
160+
proc = NdbProcess::create(prog.string().c_str(), prog.string().c_str(),
161+
nullptr, cmdargs, &pipes);
162+
else
163+
proc = NdbProcess::create_via_ssh(prog.string().c_str(), host,
164+
prog.string().c_str(), nullptr, cmdargs,
165+
&pipes);
166+
if (!proc) {
167+
fprintf(stderr, "ERROR: %s: %u: !proc.create()\n", __func__, __LINE__);
168+
return false;
169+
}
170+
171+
bool ok;
172+
FILE *rfile = pipes.open(pipes.parentRead(), "r");
173+
if (!rfile) {
174+
fprintf(stderr, "ERROR: %s: %u: !parent open read pipe()\n", __func__,
175+
__LINE__);
176+
ok = false;
177+
} else {
178+
ok = check_call_output(args, rfile);
179+
}
180+
fclose(rfile);
181+
182+
int ret = 1;
183+
if (!proc->wait(ret, 30'000)) {
184+
fprintf(stderr, "Program stopped wait failed ret=%d\n", ret);
185+
proc->stop();
186+
proc->wait(ret, 30'000);
187+
}
188+
ok &= (ret == 0);
189+
190+
return ok;
191+
}
192+
193+
int print_arguments(int argc, const char *argv[]) {
194+
for (int argi = 0; argi < argc; argi++) {
195+
const char *arg = argv[argi];
196+
int len = strlen(arg);
197+
printf("%d\n%s\n", len, arg);
198+
}
199+
return 0;
200+
}
201+
202+
int main(int argc, const char *argv[]) {
203+
const path prog = argv[0];
204+
const path full_prog = canonical(prog);
205+
int argi = 1;
206+
path ssh_prog;
207+
208+
std::string cmd;
209+
NdbProcess::Args cmdargs;
210+
bool dry_run = false;
211+
if (argc <= 1) {
212+
} else if (strcmp(argv[1], "--print-arguments") == 0) {
213+
return print_arguments(argc - 2, argv + 2);
214+
}
215+
const char *host = nullptr;
216+
for (; argi < argc; argi++) {
217+
const char *arg = argv[argi];
218+
if (strcmp(arg, "--") == 0) {
219+
argi++;
220+
break;
221+
}
222+
if (strcmp(arg, "--dry-run") == 0) {
223+
dry_run = true;
224+
} else if (strncmp(arg, "--exec=", 7) == 0) {
225+
cmd = &arg[7];
226+
} else if (strncmp(arg, "--ssh=", 6) == 0) {
227+
host = &arg[6];
228+
} else if (strncmp(arg, "--ssh", 5) == 0) {
229+
host = "localhost";
230+
} else if (strncmp(arg, "--", 2) == 0) {
231+
fprintf(stderr, "ERROR: Unknown option '%s'.\n", arg);
232+
return 2;
233+
} else
234+
break;
235+
}
236+
if (cmd.empty()) {
237+
if (host == nullptr || strcmp(host, "localhost") == 0)
238+
cmd = full_prog.string();
239+
else
240+
cmd = prog.filename().string();
241+
}
242+
cmdargs.add("--print-arguments");
243+
244+
if (argi < argc) {
245+
for (; argi < argc; argi++) {
246+
NdbProcess::Args testargs;
247+
testargs.add(argv[argi]);
248+
if (!dry_run) {
249+
bool pass = test_call_arg_passing(host, cmd, cmdargs, testargs);
250+
ok(pass, "arg = \"%s\"\n", argv[argi]);
251+
} else {
252+
cmdargs.add(testargs);
253+
fprintf(stderr, "info: %s: %u: CMD: %s\n", __func__, __LINE__,
254+
cmd.c_str());
255+
for (size_t i = 0; i < cmdargs.args().size(); i++)
256+
fprintf(stderr, "info: %s: %u: ARG#%zu: %s\n", __func__, __LINE__, i,
257+
cmdargs.args()[i].c_str());
258+
}
259+
}
260+
} else {
261+
for (int ch = 1; ch < 256; ch++) {
262+
if (ch > '0' && ch < '9') continue;
263+
if (ch > 'a' && ch < 'z') continue;
264+
if (ch > 'A' && ch < 'Z') continue;
265+
bool expect = true;
266+
#ifdef _WIN32
267+
if (host)
268+
expect = !(strchr("\r\n\032/", ch)); // For ssh win-cmd-c
269+
else
270+
expect = (ch != '\032');
271+
#else
272+
if (host) expect = (ch != '\\'); // For ssh. Wrongly guessed as Windows
273+
#endif
274+
char str[2] = {char(ch), 0};
275+
{
276+
NdbProcess::Args args;
277+
args.add(str);
278+
bool pass = test_call_arg_passing(host, cmd, cmdargs, args);
279+
ok(pass == expect, "arg = %c (ASCII %d) (%s)\n", (ch < 32 ? ' ' : ch),
280+
ch, (expect ? "supported" : "not supported"));
281+
}
282+
char str2[3] = {char(ch), 'q', 0};
283+
{
284+
NdbProcess::Args args;
285+
args.add(str2);
286+
bool pass = test_call_arg_passing(host, cmd, cmdargs, args);
287+
ok(pass == expect, "arg = %c%c (ASCII %d) (%s)\n", (ch < 32 ? ' ' : ch),
288+
str2[1], ch, (expect ? "supported" : "not supported"));
289+
}
290+
}
291+
}
292+
return exit_status();
293+
}
294+
295+
#endif

0 commit comments

Comments
 (0)