From 34cd1719c04a0d6d1bd217d021834235b140dbb1 Mon Sep 17 00:00:00 2001 From: Alan Donovan Date: Mon, 30 Nov 2015 22:37:52 -0500 Subject: [PATCH] Update to gobook@0a3e8d997823052c6a8575feca6e515ac6f8299d --- ch13/bzip-print/bzip2.c | 28 +++++++++ ch13/bzip-print/bzip2.go | 96 +++++++++++++++++++++++++++++++ ch13/bzip-print/bzip2_test.go | 40 +++++++++++++ ch13/bzip/bzip2.c | 6 ++ ch13/bzip/bzip2.go | 23 +++++++- ch7/tempconv/tempconv.go.~master~ | 64 +++++++++++++++++++++ ch8/chat/chat.go.~master~ | 89 ++++++++++++++++++++++++++++ 7 files changed, 343 insertions(+), 3 deletions(-) create mode 100644 ch13/bzip-print/bzip2.c create mode 100644 ch13/bzip-print/bzip2.go create mode 100644 ch13/bzip-print/bzip2_test.go create mode 100644 ch7/tempconv/tempconv.go.~master~ create mode 100644 ch8/chat/chat.go.~master~ diff --git a/ch13/bzip-print/bzip2.c b/ch13/bzip-print/bzip2.c new file mode 100644 index 0000000..42a3a53 --- /dev/null +++ b/ch13/bzip-print/bzip2.c @@ -0,0 +1,28 @@ +// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ + +// See page 362. +// This is the version that appears in print, +// but it does not comply with the proposed +// rules for passing pointers between Go and C. +// (https://github.com/golang/proposal/blob/master/design/12416-cgo-pointers.md) +// See gopl.io/ch13/bzip for an updated version. + +//!+ +/* This file is gopl.io/ch13/bzip/bzip2.c, */ +/* a simple wrapper for libbzip2 suitable for cgo. */ +#include + +int bz2compress(bz_stream *s, int action, + char *in, unsigned *inlen, char *out, unsigned *outlen) { + s->next_in = in; + s->avail_in = *inlen; + s->next_out = out; + s->avail_out = *outlen; + int r = BZ2_bzCompress(s, action); + *inlen -= s->avail_in; + *outlen -= s->avail_out; + return r; +} + +//!- diff --git a/ch13/bzip-print/bzip2.go b/ch13/bzip-print/bzip2.go new file mode 100644 index 0000000..1d8c771 --- /dev/null +++ b/ch13/bzip-print/bzip2.go @@ -0,0 +1,96 @@ +// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ + +// See page 362. +// This is the version that appears in print, +// but it does not comply with the proposed +// rules for passing pointers between Go and C. +// (https://github.com/golang/proposal/blob/master/design/12416-cgo-pointers.md) +// See gopl.io/ch13/bzip for an updated version. +//!+ + +// Package bzip provides a writer that uses bzip2 compression (bzip.org). +package bzip + +/* +#cgo CFLAGS: -I/usr/include +#cgo LDFLAGS: -L/usr/lib -lbz2 +#include +int bz2compress(bz_stream *s, int action, + char *in, unsigned *inlen, char *out, unsigned *outlen); +*/ +import "C" + +import ( + "io" + "unsafe" +) + +type writer struct { + w io.Writer // underlying output stream + stream *C.bz_stream + outbuf [64 * 1024]byte +} + +// NewWriter returns a writer for bzip2-compressed streams. +func NewWriter(out io.Writer) io.WriteCloser { + const ( + blockSize = 9 + verbosity = 0 + workFactor = 30 + ) + w := &writer{w: out, stream: new(C.bz_stream)} + C.BZ2_bzCompressInit(w.stream, blockSize, verbosity, workFactor) + return w +} + +//!- + +//!+write +func (w *writer) Write(data []byte) (int, error) { + if w.stream == nil { + panic("closed") + } + var total int // uncompressed bytes written + + for len(data) > 0 { + inlen, outlen := C.uint(len(data)), C.uint(cap(w.outbuf)) + C.bz2compress(w.stream, C.BZ_RUN, + (*C.char)(unsafe.Pointer(&data[0])), &inlen, + (*C.char)(unsafe.Pointer(&w.outbuf)), &outlen) + total += int(inlen) + data = data[inlen:] + if _, err := w.w.Write(w.outbuf[:outlen]); err != nil { + return total, err + } + } + return total, nil +} + +//!-write + +//!+close +// Close flushes the compressed data and closes the stream. +// It does not close the underlying io.Writer. +func (w *writer) Close() error { + if w.stream == nil { + panic("closed") + } + defer func() { + C.BZ2_bzCompressEnd(w.stream) + w.stream = nil + }() + for { + inlen, outlen := C.uint(0), C.uint(cap(w.outbuf)) + r := C.bz2compress(w.stream, C.BZ_FINISH, nil, &inlen, + (*C.char)(unsafe.Pointer(&w.outbuf)), &outlen) + if _, err := w.w.Write(w.outbuf[:outlen]); err != nil { + return err + } + if r == C.BZ_STREAM_END { + return nil + } + } +} + +//!-close diff --git a/ch13/bzip-print/bzip2_test.go b/ch13/bzip-print/bzip2_test.go new file mode 100644 index 0000000..c52aba0 --- /dev/null +++ b/ch13/bzip-print/bzip2_test.go @@ -0,0 +1,40 @@ +// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ + +package bzip_test + +import ( + "bytes" + "compress/bzip2" // reader + "io" + "testing" + + "gopl.io/ch13/bzip" // writer +) + +func TestBzip2(t *testing.T) { + var compressed, uncompressed bytes.Buffer + w := bzip.NewWriter(&compressed) + + // Write a repetitive message in a million pieces, + // compressing one copy but not the other. + tee := io.MultiWriter(w, &uncompressed) + for i := 0; i < 1000000; i++ { + io.WriteString(tee, "hello") + } + if err := w.Close(); err != nil { + t.Fatal(err) + } + + // Check the size of the compressed stream. + if got, want := compressed.Len(), 255; got != want { + t.Errorf("1 million hellos compressed to %d bytes, want %d", got, want) + } + + // Decompress and compare with original. + var decompressed bytes.Buffer + io.Copy(&decompressed, bzip2.NewReader(&compressed)) + if !bytes.Equal(uncompressed.Bytes(), decompressed.Bytes()) { + t.Error("decompression yielded a different message") + } +} diff --git a/ch13/bzip/bzip2.c b/ch13/bzip/bzip2.c index 12e8d87..3ad459b 100644 --- a/ch13/bzip/bzip2.c +++ b/ch13/bzip/bzip2.c @@ -17,6 +17,12 @@ int bz2compress(bz_stream *s, int action, int r = BZ2_bzCompress(s, action); *inlen -= s->avail_in; *outlen -= s->avail_out; + + /* "C code may store a Go pointer in C memory subject to rule 2: + * it must stop storing the pointer before it returns to Go." */ + s->next_in = NULL; + s->next_out = NULL; + return r; } diff --git a/ch13/bzip/bzip2.go b/ch13/bzip/bzip2.go index 0aed4b0..2409b84 100644 --- a/ch13/bzip/bzip2.go +++ b/ch13/bzip/bzip2.go @@ -2,6 +2,20 @@ // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ // See page 362. +// The version of this file that appears in the book does not comply +// with the proposed rules for passing pointers between Go and C. +// (https://github.com/golang/proposal/blob/master/design/12416-cgo-pointers.md) +// The rules forbid a C function like bz2compress from storing 'in' and +// 'out' (pointers to variables allocated by Go) into the Go variable 's', +// even temporarily. +// +// To comply with the rules, the bz_stream variable must be allocated +// by C code. We have introduced two C functions, bz2alloc and +// bz2free, to allocate and free instances of the bz_stream type. +// Also, we have changed bz2compress so that before it returns, it +// clears the fields of the bz_stream that contain point to Go +// variables. + //!+ // Package bzip provides a writer that uses bzip2 compression (bzip.org). @@ -11,8 +25,12 @@ package bzip #cgo CFLAGS: -I/usr/include #cgo LDFLAGS: -L/usr/lib -lbz2 #include +#include + +bz_stream* bz2alloc() { return calloc(1, sizeof(bz_stream)); } int bz2compress(bz_stream *s, int action, char *in, unsigned *inlen, char *out, unsigned *outlen); +void bz2free(bz_stream* s) { return free(s); } */ import "C" @@ -34,9 +52,7 @@ func NewWriter(out io.Writer) io.WriteCloser { verbosity = 0 workFactor = 30 ) - // NOTE: This code may not work in future Go releases. - // See http://www.gopl.io/errata.html for explanation. - w := &writer{w: out, stream: new(C.bz_stream)} + w := &writer{w: out, stream: C.bz2alloc()} C.BZ2_bzCompressInit(w.stream, blockSize, verbosity, workFactor) return w } @@ -75,6 +91,7 @@ func (w *writer) Close() error { } defer func() { C.BZ2_bzCompressEnd(w.stream) + C.bz2free(w.stream) w.stream = nil }() for { diff --git a/ch7/tempconv/tempconv.go.~master~ b/ch7/tempconv/tempconv.go.~master~ new file mode 100644 index 0000000..5ee2de8 --- /dev/null +++ b/ch7/tempconv/tempconv.go.~master~ @@ -0,0 +1,64 @@ +// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ + + +// Package tempconv performs Celsius and Fahrenheit temperature computations. +package tempconv + +import ( + "flag" + "fmt" +) + +type Celsius float64 +type Fahrenheit float64 + +func CToF(c Celsius) Fahrenheit { return Fahrenheit(c*9.0/5.0 + 32.0) } +func FToC(f Fahrenheit) Celsius { return Celsius((f - 32.0) * 5.0 / 9.0) } + +func (c Celsius) String() string { return fmt.Sprintf("%g°C", c) } + +/* +//!+flagvalue +package flag + +// Value is the interface to the value stored in a flag. +type Value interface { + String() string + Set(string) error +} +//!-flagvalue +*/ + +//!+celsiusflag +// *celsiusFlag satisfies the flag.Value interface. +type celsiusFlag struct{ Celsius } + +func (f *celsiusFlag) Set(s string) error { + var unit string + var value float64 + fmt.Sscanf(s, "%f%s", &value, &unit) // no error check needed + switch unit { + case "C", "°C": + f.Celsius = Celsius(value) + return nil + case "F", "°F": + f.Celsius = FToC(Fahrenheit(value)) + return nil + } + return fmt.Errorf("invalid temperature %q", s) +} + +//!-celsiusflag + +//!+Celsiusflag +// CelsiusFlag defines a Celsius flag with the specified name, +// default value, and usage, and returns the address of the flag variable. +// The flag argument must have a quantity and a unit, e.g., "100C". +func CelsiusFlag(name string, value Celsius, usage string) *Celsius { + f := celsiusFlag{value} + flag.CommandLine.Var(&f, name, usage) + return &f.Celsius +} + +//!-Celsiusflag diff --git a/ch8/chat/chat.go.~master~ b/ch8/chat/chat.go.~master~ new file mode 100644 index 0000000..225af17 --- /dev/null +++ b/ch8/chat/chat.go.~master~ @@ -0,0 +1,89 @@ +// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ + +//!+ + +// Chat is a server that lets clients chat with each other. +package main + +import ( + "bufio" + "fmt" + "log" + "net" +) + +//!+broadcaster +type client chan<- string // an outgoing message channel + +var ( + entering = make(chan client) + leaving = make(chan client) + messages = make(chan string) // all incoming client messages +) + +func broadcaster() { + clients := make(map[client]bool) // all connected clients + for { + select { + case msg := <-messages: + // Broadcast incoming message to all + // clients' outgoing message channels. + for cli := range clients { + cli <- msg + } + case cli := <-entering: + clients[cli] = true + case cli := <-leaving: + delete(clients, cli) + close(cli) + } + } +} + +//!-broadcaster + +//!+handleConn +func handleConn(conn net.Conn) { + ch := make(chan string) // outgoing client messages + go clientWriter(conn, ch) + + who := conn.RemoteAddr().String() + entering <- ch + messages <- who + " has arrived" + input := bufio.NewScanner(conn) + for input.Scan() { + messages <- who + ": " + input.Text() + } + messages <- who + " has left" + leaving <- ch + conn.Close() +} + +func clientWriter(conn net.Conn, ch <-chan string) { + for msg := range ch { + fmt.Fprintln(conn, msg) + } +} + +//!-handleConn + +//!+main +func main() { + listener, err := net.Listen("tcp", "localhost:8000") + if err != nil { + log.Fatal(err) + } + + go broadcaster() + for { + conn, err := listener.Accept() + if err != nil { + log.Print(err) + continue + } + go handleConn(conn) + } +} + +//!-main