github.com/devops-filetransfer/sshego@v7.0.4+incompatible/shovel.go (about) 1 package sshego 2 3 import ( 4 "io" 5 "os" 6 7 ssh "github.com/glycerine/sshego/xendor/github.com/glycerine/xcryptossh" 8 ) 9 10 // Shovel shovels data from an io.ReadCloser to an io.WriteCloser 11 // in an independent go routine started by Shovel::Start(). 12 // You can request that the shovel stop by closing ReqStop, 13 // and wait until Done is closed to know that it is finished. 14 type shovel struct { 15 Halt *ssh.Halter 16 17 // logging functionality, off by default 18 DoLog bool 19 LogReads io.Writer 20 LogWrites io.Writer 21 } 22 23 // make a new Shovel 24 func newShovel(doLog bool) *shovel { 25 return &shovel{ 26 Halt: ssh.NewHalter(), 27 DoLog: doLog, 28 LogReads: os.Stdout, 29 LogWrites: os.Stdout, 30 } 31 } 32 33 type readerNilCloser struct{ io.Reader } 34 35 func (rc *readerNilCloser) Close() error { return nil } 36 37 type writerNilCloser struct{ io.Writer } 38 39 func (wc *writerNilCloser) Close() error { return nil } 40 41 // Start starts the shovel doing an io.Copy from r to w. The 42 // goroutine that is running the copy will close the Ready 43 // channel just before starting the io.Copy. The 44 // label parameter allows reporting on when a specific shovel 45 // was shut down. 46 func (s *shovel) Start(w io.WriteCloser, r io.ReadCloser, label string) { 47 48 if s.DoLog { 49 // TeeReader returns a Reader that writes to w what it reads from r. 50 // All reads from r performed through it are matched with 51 // corresponding writes to w. There is no internal buffering - 52 // the write must complete before the read completes. 53 // Any error encountered while writing is reported as a read error. 54 r = &readerNilCloser{io.TeeReader(r, s.LogReads)} 55 w = &writerNilCloser{io.MultiWriter(w, s.LogWrites)} 56 } 57 58 go func() { 59 var err error 60 var n int64 61 defer func() { 62 s.Halt.MarkDone() 63 p("shovel %s copied %d bytes before shutting down", label, n) 64 }() 65 s.Halt.MarkReady() 66 n, err = io.Copy(w, r) 67 if err != nil { 68 // don't freak out, the network connection got closed most likely. 69 // e.g. read tcp 127.0.0.1:33631: use of closed network connection 70 //panic(fmt.Sprintf("in Shovel '%s', io.Copy failed: %v\n", label, err)) 71 return 72 } 73 }() 74 go func() { 75 <-s.Halt.ReqStopChan() 76 r.Close() // causes io.Copy to finish 77 w.Close() 78 s.Halt.MarkDone() 79 }() 80 } 81 82 // stop the shovel goroutine. returns only once the goroutine is done. 83 func (s *shovel) Stop() { 84 s.Halt.RequestStop() 85 <-s.Halt.DoneChan() 86 } 87 88 // a shovelPair manages the forwarding of a bidirectional 89 // channel, such as that in forwarding an ssh connection. 90 type shovelPair struct { 91 AB *shovel 92 BA *shovel 93 Halt *ssh.Halter 94 95 DoLog bool 96 } 97 98 // make a new shovelPair 99 func newShovelPair(doLog bool) *shovelPair { 100 pair := &shovelPair{ 101 AB: newShovel(doLog), 102 BA: newShovel(doLog), 103 Halt: ssh.NewHalter(), 104 } 105 pair.Halt.AddDownstream(pair.AB.Halt) 106 pair.Halt.AddDownstream(pair.BA.Halt) 107 return pair 108 } 109 110 // Start the pair of shovels. abLabel will label the a<-b shovel. baLabel will 111 // label the b<-a shovel. 112 func (s *shovelPair) Start(a io.ReadWriteCloser, b io.ReadWriteCloser, abLabel string, baLabel string) { 113 s.AB.Start(a, b, abLabel) 114 <-s.AB.Halt.ReadyChan() 115 s.BA.Start(b, a, baLabel) 116 <-s.BA.Halt.ReadyChan() 117 s.Halt.MarkReady() 118 119 // if one stops, shut down the other 120 go func() { 121 select { 122 case <-s.Halt.ReqStopChan(): 123 case <-s.Halt.DoneChan(): 124 case <-s.AB.Halt.ReqStopChan(): 125 case <-s.AB.Halt.DoneChan(): 126 case <-s.BA.Halt.ReqStopChan(): 127 case <-s.BA.Halt.DoneChan(): 128 } 129 s.AB.Stop() 130 s.BA.Stop() 131 s.Halt.RequestStop() 132 s.Halt.MarkDone() 133 }() 134 } 135 136 func (s *shovelPair) Stop() { 137 s.Halt.RequestStop() 138 s.AB.Stop() 139 s.BA.Stop() 140 }