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  }