|
|
|
package server
|
|
|
|
|
|
|
|
import (
|
|
|
|
"container/list"
|
|
|
|
"encoding/json"
|
|
|
|
|
|
|
|
"io"
|
|
|
|
"sync"
|
|
|
|
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
|
|
)
|
|
|
|
|
|
|
|
type ttyShareSession struct {
|
|
|
|
mainRWLock sync.RWMutex
|
|
|
|
ttyReceiverConnections *list.List
|
|
|
|
isAlive bool
|
|
|
|
lastWindowSizeMsg MsgTTYWinSize
|
|
|
|
ttyWriter io.Writer
|
|
|
|
}
|
|
|
|
|
|
|
|
func newTTYShareSession(ttyWriter io.Writer) *ttyShareSession {
|
|
|
|
|
|
|
|
ttyShareSession := &ttyShareSession{
|
|
|
|
ttyReceiverConnections: list.New(),
|
|
|
|
ttyWriter: ttyWriter,
|
|
|
|
}
|
|
|
|
|
|
|
|
return ttyShareSession
|
|
|
|
}
|
|
|
|
|
|
|
|
func copyList(l *list.List) *list.List {
|
|
|
|
newList := list.New()
|
|
|
|
for e := l.Front(); e != nil; e = e.Next() {
|
|
|
|
newList.PushBack(e.Value)
|
|
|
|
}
|
|
|
|
return newList
|
|
|
|
}
|
|
|
|
|
|
|
|
func (session *ttyShareSession) WindowSize(cols, rows int) (err error) {
|
|
|
|
msg := MsgTTYWinSize{
|
|
|
|
Cols: cols,
|
|
|
|
Rows: rows,
|
|
|
|
}
|
|
|
|
|
|
|
|
session.mainRWLock.Lock()
|
|
|
|
session.lastWindowSizeMsg = msg
|
|
|
|
session.mainRWLock.Unlock()
|
|
|
|
|
|
|
|
data, _ := MarshalMsg(msg)
|
|
|
|
|
|
|
|
session.forEachReceiverLock(func(rcvConn *TTYProtocolWriter) bool {
|
|
|
|
_, e := rcvConn.WriteRawData(data)
|
|
|
|
if e != nil {
|
|
|
|
err = e
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
})
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func (session *ttyShareSession) Write(buff []byte) (written int, err error) {
|
|
|
|
msg := MsgTTYWrite{
|
|
|
|
Data: buff,
|
|
|
|
Size: len(buff),
|
|
|
|
}
|
|
|
|
|
|
|
|
data, _ := MarshalMsg(msg)
|
|
|
|
|
|
|
|
log.Debugf("Writing %s\n", string(buff))
|
|
|
|
session.forEachReceiverLock(func(rcvConn *TTYProtocolWriter) bool {
|
|
|
|
_, e := rcvConn.WriteRawData(data)
|
|
|
|
if e != nil {
|
|
|
|
err = e
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
})
|
|
|
|
|
|
|
|
// TODO: fix this
|
|
|
|
written = len(buff)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Runs the callback cb for each of the receivers in the list of the receivers, as it was when
|
|
|
|
// this function was called. Note that there might be receivers which might have lost
|
|
|
|
// the connection since this function was called.
|
|
|
|
// Return false in the callback to not continue for the rest of the receivers
|
|
|
|
func (session *ttyShareSession) forEachReceiverLock(cb func(rcvConn *TTYProtocolWriter) bool) {
|
|
|
|
session.mainRWLock.RLock()
|
|
|
|
// TODO: Maybe find a better way?
|
|
|
|
rcvsCopy := copyList(session.ttyReceiverConnections)
|
|
|
|
session.mainRWLock.RUnlock()
|
|
|
|
|
|
|
|
for receiverE := rcvsCopy.Front(); receiverE != nil; receiverE = receiverE.Next() {
|
|
|
|
receiver := receiverE.Value.(*TTYProtocolWriter)
|
|
|
|
if !cb(receiver) {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Will run on the TTYReceiver connection go routine (e.g.: on the websockets connection routine)
|
|
|
|
// When HandleReceiver will exit, the connection to the TTYReceiver will be closed
|
|
|
|
func (session *ttyShareSession) HandleReceiver(rawConn *WSConnection) {
|
|
|
|
rcvReader := NewTTYProtocolReader(rawConn)
|
|
|
|
rcvWriter := NewTTYProtocolWriter(rawConn)
|
|
|
|
|
|
|
|
// Add the receiver to the list of receivers in the seesion, so we need to write-lock
|
|
|
|
session.mainRWLock.Lock()
|
|
|
|
rcvHandleEl := session.ttyReceiverConnections.PushBack(rcvWriter)
|
|
|
|
lastWindowSizeData, _ := MarshalMsg(session.lastWindowSizeMsg)
|
|
|
|
session.mainRWLock.Unlock()
|
|
|
|
|
|
|
|
log.Debugf("Got new TTYReceiver connection (%s). Serving it..", rawConn.Address())
|
|
|
|
|
|
|
|
// Sending the initial size of the window, if we have one
|
|
|
|
rcvWriter.WriteRawData(lastWindowSizeData)
|
|
|
|
|
|
|
|
// Wait until the TTYReceiver will close the connection on its end
|
|
|
|
for {
|
|
|
|
msg, err := rcvReader.ReadMessage()
|
|
|
|
if err != nil {
|
|
|
|
log.Warnf("Finishing handling the TTYReceiver loop because: %s", err.Error())
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
// We only support MsgTTYWrite from the web terminal for now
|
|
|
|
if msg.Type != MsgIDWrite {
|
|
|
|
log.Warnf("TTYReceiver sent unknown message type %s", msg.Type)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
var msgW MsgTTYWrite
|
|
|
|
json.Unmarshal(msg.Data, &msgW)
|
|
|
|
session.ttyWriter.Write(msgW.Data)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove the recevier from the list of the receiver of this session, so we need to write-lock
|
|
|
|
session.mainRWLock.Lock()
|
|
|
|
session.ttyReceiverConnections.Remove(rcvHandleEl)
|
|
|
|
session.mainRWLock.Unlock()
|
|
|
|
|
|
|
|
rawConn.Close()
|
|
|
|
log.Debugf("Closed receiver connection")
|
|
|
|
}
|