summaryrefslogtreecommitdiff
path: root/indra/llcommon/llprocess.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'indra/llcommon/llprocess.cpp')
-rw-r--r--indra/llcommon/llprocess.cpp107
1 files changed, 88 insertions, 19 deletions
diff --git a/indra/llcommon/llprocess.cpp b/indra/llcommon/llprocess.cpp
index 3b17b819bd..edfdebfe87 100644
--- a/indra/llcommon/llprocess.cpp
+++ b/indra/llcommon/llprocess.cpp
@@ -220,7 +220,8 @@ public:
// Essential to initialize our std::istream with our special streambuf!
mStream(&mStreambuf),
mPump("ReadPipe", true), // tweak name as needed to avoid collisions
- mLimit(0)
+ mLimit(0),
+ mEOF(false)
{
mConnection = LLEventPumps::instance().obtain("mainloop")
.listen(LLEventPump::inventName("ReadPipe"),
@@ -230,11 +231,25 @@ public:
// Much of the implementation is simply connecting the abstract virtual
// methods with implementation data concealed from the base class.
virtual std::istream& get_istream() { return mStream; }
+ virtual std::string getline() { return LLProcess::getline(mStream); }
virtual LLEventPump& getPump() { return mPump; }
virtual void setLimit(size_type limit) { mLimit = limit; }
virtual size_type getLimit() const { return mLimit; }
virtual size_type size() const { return mStreambuf.size(); }
+ virtual std::string read(size_type len)
+ {
+ // Read specified number of bytes into a buffer. Make a buffer big
+ // enough.
+ size_type readlen((std::min)(size(), len));
+ std::vector<char> buffer(readlen);
+ mStream.read(&buffer[0], readlen);
+ // Since we've already clamped 'readlen', we can think of no reason
+ // why mStream.read() should read fewer than 'readlen' bytes.
+ // Nonetheless, use the actual retrieved length.
+ return std::string(&buffer[0], mStream.gcount());
+ }
+
virtual std::string peek(size_type offset=0, size_type len=npos) const
{
// Constrain caller's offset and len to overlap actual buffer content.
@@ -287,14 +302,18 @@ public:
return (found == end)? npos : (found - begin);
}
-private:
bool tick(const LLSD&)
{
+ // Once we've hit EOF, skip all the rest of this.
+ if (mEOF)
+ return false;
+
typedef boost::asio::streambuf::mutable_buffers_type mutable_buffer_sequence;
// Try, every time, to read into our streambuf. In fact, we have no
// idea how much data the child might be trying to send: keep trying
// until we're convinced we've temporarily exhausted the pipe.
- bool exhausted = false;
+ enum PipeState { RETRY, EXHAUSTED, CLOSED };
+ PipeState state = RETRY;
std::size_t committed(0);
do
{
@@ -329,7 +348,9 @@ private:
}
// Either way, though, we won't need any more tick() calls.
mConnection.disconnect();
- exhausted = true; // also break outer retry loop
+ // Ignore any subsequent calls we might get anyway.
+ mEOF = true;
+ state = CLOSED; // also break outer retry loop
break;
}
@@ -347,7 +368,7 @@ private:
if (gotten < toread)
{
// break outer retry loop too
- exhausted = true;
+ state = EXHAUSTED;
break;
}
}
@@ -356,15 +377,20 @@ private:
mStreambuf.commit(tocommit);
committed += tocommit;
- // 'exhausted' is set when we can't fill any one buffer of the
- // mutable_buffer_sequence established by the current prepare()
- // call -- whether due to error or not enough bytes. That is,
- // 'exhausted' is still false when we've filled every physical
+ // state is changed from RETRY when we can't fill any one buffer
+ // of the mutable_buffer_sequence established by the current
+ // prepare() call -- whether due to error or not enough bytes.
+ // That is, if state is still RETRY, we've filled every physical
// buffer in the mutable_buffer_sequence. In that case, for all we
// know, the child might have still more data pending -- go for it!
- } while (! exhausted);
-
- if (committed)
+ } while (state == RETRY);
+
+ // Once we recognize that the pipe is closed, make one more call to
+ // listener. The listener might be waiting for a particular substring
+ // to arrive, or a particular length of data or something. The event
+ // with "eof" == true announces that nothing further will arrive, so
+ // use it or lose it.
+ if (committed || state == CLOSED)
{
// If we actually received new data, publish it on our LLEventPump
// as advertised. Constrain it by mLimit. But show listener the
@@ -373,14 +399,16 @@ private:
mPump.post(LLSDMap
("data", peek(0, datasize))
("len", LLSD::Integer(mStreambuf.size()))
- ("index", LLSD::Integer(mIndex))
+ ("slot", LLSD::Integer(mIndex))
("name", whichfile(mIndex))
- ("desc", mDesc));
+ ("desc", mDesc)
+ ("eof", state == CLOSED));
}
return false;
}
+private:
std::string mDesc;
apr_file_t* mPipe;
LLProcess::FILESLOT mIndex;
@@ -389,6 +417,7 @@ private:
std::istream mStream;
LLEventStream mPump;
size_type mLimit;
+ bool mEOF;
};
/// Need an exception to avoid constructing an invalid LLProcess object, but
@@ -641,17 +670,22 @@ static std::string getDesc(const LLProcess::Params& params)
// Caller didn't say. Use the executable name -- but use just the filename
// part. On Mac, for instance, full pathnames get cumbersome.
+ return LLProcess::basename(params.executable);
+}
+
+//static
+std::string LLProcess::basename(const std::string& path)
+{
// If there are Linden utility functions to manipulate pathnames, I
// haven't found them -- and for this usage, Boost.Filesystem seems kind
// of heavyweight.
- std::string executable(params.executable);
- std::string::size_type delim = executable.find_last_of("\\/");
- // If executable contains no pathname delimiters, return the whole thing.
+ std::string::size_type delim = path.find_last_of("\\/");
+ // If path contains no pathname delimiters, return the whole thing.
if (delim == std::string::npos)
- return executable;
+ return path;
// Return just the part beyond the last delimiter.
- return executable.substr(delim + 1);
+ return path.substr(delim + 1);
}
LLProcess::~LLProcess()
@@ -804,6 +838,24 @@ void LLProcess::handle_status(int reason, int status)
// KILLED; refine below.
mStatus.mState = EXITED;
+ // Make last-gasp calls for each of the ReadPipes we have on hand. Since
+ // they're listening on "mainloop", we can be sure they'll eventually
+ // collect all pending data from the child. But we want to be able to
+ // guarantee to our consumer that by the time we post on the "postend"
+ // LLEventPump, our ReadPipes are already buffering all the data there
+ // will ever be from the child. That lets the "postend" listener decide
+ // what to do with that final data.
+ for (size_t i = 0; i < mPipes.size(); ++i)
+ {
+ std::string error;
+ ReadPipeImpl* ppipe = getPipePtr<ReadPipeImpl>(error, FILESLOT(i));
+ if (ppipe)
+ {
+ static LLSD trivial;
+ ppipe->tick(trivial);
+ }
+ }
+
// wi->rv = apr_proc_wait(wi->child, &wi->rc, &wi->why, APR_NOWAIT);
// It's just wrong to call apr_proc_wait() here. The only way APR knows to
// call us with APR_OC_REASON_DEATH is that it's already reaped this child
@@ -919,6 +971,23 @@ boost::optional<LLProcess::ReadPipe&> LLProcess::getOptReadPipe(FILESLOT slot)
return getOptPipe<ReadPipe>(slot);
}
+//static
+std::string LLProcess::getline(std::istream& in)
+{
+ std::string line;
+ std::getline(in, line);
+ // Blur the distinction between "\r\n" and plain "\n". std::getline() will
+ // have eaten the "\n", but we could still end up with a trailing "\r".
+ std::string::size_type lastpos = line.find_last_not_of("\r");
+ if (lastpos != std::string::npos)
+ {
+ // Found at least one character that's not a trailing '\r'. SKIP OVER
+ // IT and erase the rest of the line.
+ line.erase(lastpos+1);
+ }
+ return line;
+}
+
std::ostream& operator<<(std::ostream& out, const LLProcess::Params& params)
{
if (params.cwd.isProvided())