blob: 631f76ebe743c5214ab2347441ce4bc0081cbede [file] [log] [blame]
// Copyright 2023 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef NINJA_STDIO_REDIRECTION_H_
#define NINJA_STDIO_REDIRECTION_H_
#include <stdio.h>
#include <string>
#include "async_loop.h"
#include "ipc_handle.h"
/// Multiple classes related to redirecting stdio streams.
/// A class used to manage redirection of stdin/stdout/stderr
/// between two processes, using an IpcHandle for communication.
///
/// IMPORTANT: THIS DOES NOT WORK ON WINDOWS. Due to Win32
/// technical limitations (i.e. DuplicateHandle() will always
/// fail to duplicate console handlers from another process,
/// which happens during ReceiveStandardDescriptors).
///
/// Usage is:
/// 1) Create instance, passing a reference to the handle.
///
/// 2) On the client, call SendStandardDescriptors() to send
/// standard descriptors to the server. This does not change
/// the current process.
///
/// 3) On the server, call ReceiveStandardDescriptors() to
/// receive them from the handle, and modify the process'
/// current standard descriptors to use them.
///
/// 4) The destructor will restore the previous standard
/// descriptors, in the case where ReceiveStandardDescriptors()
/// was called.
///
class StdioRedirector {
public:
StdioRedirector(IpcHandle& connection);
~StdioRedirector();
bool SendStandardDescriptors(std::string* err);
bool ReceiveStandardDescriptors(std::string* err);
protected:
IpcHandle& connection_;
IpcHandle old_stdin_;
IpcHandle old_stdout_;
IpcHandle old_stderr_;
};
/// A class used to redirect an stdout or stderr to a string buffer.
/// temporarily. Only use this for testing for small outputs, as
/// its implementation relies on async i/o on an anonymous pipe
/// for properly reading the data. In practice, a blocking printf()
/// call will deadlock if the pipe fills up entirely!
///
/// Usage is:
/// - Create instance, passing an AsyncLoop reference and
/// the stdout or stderr FILE pointer.
///
/// - Do small fprintf() or fputs() of the stream, the data
/// will be sent to the pipe, if this happens on the same
/// thread than the one running the AsyncLoop, a deadlock
/// will happen when the pipe is full!
///
/// - Call Restore() to restore the stdio stream to its
/// previous state. This does not destroyed data in the
/// pipe or in the buffer.
///
/// - Call WaitForResult() to receive the result after
/// draining the pipe completely. This calls Restore()
/// implicitly.
///
class StdioAsyncStringRedirector {
public:
/// Constructor flushes |stream|, which must be stdout or stderr,
/// then redirects its underlying file descriptor / handle to the
/// write end of a pipe, whose read end is managed by this instance
/// using asynchronous read operations. |async_loop| is a reference
/// to the global AsyncLoop instance.
///
/// On Posix, the write end is in blocking mode and does _not_
/// close on exec(), intentionally, allowing it to be passed
/// to other processes during fork() + exec().
StdioAsyncStringRedirector(AsyncLoop& async_loop, FILE* stream);
/// Destructor restores previous stream handle.
~StdioAsyncStringRedirector();
/// Flush the stream, close it, then restore its previous handle.
/// Can be called multiple times safely. Invoked implicitly by
/// the destructor and by WaitForResult().
void Restore();
/// Return true if the write end of the pipe has not been closed.
/// Note that normally Restore() closes it, unless another handle
/// exists on the system pointing to the pipe's write end
/// (e.g. when stdout was passed to another process).
bool IsActive() const;
/// Call Restore() then return buffered result after waiting
/// at most |timeout_ms| for the pipe's write end to be closed
/// (which may be prevented by another active file descriptor
/// pointing to it, e.g. when stdout was passed to another
/// process before this call).
///
/// Set |*result| to the buffered output. Return true in case of
/// success, meaning the pipe is closed and the result is
/// complete, or false in case of timeout, meaning the result
/// might be incomplete, since there is no guarantee that the
/// peer has properly flushed its stdout.
bool WaitForResult(std::string* result, int64_t timeout_ms = 1000);
private:
void StartRead();
void Close();
FILE* stream_;
int stream_fd_;
IpcHandle saved_handle_;
AsyncLoop& async_loop_;
AsyncHandle read_handle_;
std::string result_;
char buffer_[256]; // TODO(digit): Read directly into |result_|.
};
#endif // NINJA_STDIO_REDIRECTION_H_