diff options
Diffstat (limited to 'libgo/go/exp')
-rw-r--r-- | libgo/go/exp/inotify/inotify_linux.go | 288 | ||||
-rw-r--r-- | libgo/go/exp/inotify/inotify_linux_test.go | 96 | ||||
-rw-r--r-- | libgo/go/exp/ssh/channel.go | 2 | ||||
-rw-r--r-- | libgo/go/exp/ssh/client.go | 490 | ||||
-rw-r--r-- | libgo/go/exp/ssh/doc.go | 48 | ||||
-rw-r--r-- | libgo/go/exp/ssh/messages.go | 2 | ||||
-rw-r--r-- | libgo/go/exp/ssh/server.go | 151 | ||||
-rw-r--r-- | libgo/go/exp/ssh/session.go | 132 | ||||
-rw-r--r-- | libgo/go/exp/ssh/transport.go | 17 | ||||
-rw-r--r-- | libgo/go/exp/ssh/transport_test.go | 10 | ||||
-rw-r--r-- | libgo/go/exp/types/gcimporter.go | 5 | ||||
-rw-r--r-- | libgo/go/exp/winfsnotify/winfsnotify_test.go | 10 |
12 files changed, 1166 insertions, 85 deletions
diff --git a/libgo/go/exp/inotify/inotify_linux.go b/libgo/go/exp/inotify/inotify_linux.go new file mode 100644 index 0000000..ee3c75f --- /dev/null +++ b/libgo/go/exp/inotify/inotify_linux.go @@ -0,0 +1,288 @@ +// Copyright 2010 The Go 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 inotify implements a wrapper for the Linux inotify system. + +Example: + watcher, err := inotify.NewWatcher() + if err != nil { + log.Fatal(err) + } + err = watcher.Watch("/tmp") + if err != nil { + log.Fatal(err) + } + for { + select { + case ev := <-watcher.Event: + log.Println("event:", ev) + case err := <-watcher.Error: + log.Println("error:", err) + } + } + +*/ +package inotify + +import ( + "fmt" + "os" + "strings" + "syscall" + "unsafe" +) + +type Event struct { + Mask uint32 // Mask of events + Cookie uint32 // Unique cookie associating related events (for rename(2)) + Name string // File name (optional) +} + +type watch struct { + wd uint32 // Watch descriptor (as returned by the inotify_add_watch() syscall) + flags uint32 // inotify flags of this watch (see inotify(7) for the list of valid flags) +} + +type Watcher struct { + fd int // File descriptor (as returned by the inotify_init() syscall) + watches map[string]*watch // Map of inotify watches (key: path) + paths map[int]string // Map of watched paths (key: watch descriptor) + Error chan os.Error // Errors are sent on this channel + Event chan *Event // Events are returned on this channel + done chan bool // Channel for sending a "quit message" to the reader goroutine + isClosed bool // Set to true when Close() is first called +} + +// NewWatcher creates and returns a new inotify instance using inotify_init(2) +func NewWatcher() (*Watcher, os.Error) { + fd, errno := syscall.InotifyInit() + if fd == -1 { + return nil, os.NewSyscallError("inotify_init", errno) + } + w := &Watcher{ + fd: fd, + watches: make(map[string]*watch), + paths: make(map[int]string), + Event: make(chan *Event), + Error: make(chan os.Error), + done: make(chan bool, 1), + } + + go w.readEvents() + return w, nil +} + +// Close closes an inotify watcher instance +// It sends a message to the reader goroutine to quit and removes all watches +// associated with the inotify instance +func (w *Watcher) Close() os.Error { + if w.isClosed { + return nil + } + w.isClosed = true + + // Send "quit" message to the reader goroutine + w.done <- true + for path := range w.watches { + w.RemoveWatch(path) + } + + return nil +} + +// AddWatch adds path to the watched file set. +// The flags are interpreted as described in inotify_add_watch(2). +func (w *Watcher) AddWatch(path string, flags uint32) os.Error { + if w.isClosed { + return os.NewError("inotify instance already closed") + } + + watchEntry, found := w.watches[path] + if found { + watchEntry.flags |= flags + flags |= syscall.IN_MASK_ADD + } + wd, errno := syscall.InotifyAddWatch(w.fd, path, flags) + if wd == -1 { + return &os.PathError{"inotify_add_watch", path, os.Errno(errno)} + } + + if !found { + w.watches[path] = &watch{wd: uint32(wd), flags: flags} + w.paths[wd] = path + } + return nil +} + +// Watch adds path to the watched file set, watching all events. +func (w *Watcher) Watch(path string) os.Error { + return w.AddWatch(path, IN_ALL_EVENTS) +} + +// RemoveWatch removes path from the watched file set. +func (w *Watcher) RemoveWatch(path string) os.Error { + watch, ok := w.watches[path] + if !ok { + return os.NewError(fmt.Sprintf("can't remove non-existent inotify watch for: %s", path)) + } + success, errno := syscall.InotifyRmWatch(w.fd, watch.wd) + if success == -1 { + return os.NewSyscallError("inotify_rm_watch", errno) + } + delete(w.watches, path) + return nil +} + +// readEvents reads from the inotify file descriptor, converts the +// received events into Event objects and sends them via the Event channel +func (w *Watcher) readEvents() { + var ( + buf [syscall.SizeofInotifyEvent * 4096]byte // Buffer for a maximum of 4096 raw events + n int // Number of bytes read with read() + errno int // Syscall errno + ) + + for { + n, errno = syscall.Read(w.fd, buf[0:]) + // See if there is a message on the "done" channel + var done bool + select { + case done = <-w.done: + default: + } + + // If EOF or a "done" message is received + if n == 0 || done { + errno := syscall.Close(w.fd) + if errno == -1 { + w.Error <- os.NewSyscallError("close", errno) + } + close(w.Event) + close(w.Error) + return + } + if n < 0 { + w.Error <- os.NewSyscallError("read", errno) + continue + } + if n < syscall.SizeofInotifyEvent { + w.Error <- os.NewError("inotify: short read in readEvents()") + continue + } + + var offset uint32 = 0 + // We don't know how many events we just read into the buffer + // While the offset points to at least one whole event... + for offset <= uint32(n-syscall.SizeofInotifyEvent) { + // Point "raw" to the event in the buffer + raw := (*syscall.InotifyEvent)(unsafe.Pointer(&buf[offset])) + event := new(Event) + event.Mask = uint32(raw.Mask) + event.Cookie = uint32(raw.Cookie) + nameLen := uint32(raw.Len) + // If the event happened to the watched directory or the watched file, the kernel + // doesn't append the filename to the event, but we would like to always fill the + // the "Name" field with a valid filename. We retrieve the path of the watch from + // the "paths" map. + event.Name = w.paths[int(raw.Wd)] + if nameLen > 0 { + // Point "bytes" at the first byte of the filename + bytes := (*[syscall.PathMax]byte)(unsafe.Pointer(&buf[offset+syscall.SizeofInotifyEvent])) + // The filename is padded with NUL bytes. TrimRight() gets rid of those. + event.Name += "/" + strings.TrimRight(string(bytes[0:nameLen]), "\000") + } + // Send the event on the events channel + w.Event <- event + + // Move to the next event in the buffer + offset += syscall.SizeofInotifyEvent + nameLen + } + } +} + +// String formats the event e in the form +// "filename: 0xEventMask = IN_ACCESS|IN_ATTRIB_|..." +func (e *Event) String() string { + var events string = "" + + m := e.Mask + for _, b := range eventBits { + if m&b.Value != 0 { + m &^= b.Value + events += "|" + b.Name + } + } + + if m != 0 { + events += fmt.Sprintf("|%#x", m) + } + if len(events) > 0 { + events = " == " + events[1:] + } + + return fmt.Sprintf("%q: %#x%s", e.Name, e.Mask, events) +} + +const ( + // Options for inotify_init() are not exported + // IN_CLOEXEC uint32 = syscall.IN_CLOEXEC + // IN_NONBLOCK uint32 = syscall.IN_NONBLOCK + + // Options for AddWatch + IN_DONT_FOLLOW uint32 = syscall.IN_DONT_FOLLOW + IN_ONESHOT uint32 = syscall.IN_ONESHOT + IN_ONLYDIR uint32 = syscall.IN_ONLYDIR + + // The "IN_MASK_ADD" option is not exported, as AddWatch + // adds it automatically, if there is already a watch for the given path + // IN_MASK_ADD uint32 = syscall.IN_MASK_ADD + + // Events + IN_ACCESS uint32 = syscall.IN_ACCESS + IN_ALL_EVENTS uint32 = syscall.IN_ALL_EVENTS + IN_ATTRIB uint32 = syscall.IN_ATTRIB + IN_CLOSE uint32 = syscall.IN_CLOSE + IN_CLOSE_NOWRITE uint32 = syscall.IN_CLOSE_NOWRITE + IN_CLOSE_WRITE uint32 = syscall.IN_CLOSE_WRITE + IN_CREATE uint32 = syscall.IN_CREATE + IN_DELETE uint32 = syscall.IN_DELETE + IN_DELETE_SELF uint32 = syscall.IN_DELETE_SELF + IN_MODIFY uint32 = syscall.IN_MODIFY + IN_MOVE uint32 = syscall.IN_MOVE + IN_MOVED_FROM uint32 = syscall.IN_MOVED_FROM + IN_MOVED_TO uint32 = syscall.IN_MOVED_TO + IN_MOVE_SELF uint32 = syscall.IN_MOVE_SELF + IN_OPEN uint32 = syscall.IN_OPEN + + // Special events + IN_ISDIR uint32 = syscall.IN_ISDIR + IN_IGNORED uint32 = syscall.IN_IGNORED + IN_Q_OVERFLOW uint32 = syscall.IN_Q_OVERFLOW + IN_UNMOUNT uint32 = syscall.IN_UNMOUNT +) + +var eventBits = []struct { + Value uint32 + Name string +}{ + {IN_ACCESS, "IN_ACCESS"}, + {IN_ATTRIB, "IN_ATTRIB"}, + {IN_CLOSE, "IN_CLOSE"}, + {IN_CLOSE_NOWRITE, "IN_CLOSE_NOWRITE"}, + {IN_CLOSE_WRITE, "IN_CLOSE_WRITE"}, + {IN_CREATE, "IN_CREATE"}, + {IN_DELETE, "IN_DELETE"}, + {IN_DELETE_SELF, "IN_DELETE_SELF"}, + {IN_MODIFY, "IN_MODIFY"}, + {IN_MOVE, "IN_MOVE"}, + {IN_MOVED_FROM, "IN_MOVED_FROM"}, + {IN_MOVED_TO, "IN_MOVED_TO"}, + {IN_MOVE_SELF, "IN_MOVE_SELF"}, + {IN_OPEN, "IN_OPEN"}, + {IN_ISDIR, "IN_ISDIR"}, + {IN_IGNORED, "IN_IGNORED"}, + {IN_Q_OVERFLOW, "IN_Q_OVERFLOW"}, + {IN_UNMOUNT, "IN_UNMOUNT"}, +} diff --git a/libgo/go/exp/inotify/inotify_linux_test.go b/libgo/go/exp/inotify/inotify_linux_test.go new file mode 100644 index 0000000..a6bb46f --- /dev/null +++ b/libgo/go/exp/inotify/inotify_linux_test.go @@ -0,0 +1,96 @@ +// Copyright 2010 The Go 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 inotify + +import ( + "os" + "testing" + "time" +) + +func TestInotifyEvents(t *testing.T) { + // Create an inotify watcher instance and initialize it + watcher, err := NewWatcher() + if err != nil { + t.Fatalf("NewWatcher() failed: %s", err) + } + + // Add a watch for "_test" + err = watcher.Watch("_test") + if err != nil { + t.Fatalf("Watcher.Watch() failed: %s", err) + } + + // Receive errors on the error channel on a separate goroutine + go func() { + for err := range watcher.Error { + t.Fatalf("error received: %s", err) + } + }() + + const testFile string = "_test/TestInotifyEvents.testfile" + + // Receive events on the event channel on a separate goroutine + eventstream := watcher.Event + var eventsReceived = 0 + done := make(chan bool) + go func() { + for event := range eventstream { + // Only count relevant events + if event.Name == testFile { + eventsReceived++ + t.Logf("event received: %s", event) + } else { + t.Logf("unexpected event received: %s", event) + } + } + done <- true + }() + + // Create a file + // This should add at least one event to the inotify event queue + _, err = os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666) + if err != nil { + t.Fatalf("creating test file failed: %s", err) + } + + // We expect this event to be received almost immediately, but let's wait 1 s to be sure + time.Sleep(1000e6) // 1000 ms + if eventsReceived == 0 { + t.Fatal("inotify event hasn't been received after 1 second") + } + + // Try closing the inotify instance + t.Log("calling Close()") + watcher.Close() + t.Log("waiting for the event channel to become closed...") + select { + case <-done: + t.Log("event channel closed") + case <-time.After(1e9): + t.Fatal("event stream was not closed after 1 second") + } +} + +func TestInotifyClose(t *testing.T) { + watcher, _ := NewWatcher() + watcher.Close() + + done := false + go func() { + watcher.Close() + done = true + }() + + time.Sleep(50e6) // 50 ms + if !done { + t.Fatal("double Close() test failed: second Close() call didn't return") + } + + err := watcher.Watch("_test") + if err == nil { + t.Fatal("expected error on Watch() after Close(), got nil") + } +} diff --git a/libgo/go/exp/ssh/channel.go b/libgo/go/exp/ssh/channel.go index 922584f..f69b735 100644 --- a/libgo/go/exp/ssh/channel.go +++ b/libgo/go/exp/ssh/channel.go @@ -68,7 +68,7 @@ type channel struct { weClosed bool dead bool - serverConn *ServerConnection + serverConn *ServerConn myId, theirId uint32 myWindow, theirWindow uint32 maxPacketSize uint32 diff --git a/libgo/go/exp/ssh/client.go b/libgo/go/exp/ssh/client.go new file mode 100644 index 0000000..3311385 --- /dev/null +++ b/libgo/go/exp/ssh/client.go @@ -0,0 +1,490 @@ +// Copyright 2011 The Go 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 ssh + +import ( + "big" + "crypto" + "crypto/rand" + "fmt" + "io" + "net" + "os" + "sync" +) + +// clientVersion is the fixed identification string that the client will use. +var clientVersion = []byte("SSH-2.0-Go\r\n") + +// ClientConn represents the client side of an SSH connection. +type ClientConn struct { + *transport + config *ClientConfig + chanlist +} + +// Client returns a new SSH client connection using c as the underlying transport. +func Client(c net.Conn, config *ClientConfig) (*ClientConn, os.Error) { + conn := &ClientConn{ + transport: newTransport(c, config.rand()), + config: config, + } + if err := conn.handshake(); err != nil { + conn.Close() + return nil, err + } + if err := conn.authenticate(); err != nil { + conn.Close() + return nil, err + } + go conn.mainLoop() + return conn, nil +} + +// handshake performs the client side key exchange. See RFC 4253 Section 7. +func (c *ClientConn) handshake() os.Error { + var magics handshakeMagics + + if _, err := c.Write(clientVersion); err != nil { + return err + } + if err := c.Flush(); err != nil { + return err + } + magics.clientVersion = clientVersion[:len(clientVersion)-2] + + // read remote server version + version, err := readVersion(c) + if err != nil { + return err + } + magics.serverVersion = version + clientKexInit := kexInitMsg{ + KexAlgos: supportedKexAlgos, + ServerHostKeyAlgos: supportedHostKeyAlgos, + CiphersClientServer: supportedCiphers, + CiphersServerClient: supportedCiphers, + MACsClientServer: supportedMACs, + MACsServerClient: supportedMACs, + CompressionClientServer: supportedCompressions, + CompressionServerClient: supportedCompressions, + } + kexInitPacket := marshal(msgKexInit, clientKexInit) + magics.clientKexInit = kexInitPacket + + if err := c.writePacket(kexInitPacket); err != nil { + return err + } + packet, err := c.readPacket() + if err != nil { + return err + } + + magics.serverKexInit = packet + + var serverKexInit kexInitMsg + if err = unmarshal(&serverKexInit, packet, msgKexInit); err != nil { + return err + } + + kexAlgo, hostKeyAlgo, ok := findAgreedAlgorithms(c.transport, &clientKexInit, &serverKexInit) + if !ok { + return os.NewError("ssh: no common algorithms") + } + + if serverKexInit.FirstKexFollows && kexAlgo != serverKexInit.KexAlgos[0] { + // The server sent a Kex message for the wrong algorithm, + // which we have to ignore. + if _, err := c.readPacket(); err != nil { + return err + } + } + + var H, K []byte + var hashFunc crypto.Hash + switch kexAlgo { + case kexAlgoDH14SHA1: + hashFunc = crypto.SHA1 + dhGroup14Once.Do(initDHGroup14) + H, K, err = c.kexDH(dhGroup14, hashFunc, &magics, hostKeyAlgo) + default: + err = fmt.Errorf("ssh: unexpected key exchange algorithm %v", kexAlgo) + } + if err != nil { + return err + } + + if err = c.writePacket([]byte{msgNewKeys}); err != nil { + return err + } + if err = c.transport.writer.setupKeys(clientKeys, K, H, H, hashFunc); err != nil { + return err + } + if packet, err = c.readPacket(); err != nil { + return err + } + if packet[0] != msgNewKeys { + return UnexpectedMessageError{msgNewKeys, packet[0]} + } + return c.transport.reader.setupKeys(serverKeys, K, H, H, hashFunc) +} + +// authenticate authenticates with the remote server. See RFC 4252. +// Only "password" authentication is supported. +func (c *ClientConn) authenticate() os.Error { + if err := c.writePacket(marshal(msgServiceRequest, serviceRequestMsg{serviceUserAuth})); err != nil { + return err + } + packet, err := c.readPacket() + if err != nil { + return err + } + + var serviceAccept serviceAcceptMsg + if err = unmarshal(&serviceAccept, packet, msgServiceAccept); err != nil { + return err + } + + // TODO(dfc) support proper authentication method negotation + method := "none" + if c.config.Password != "" { + method = "password" + } + if err := c.sendUserAuthReq(method); err != nil { + return err + } + + if packet, err = c.readPacket(); err != nil { + return err + } + + if packet[0] != msgUserAuthSuccess { + return UnexpectedMessageError{msgUserAuthSuccess, packet[0]} + } + return nil +} + +func (c *ClientConn) sendUserAuthReq(method string) os.Error { + length := stringLength([]byte(c.config.Password)) + 1 + payload := make([]byte, length) + // always false for password auth, see RFC 4252 Section 8. + payload[0] = 0 + marshalString(payload[1:], []byte(c.config.Password)) + + return c.writePacket(marshal(msgUserAuthRequest, userAuthRequestMsg{ + User: c.config.User, + Service: serviceSSH, + Method: method, + Payload: payload, + })) +} + +// kexDH performs Diffie-Hellman key agreement on a ClientConn. The +// returned values are given the same names as in RFC 4253, section 8. +func (c *ClientConn) kexDH(group *dhGroup, hashFunc crypto.Hash, magics *handshakeMagics, hostKeyAlgo string) ([]byte, []byte, os.Error) { + x, err := rand.Int(c.config.rand(), group.p) + if err != nil { + return nil, nil, err + } + X := new(big.Int).Exp(group.g, x, group.p) + kexDHInit := kexDHInitMsg{ + X: X, + } + if err := c.writePacket(marshal(msgKexDHInit, kexDHInit)); err != nil { + return nil, nil, err + } + + packet, err := c.readPacket() + if err != nil { + return nil, nil, err + } + + var kexDHReply = new(kexDHReplyMsg) + if err = unmarshal(kexDHReply, packet, msgKexDHReply); err != nil { + return nil, nil, err + } + + if kexDHReply.Y.Sign() == 0 || kexDHReply.Y.Cmp(group.p) >= 0 { + return nil, nil, os.NewError("server DH parameter out of bounds") + } + + kInt := new(big.Int).Exp(kexDHReply.Y, x, group.p) + h := hashFunc.New() + writeString(h, magics.clientVersion) + writeString(h, magics.serverVersion) + writeString(h, magics.clientKexInit) + writeString(h, magics.serverKexInit) + writeString(h, kexDHReply.HostKey) + writeInt(h, X) + writeInt(h, kexDHReply.Y) + K := make([]byte, intLength(kInt)) + marshalInt(K, kInt) + h.Write(K) + + H := h.Sum() + + return H, K, nil +} + +// openChan opens a new client channel. The most common session type is "session". +// The full set of valid session types are listed in RFC 4250 4.9.1. +func (c *ClientConn) openChan(typ string) (*clientChan, os.Error) { + ch := c.newChan(c.transport) + if err := c.writePacket(marshal(msgChannelOpen, channelOpenMsg{ + ChanType: typ, + PeersId: ch.id, + PeersWindow: 1 << 14, + MaxPacketSize: 1 << 15, // RFC 4253 6.1 + })); err != nil { + c.chanlist.remove(ch.id) + return nil, err + } + // wait for response + switch msg := (<-ch.msg).(type) { + case *channelOpenConfirmMsg: + ch.peersId = msg.MyId + case *channelOpenFailureMsg: + c.chanlist.remove(ch.id) + return nil, os.NewError(msg.Message) + default: + c.chanlist.remove(ch.id) + return nil, os.NewError("Unexpected packet") + } + return ch, nil +} + +// mainloop reads incoming messages and routes channel messages +// to their respective ClientChans. +func (c *ClientConn) mainLoop() { + for { + packet, err := c.readPacket() + if err != nil { + // TODO(dfc) signal the underlying close to all channels + c.Close() + return + } + // TODO(dfc) A note on blocking channel use. + // The msg, win, data and dataExt channels of a clientChan can + // cause this loop to block indefinately if the consumer does + // not service them. + switch msg := decode(packet).(type) { + case *channelOpenMsg: + c.getChan(msg.PeersId).msg <- msg + case *channelOpenConfirmMsg: + c.getChan(msg.PeersId).msg <- msg + case *channelOpenFailureMsg: + c.getChan(msg.PeersId).msg <- msg + case *channelCloseMsg: + ch := c.getChan(msg.PeersId) + close(ch.win) + close(ch.data) + close(ch.dataExt) + c.chanlist.remove(msg.PeersId) + case *channelEOFMsg: + c.getChan(msg.PeersId).msg <- msg + case *channelRequestSuccessMsg: + c.getChan(msg.PeersId).msg <- msg + case *channelRequestFailureMsg: + c.getChan(msg.PeersId).msg <- msg + case *channelRequestMsg: + c.getChan(msg.PeersId).msg <- msg + case *windowAdjustMsg: + c.getChan(msg.PeersId).win <- int(msg.AdditionalBytes) + case *channelData: + c.getChan(msg.PeersId).data <- msg.Payload + case *channelExtendedData: + // RFC 4254 5.2 defines data_type_code 1 to be data destined + // for stderr on interactive sessions. Other data types are + // silently discarded. + if msg.Datatype == 1 { + c.getChan(msg.PeersId).dataExt <- msg.Payload + } + default: + fmt.Printf("mainLoop: unhandled %#v\n", msg) + } + } +} + +// Dial connects to the given network address using net.Dial and +// then initiates a SSH handshake, returning the resulting client connection. +func Dial(network, addr string, config *ClientConfig) (*ClientConn, os.Error) { + conn, err := net.Dial(network, addr) + if err != nil { + return nil, err + } + return Client(conn, config) +} + +// A ClientConfig structure is used to configure a ClientConn. After one has +// been passed to an SSH function it must not be modified. +type ClientConfig struct { + // Rand provides the source of entropy for key exchange. If Rand is + // nil, the cryptographic random reader in package crypto/rand will + // be used. + Rand io.Reader + + // The username to authenticate. + User string + + // Used for "password" method authentication. + Password string +} + +func (c *ClientConfig) rand() io.Reader { + if c.Rand == nil { + return rand.Reader + } + return c.Rand +} + +// A clientChan represents a single RFC 4254 channel that is multiplexed +// over a single SSH connection. +type clientChan struct { + packetWriter + id, peersId uint32 + data chan []byte // receives the payload of channelData messages + dataExt chan []byte // receives the payload of channelExtendedData messages + win chan int // receives window adjustments + msg chan interface{} // incoming messages +} + +func newClientChan(t *transport, id uint32) *clientChan { + return &clientChan{ + packetWriter: t, + id: id, + data: make(chan []byte, 16), + dataExt: make(chan []byte, 16), + win: make(chan int, 16), + msg: make(chan interface{}, 16), + } +} + +// Close closes the channel. This does not close the underlying connection. +func (c *clientChan) Close() os.Error { + return c.writePacket(marshal(msgChannelClose, channelCloseMsg{ + PeersId: c.id, + })) +} + +func (c *clientChan) sendChanReq(req channelRequestMsg) os.Error { + if err := c.writePacket(marshal(msgChannelRequest, req)); err != nil { + return err + } + msg := <-c.msg + if _, ok := msg.(*channelRequestSuccessMsg); ok { + return nil + } + return fmt.Errorf("failed to complete request: %s, %#v", req.Request, msg) +} + +// Thread safe channel list. +type chanlist struct { + // protects concurrent access to chans + sync.Mutex + // chans are indexed by the local id of the channel, clientChan.id. + // The PeersId value of messages received by ClientConn.mainloop is + // used to locate the right local clientChan in this slice. + chans []*clientChan +} + +// Allocate a new ClientChan with the next avail local id. +func (c *chanlist) newChan(t *transport) *clientChan { + c.Lock() + defer c.Unlock() + for i := range c.chans { + if c.chans[i] == nil { + ch := newClientChan(t, uint32(i)) + c.chans[i] = ch + return ch + } + } + i := len(c.chans) + ch := newClientChan(t, uint32(i)) + c.chans = append(c.chans, ch) + return ch +} + +func (c *chanlist) getChan(id uint32) *clientChan { + c.Lock() + defer c.Unlock() + return c.chans[int(id)] +} + +func (c *chanlist) remove(id uint32) { + c.Lock() + defer c.Unlock() + c.chans[int(id)] = nil +} + +// A chanWriter represents the stdin of a remote process. +type chanWriter struct { + win chan int // receives window adjustments + id uint32 // this channel's id + rwin int // current rwin size + packetWriter // for sending channelDataMsg +} + +// Write writes data to the remote process's standard input. +func (w *chanWriter) Write(data []byte) (n int, err os.Error) { + for { + if w.rwin == 0 { + win, ok := <-w.win + if !ok { + return 0, os.EOF + } + w.rwin += win + continue + } + n = len(data) + packet := make([]byte, 0, 9+n) + packet = append(packet, msgChannelData, + byte(w.id)>>24, byte(w.id)>>16, byte(w.id)>>8, byte(w.id), + byte(n)>>24, byte(n)>>16, byte(n)>>8, byte(n)) + err = w.writePacket(append(packet, data...)) + w.rwin -= n + return + } + panic("unreachable") +} + +func (w *chanWriter) Close() os.Error { + return w.writePacket(marshal(msgChannelEOF, channelEOFMsg{w.id})) +} + +// A chanReader represents stdout or stderr of a remote process. +type chanReader struct { + // TODO(dfc) a fixed size channel may not be the right data structure. + // If writes to this channel block, they will block mainLoop, making + // it unable to receive new messages from the remote side. + data chan []byte // receives data from remote + id uint32 + packetWriter // for sending windowAdjustMsg + buf []byte +} + +// Read reads data from the remote process's stdout or stderr. +func (r *chanReader) Read(data []byte) (int, os.Error) { + var ok bool + for { + if len(r.buf) > 0 { + n := copy(data, r.buf) + r.buf = r.buf[n:] + msg := windowAdjustMsg{ + PeersId: r.id, + AdditionalBytes: uint32(n), + } + return n, r.writePacket(marshal(msgChannelWindowAdjust, msg)) + } + r.buf, ok = <-r.data + if !ok { + return 0, os.EOF + } + } + panic("unreachable") +} + +func (r *chanReader) Close() os.Error { + return r.writePacket(marshal(msgChannelEOF, channelEOFMsg{r.id})) +} diff --git a/libgo/go/exp/ssh/doc.go b/libgo/go/exp/ssh/doc.go index 54a7ba9..fc842b0 100644 --- a/libgo/go/exp/ssh/doc.go +++ b/libgo/go/exp/ssh/doc.go @@ -3,7 +3,7 @@ // license that can be found in the LICENSE file. /* -Package ssh implements an SSH server. +Package ssh implements an SSH client and server. SSH is a transport security protocol, an authentication protocol and a family of application protocols. The most typical application level @@ -11,26 +11,29 @@ protocol is a remote shell and this is specifically implemented. However, the multiplexed nature of SSH is exposed to users that wish to support others. -An SSH server is represented by a Server, which manages a number of -ServerConnections and handles authentication. +An SSH server is represented by a ServerConfig, which holds certificate +details and handles authentication of ServerConns. - var s Server - s.PubKeyCallback = pubKeyAuth - s.PasswordCallback = passwordAuth + config := new(ServerConfig) + config.PubKeyCallback = pubKeyAuth + config.PasswordCallback = passwordAuth pemBytes, err := ioutil.ReadFile("id_rsa") if err != nil { panic("Failed to load private key") } - err = s.SetRSAPrivateKey(pemBytes) + err = config.SetRSAPrivateKey(pemBytes) if err != nil { panic("Failed to parse private key") } -Once a Server has been set up, connections can be attached. +Once a ServerConfig has been configured, connections can be accepted. - var sConn ServerConnection - sConn.Server = &s + listener := Listen("tcp", "0.0.0.0:2022", config) + sConn, err := listener.Accept() + if err != nil { + panic("failed to accept incoming connection") + } err = sConn.Handshake(conn) if err != nil { panic("failed to handshake") @@ -38,7 +41,6 @@ Once a Server has been set up, connections can be attached. An SSH connection multiplexes several channels, which must be accepted themselves: - for { channel, err := sConn.Accept() if err != nil { @@ -75,5 +77,29 @@ present a simple terminal interface. } return }() + +An SSH client is represented with a ClientConn. Currently only the "password" +authentication method is supported. + + config := &ClientConfig{ + User: "username", + Password: "123456", + } + client, err := Dial("yourserver.com:22", config) + +Each ClientConn can support multiple interactive sessions, represented by a Session. + + session, err := client.NewSession() + +Once a Session is created, you can execute a single command on the remote side +using the Exec method. + + if err := session.Exec("/usr/bin/whoami"); err != nil { + panic("Failed to exec: " + err.String()) + } + reader := bufio.NewReader(session.Stdin) + line, _, _ := reader.ReadLine() + fmt.Println(line) + session.Close() */ package ssh diff --git a/libgo/go/exp/ssh/messages.go b/libgo/go/exp/ssh/messages.go index 1d0bc57..7771f2b 100644 --- a/libgo/go/exp/ssh/messages.go +++ b/libgo/go/exp/ssh/messages.go @@ -154,7 +154,7 @@ type channelData struct { type channelExtendedData struct { PeersId uint32 Datatype uint32 - Data string + Payload []byte `ssh:"rest"` } type channelRequestMsg struct { diff --git a/libgo/go/exp/ssh/server.go b/libgo/go/exp/ssh/server.go index 410cafc..3a640fc 100644 --- a/libgo/go/exp/ssh/server.go +++ b/libgo/go/exp/ssh/server.go @@ -10,19 +10,23 @@ import ( "crypto" "crypto/rand" "crypto/rsa" - _ "crypto/sha1" "crypto/x509" "encoding/pem" + "io" "net" "os" "sync" ) -// Server represents an SSH server. A Server may have several ServerConnections. -type Server struct { +type ServerConfig struct { rsa *rsa.PrivateKey rsaSerialized []byte + // Rand provides the source of entropy for key exchange. If Rand is + // nil, the cryptographic random reader in package crypto/rand will + // be used. + Rand io.Reader + // NoClientAuth is true if clients are allowed to connect without // authenticating. NoClientAuth bool @@ -38,11 +42,18 @@ type Server struct { PubKeyCallback func(user, algo string, pubkey []byte) bool } +func (c *ServerConfig) rand() io.Reader { + if c.Rand == nil { + return rand.Reader + } + return c.Rand +} + // SetRSAPrivateKey sets the private key for a Server. A Server must have a // private key configured in order to accept connections. The private key must // be in the form of a PEM encoded, PKCS#1, RSA private key. The file "id_rsa" // typically contains such a key. -func (s *Server) SetRSAPrivateKey(pemBytes []byte) os.Error { +func (s *ServerConfig) SetRSAPrivateKey(pemBytes []byte) os.Error { block, _ := pem.Decode(pemBytes) if block == nil { return os.NewError("ssh: no key found") @@ -109,7 +120,7 @@ func parseRSASig(in []byte) (sig []byte, ok bool) { } // cachedPubKey contains the results of querying whether a public key is -// acceptable for a user. The cache only applies to a single ServerConnection. +// acceptable for a user. The cache only applies to a single ServerConn. type cachedPubKey struct { user, algo string pubKey []byte @@ -118,11 +129,10 @@ type cachedPubKey struct { const maxCachedPubKeys = 16 -// ServerConnection represents an incomming connection to a Server. -type ServerConnection struct { - Server *Server - +// A ServerConn represents an incomming connection. +type ServerConn struct { *transport + config *ServerConfig channels map[uint32]*channel nextChanId uint32 @@ -139,9 +149,20 @@ type ServerConnection struct { cachedPubKeys []cachedPubKey } +// Server returns a new SSH server connection +// using c as the underlying transport. +func Server(c net.Conn, config *ServerConfig) *ServerConn { + conn := &ServerConn{ + transport: newTransport(c, config.rand()), + channels: make(map[uint32]*channel), + config: config, + } + return conn +} + // kexDH performs Diffie-Hellman key agreement on a ServerConnection. The // returned values are given the same names as in RFC 4253, section 8. -func (s *ServerConnection) kexDH(group *dhGroup, hashFunc crypto.Hash, magics *handshakeMagics, hostKeyAlgo string) (H, K []byte, err os.Error) { +func (s *ServerConn) kexDH(group *dhGroup, hashFunc crypto.Hash, magics *handshakeMagics, hostKeyAlgo string) (H, K []byte, err os.Error) { packet, err := s.readPacket() if err != nil { return @@ -155,7 +176,7 @@ func (s *ServerConnection) kexDH(group *dhGroup, hashFunc crypto.Hash, magics *h return nil, nil, os.NewError("client DH parameter out of bounds") } - y, err := rand.Int(rand.Reader, group.p) + y, err := rand.Int(s.config.rand(), group.p) if err != nil { return } @@ -166,7 +187,7 @@ func (s *ServerConnection) kexDH(group *dhGroup, hashFunc crypto.Hash, magics *h var serializedHostKey []byte switch hostKeyAlgo { case hostAlgoRSA: - serializedHostKey = s.Server.rsaSerialized + serializedHostKey = s.config.rsaSerialized default: return nil, nil, os.NewError("internal error") } @@ -192,7 +213,7 @@ func (s *ServerConnection) kexDH(group *dhGroup, hashFunc crypto.Hash, magics *h var sig []byte switch hostKeyAlgo { case hostAlgoRSA: - sig, err = rsa.SignPKCS1v15(rand.Reader, s.Server.rsa, hashFunc, hh) + sig, err = rsa.SignPKCS1v15(s.config.rand(), s.config.rsa, hashFunc, hh) if err != nil { return } @@ -257,19 +278,20 @@ func buildDataSignedForAuth(sessionId []byte, req userAuthRequestMsg, algo, pubK return ret } -// Handshake performs an SSH transport and client authentication on the given ServerConnection. -func (s *ServerConnection) Handshake(conn net.Conn) os.Error { +// Handshake performs an SSH transport and client authentication on the given ServerConn. +func (s *ServerConn) Handshake() os.Error { var magics handshakeMagics - s.transport = newTransport(conn, rand.Reader) - - if _, err := conn.Write(serverVersion); err != nil { + if _, err := s.Write(serverVersion); err != nil { + return err + } + if err := s.Flush(); err != nil { return err } magics.serverVersion = serverVersion[:len(serverVersion)-2] - version, ok := readVersion(s.transport) - if !ok { - return os.NewError("failed to read version string from client") + version, err := readVersion(s) + if err != nil { + return err } magics.clientVersion = version @@ -310,8 +332,7 @@ func (s *ServerConnection) Handshake(conn net.Conn) os.Error { if clientKexInit.FirstKexFollows && kexAlgo != clientKexInit.KexAlgos[0] { // The client sent a Kex message for the wrong algorithm, // which we have to ignore. - _, err := s.readPacket() - if err != nil { + if _, err := s.readPacket(); err != nil { return err } } @@ -324,32 +345,27 @@ func (s *ServerConnection) Handshake(conn net.Conn) os.Error { dhGroup14Once.Do(initDHGroup14) H, K, err = s.kexDH(dhGroup14, hashFunc, &magics, hostKeyAlgo) default: - err = os.NewError("ssh: internal error") + err = os.NewError("ssh: unexpected key exchange algorithm " + kexAlgo) } - if err != nil { return err } - packet = []byte{msgNewKeys} - if err = s.writePacket(packet); err != nil { + if err = s.writePacket([]byte{msgNewKeys}); err != nil { return err } if err = s.transport.writer.setupKeys(serverKeys, K, H, H, hashFunc); err != nil { return err } - if packet, err = s.readPacket(); err != nil { return err } + if packet[0] != msgNewKeys { return UnexpectedMessageError{msgNewKeys, packet[0]} } - s.transport.reader.setupKeys(clientKeys, K, H, H, hashFunc) - - packet, err = s.readPacket() - if err != nil { + if packet, err = s.readPacket(); err != nil { return err } @@ -360,20 +376,16 @@ func (s *ServerConnection) Handshake(conn net.Conn) os.Error { if serviceRequest.Service != serviceUserAuth { return os.NewError("ssh: requested service '" + serviceRequest.Service + "' before authenticating") } - serviceAccept := serviceAcceptMsg{ Service: serviceUserAuth, } - packet = marshal(msgServiceAccept, serviceAccept) - if err = s.writePacket(packet); err != nil { + if err = s.writePacket(marshal(msgServiceAccept, serviceAccept)); err != nil { return err } if err = s.authenticate(H); err != nil { return err } - - s.channels = make(map[uint32]*channel) return nil } @@ -382,8 +394,8 @@ func isAcceptableAlgo(algo string) bool { } // testPubKey returns true if the given public key is acceptable for the user. -func (s *ServerConnection) testPubKey(user, algo string, pubKey []byte) bool { - if s.Server.PubKeyCallback == nil || !isAcceptableAlgo(algo) { +func (s *ServerConn) testPubKey(user, algo string, pubKey []byte) bool { + if s.config.PubKeyCallback == nil || !isAcceptableAlgo(algo) { return false } @@ -393,7 +405,7 @@ func (s *ServerConnection) testPubKey(user, algo string, pubKey []byte) bool { } } - result := s.Server.PubKeyCallback(user, algo, pubKey) + result := s.config.PubKeyCallback(user, algo, pubKey) if len(s.cachedPubKeys) < maxCachedPubKeys { c := cachedPubKey{ user: user, @@ -408,7 +420,7 @@ func (s *ServerConnection) testPubKey(user, algo string, pubKey []byte) bool { return result } -func (s *ServerConnection) authenticate(H []byte) os.Error { +func (s *ServerConn) authenticate(H []byte) os.Error { var userAuthReq userAuthRequestMsg var err os.Error var packet []byte @@ -428,11 +440,11 @@ userAuthLoop: switch userAuthReq.Method { case "none": - if s.Server.NoClientAuth { + if s.config.NoClientAuth { break userAuthLoop } case "password": - if s.Server.PasswordCallback == nil { + if s.config.PasswordCallback == nil { break } payload := userAuthReq.Payload @@ -445,11 +457,11 @@ userAuthLoop: return ParseError{msgUserAuthRequest} } - if s.Server.PasswordCallback(userAuthReq.User, string(password)) { + if s.config.PasswordCallback(userAuthReq.User, string(password)) { break userAuthLoop } case "publickey": - if s.Server.PubKeyCallback == nil { + if s.config.PubKeyCallback == nil { break } payload := userAuthReq.Payload @@ -520,10 +532,10 @@ userAuthLoop: } var failureMsg userAuthFailureMsg - if s.Server.PasswordCallback != nil { + if s.config.PasswordCallback != nil { failureMsg.Methods = append(failureMsg.Methods, "password") } - if s.Server.PubKeyCallback != nil { + if s.config.PubKeyCallback != nil { failureMsg.Methods = append(failureMsg.Methods, "publickey") } @@ -546,9 +558,9 @@ userAuthLoop: const defaultWindowSize = 32768 -// Accept reads and processes messages on a ServerConnection. It must be called +// Accept reads and processes messages on a ServerConn. It must be called // in order to demultiplex messages to any resulting Channels. -func (s *ServerConnection) Accept() (Channel, os.Error) { +func (s *ServerConn) Accept() (Channel, os.Error) { if s.err != nil { return nil, s.err } @@ -643,3 +655,44 @@ func (s *ServerConnection) Accept() (Channel, os.Error) { panic("unreachable") } + +// A Listener implements a network listener (net.Listener) for SSH connections. +type Listener struct { + listener net.Listener + config *ServerConfig +} + +// Accept waits for and returns the next incoming SSH connection. +// The receiver should call Handshake() in another goroutine +// to avoid blocking the accepter. +func (l *Listener) Accept() (*ServerConn, os.Error) { + c, err := l.listener.Accept() + if err != nil { + return nil, err + } + conn := Server(c, l.config) + return conn, nil +} + +// Addr returns the listener's network address. +func (l *Listener) Addr() net.Addr { + return l.listener.Addr() +} + +// Close closes the listener. +func (l *Listener) Close() os.Error { + return l.listener.Close() +} + +// Listen creates an SSH listener accepting connections on +// the given network address using net.Listen. +func Listen(network, addr string, config *ServerConfig) (*Listener, os.Error) { + l, err := net.Listen(network, addr) + if err != nil { + return nil, err + } + return &Listener{ + l, + config, + }, nil +} diff --git a/libgo/go/exp/ssh/session.go b/libgo/go/exp/ssh/session.go new file mode 100644 index 0000000..13df2f0 --- /dev/null +++ b/libgo/go/exp/ssh/session.go @@ -0,0 +1,132 @@ +// Copyright 2011 The Go 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 ssh + +// Session implements an interactive session described in +// "RFC 4254, section 6". + +import ( + "encoding/binary" + "io" + "os" +) + +// A Session represents a connection to a remote command or shell. +type Session struct { + // Writes to Stdin are made available to the remote command's standard input. + // Closing Stdin causes the command to observe an EOF on its standard input. + Stdin io.WriteCloser + + // Reads from Stdout and Stderr consume from the remote command's standard + // output and error streams, respectively. + // There is a fixed amount of buffering that is shared for the two streams. + // Failing to read from either may eventually cause the command to block. + // Closing Stdout unblocks such writes and causes them to return errors. + Stdout io.ReadCloser + Stderr io.Reader + + *clientChan // the channel backing this session + + started bool // started is set to true once a Shell or Exec is invoked. +} + +// Setenv sets an environment variable that will be applied to any +// command executed by Shell or Exec. +func (s *Session) Setenv(name, value string) os.Error { + n, v := []byte(name), []byte(value) + nlen, vlen := stringLength(n), stringLength(v) + payload := make([]byte, nlen+vlen) + marshalString(payload[:nlen], n) + marshalString(payload[nlen:], v) + + return s.sendChanReq(channelRequestMsg{ + PeersId: s.id, + Request: "env", + WantReply: true, + RequestSpecificData: payload, + }) +} + +// An empty mode list (a string of 1 character, opcode 0), see RFC 4254 Section 8. +var emptyModeList = []byte{0, 0, 0, 1, 0} + +// RequestPty requests the association of a pty with the session on the remote host. +func (s *Session) RequestPty(term string, h, w int) os.Error { + buf := make([]byte, 4+len(term)+16+len(emptyModeList)) + b := marshalString(buf, []byte(term)) + binary.BigEndian.PutUint32(b, uint32(h)) + binary.BigEndian.PutUint32(b[4:], uint32(w)) + binary.BigEndian.PutUint32(b[8:], uint32(h*8)) + binary.BigEndian.PutUint32(b[12:], uint32(w*8)) + copy(b[16:], emptyModeList) + + return s.sendChanReq(channelRequestMsg{ + PeersId: s.id, + Request: "pty-req", + WantReply: true, + RequestSpecificData: buf, + }) +} + +// Exec runs cmd on the remote host. Typically, the remote +// server passes cmd to the shell for interpretation. +// A Session only accepts one call to Exec or Shell. +func (s *Session) Exec(cmd string) os.Error { + if s.started { + return os.NewError("session already started") + } + cmdLen := stringLength([]byte(cmd)) + payload := make([]byte, cmdLen) + marshalString(payload, []byte(cmd)) + s.started = true + + return s.sendChanReq(channelRequestMsg{ + PeersId: s.id, + Request: "exec", + WantReply: true, + RequestSpecificData: payload, + }) +} + +// Shell starts a login shell on the remote host. A Session only +// accepts one call to Exec or Shell. +func (s *Session) Shell() os.Error { + if s.started { + return os.NewError("session already started") + } + s.started = true + + return s.sendChanReq(channelRequestMsg{ + PeersId: s.id, + Request: "shell", + WantReply: true, + }) +} + +// NewSession returns a new interactive session on the remote host. +func (c *ClientConn) NewSession() (*Session, os.Error) { + ch, err := c.openChan("session") + if err != nil { + return nil, err + } + return &Session{ + Stdin: &chanWriter{ + packetWriter: ch, + id: ch.id, + win: ch.win, + }, + Stdout: &chanReader{ + packetWriter: ch, + id: ch.id, + data: ch.data, + }, + Stderr: &chanReader{ + packetWriter: ch, + id: ch.id, + data: ch.dataExt, + }, + clientChan: ch, + }, nil +} diff --git a/libgo/go/exp/ssh/transport.go b/libgo/go/exp/ssh/transport.go index 5994004..97eaf97 100644 --- a/libgo/go/exp/ssh/transport.go +++ b/libgo/go/exp/ssh/transport.go @@ -332,16 +332,15 @@ func (t truncatingMAC) Size() int { const maxVersionStringBytes = 1024 // Read version string as specified by RFC 4253, section 4.2. -func readVersion(r io.Reader) (versionString []byte, ok bool) { - versionString = make([]byte, 0, 64) - seenCR := false - +func readVersion(r io.Reader) ([]byte, os.Error) { + versionString := make([]byte, 0, 64) + var ok, seenCR bool var buf [1]byte forEachByte: for len(versionString) < maxVersionStringBytes { _, err := io.ReadFull(r, buf[:]) if err != nil { - return + return nil, err } b := buf[0] @@ -360,10 +359,10 @@ forEachByte: versionString = append(versionString, b) } - if ok { - // We need to remove the CR from versionString - versionString = versionString[:len(versionString)-1] + if !ok { + return nil, os.NewError("failed to read version string") } - return + // We need to remove the CR from versionString + return versionString[:len(versionString)-1], nil } diff --git a/libgo/go/exp/ssh/transport_test.go b/libgo/go/exp/ssh/transport_test.go index 9a610a7..b2e2a7f 100644 --- a/libgo/go/exp/ssh/transport_test.go +++ b/libgo/go/exp/ssh/transport_test.go @@ -12,9 +12,9 @@ import ( func TestReadVersion(t *testing.T) { buf := []byte(serverVersion) - result, ok := readVersion(bufio.NewReader(bytes.NewBuffer(buf))) - if !ok { - t.Error("readVersion didn't read version correctly") + result, err := readVersion(bufio.NewReader(bytes.NewBuffer(buf))) + if err != nil { + t.Errorf("readVersion didn't read version correctly: %s", err) } if !bytes.Equal(buf[:len(buf)-2], result) { t.Error("version read did not match expected") @@ -23,7 +23,7 @@ func TestReadVersion(t *testing.T) { func TestReadVersionTooLong(t *testing.T) { buf := make([]byte, maxVersionStringBytes+1) - if _, ok := readVersion(bufio.NewReader(bytes.NewBuffer(buf))); ok { + if _, err := readVersion(bufio.NewReader(bytes.NewBuffer(buf))); err == nil { t.Errorf("readVersion consumed %d bytes without error", len(buf)) } } @@ -31,7 +31,7 @@ func TestReadVersionTooLong(t *testing.T) { func TestReadVersionWithoutCRLF(t *testing.T) { buf := []byte(serverVersion) buf = buf[:len(buf)-1] - if _, ok := readVersion(bufio.NewReader(bytes.NewBuffer(buf))); ok { + if _, err := readVersion(bufio.NewReader(bytes.NewBuffer(buf))); err == nil { t.Error("readVersion did not notice \\n was missing") } } diff --git a/libgo/go/exp/types/gcimporter.go b/libgo/go/exp/types/gcimporter.go index fe90f91..e744a63 100644 --- a/libgo/go/exp/types/gcimporter.go +++ b/libgo/go/exp/types/gcimporter.go @@ -289,9 +289,10 @@ func (p *gcParser) parseExportedName() (*ast.Object, string) { // BasicType = identifier . // func (p *gcParser) parseBasicType() Type { - obj := Universe.Lookup(p.expect(scanner.Ident)) + id := p.expect(scanner.Ident) + obj := Universe.Lookup(id) if obj == nil || obj.Kind != ast.Typ { - p.errorf("not a basic type: %s", obj.Name) + p.errorf("not a basic type: %s", id) } return obj.Type.(Type) } diff --git a/libgo/go/exp/winfsnotify/winfsnotify_test.go b/libgo/go/exp/winfsnotify/winfsnotify_test.go index edf2165..6e264d0 100644 --- a/libgo/go/exp/winfsnotify/winfsnotify_test.go +++ b/libgo/go/exp/winfsnotify/winfsnotify_test.go @@ -6,8 +6,8 @@ package winfsnotify import ( "os" - "time" "testing" + "time" ) func expect(t *testing.T, eventstream <-chan *Event, name string, mask uint32) { @@ -70,15 +70,11 @@ func TestNotifyEvents(t *testing.T) { if _, err = file.WriteString("hello, world"); err != nil { t.Fatalf("failed to write to test file: %s", err) } - if err = file.Sync(); err != nil { - t.Fatalf("failed to sync test file: %s", err) - } - expect(t, watcher.Event, testFile, FS_MODIFY) - expect(t, watcher.Event, testFile, FS_MODIFY) - if err = file.Close(); err != nil { t.Fatalf("failed to close test file: %s", err) } + expect(t, watcher.Event, testFile, FS_MODIFY) + expect(t, watcher.Event, testFile, FS_MODIFY) if err = os.Rename(testFile, testFile2); err != nil { t.Fatalf("failed to rename test file: %s", err) |