blob: c1007f7533f1b375a98d109bd947889b0ee08669 [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/sync/mutex.h>
#include <lib/zxio/inception.h>
#include <lib/zxio/null.h>
#include <lib/zxio/ops.h>
#include <zircon/syscalls/log.h>
#include <array>
#include "private.h"
#define LOGBUF_MAX (ZX_LOG_RECORD_MAX - sizeof(zx_log_record_t))
// A |zxio_t| backend that uses a debuglog.
//
// The |handle| handle is a Zircon debuglog object.
typedef struct zxio_debuglog {
zxio_t io;
zx::debuglog handle;
sync_mutex_t lock;
struct {
unsigned next;
std::unique_ptr<std::array<char, LOGBUF_MAX>> pending;
} buffer __TA_GUARDED(lock);
} zxio_debuglog_t;
static_assert(sizeof(zxio_debuglog_t) <= sizeof(zxio_storage_t),
"zxio_debuglog_t must fit inside zxio_storage_t.");
static zx_status_t zxio_debuglog_close(zxio_t* io) {
auto debuglog = reinterpret_cast<zxio_debuglog_t*>(io);
debuglog->~zxio_debuglog_t();
return ZX_OK;
}
zx_status_t zxio_debuglog_clone(zxio_t* io, zx_handle_t* out_handle) {
auto debuglog = reinterpret_cast<zxio_debuglog_t*>(io);
zx::debuglog handle;
zx_status_t status = debuglog->handle.duplicate(ZX_RIGHT_SAME_RIGHTS, &handle);
*out_handle = handle.release();
return status;
}
static zx_status_t zxio_debuglog_write_vector(zxio_t* io, const zx_iovec_t* vector,
size_t vector_count, zxio_flags_t flags,
size_t* out_actual) {
if (flags) {
return ZX_ERR_NOT_SUPPORTED;
}
auto debuglog = reinterpret_cast<zxio_debuglog_t*>(io);
auto& outgoing = debuglog->buffer;
sync_mutex_lock(&debuglog->lock);
if (outgoing.pending == nullptr) {
outgoing.pending = std::make_unique<std::array<char, LOGBUF_MAX>>();
}
zx_status_t status = zxio_do_vector(
vector, vector_count, out_actual, [&](void* buffer, size_t capacity, 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(debuglog->lock) {}();
const char* data = static_cast<const char*>(buffer);
for (size_t i = 0; i < capacity; ++i) {
char c = *data++;
if (c == '\n') {
debuglog->handle.write(0, outgoing.pending->data(), outgoing.next);
outgoing.next = 0;
continue;
}
if (c < ' ') {
continue;
}
(*outgoing.pending)[outgoing.next++] = c;
if (outgoing.next == LOGBUF_MAX) {
debuglog->handle.write(0, outgoing.pending->data(), outgoing.next);
outgoing.next = 0;
continue;
}
}
*out_actual = capacity;
return ZX_OK;
});
sync_mutex_unlock(&debuglog->lock);
return status;
}
static zx_status_t zxio_debuglog_isatty(zxio_t* io, 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;
}
static constexpr zxio_ops_t zxio_debuglog_ops = ([]() {
zxio_ops_t ops = zxio_default_ops;
ops.close = zxio_debuglog_close;
ops.clone = zxio_debuglog_clone;
ops.write_vector = zxio_debuglog_write_vector;
ops.isatty = zxio_debuglog_isatty;
return ops;
})();
zx_status_t zxio_debuglog_init(zxio_storage_t* storage, zx::debuglog handle) {
auto debuglog = new (storage) zxio_debuglog_t{
.io = storage->io,
.handle = std::move(handle),
.lock = {},
.buffer = {},
};
zxio_init(&debuglog->io, &zxio_debuglog_ops);
return ZX_OK;
}