Skip to content

Commit 06fc951

Browse files
committed
Create proper serializer for BuildResult status
The casts were not safe with respect to unknown values, but these are.
1 parent 7448aed commit 06fc951

File tree

6 files changed

+147
-78
lines changed

6 files changed

+147
-78
lines changed

src/libstore/common-protocol.cc

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,4 +99,51 @@ void CommonProto::Serialise<std::optional<ContentAddress>>::write(
9999
conn.to << (caOpt ? renderContentAddress(*caOpt) : "");
100100
}
101101

102+
/**
103+
* Mapping from protocol wire values to BuildResultStatus.
104+
*
105+
* The array index is the wire value.
106+
* Note: HashMismatch is not in the protocol; it gets converted
107+
* to OutputRejected before serialization.
108+
*/
109+
constexpr static BuildResultStatus buildResultStatusTable[] = {
110+
BuildResultSuccessStatus::Built, // 0
111+
BuildResultSuccessStatus::Substituted, // 1
112+
BuildResultSuccessStatus::AlreadyValid, // 2
113+
BuildResultFailureStatus::PermanentFailure, // 3
114+
BuildResultFailureStatus::InputRejected, // 4
115+
BuildResultFailureStatus::OutputRejected, // 5
116+
BuildResultFailureStatus::TransientFailure, // 6
117+
BuildResultFailureStatus::CachedFailure, // 7
118+
BuildResultFailureStatus::TimedOut, // 8
119+
BuildResultFailureStatus::MiscFailure, // 9
120+
BuildResultFailureStatus::DependencyFailed, // 10
121+
BuildResultFailureStatus::LogLimitExceeded, // 11
122+
BuildResultFailureStatus::NotDeterministic, // 12
123+
BuildResultSuccessStatus::ResolvesToAlreadyValid, // 13
124+
BuildResultFailureStatus::NoSubstituters, // 14
125+
};
126+
127+
BuildResultStatus
128+
CommonProto::Serialise<BuildResultStatus>::read(const StoreDirConfig & store, CommonProto::ReadConn conn)
129+
{
130+
auto rawStatus = readNum<uint8_t>(conn.from);
131+
132+
if (rawStatus >= std::size(buildResultStatusTable))
133+
throw Error("Invalid BuildResult status %d from remote", rawStatus);
134+
135+
return buildResultStatusTable[rawStatus];
136+
}
137+
138+
void CommonProto::Serialise<BuildResultStatus>::write(
139+
const StoreDirConfig & store, CommonProto::WriteConn conn, const BuildResultStatus & status)
140+
{
141+
for (auto && [wire, val] : enumerate(buildResultStatusTable))
142+
if (val == status) {
143+
conn.to << uint8_t(wire);
144+
return;
145+
}
146+
unreachable();
147+
}
148+
102149
} // namespace nix

src/libstore/include/nix/store/build-result.hh

Lines changed: 45 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -11,24 +11,52 @@
1111

1212
namespace nix {
1313

14+
/**
15+
* Names must be disjoint with `BuildResultFailureStatus`.
16+
*
17+
* @note Prefer using `BuildResult::Success::Status`, this name is just
18+
* for sake of forward declarations.
19+
*/
20+
enum struct BuildResultSuccessStatus : uint8_t {
21+
Built,
22+
Substituted,
23+
AlreadyValid,
24+
ResolvesToAlreadyValid,
25+
};
26+
27+
/**
28+
* Names must be disjoint with `BuildResultSuccessStatus`.
29+
*
30+
* @note Prefer using `BuildResult::Failure::Status`, this name is just
31+
* for sake of forward declarations.
32+
*/
33+
enum struct BuildResultFailureStatus : uint8_t {
34+
PermanentFailure,
35+
InputRejected,
36+
OutputRejected,
37+
/// possibly transient
38+
TransientFailure,
39+
/// no longer used
40+
CachedFailure,
41+
TimedOut,
42+
MiscFailure,
43+
DependencyFailed,
44+
LogLimitExceeded,
45+
NotDeterministic,
46+
NoSubstituters,
47+
/// A certain type of `OutputRejected`. The protocols do not yet
48+
/// know about this one, so change it back to `OutputRejected`
49+
/// before serialization.
50+
HashMismatch,
51+
};
52+
1453
struct BuildResult
1554
{
1655
struct Success
1756
{
18-
/**
19-
* @note This is directly used in the nix-store --serve protocol.
20-
* That means we need to worry about compatibility across versions.
21-
* Therefore, don't remove status codes, and only add new status
22-
* codes at the end of the list.
23-
*
24-
* Must be disjoint with `Failure::Status`.
25-
*/
26-
enum Status : uint8_t {
27-
Built = 0,
28-
Substituted = 1,
29-
AlreadyValid = 2,
30-
ResolvesToAlreadyValid = 13,
31-
} status;
57+
using Status = enum BuildResultSuccessStatus;
58+
using enum Status;
59+
Status status;
3260

3361
/**
3462
* For derivations, a mapping from the names of the wanted outputs
@@ -38,43 +66,13 @@ struct BuildResult
3866

3967
bool operator==(const BuildResult::Success &) const noexcept;
4068
std::strong_ordering operator<=>(const BuildResult::Success &) const noexcept;
41-
42-
static bool statusIs(uint8_t status)
43-
{
44-
return status == Built || status == Substituted || status == AlreadyValid
45-
|| status == ResolvesToAlreadyValid;
46-
}
4769
};
4870

4971
struct Failure
5072
{
51-
/**
52-
* @note This is directly used in the nix-store --serve protocol.
53-
* That means we need to worry about compatibility across versions.
54-
* Therefore, don't remove status codes, and only add new status
55-
* codes at the end of the list.
56-
*
57-
* Must be disjoint with `Success::Status`.
58-
*/
59-
enum Status : uint8_t {
60-
PermanentFailure = 3,
61-
InputRejected = 4,
62-
OutputRejected = 5,
63-
/// possibly transient
64-
TransientFailure = 6,
65-
/// no longer used
66-
CachedFailure = 7,
67-
TimedOut = 8,
68-
MiscFailure = 9,
69-
DependencyFailed = 10,
70-
LogLimitExceeded = 11,
71-
NotDeterministic = 12,
72-
NoSubstituters = 14,
73-
/// A certain type of `OutputRejected`. The protocols do not yet
74-
/// know about this one, so change it back to `OutputRejected`
75-
/// before serialization.
76-
HashMismatch = 15,
77-
} status = MiscFailure;
73+
using Status = enum BuildResultFailureStatus;
74+
using enum Status;
75+
Status status = MiscFailure;
7876

7977
/**
8078
* Information about the error if the build failed.

src/libstore/include/nix/store/common-protocol.hh

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
#include "nix/util/serialise.hh"
55

6+
#include <variant>
7+
68
namespace nix {
79

810
struct StoreDirConfig;
@@ -13,6 +15,8 @@ class StorePath;
1315
struct ContentAddress;
1416
struct DrvOutput;
1517
struct Realisation;
18+
enum struct BuildResultSuccessStatus : uint8_t;
19+
enum struct BuildResultFailureStatus : uint8_t;
1620

1721
/**
1822
* Shared serializers between the worker protocol, serve protocol, and a
@@ -83,7 +87,6 @@ DECLARE_COMMON_SERIALISER(std::tuple<Ts...>);
8387

8488
template<typename K, typename V>
8589
DECLARE_COMMON_SERIALISER(std::map<K COMMA_ V>);
86-
#undef COMMA_
8790

8891
/**
8992
* These use the empty string for the null case, relying on the fact
@@ -104,4 +107,14 @@ DECLARE_COMMON_SERIALISER(std::optional<StorePath>);
104107
template<>
105108
DECLARE_COMMON_SERIALISER(std::optional<ContentAddress>);
106109

110+
/**
111+
* The success and failure codes never overlay in enum tag values in the wire formats
112+
*/
113+
using BuildResultStatus = std::variant<BuildResultSuccessStatus, BuildResultFailureStatus>;
114+
115+
template<>
116+
DECLARE_COMMON_SERIALISER(BuildResultStatus);
117+
118+
#undef COMMA_
119+
107120
} // namespace nix

src/libstore/legacy-ssh-store.cc

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include "nix/util/archive.hh"
44
#include "nix/util/pool.hh"
55
#include "nix/store/remote-store.hh"
6+
#include "nix/store/common-protocol.hh"
67
#include "nix/store/serve-protocol.hh"
78
#include "nix/store/serve-protocol-connection.hh"
89
#include "nix/store/serve-protocol-impl.hh"
@@ -241,13 +242,11 @@ void LegacySSHStore::buildPaths(
241242

242243
conn->to.flush();
243244

244-
auto status = readInt(conn->from);
245-
if (!BuildResult::Success::statusIs(status)) {
246-
BuildResult::Failure failure{
247-
.status = (BuildResult::Failure::Status) status,
248-
};
249-
conn->from >> failure.errorMsg;
250-
throw Error(failure.status, std::move(failure.errorMsg));
245+
auto status = CommonProto::Serialise<BuildResultStatus>::read(*this, {conn->from});
246+
if (auto * failure = std::get_if<BuildResultFailureStatus>(&status)) {
247+
std::string errorMsg;
248+
conn->from >> errorMsg;
249+
throw BuildError(*failure, std::move(errorMsg));
251250
}
252251
}
253252

src/libstore/serve-protocol.cc

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#include "nix/store/path-with-outputs.hh"
33
#include "nix/store/store-api.hh"
44
#include "nix/store/build-result.hh"
5+
#include "nix/store/common-protocol.hh"
56
#include "nix/store/serve-protocol.hh"
67
#include "nix/store/serve-protocol-impl.hh"
78
#include "nix/util/archive.hh"
@@ -15,30 +16,35 @@ namespace nix {
1516

1617
BuildResult ServeProto::Serialise<BuildResult>::read(const StoreDirConfig & store, ServeProto::ReadConn conn)
1718
{
18-
BuildResult status;
19+
BuildResult res;
1920
BuildResult::Success success;
2021
BuildResult::Failure failure;
2122

22-
auto rawStatus = readInt(conn.from);
23+
auto status = ServeProto::Serialise<BuildResultStatus>::read(store, {conn.from});
2324
conn.from >> failure.errorMsg;
2425

2526
if (GET_PROTOCOL_MINOR(conn.version) >= 3)
26-
conn.from >> status.timesBuilt >> failure.isNonDeterministic >> status.startTime >> status.stopTime;
27+
conn.from >> res.timesBuilt >> failure.isNonDeterministic >> res.startTime >> res.stopTime;
2728
if (GET_PROTOCOL_MINOR(conn.version) >= 6) {
2829
auto builtOutputs = ServeProto::Serialise<DrvOutputs>::read(store, conn);
2930
for (auto && [output, realisation] : builtOutputs)
3031
success.builtOutputs.insert_or_assign(std::move(output.outputName), std::move(realisation));
3132
}
3233

33-
if (BuildResult::Success::statusIs(rawStatus)) {
34-
success.status = static_cast<BuildResult::Success::Status>(rawStatus);
35-
status.inner = std::move(success);
36-
} else {
37-
failure.status = static_cast<BuildResult::Failure::Status>(rawStatus);
38-
status.inner = std::move(failure);
39-
}
34+
res.inner = std::visit(
35+
overloaded{
36+
[&](BuildResult::Success::Status s) -> decltype(res.inner) {
37+
success.status = s;
38+
return std::move(success);
39+
},
40+
[&](BuildResult::Failure::Status s) -> decltype(res.inner) {
41+
failure.status = s;
42+
return std::move(failure);
43+
},
44+
},
45+
status);
4046

41-
return status;
47+
return res;
4248
}
4349

4450
void ServeProto::Serialise<BuildResult>::write(
@@ -63,11 +69,11 @@ void ServeProto::Serialise<BuildResult>::write(
6369
std::visit(
6470
overloaded{
6571
[&](const BuildResult::Failure & failure) {
66-
conn.to << failure.status;
72+
ServeProto::write(store, {conn.to}, BuildResultStatus{failure.status});
6773
common(failure.errorMsg, failure.isNonDeterministic, decltype(BuildResult::Success::builtOutputs){});
6874
},
6975
[&](const BuildResult::Success & success) {
70-
conn.to << success.status;
76+
ServeProto::write(store, {conn.to}, BuildResultStatus{success.status});
7177
common(/*errorMsg=*/"", /*isNonDeterministic=*/false, success.builtOutputs);
7278
},
7379
},

src/libstore/worker-protocol.cc

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include "nix/store/store-api.hh"
44
#include "nix/store/gc-store.hh"
55
#include "nix/store/build-result.hh"
6+
#include "nix/store/common-protocol.hh"
67
#include "nix/store/worker-protocol.hh"
78
#include "nix/store/worker-protocol-impl.hh"
89
#include "nix/util/archive.hh"
@@ -209,7 +210,7 @@ BuildResult WorkerProto::Serialise<BuildResult>::read(const StoreDirConfig & sto
209210
BuildResult::Success success;
210211
BuildResult::Failure failure;
211212

212-
auto rawStatus = readInt(conn.from);
213+
auto status = WorkerProto::Serialise<BuildResultStatus>::read(store, {conn.from});
213214
conn.from >> failure.errorMsg;
214215

215216
if (GET_PROTOCOL_MINOR(conn.version) >= 29) {
@@ -225,13 +226,18 @@ BuildResult WorkerProto::Serialise<BuildResult>::read(const StoreDirConfig & sto
225226
success.builtOutputs.insert_or_assign(std::move(output.outputName), std::move(realisation));
226227
}
227228

228-
if (BuildResult::Success::statusIs(rawStatus)) {
229-
success.status = static_cast<BuildResult::Success::Status>(rawStatus);
230-
res.inner = std::move(success);
231-
} else {
232-
failure.status = static_cast<BuildResult::Failure::Status>(rawStatus);
233-
res.inner = std::move(failure);
234-
}
229+
res.inner = std::visit(
230+
overloaded{
231+
[&](BuildResult::Success::Status s) -> decltype(res.inner) {
232+
success.status = s;
233+
return std::move(success);
234+
},
235+
[&](BuildResult::Failure::Status s) -> decltype(res.inner) {
236+
failure.status = s;
237+
return std::move(failure);
238+
},
239+
},
240+
status);
235241

236242
return res;
237243
}
@@ -263,11 +269,11 @@ void WorkerProto::Serialise<BuildResult>::write(
263269
std::visit(
264270
overloaded{
265271
[&](const BuildResult::Failure & failure) {
266-
conn.to << failure.status;
272+
WorkerProto::write(store, {conn.to}, BuildResultStatus{failure.status});
267273
common(failure.errorMsg, failure.isNonDeterministic, decltype(BuildResult::Success::builtOutputs){});
268274
},
269275
[&](const BuildResult::Success & success) {
270-
conn.to << success.status;
276+
WorkerProto::write(store, {conn.to}, BuildResultStatus{success.status});
271277
common(/*errorMsg=*/"", /*isNonDeterministic=*/false, success.builtOutputs);
272278
},
273279
},

0 commit comments

Comments
 (0)