blob: ac1b7f29118a24940d0ec811059187fc16f10eaa [file] [log] [blame]
// Copyright 2022 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.
package botanist
import (
"bytes"
"context"
"io"
"math"
"sync"
"go.fuchsia.dev/fuchsia/tools/lib/logger"
"go.fuchsia.dev/fuchsia/tools/lib/streams"
)
// LockedWriter is a wrapper around a writer that locks around each write so
// that multiple writes won't interleave with each other.
type LockedWriter struct {
mu sync.Mutex
writer io.Writer
}
// NewLockedWriter returns a LockedWriter that associates a new lock with the
// provided writer.
func NewLockedWriter(writer io.Writer) *LockedWriter {
return &LockedWriter{
mu: sync.Mutex{},
writer: writer,
}
}
func (w *LockedWriter) Write(data []byte) (int, error) {
w.mu.Lock()
defer w.mu.Unlock()
return w.writer.Write(data)
}
// LineWriter is a wrapper around a writer that writes line by line so
// that multiple writers to the same underlying writer won't interleave
// their writes midline.
type LineWriter struct {
writer io.Writer
line []byte
}
// NewLineWriter returns a new LineWriter.
func NewLineWriter(writer io.Writer) *LineWriter {
return &LineWriter{
writer: writer,
}
}
// Write stores bytes until it gets a newline and then writes to the underlying
// writer line by line. If the underlying Write() returns an err, this writer
// will return the number of bytes of the current data that were written.
// Otherwise, it returns the full length of the data to notify callers that it
// has received the whole data.
func (w *LineWriter) Write(data []byte) (int, error) {
lines := bytes.SplitAfter(data, []byte("\n"))
written := 0
for _, line := range lines {
if bytes.HasSuffix(line, []byte("\n")) {
n, err := w.writer.Write(append(w.line, line...))
written += int(math.Max(0, float64(n-len(line))))
if err != nil {
return written, err
}
w.line = []byte{}
} else {
w.line = append(w.line, line...)
}
}
return len(data), nil
}
// NewStiodWriters returns a new LineWriter for the stdout and stderr associated
// with the provided context. It also returns a function to flush out any
// remaining data not written by Write because it didn't end with a newline.
func NewStdioWriters(ctx context.Context) (io.Writer, io.Writer, func()) {
stdoutWriter := NewLineWriter(streams.Stdout(ctx))
stderrWriter := NewLineWriter(streams.Stderr(ctx))
flush := func() {
// Flush out the rest of the data stored by the writers.
if len(stdoutWriter.line) > 0 {
if _, err := stdoutWriter.Write([]byte("\n")); err != nil {
logger.Debugf(ctx, "failed to flush out data to stdout %q: %s", string(stdoutWriter.line), err)
}
}
if len(stderrWriter.line) > 0 {
if _, err := stderrWriter.Write([]byte("\n")); err != nil {
logger.Debugf(ctx, "failed to flush out data to stderr %q: %s", string(stderrWriter.line), err)
}
}
}
return stdoutWriter, stderrWriter, flush
}