blob: bb56977fc9210b7a618318a376b5f8d48ef9f0ee [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <lib/zxio/null.h>
#include <lib/zxio/ops.h>
#include <zircon/syscalls/log.h>
#include <array>
#include <mutex>
#include "sdk/lib/zxio/private.h"
#include "sdk/lib/zxio/vector.h"
namespace {
// A |zxio_t| backend that uses a debuglog.
//
// The |handle| handle is a Zircon debuglog object.
class Debuglog : public HasIo {
public:
explicit Debuglog(zx::debuglog debuglog) : HasIo(kOps), handle_(std::move(debuglog)) {}
private:
zx_status_t Close(bool should_wait);
zx_status_t Clone(zx_handle_t* out_handle);
zx_status_t Writev(const zx_iovec_t* vector, size_t vector_count, zxio_flags_t flags,
size_t* out_actual);
zx_status_t IsATty(bool* tty);
static const zxio_ops_t kOps;
struct Buffer {
std::array<char, ZX_LOG_RECORD_DATA_MAX> pending;
decltype(pending)::iterator it;
};
zx::debuglog handle_;
std::mutex lock_;
std::unique_ptr<Buffer> buffer_ __TA_GUARDED(lock_);
};
constexpr zxio_ops_t Debuglog::kOps = ([]() {
using Adaptor = Adaptor<Debuglog>;
zxio_ops_t ops = zxio_default_ops;
ops.close = Adaptor::From<&Debuglog::Close>;
ops.clone = Adaptor::From<&Debuglog::Clone>;
ops.writev = Adaptor::From<&Debuglog::Writev>;
ops.isatty = Adaptor::From<&Debuglog::IsATty>;
return ops;
})();
zx_status_t Debuglog::Close(const bool should_wait) {
this->~Debuglog();
return ZX_OK;
}
zx_status_t Debuglog::Clone(zx_handle_t* out_handle) {
zx::debuglog handle;
zx_status_t status = handle_.duplicate(ZX_RIGHT_SAME_RIGHTS, &handle);
*out_handle = handle.release();
return status;
}
zx_status_t Debuglog::Writev(const zx_iovec_t* vector, size_t vector_count, zxio_flags_t flags,
size_t* out_actual) {
if (flags) {
return ZX_ERR_NOT_SUPPORTED;
}
std::lock_guard lock(lock_);
if (buffer_ == nullptr) {
buffer_ = std::make_unique<Buffer>();
buffer_->it = buffer_->pending.begin();
}
Buffer& outgoing = *buffer_;
auto flush = [&]() __TA_REQUIRES(lock_) {
zx_status_t status =
handle_.write(0, outgoing.pending.data(), outgoing.it - outgoing.pending.begin());
outgoing.it = outgoing.pending.begin();
return status;
};
auto write = [&](void* buffer, size_t capacity, size_t total_so_far, size_t* out_actual) {
// Convince the compiler that the lock is held here. This is safe because the lock is held over
// the call to zxio_do_vector and zxio_do_vector is synchronous, so it cannot extend the life of
// this lambda.
//
// This is required because the compiler does its analysis function-by-function. In this
// function, it can't see that the lock is held by the calling scope. If We annotate this
// function with TA_REQUIRES, then this function compiles, but its call in zxio_do_vector
// doesn't, because the compiler can't tell that the lock is held in that function.
[]() __TA_ASSERT(lock_) {}();
const char* data = static_cast<const char*>(buffer);
if (data == nullptr && capacity == 0) {
// The musl (libc) layer above uses f->write(f, 0, 0) to indicate the need for a
// flush-out-of-process (when client code does fflush(FILE), and in a few other cases). That
// call will result in vector[i].buffer == nullptr && vector[i].capacity == 0 at this layer.
//
// We check for both because we don't want to hide / normalize data == nullptr with capacity
// not 0, and we don't want to trigger on data != nullptr and capacity == 0 since that might
// match on additional cases beyond the f->write(f, 0, 0) data == nullptr && capacity == 0
// case.
//
// We currently know that the (nullptr, 0; flush) entry, when present, will be the last entry,
// but this implementation can handle additional batched write entries beyond the flush
// without incorrectly implying flush of the later batched write entries. Such additional
// batched write entries beyond the flush seem unlikely to be added in future, but since
// allowing for them isn't really any additional cost, we allow for them here.
//
// Since no layer above is filtering out data == nullptr && capacity == 0 passed by client
// code in a vector to writev(), a flush at this layer is reachable by client code that calls
// writev() (or sendmsg() or similar), which seems fine, especially given that only the
// Debuglog currently needs to do anything with such a flush - it's just ignored by other zxio
// implementations since their fd-layer writes aren't buffered in-process.
//
// A (nullptr, 0) entry will flush out previously-buffered data, even if the incoming vector
// doesn't have \n at the end of previosuly-buffered data (for example: ZX_PANIC("foo")
// without \n after "foo").
zx_status_t status = flush();
if (status != ZX_OK) {
return status;
}
}
// If the above if condition is true, the capacity will be 0 so this loop won't execute its
// body. We could have the loop under an "else" to (at least nominally / syntactically) avoid
// evaluating the for loop termination condition since we know it'll be false anyway when the if
// condition above was true, but we choose instead to indent this loop less.
for (size_t i = 0; i < capacity; ++i) {
char c = data[i];
if (c == '\n') {
zx_status_t status = flush();
if (status != ZX_OK) {
return status;
}
continue;
}
if (c < ' ') {
continue;
}
*outgoing.it = c;
++outgoing.it;
if (outgoing.it == outgoing.pending.end()) {
zx_status_t status = flush();
if (status != ZX_OK) {
return status;
}
continue;
}
}
*out_actual = capacity;
return ZX_OK;
};
return zxio_do_vector(vector, vector_count, out_actual, write);
}
zx_status_t Debuglog::IsATty(bool* tty) {
// debuglog needs to be a tty in order to tell stdio
// to use line-buffering semantics - bunching up log messages
// for an arbitrary amount of time makes for confusing results!
*tty = true;
return ZX_OK;
}
} // namespace
zx_status_t zxio_debuglog_init(zxio_storage_t* storage, zx::debuglog handle) {
new (storage) Debuglog(std::move(handle));
return ZX_OK;
}