diff --git a/src/gopheros/device/driver.go b/src/gopheros/device/driver.go index aec2c38..15a6316 100644 --- a/src/gopheros/device/driver.go +++ b/src/gopheros/device/driver.go @@ -1,6 +1,9 @@ package device -import "gopheros/kernel" +import ( + "gopheros/kernel" + "io" +) // Driver is an interface implemented by all drivers. type Driver interface { @@ -10,8 +13,10 @@ type Driver interface { // DriverVersion returns the driver version. DriverVersion() (major uint16, minor uint16, patch uint16) - // DriverInit initializes the device driver. - DriverInit() *kernel.Error + // DriverInit initializes the device driver. If the driver init code + // needs to log some output, it can use the supplied io.Writer in + // conjunction with a call to kfmt.Fprint. + DriverInit(io.Writer) *kernel.Error } // ProbeFn is a function that scans for the presence of a particular diff --git a/src/gopheros/device/tty/vt.go b/src/gopheros/device/tty/vt.go index ba48be0..9acadf3 100644 --- a/src/gopheros/device/tty/vt.go +++ b/src/gopheros/device/tty/vt.go @@ -258,7 +258,7 @@ func (t *VT) DriverVersion() (uint16, uint16, uint16) { } // DriverInit initializes this driver. -func (t *VT) DriverInit() *kernel.Error { return nil } +func (t *VT) DriverInit(_ io.Writer) *kernel.Error { return nil } func probeForVT() device.Driver { return NewVT(DefaultTabWidth, DefaultScrollback) diff --git a/src/gopheros/device/tty/vt_test.go b/src/gopheros/device/tty/vt_test.go index b1bf40f..066d25f 100644 --- a/src/gopheros/device/tty/vt_test.go +++ b/src/gopheros/device/tty/vt_test.go @@ -324,7 +324,7 @@ func TestVtSetState(t *testing.T) { func TestVTDriverInterface(t *testing.T) { var dev device.Driver = NewVT(0, 0) - if err := dev.DriverInit(); err != nil { + if err := dev.DriverInit(nil); err != nil { t.Fatal(err) } diff --git a/src/gopheros/device/video/console/vga_text.go b/src/gopheros/device/video/console/vga_text.go index d30291d..f5be66b 100644 --- a/src/gopheros/device/video/console/vga_text.go +++ b/src/gopheros/device/video/console/vga_text.go @@ -6,6 +6,7 @@ import ( "gopheros/kernel/cpu" "gopheros/kernel/hal/multiboot" "image/color" + "io" "reflect" "unsafe" ) @@ -197,7 +198,7 @@ func (cons *VgaTextConsole) DriverVersion() (uint16, uint16, uint16) { } // DriverInit initializes this driver. -func (cons *VgaTextConsole) DriverInit() *kernel.Error { return nil } +func (cons *VgaTextConsole) DriverInit(_ io.Writer) *kernel.Error { return nil } // probeForVgaTextConsole checks for the presence of a vga text console. func probeForVgaTextConsole() device.Driver { diff --git a/src/gopheros/device/video/console/vga_text_test.go b/src/gopheros/device/video/console/vga_text_test.go index 0c81441..3d5cb25 100644 --- a/src/gopheros/device/video/console/vga_text_test.go +++ b/src/gopheros/device/video/console/vga_text_test.go @@ -311,7 +311,7 @@ func TestVgaTextSetPaletteColor(t *testing.T) { func TestVgaTextDriverInterface(t *testing.T) { var dev device.Driver = NewVgaTextConsole(80, 25, 0) - if err := dev.DriverInit(); err != nil { + if err := dev.DriverInit(nil); err != nil { t.Fatal(err) } diff --git a/src/gopheros/kernel/hal/hal.go b/src/gopheros/kernel/hal/hal.go index 3f5d06f..ff91e05 100644 --- a/src/gopheros/kernel/hal/hal.go +++ b/src/gopheros/kernel/hal/hal.go @@ -1,6 +1,7 @@ package hal import ( + "bytes" "gopheros/device" "gopheros/device/tty" "gopheros/device/video/console" @@ -13,7 +14,10 @@ type managedDevices struct { activeTTY tty.Device } -var devices managedDevices +var ( + devices managedDevices + strBuf bytes.Buffer +) // ActiveTTY returns the currently active TTY func ActiveTTY() tty.Device { @@ -43,7 +47,10 @@ func DetectHardware() { // each detected device. The function returns a list of device drivers that // were successfully initialized. func probe(hwProbeFns []device.ProbeFn) []device.Driver { - var drivers []device.Driver + var ( + drivers []device.Driver + w = kfmt.PrefixWriter{Sink: kfmt.GetOutputSink()} + ) for _, probeFn := range hwProbeFns { drv := probeFn() @@ -51,16 +58,18 @@ func probe(hwProbeFns []device.ProbeFn) []device.Driver { continue } + strBuf.Reset() major, minor, patch := drv.DriverVersion() + kfmt.Fprintf(&strBuf, "[hal] %s(%d.%d.%d): ", drv.DriverName(), major, minor, patch) + w.Prefix = strBuf.Bytes() - kfmt.Printf("[hal] %s(%d.%d.%d): ", drv.DriverName(), major, minor, patch) - if err := drv.DriverInit(); err != nil { - kfmt.Printf("init failed: %s\n", err.Message) + if err := drv.DriverInit(&w); err != nil { + kfmt.Fprintf(&w, "init failed: %s\n", err.Message) continue } + kfmt.Fprintf(&w, "initialized\n") drivers = append(drivers, drv) - kfmt.Printf("initialized\n") } return drivers diff --git a/src/gopheros/kernel/kfmt/fmt.go b/src/gopheros/kernel/kfmt/fmt.go index 7780dcd..3683c2c 100644 --- a/src/gopheros/kernel/kfmt/fmt.go +++ b/src/gopheros/kernel/kfmt/fmt.go @@ -31,6 +31,14 @@ var ( outputSink io.Writer ) +// GetOutputSink returns the default target for calls to Printf. +func GetOutputSink() io.Writer { + if outputSink == nil { + return &earlyPrintBuffer + } + return outputSink +} + // SetOutputSink sets the default target for calls to Printf to w and copies // any data accumulated in the earlyPrintBuffer to itt . func SetOutputSink(w io.Writer) { diff --git a/src/gopheros/kernel/kfmt/fmt_test.go b/src/gopheros/kernel/kfmt/fmt_test.go index 90d9bf0..f41f5bd 100644 --- a/src/gopheros/kernel/kfmt/fmt_test.go +++ b/src/gopheros/kernel/kfmt/fmt_test.go @@ -149,9 +149,17 @@ func TestPrintf(t *testing.T) { }, } + if sink := GetOutputSink(); sink != &earlyPrintBuffer { + t.Fatal("expected GetOutputSink() to return the earlyPrintBuffer when no output sink has been set") + } + var buf bytes.Buffer SetOutputSink(&buf) + if sink := GetOutputSink(); sink != &buf { + t.Fatal("expected GetOutputSink() to return the value passed to SetOutputSink") + } + for specIndex, spec := range specs { buf.Reset() spec.fn() diff --git a/src/gopheros/kernel/kfmt/prefix_writer.go b/src/gopheros/kernel/kfmt/prefix_writer.go new file mode 100644 index 0000000..b326dbd --- /dev/null +++ b/src/gopheros/kernel/kfmt/prefix_writer.go @@ -0,0 +1,57 @@ +package kfmt + +import "io" + +// PrefixWriter is an io.Writer that wraps another io.Writer and injects a +// prefix at the beginning of each line. +type PrefixWriter struct { + // A writer where all writes get sent to. + Sink io.Writer + + // The prefix injected at the beginning of each line. + Prefix []byte + + bytesAfterPrefix int +} + +// Write writes len(p) bytes from p to the underlying data stream and returns +// back the number of bytes written. The PrefixWriter keeps track of the +// beginning of new lines and injects the configured prefix at each new line. +// The injected prefix is not included in the number of written bytes returned +// by this method. +func (w *PrefixWriter) Write(p []byte) (int, error) { + var ( + written int + startIndex, curIndex int + ) + + if w.bytesAfterPrefix == 0 && len(p) != 0 { + w.Sink.Write(w.Prefix) + } + + for ; curIndex < len(p); curIndex++ { + if p[curIndex] == '\n' { + n, err := w.Sink.Write(p[startIndex : curIndex+1]) + if curIndex+1 != len(p) { + w.Sink.Write(w.Prefix) + } + written += n + if err != nil { + return written, err + } + w.bytesAfterPrefix = 0 + startIndex = curIndex + 1 + } + } + + if startIndex < curIndex { + n, err := w.Sink.Write(p[startIndex:curIndex]) + written += n + w.bytesAfterPrefix = n + if err != nil { + return written, err + } + } + + return written, nil +} diff --git a/src/gopheros/kernel/kfmt/prefix_writer_test.go b/src/gopheros/kernel/kfmt/prefix_writer_test.go new file mode 100644 index 0000000..0a005ab --- /dev/null +++ b/src/gopheros/kernel/kfmt/prefix_writer_test.go @@ -0,0 +1,92 @@ +package kfmt + +import ( + "bytes" + "errors" + "testing" +) + +func TestPrefixWriter(t *testing.T) { + specs := []struct { + input string + exp string + }{ + { + "", + "", + }, + { + "\n", + "prefix: \n", + }, + { + "no line break anywhere", + "prefix: no line break anywhere", + }, + { + "line feed at the end\n", + "prefix: line feed at the end\n", + }, + { + "\nthe big brown\nfog jumped\nover the lazy\ndog", + "prefix: \nprefix: the big brown\nprefix: fog jumped\nprefix: over the lazy\nprefix: dog", + }, + } + + var ( + buf bytes.Buffer + w = PrefixWriter{ + Sink: &buf, + Prefix: []byte("prefix: "), + } + ) + + for specIndex, spec := range specs { + buf.Reset() + w.bytesAfterPrefix = 0 + + wrote, err := w.Write([]byte(spec.input)) + if err != nil { + t.Errorf("[spec %d] unexpected error: %v", specIndex, err) + } + + if expLen := len(spec.input); expLen != wrote { + t.Errorf("[spec %d] expected writer to write %d bytes; wrote %d", specIndex, expLen, wrote) + } + + if got := buf.String(); got != spec.exp { + t.Errorf("[spec %d] expected output:\n%q\ngot:\n%q", specIndex, spec.exp, got) + } + } +} + +func TestPrefixWriterErrors(t *testing.T) { + specs := []string{ + "no line break anywhere", + "\nthe big brown\nfog jumped\nover the lazy\ndog", + } + + var ( + expErr = errors.New("write failed") + w = PrefixWriter{ + Sink: writerThatAlwaysErrors{expErr}, + Prefix: []byte("prefix: "), + } + ) + + for specIndex, spec := range specs { + w.bytesAfterPrefix = 0 + _, err := w.Write([]byte(spec)) + if err != expErr { + t.Errorf("[spec %d] expected error: %v; got %v", specIndex, expErr, err) + } + } +} + +type writerThatAlwaysErrors struct { + err error +} + +func (w writerThatAlwaysErrors) Write(_ []byte) (int, error) { + return 0, w.err +}