Skip to content

Commit a458da1

Browse files
authored
Fix process substitution segfaults on GNU/Linux (#253)
Fixes: #252 Signed-off-by: Juan Cruz Viotti <[email protected]>
1 parent 1aaa30c commit a458da1

12 files changed

+91
-45
lines changed

DEPENDENCIES

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
vendorpull https://github.com/sourcemeta/vendorpull dea311b5bfb53b6926a4140267959ae334d3ecf4
2-
core https://github.com/sourcemeta/core 4cb5ab6a3641b9942c774b4d167eeb0ffb7257ea
2+
core https://github.com/sourcemeta/core 64063dc926a074dd9b7add8478d891dd39a8fc9f
33
hydra https://github.com/sourcemeta/hydra a67b879df800e834ed8a2d056f98398f7211d870
44
jsonbinpack https://github.com/sourcemeta/jsonbinpack c7bb7f4d903c3b6925b62ce2bb5b8d360e01ce84
55
blaze https://github.com/sourcemeta/blaze bf456d47dfd7fc51f077da268412735b4c6f9fa7

src/command_decode.cc

+4-5
Original file line numberDiff line numberDiff line change
@@ -47,22 +47,21 @@ auto sourcemeta::jsonschema::cli::decode(
4747
resolver(options, options.contains("h") || options.contains("http")));
4848
const auto encoding{sourcemeta::jsonbinpack::load(schema)};
4949

50-
std::ifstream input_stream{std::filesystem::canonical(options.at("").front()),
50+
std::ifstream input_stream{sourcemeta::jsonschema::cli::safe_weakly_canonical(
51+
options.at("").front()),
5152
std::ios::binary};
5253
assert(!input_stream.fail());
5354
assert(input_stream.is_open());
5455

5556
const std::filesystem::path output{options.at("").at(1)};
56-
std::ofstream output_stream(std::filesystem::weakly_canonical(output),
57-
std::ios::binary);
57+
std::ofstream output_stream(safe_weakly_canonical(output), std::ios::binary);
5858
output_stream.exceptions(std::ios_base::badbit);
5959
sourcemeta::jsonbinpack::Decoder decoder{input_stream};
6060

6161
if (output.extension() == ".jsonl") {
6262
log_verbose(options)
6363
<< "Interpreting input as JSONL: "
64-
<< std::filesystem::weakly_canonical(options.at("").front()).string()
65-
<< "\n";
64+
<< safe_weakly_canonical(options.at("").front()).string() << "\n";
6665

6766
std::size_t count{0};
6867
while (has_data(input_stream)) {

src/command_encode.cc

+5-8
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,11 @@ auto sourcemeta::jsonschema::cli::encode(
4040

4141
if (document.extension() == ".jsonl") {
4242
log_verbose(options) << "Interpreting input as JSONL: "
43-
<< std::filesystem::weakly_canonical(document).string()
44-
<< "\n";
43+
<< safe_weakly_canonical(document).string() << "\n";
4544

4645
auto stream{sourcemeta::core::read_file(document)};
47-
std::ofstream output_stream(
48-
std::filesystem::weakly_canonical(options.at("").at(1)),
49-
std::ios::binary);
46+
std::ofstream output_stream(safe_weakly_canonical(options.at("").at(1)),
47+
std::ios::binary);
5048
output_stream.exceptions(std::ios_base::badbit);
5149
sourcemeta::jsonbinpack::Encoder encoder{output_stream};
5250
std::size_t count{0};
@@ -66,9 +64,8 @@ auto sourcemeta::jsonschema::cli::encode(
6664
} else {
6765
const auto entry{
6866
sourcemeta::jsonschema::cli::read_file(options.at("").front())};
69-
std::ofstream output_stream(
70-
std::filesystem::weakly_canonical(options.at("").at(1)),
71-
std::ios::binary);
67+
std::ofstream output_stream(safe_weakly_canonical(options.at("").at(1)),
68+
std::ios::binary);
7269
output_stream.exceptions(std::ios_base::badbit);
7370
sourcemeta::jsonbinpack::Encoder encoder{output_stream};
7471
encoder.write(entry, encoding);

src/command_metaschema.cc

+6-4
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,10 @@ auto sourcemeta::jsonschema::cli::metaschema(
3030
if (!sourcemeta::core::is_schema(entry.second)) {
3131
std::cerr << "error: The schema file you provided does not represent a "
3232
"valid JSON Schema\n "
33-
<< std::filesystem::canonical(entry.first).string() << "\n";
33+
<< sourcemeta::jsonschema::cli::safe_weakly_canonical(
34+
entry.first)
35+
.string()
36+
<< "\n";
3437
return EXIT_FAILURE;
3538
}
3639

@@ -57,11 +60,10 @@ auto sourcemeta::jsonschema::cli::metaschema(
5760
if (evaluator.validate(cache.at(dialect.value()), entry.second,
5861
std::ref(output))) {
5962
log_verbose(options)
60-
<< "ok: " << std::filesystem::weakly_canonical(entry.first).string()
63+
<< "ok: " << safe_weakly_canonical(entry.first).string()
6164
<< "\n matches " << dialect.value() << "\n";
6265
} else {
63-
std::cerr << "fail: "
64-
<< std::filesystem::weakly_canonical(entry.first).string()
66+
std::cerr << "fail: " << safe_weakly_canonical(entry.first).string()
6567
<< "\n";
6668
print(output, std::cerr);
6769
result = false;

src/command_test.cc

+3-2
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,9 @@ static auto get_data(const sourcemeta::core::JSON &test_case,
4848
assert(test_case.defines("dataPath"));
4949
assert(test_case.at("dataPath").is_string());
5050

51-
const std::filesystem::path data_path{std::filesystem::weakly_canonical(
52-
base / test_case.at("dataPath").to_string())};
51+
const std::filesystem::path data_path{
52+
sourcemeta::jsonschema::cli::safe_weakly_canonical(
53+
base / test_case.at("dataPath").to_string())};
5354
if (verbose) {
5455
std::cerr << "Reading test instance file: " << data_path.string() << "\n";
5556
}

src/command_validate.cc

+15-18
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,9 @@ auto sourcemeta::jsonschema::cli::validate(
4747
if (!sourcemeta::core::is_schema(schema)) {
4848
std::cerr << "error: The schema file you provided does not represent a "
4949
"valid JSON Schema\n "
50-
<< std::filesystem::canonical(schema_path).string() << "\n";
50+
<< sourcemeta::jsonschema::cli::safe_weakly_canonical(schema_path)
51+
.string()
52+
<< "\n";
5153
return EXIT_FAILURE;
5254
}
5355

@@ -69,9 +71,9 @@ auto sourcemeta::jsonschema::cli::validate(
6971
for (; iterator != options.at("").cend(); ++iterator) {
7072
const std::filesystem::path instance_path{*iterator};
7173
if (instance_path.extension() == ".jsonl") {
72-
log_verbose(options)
73-
<< "Interpreting input as JSONL: "
74-
<< std::filesystem::weakly_canonical(instance_path).string() << "\n";
74+
log_verbose(options) << "Interpreting input as JSONL: "
75+
<< safe_weakly_canonical(instance_path).string()
76+
<< "\n";
7577
std::size_t index{0};
7678
auto stream{sourcemeta::core::read_file(instance_path)};
7779
try {
@@ -109,17 +111,14 @@ auto sourcemeta::jsonschema::cli::validate(
109111
result = subresult;
110112
} else if (subresult) {
111113
log_verbose(options)
112-
<< "ok: "
113-
<< std::filesystem::weakly_canonical(instance_path).string()
114+
<< "ok: " << safe_weakly_canonical(instance_path).string()
114115
<< " (entry #" << index << ")"
115-
<< "\n matches "
116-
<< std::filesystem::weakly_canonical(schema_path).string()
116+
<< "\n matches " << safe_weakly_canonical(schema_path).string()
117117
<< "\n";
118118
} else {
119-
std::cerr
120-
<< "fail: "
121-
<< std::filesystem::weakly_canonical(instance_path).string()
122-
<< " (entry #" << index << ")\n\n";
119+
std::cerr << "fail: "
120+
<< safe_weakly_canonical(instance_path).string()
121+
<< " (entry #" << index << ")\n\n";
123122
sourcemeta::core::prettify(instance, std::cerr);
124123
std::cerr << "\n\n";
125124
std::cerr << error.str();
@@ -171,13 +170,11 @@ auto sourcemeta::jsonschema::cli::validate(
171170
result = subresult;
172171
} else if (subresult) {
173172
log_verbose(options)
174-
<< "ok: "
175-
<< std::filesystem::weakly_canonical(instance_path).string()
176-
<< "\n matches "
177-
<< std::filesystem::weakly_canonical(schema_path).string() << "\n";
173+
<< "ok: " << safe_weakly_canonical(instance_path).string()
174+
<< "\n matches " << safe_weakly_canonical(schema_path).string()
175+
<< "\n";
178176
} else {
179-
std::cerr << "fail: "
180-
<< std::filesystem::weakly_canonical(instance_path).string()
177+
std::cerr << "fail: " << safe_weakly_canonical(instance_path).string()
181178
<< "\n";
182179
std::cerr << error.str();
183180
print(output, std::cerr);

src/main.cc

+10-3
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
#include "command.h"
1313
#include "configure.h"
14+
#include "utils.h"
1415

1516
constexpr std::string_view USAGE_DETAILS{R"EOF(
1617
Global Options:
@@ -143,7 +144,9 @@ auto main(int argc, char *argv[]) noexcept -> int {
143144
} catch (const sourcemeta::core::JSONFileParseError &error) {
144145
std::cerr << "error: " << error.what() << " at line " << error.line()
145146
<< " and column " << error.column() << "\n "
146-
<< std::filesystem::weakly_canonical(error.path()).string()
147+
<< sourcemeta::jsonschema::cli::safe_weakly_canonical(
148+
error.path())
149+
.string()
147150
<< "\n";
148151
return EXIT_FAILURE;
149152
} catch (const sourcemeta::core::JSONParseError &error) {
@@ -154,12 +157,16 @@ auto main(int argc, char *argv[]) noexcept -> int {
154157
// See https://en.cppreference.com/w/cpp/error/errc
155158
if (error.code() == std::errc::no_such_file_or_directory) {
156159
std::cerr << "error: " << error.code().message() << "\n "
157-
<< std::filesystem::weakly_canonical(error.path1()).string()
160+
<< sourcemeta::jsonschema::cli::safe_weakly_canonical(
161+
error.path1())
162+
.string()
158163
<< "\n";
159164
} else if (error.code() == std::errc::is_a_directory) {
160165
std::cerr << "error: The input was supposed to be a file but it is a "
161166
"directory\n "
162-
<< std::filesystem::weakly_canonical(error.path1()).string()
167+
<< sourcemeta::jsonschema::cli::safe_weakly_canonical(
168+
error.path1())
169+
.string()
163170
<< "\n";
164171
} else {
165172
std::cerr << "error: " << error.what() << "\n";

src/utils.cc

+12-3
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ auto handle_json_entry(
4444
if (std::filesystem::is_directory(entry_path)) {
4545
for (auto const &entry :
4646
std::filesystem::recursive_directory_iterator{entry_path}) {
47-
const auto canonical{std::filesystem::canonical(entry.path())};
47+
const auto canonical{
48+
sourcemeta::jsonschema::cli::safe_weakly_canonical(entry.path())};
4849
if (!std::filesystem::is_directory(entry) &&
4950
std::any_of(extensions.cbegin(), extensions.cend(),
5051
[&canonical](const auto &extension) {
@@ -65,10 +66,11 @@ auto handle_json_entry(
6566
}
6667
}
6768
} else {
68-
const auto canonical{std::filesystem::canonical(entry_path)};
69+
const auto canonical{
70+
sourcemeta::jsonschema::cli::safe_weakly_canonical(entry_path)};
6971
if (!std::filesystem::exists(canonical)) {
7072
std::ostringstream error;
71-
error << "No such file or directory: " << canonical.string();
73+
error << "No such file or directory\n " << canonical.string();
7274
throw std::runtime_error(error.str());
7375
}
7476

@@ -366,4 +368,11 @@ auto parse_ignore(
366368
return result;
367369
}
368370

371+
auto safe_weakly_canonical(const std::filesystem::path &input)
372+
-> std::filesystem::path {
373+
return std::filesystem::is_fifo(input)
374+
? input
375+
: std::filesystem::weakly_canonical(input);
376+
}
377+
369378
} // namespace sourcemeta::jsonschema::cli

src/utils.h

+3
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ auto parse_ignore(
5050
const std::map<std::string, std::vector<std::string>> &options)
5151
-> std::set<std::filesystem::path>;
5252

53+
auto safe_weakly_canonical(const std::filesystem::path &input)
54+
-> std::filesystem::path;
55+
5356
} // namespace sourcemeta::jsonschema::cli
5457

5558
#endif

test/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ add_jsonschema_test_unix(validate/fail_many)
8181
add_jsonschema_test_unix(validate/fail_many_verbose)
8282
add_jsonschema_test_unix(validate/fail_yaml)
8383
add_jsonschema_test_unix(validate/pass_json_ref_yaml)
84+
add_jsonschema_test_unix(validate/pass_process_substitution)
8485

8586
# TODO: Make this test pass on Linux. It hangs for some reason
8687
if(NOT LINUX)
+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#!/bin/bash
2+
3+
set -o errexit
4+
set -o nounset
5+
6+
TMP="$(mktemp -d)"
7+
clean() { rm -rf "$TMP"; }
8+
trap clean EXIT
9+
10+
cat << 'EOF' > "$TMP/schema.json"
11+
{
12+
"$schema": "https://json-schema.org/draft/2020-12/schema",
13+
"type": "object",
14+
"properties": {
15+
"name": {
16+
"type": "string"
17+
}
18+
},
19+
"required": ["name"]
20+
}
21+
EOF
22+
23+
INSTANCE="{\"name\":\"demo\"}"
24+
25+
"$1" validate --verbose "$TMP/schema.json" <(echo "$INSTANCE")

vendor/core/src/core/json/json.cc

+6-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)