decompress(): Use a Source and Sink
This allows decompression to happen in O(1) memory.
This commit is contained in:
parent
64441f0551
commit
3e6b194d78
6 changed files with 210 additions and 91 deletions
|
@ -203,22 +203,18 @@ void BinaryCacheStore::narFromPath(const Path & storePath, Sink & sink)
|
||||||
stats.narRead++;
|
stats.narRead++;
|
||||||
stats.narReadCompressedBytes += nar->size();
|
stats.narReadCompressedBytes += nar->size();
|
||||||
|
|
||||||
/* Decompress the NAR. FIXME: would be nice to have the remote
|
uint64_t narSize = 0;
|
||||||
side do this. */
|
|
||||||
try {
|
|
||||||
nar = decompress(info->compression, *nar);
|
|
||||||
} catch (UnknownCompressionMethod &) {
|
|
||||||
throw Error(format("binary cache path '%s' uses unknown compression method '%s'")
|
|
||||||
% storePath % info->compression);
|
|
||||||
}
|
|
||||||
|
|
||||||
stats.narReadBytes += nar->size();
|
StringSource source(*nar);
|
||||||
|
|
||||||
printMsg(lvlTalkative, format("exporting path '%1%' (%2% bytes)") % storePath % nar->size());
|
LambdaSink wrapperSink([&](const unsigned char * data, size_t len) {
|
||||||
|
sink(data, len);
|
||||||
|
narSize += len;
|
||||||
|
});
|
||||||
|
|
||||||
assert(nar->size() % 8 == 0);
|
decompress(info->compression, source, wrapperSink);
|
||||||
|
|
||||||
sink((unsigned char *) nar->c_str(), nar->size());
|
stats.narReadBytes += narSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
void BinaryCacheStore::queryPathInfoUncached(const Path & storePath,
|
void BinaryCacheStore::queryPathInfoUncached(const Path & storePath,
|
||||||
|
|
|
@ -17,7 +17,23 @@
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
static ref<std::string> decompressXZ(const std::string & in)
|
static const size_t bufSize = 32 * 1024;
|
||||||
|
|
||||||
|
static void decompressNone(Source & source, Sink & sink)
|
||||||
|
{
|
||||||
|
std::vector<unsigned char> buf(bufSize);
|
||||||
|
while (true) {
|
||||||
|
size_t n;
|
||||||
|
try {
|
||||||
|
n = source.read(buf.data(), buf.size());
|
||||||
|
} catch (EndOfFile &) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
sink(buf.data(), n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void decompressXZ(Source & source, Sink & sink)
|
||||||
{
|
{
|
||||||
lzma_stream strm(LZMA_STREAM_INIT);
|
lzma_stream strm(LZMA_STREAM_INIT);
|
||||||
|
|
||||||
|
@ -29,36 +45,44 @@ static ref<std::string> decompressXZ(const std::string & in)
|
||||||
Finally free([&]() { lzma_end(&strm); });
|
Finally free([&]() { lzma_end(&strm); });
|
||||||
|
|
||||||
lzma_action action = LZMA_RUN;
|
lzma_action action = LZMA_RUN;
|
||||||
uint8_t outbuf[BUFSIZ];
|
std::vector<uint8_t> inbuf(bufSize), outbuf(bufSize);
|
||||||
ref<std::string> res = make_ref<std::string>();
|
strm.next_in = nullptr;
|
||||||
strm.next_in = (uint8_t *) in.c_str();
|
strm.avail_in = 0;
|
||||||
strm.avail_in = in.size();
|
strm.next_out = outbuf.data();
|
||||||
strm.next_out = outbuf;
|
strm.avail_out = outbuf.size();
|
||||||
strm.avail_out = sizeof(outbuf);
|
bool eof = false;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
checkInterrupt();
|
checkInterrupt();
|
||||||
|
|
||||||
|
if (strm.avail_in == 0 && !eof) {
|
||||||
|
strm.next_in = inbuf.data();
|
||||||
|
try {
|
||||||
|
strm.avail_in = source.read((unsigned char *) strm.next_in, inbuf.size());
|
||||||
|
} catch (EndOfFile &) {
|
||||||
|
eof = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (strm.avail_in == 0)
|
if (strm.avail_in == 0)
|
||||||
action = LZMA_FINISH;
|
action = LZMA_FINISH;
|
||||||
|
|
||||||
lzma_ret ret = lzma_code(&strm, action);
|
lzma_ret ret = lzma_code(&strm, action);
|
||||||
|
|
||||||
if (strm.avail_out == 0 || ret == LZMA_STREAM_END) {
|
if (strm.avail_out < outbuf.size()) {
|
||||||
res->append((char *) outbuf, sizeof(outbuf) - strm.avail_out);
|
sink((unsigned char *) outbuf.data(), outbuf.size() - strm.avail_out);
|
||||||
strm.next_out = outbuf;
|
strm.next_out = outbuf.data();
|
||||||
strm.avail_out = sizeof(outbuf);
|
strm.avail_out = outbuf.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ret == LZMA_STREAM_END)
|
if (ret == LZMA_STREAM_END) return;
|
||||||
return res;
|
|
||||||
|
|
||||||
if (ret != LZMA_OK)
|
if (ret != LZMA_OK)
|
||||||
throw CompressionError("error %d while decompressing xz file", ret);
|
throw CompressionError("error %d while decompressing xz file", ret);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static ref<std::string> decompressBzip2(const std::string & in)
|
static void decompressBzip2(Source & source, Sink & sink)
|
||||||
{
|
{
|
||||||
bz_stream strm;
|
bz_stream strm;
|
||||||
memset(&strm, 0, sizeof(strm));
|
memset(&strm, 0, sizeof(strm));
|
||||||
|
@ -69,39 +93,50 @@ static ref<std::string> decompressBzip2(const std::string & in)
|
||||||
|
|
||||||
Finally free([&]() { BZ2_bzDecompressEnd(&strm); });
|
Finally free([&]() { BZ2_bzDecompressEnd(&strm); });
|
||||||
|
|
||||||
char outbuf[BUFSIZ];
|
std::vector<char> inbuf(bufSize), outbuf(bufSize);
|
||||||
ref<std::string> res = make_ref<std::string>();
|
strm.next_in = nullptr;
|
||||||
strm.next_in = (char *) in.c_str();
|
strm.avail_in = 0;
|
||||||
strm.avail_in = in.size();
|
strm.next_out = outbuf.data();
|
||||||
strm.next_out = outbuf;
|
strm.avail_out = outbuf.size();
|
||||||
strm.avail_out = sizeof(outbuf);
|
bool eof = false;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
checkInterrupt();
|
checkInterrupt();
|
||||||
|
|
||||||
int ret = BZ2_bzDecompress(&strm);
|
if (strm.avail_in == 0 && !eof) {
|
||||||
|
strm.next_in = inbuf.data();
|
||||||
if (strm.avail_out == 0 || ret == BZ_STREAM_END) {
|
try {
|
||||||
res->append(outbuf, sizeof(outbuf) - strm.avail_out);
|
strm.avail_in = source.read((unsigned char *) strm.next_in, inbuf.size());
|
||||||
strm.next_out = outbuf;
|
} catch (EndOfFile &) {
|
||||||
strm.avail_out = sizeof(outbuf);
|
eof = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ret == BZ_STREAM_END)
|
int ret = BZ2_bzDecompress(&strm);
|
||||||
return res;
|
|
||||||
|
if (strm.avail_in == 0 && strm.avail_out == outbuf.size() && eof)
|
||||||
|
throw CompressionError("bzip2 data ends prematurely");
|
||||||
|
|
||||||
|
if (strm.avail_out < outbuf.size()) {
|
||||||
|
sink((unsigned char *) outbuf.data(), outbuf.size() - strm.avail_out);
|
||||||
|
strm.next_out = outbuf.data();
|
||||||
|
strm.avail_out = outbuf.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ret == BZ_STREAM_END) return;
|
||||||
|
|
||||||
if (ret != BZ_OK)
|
if (ret != BZ_OK)
|
||||||
throw CompressionError("error while decompressing bzip2 file");
|
throw CompressionError("error while decompressing bzip2 file");
|
||||||
|
|
||||||
if (strm.avail_in == 0)
|
|
||||||
throw CompressionError("bzip2 data ends prematurely");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static ref<std::string> decompressBrotli(const std::string & in)
|
static void decompressBrotli(Source & source, Sink & sink)
|
||||||
{
|
{
|
||||||
#if !HAVE_BROTLI
|
#if !HAVE_BROTLI
|
||||||
return make_ref<std::string>(runProgram(BROTLI, true, {"-d"}, {in}));
|
RunOptions options(BROTLI, {"-d"});
|
||||||
|
options.stdin = &source;
|
||||||
|
options.stdout = &sink;
|
||||||
|
runProgram2(options);
|
||||||
#else
|
#else
|
||||||
auto *s = BrotliDecoderCreateInstance(nullptr, nullptr, nullptr);
|
auto *s = BrotliDecoderCreateInstance(nullptr, nullptr, nullptr);
|
||||||
if (!s)
|
if (!s)
|
||||||
|
@ -109,16 +144,26 @@ static ref<std::string> decompressBrotli(const std::string & in)
|
||||||
|
|
||||||
Finally free([s]() { BrotliDecoderDestroyInstance(s); });
|
Finally free([s]() { BrotliDecoderDestroyInstance(s); });
|
||||||
|
|
||||||
uint8_t outbuf[BUFSIZ];
|
std::vector<uint8_t> inbuf(bufSize), outbuf(bufSize);
|
||||||
ref<std::string> res = make_ref<std::string>();
|
const uint8_t * next_in = nullptr;
|
||||||
const uint8_t *next_in = (uint8_t *)in.c_str();
|
size_t avail_in = 0;
|
||||||
size_t avail_in = in.size();
|
bool eof = false;
|
||||||
uint8_t *next_out = outbuf;
|
|
||||||
size_t avail_out = sizeof(outbuf);
|
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
checkInterrupt();
|
checkInterrupt();
|
||||||
|
|
||||||
|
if (avail_in == 0 && !eof) {
|
||||||
|
next_in = inbuf.data();
|
||||||
|
try {
|
||||||
|
avail_in = source.read((unsigned char *) next_in, inbuf.size());
|
||||||
|
} catch (EndOfFile &) {
|
||||||
|
eof = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t * next_out = outbuf.data();
|
||||||
|
size_t avail_out = outbuf.size();
|
||||||
|
|
||||||
auto ret = BrotliDecoderDecompressStream(s,
|
auto ret = BrotliDecoderDecompressStream(s,
|
||||||
&avail_in, &next_in,
|
&avail_in, &next_in,
|
||||||
&avail_out, &next_out,
|
&avail_out, &next_out,
|
||||||
|
@ -128,51 +173,49 @@ static ref<std::string> decompressBrotli(const std::string & in)
|
||||||
case BROTLI_DECODER_RESULT_ERROR:
|
case BROTLI_DECODER_RESULT_ERROR:
|
||||||
throw CompressionError("error while decompressing brotli file");
|
throw CompressionError("error while decompressing brotli file");
|
||||||
case BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT:
|
case BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT:
|
||||||
throw CompressionError("incomplete or corrupt brotli file");
|
if (eof)
|
||||||
|
throw CompressionError("incomplete or corrupt brotli file");
|
||||||
|
break;
|
||||||
case BROTLI_DECODER_RESULT_SUCCESS:
|
case BROTLI_DECODER_RESULT_SUCCESS:
|
||||||
if (avail_in != 0)
|
if (avail_in != 0)
|
||||||
throw CompressionError("unexpected input after brotli decompression");
|
throw CompressionError("unexpected input after brotli decompression");
|
||||||
break;
|
break;
|
||||||
case BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT:
|
case BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT:
|
||||||
// I'm not sure if this can happen, but abort if this happens with empty buffer
|
// I'm not sure if this can happen, but abort if this happens with empty buffer
|
||||||
if (avail_out == sizeof(outbuf))
|
if (avail_out == outbuf.size())
|
||||||
throw CompressionError("brotli decompression requires larger buffer");
|
throw CompressionError("brotli decompression requires larger buffer");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Always ensure we have full buffer for next invocation
|
// Always ensure we have full buffer for next invocation
|
||||||
if (avail_out < sizeof(outbuf)) {
|
if (avail_out < outbuf.size())
|
||||||
res->append((char*)outbuf, sizeof(outbuf) - avail_out);
|
sink((unsigned char *) outbuf.data(), outbuf.size() - avail_out);
|
||||||
next_out = outbuf;
|
|
||||||
avail_out = sizeof(outbuf);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ret == BROTLI_DECODER_RESULT_SUCCESS) return res;
|
if (ret == BROTLI_DECODER_RESULT_SUCCESS) return;
|
||||||
}
|
}
|
||||||
#endif // HAVE_BROTLI
|
#endif // HAVE_BROTLI
|
||||||
}
|
}
|
||||||
|
|
||||||
ref<std::string> compress(const std::string & method, const std::string & in, const bool parallel)
|
|
||||||
{
|
|
||||||
StringSink ssink;
|
|
||||||
auto sink = makeCompressionSink(method, ssink, parallel);
|
|
||||||
(*sink)(in);
|
|
||||||
sink->finish();
|
|
||||||
return ssink.s;
|
|
||||||
}
|
|
||||||
|
|
||||||
ref<std::string> decompress(const std::string & method, const std::string & in)
|
ref<std::string> decompress(const std::string & method, const std::string & in)
|
||||||
|
{
|
||||||
|
StringSource source(in);
|
||||||
|
StringSink sink;
|
||||||
|
decompress(method, source, sink);
|
||||||
|
return sink.s;
|
||||||
|
}
|
||||||
|
|
||||||
|
void decompress(const std::string & method, Source & source, Sink & sink)
|
||||||
{
|
{
|
||||||
if (method == "none")
|
if (method == "none")
|
||||||
return make_ref<std::string>(in);
|
return decompressNone(source, sink);
|
||||||
else if (method == "xz")
|
else if (method == "xz")
|
||||||
return decompressXZ(in);
|
return decompressXZ(source, sink);
|
||||||
else if (method == "bzip2")
|
else if (method == "bzip2")
|
||||||
return decompressBzip2(in);
|
return decompressBzip2(source, sink);
|
||||||
else if (method == "br")
|
else if (method == "br")
|
||||||
return decompressBrotli(in);
|
return decompressBrotli(source, sink);
|
||||||
else
|
else
|
||||||
throw UnknownCompressionMethod(format("unknown compression method '%s'") % method);
|
throw UnknownCompressionMethod("unknown compression method '%s'", method);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct NoneSink : CompressionSink
|
struct NoneSink : CompressionSink
|
||||||
|
@ -499,4 +542,13 @@ ref<CompressionSink> makeCompressionSink(const std::string & method, Sink & next
|
||||||
throw UnknownCompressionMethod(format("unknown compression method '%s'") % method);
|
throw UnknownCompressionMethod(format("unknown compression method '%s'") % method);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ref<std::string> compress(const std::string & method, const std::string & in, const bool parallel)
|
||||||
|
{
|
||||||
|
StringSink ssink;
|
||||||
|
auto sink = makeCompressionSink(method, ssink, parallel);
|
||||||
|
(*sink)(in);
|
||||||
|
sink->finish();
|
||||||
|
return ssink.s;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,10 +8,12 @@
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
ref<std::string> compress(const std::string & method, const std::string & in, const bool parallel = false);
|
|
||||||
|
|
||||||
ref<std::string> decompress(const std::string & method, const std::string & in);
|
ref<std::string> decompress(const std::string & method, const std::string & in);
|
||||||
|
|
||||||
|
void decompress(const std::string & method, Source & source, Sink & sink);
|
||||||
|
|
||||||
|
ref<std::string> compress(const std::string & method, const std::string & in, const bool parallel = false);
|
||||||
|
|
||||||
struct CompressionSink : BufferedSink
|
struct CompressionSink : BufferedSink
|
||||||
{
|
{
|
||||||
virtual void finish() = 0;
|
virtual void finish() = 0;
|
||||||
|
|
|
@ -56,7 +56,7 @@ struct Source
|
||||||
void operator () (unsigned char * data, size_t len);
|
void operator () (unsigned char * data, size_t len);
|
||||||
|
|
||||||
/* Store up to ‘len’ in the buffer pointed to by ‘data’, and
|
/* Store up to ‘len’ in the buffer pointed to by ‘data’, and
|
||||||
return the number of bytes stored. If blocks until at least
|
return the number of bytes stored. It blocks until at least
|
||||||
one byte is available. */
|
one byte is available. */
|
||||||
virtual size_t read(unsigned char * data, size_t len) = 0;
|
virtual size_t read(unsigned char * data, size_t len) = 0;
|
||||||
|
|
||||||
|
@ -175,6 +175,22 @@ struct TeeSource : Source
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/* Convert a function into a sink. */
|
||||||
|
struct LambdaSink : Sink
|
||||||
|
{
|
||||||
|
typedef std::function<void(const unsigned char *, size_t)> lambda_t;
|
||||||
|
|
||||||
|
lambda_t lambda;
|
||||||
|
|
||||||
|
LambdaSink(const lambda_t & lambda) : lambda(lambda) { }
|
||||||
|
|
||||||
|
virtual void operator () (const unsigned char * data, size_t len)
|
||||||
|
{
|
||||||
|
lambda(data, len);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
void writePadding(size_t len, Sink & sink);
|
void writePadding(size_t len, Sink & sink);
|
||||||
void writeString(const unsigned char * buf, size_t len, Sink & sink);
|
void writeString(const unsigned char * buf, size_t len, Sink & sink);
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
#include "affinity.hh"
|
#include "affinity.hh"
|
||||||
#include "sync.hh"
|
#include "sync.hh"
|
||||||
#include "finally.hh"
|
#include "finally.hh"
|
||||||
|
#include "serialise.hh"
|
||||||
|
|
||||||
#include <cctype>
|
#include <cctype>
|
||||||
#include <cerrno>
|
#include <cerrno>
|
||||||
|
@ -568,19 +569,25 @@ void writeFull(int fd, const string & s, bool allowInterrupts)
|
||||||
|
|
||||||
string drainFD(int fd)
|
string drainFD(int fd)
|
||||||
{
|
{
|
||||||
string result;
|
StringSink sink;
|
||||||
unsigned char buffer[4096];
|
drainFD(fd, sink);
|
||||||
|
return std::move(*sink.s);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void drainFD(int fd, Sink & sink)
|
||||||
|
{
|
||||||
|
std::vector<unsigned char> buf(4096);
|
||||||
while (1) {
|
while (1) {
|
||||||
checkInterrupt();
|
checkInterrupt();
|
||||||
ssize_t rd = read(fd, buffer, sizeof buffer);
|
ssize_t rd = read(fd, buf.data(), buf.size());
|
||||||
if (rd == -1) {
|
if (rd == -1) {
|
||||||
if (errno != EINTR)
|
if (errno != EINTR)
|
||||||
throw SysError("reading from file");
|
throw SysError("reading from file");
|
||||||
}
|
}
|
||||||
else if (rd == 0) break;
|
else if (rd == 0) break;
|
||||||
else result.append((char *) buffer, rd);
|
else sink(buf.data(), rd);
|
||||||
}
|
}
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -920,20 +927,47 @@ string runProgram(Path program, bool searchPath, const Strings & args,
|
||||||
return res.second;
|
return res.second;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::pair<int, std::string> runProgram(const RunOptions & options)
|
std::pair<int, std::string> runProgram(const RunOptions & options_)
|
||||||
|
{
|
||||||
|
RunOptions options(options_);
|
||||||
|
StringSink sink;
|
||||||
|
options.stdout = &sink;
|
||||||
|
|
||||||
|
int status = 0;
|
||||||
|
|
||||||
|
try {
|
||||||
|
runProgram2(options);
|
||||||
|
} catch (ExecError & e) {
|
||||||
|
status = e.status;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {status, std::move(*sink.s)};
|
||||||
|
}
|
||||||
|
|
||||||
|
void runProgram2(const RunOptions & options)
|
||||||
{
|
{
|
||||||
checkInterrupt();
|
checkInterrupt();
|
||||||
|
|
||||||
|
assert(!(options.stdin && options.input));
|
||||||
|
|
||||||
|
std::unique_ptr<Source> source_;
|
||||||
|
Source * source = options.stdin;
|
||||||
|
|
||||||
|
if (options.input) {
|
||||||
|
source_ = std::make_unique<StringSource>(*options.input);
|
||||||
|
source = source_.get();
|
||||||
|
}
|
||||||
|
|
||||||
/* Create a pipe. */
|
/* Create a pipe. */
|
||||||
Pipe out, in;
|
Pipe out, in;
|
||||||
out.create();
|
if (options.stdout) out.create();
|
||||||
if (options.input) in.create();
|
if (source) in.create();
|
||||||
|
|
||||||
/* Fork. */
|
/* Fork. */
|
||||||
Pid pid = startProcess([&]() {
|
Pid pid = startProcess([&]() {
|
||||||
if (dup2(out.writeSide.get(), STDOUT_FILENO) == -1)
|
if (options.stdout && dup2(out.writeSide.get(), STDOUT_FILENO) == -1)
|
||||||
throw SysError("dupping stdout");
|
throw SysError("dupping stdout");
|
||||||
if (options.input && dup2(in.readSide.get(), STDIN_FILENO) == -1)
|
if (source && dup2(in.readSide.get(), STDIN_FILENO) == -1)
|
||||||
throw SysError("dupping stdin");
|
throw SysError("dupping stdin");
|
||||||
|
|
||||||
Strings args_(options.args);
|
Strings args_(options.args);
|
||||||
|
@ -961,11 +995,20 @@ std::pair<int, std::string> runProgram(const RunOptions & options)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
if (options.input) {
|
if (source) {
|
||||||
in.readSide = -1;
|
in.readSide = -1;
|
||||||
writerThread = std::thread([&]() {
|
writerThread = std::thread([&]() {
|
||||||
try {
|
try {
|
||||||
writeFull(in.writeSide.get(), *options.input);
|
std::vector<unsigned char> buf(8 * 1024);
|
||||||
|
while (true) {
|
||||||
|
size_t n;
|
||||||
|
try {
|
||||||
|
n = source->read(buf.data(), buf.size());
|
||||||
|
} catch (EndOfFile &) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
writeFull(in.writeSide.get(), buf.data(), n);
|
||||||
|
}
|
||||||
promise.set_value();
|
promise.set_value();
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
promise.set_exception(std::current_exception());
|
promise.set_exception(std::current_exception());
|
||||||
|
@ -974,15 +1017,17 @@ std::pair<int, std::string> runProgram(const RunOptions & options)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
string result = drainFD(out.readSide.get());
|
if (options.stdout)
|
||||||
|
drainFD(out.readSide.get(), *options.stdout);
|
||||||
|
|
||||||
/* Wait for the child to finish. */
|
/* Wait for the child to finish. */
|
||||||
int status = pid.wait();
|
int status = pid.wait();
|
||||||
|
|
||||||
/* Wait for the writer thread to finish. */
|
/* Wait for the writer thread to finish. */
|
||||||
if (options.input) promise.get_future().get();
|
if (source) promise.get_future().get();
|
||||||
|
|
||||||
return {status, result};
|
if (status)
|
||||||
|
throw ExecError(status, fmt("program '%1%' %2%", options.program, statusToString(status)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,9 @@
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
|
struct Sink;
|
||||||
|
struct Source;
|
||||||
|
|
||||||
|
|
||||||
/* Return an environment variable. */
|
/* Return an environment variable. */
|
||||||
string getEnv(const string & key, const string & def = "");
|
string getEnv(const string & key, const string & def = "");
|
||||||
|
@ -150,6 +153,7 @@ MakeError(EndOfFile, Error)
|
||||||
/* Read a file descriptor until EOF occurs. */
|
/* Read a file descriptor until EOF occurs. */
|
||||||
string drainFD(int fd);
|
string drainFD(int fd);
|
||||||
|
|
||||||
|
void drainFD(int fd, Sink & sink);
|
||||||
|
|
||||||
|
|
||||||
/* Automatic cleanup of resources. */
|
/* Automatic cleanup of resources. */
|
||||||
|
@ -256,6 +260,8 @@ struct RunOptions
|
||||||
bool searchPath = true;
|
bool searchPath = true;
|
||||||
Strings args;
|
Strings args;
|
||||||
std::experimental::optional<std::string> input;
|
std::experimental::optional<std::string> input;
|
||||||
|
Source * stdin = nullptr;
|
||||||
|
Sink * stdout = nullptr;
|
||||||
bool _killStderr = false;
|
bool _killStderr = false;
|
||||||
|
|
||||||
RunOptions(const Path & program, const Strings & args)
|
RunOptions(const Path & program, const Strings & args)
|
||||||
|
@ -266,6 +272,8 @@ struct RunOptions
|
||||||
|
|
||||||
std::pair<int, std::string> runProgram(const RunOptions & options);
|
std::pair<int, std::string> runProgram(const RunOptions & options);
|
||||||
|
|
||||||
|
void runProgram2(const RunOptions & options);
|
||||||
|
|
||||||
|
|
||||||
class ExecError : public Error
|
class ExecError : public Error
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in a new issue