mirror of https://gogs.blitter.com/RLabs/xs
Client->Remote xc progress indication using pv
Signed-off-by: Russ Magee <rmagee@gmail.com>
This commit is contained in:
parent
6ac6f02b3b
commit
64a314df11
94
xs/xs.go
94
xs/xs.go
|
@ -240,21 +240,21 @@ func GetSize() (cols, rows int, err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// doCopyMode begins a secure xs local<->remote file copy operation.
|
func buildCmdLocalToRemote(copyQuiet bool, copyLimitBPS uint, files string) (captureStderr bool, cmd string, args []string) {
|
||||||
//
|
// TODO: detect if we have 'pv'
|
||||||
// TODO: reduce gocyclo
|
// pipeview http://www.ivarch.com/programs/pv.shtml
|
||||||
func doCopyMode(conn *xsnet.Conn, remoteDest bool, files string, rec *xs.Session) (exitStatus uint32, err error) {
|
// and use it for nice client progress display.
|
||||||
if remoteDest {
|
_, pverr := os.Stat("/usr/bin/pv")
|
||||||
log.Println("local files:", files, "remote filepath:", string(rec.Cmd()))
|
if pverr != nil {
|
||||||
|
_, pverr = os.Stat("/usr/local/bin/pv")
|
||||||
|
}
|
||||||
|
|
||||||
var c *exec.Cmd
|
if pverr != nil {
|
||||||
|
// copyQuiet and copyLimitBPS are not applicable in dumb copy mode
|
||||||
|
|
||||||
//os.Clearenv()
|
captureStderr = true
|
||||||
//os.Setenv("HOME", u.HomeDir)
|
cmd = "/bin/tar"
|
||||||
//os.Setenv("TERM", "vt102") // TODO: server or client option?
|
args = []string{"-cz", "-f", "/dev/stdout"}
|
||||||
|
|
||||||
cmdName := "/bin/tar"
|
|
||||||
cmdArgs := []string{"-cz", "-f", "/dev/stdout"}
|
|
||||||
files = strings.TrimSpace(files)
|
files = strings.TrimSpace(files)
|
||||||
// Awesome fact: tar actually can take multiple -C args, and
|
// Awesome fact: tar actually can take multiple -C args, and
|
||||||
// changes to the dest dir *as it sees each one*. This enables
|
// changes to the dest dir *as it sees each one*. This enables
|
||||||
|
@ -272,22 +272,70 @@ func doCopyMode(conn *xsnet.Conn, remoteDest bool, files string, rec *xs.Session
|
||||||
v, _ = filepath.Abs(v) // #nosec
|
v, _ = filepath.Abs(v) // #nosec
|
||||||
dirTmp, fileTmp := path.Split(v)
|
dirTmp, fileTmp := path.Split(v)
|
||||||
if dirTmp == "" {
|
if dirTmp == "" {
|
||||||
cmdArgs = append(cmdArgs, fileTmp)
|
args = append(args, fileTmp)
|
||||||
} else {
|
} else {
|
||||||
cmdArgs = append(cmdArgs, "-C", dirTmp, fileTmp)
|
args = append(args, "-C", dirTmp, fileTmp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
captureStderr = copyQuiet
|
||||||
|
bandwidthInBytesPerSec := " -L " + fmt.Sprintf("%d", copyLimitBPS)
|
||||||
|
displayOpts := " -f -pr "
|
||||||
|
cmd = "/bin/bash"
|
||||||
|
args = []string{"-c", "/bin/tar -cz -f /dev/stdout "}
|
||||||
|
files = strings.TrimSpace(files)
|
||||||
|
// Awesome fact: tar actually can take multiple -C args, and
|
||||||
|
// changes to the dest dir *as it sees each one*. This enables
|
||||||
|
// its use below, where clients can send scattered sets of source
|
||||||
|
// files and dirs to be extracted to a single dest dir server-side,
|
||||||
|
// whilst preserving the subtrees of dirs on the other side.
|
||||||
|
// Eg., tar -c -f /dev/stdout -C /dirA fileInA -C /some/where/dirB fileInB /foo/dirC
|
||||||
|
// packages fileInA, fileInB, and dirC at a single toplevel in the tar.
|
||||||
|
// The tar authors are/were real smarties :)
|
||||||
|
//
|
||||||
|
// This is the 'scatter/gather' logic to allow specification of
|
||||||
|
// files and dirs in different trees to be deposited in a single
|
||||||
|
// remote destDir.
|
||||||
|
for _, v := range strings.Split(files, " ") {
|
||||||
|
v, _ = filepath.Abs(v) // #nosec
|
||||||
|
dirTmp, fileTmp := path.Split(v)
|
||||||
|
if dirTmp == "" {
|
||||||
|
args[1] = args[1] + fileTmp + " "
|
||||||
|
} else {
|
||||||
|
args[1] = args[1] + " -C " + dirTmp + " " + fileTmp + " "
|
||||||
|
}
|
||||||
|
}
|
||||||
|
args[1] = args[1] + "| pv" + displayOpts + bandwidthInBytesPerSec + " -s $(du -cb " + files + " | tail -1 | cut -f 1) -c"
|
||||||
|
}
|
||||||
|
|
||||||
log.Printf("[%v %v]\n", cmdName, cmdArgs)
|
log.Printf("[%v %v]\n", cmd, args)
|
||||||
// NOTE the lack of quotes around --xform option's sed expression.
|
return
|
||||||
// When args are passed in exec() format, no quoting is required
|
}
|
||||||
// (as this isn't input from a shell) (right? -rlm 20180823)
|
|
||||||
|
// doCopyMode begins a secure xs local<->remote file copy operation.
|
||||||
|
//
|
||||||
|
// TODO: reduce gocyclo
|
||||||
|
func doCopyMode(conn *xsnet.Conn, remoteDest bool, files string, copyQuiet bool, copyLimitBPS uint, rec *xs.Session) (exitStatus uint32, err error) {
|
||||||
|
if remoteDest {
|
||||||
|
log.Println("local files:", files, "remote filepath:", string(rec.Cmd()))
|
||||||
|
|
||||||
|
var c *exec.Cmd
|
||||||
|
|
||||||
|
//os.Clearenv()
|
||||||
|
//os.Setenv("HOME", u.HomeDir)
|
||||||
|
//os.Setenv("TERM", "vt102") // TODO: server or client option?
|
||||||
|
|
||||||
|
captureStderr, cmdName, cmdArgs := buildCmdLocalToRemote(copyQuiet, copyLimitBPS, strings.TrimSpace(files))
|
||||||
c = exec.Command(cmdName, cmdArgs...) // #nosec
|
c = exec.Command(cmdName, cmdArgs...) // #nosec
|
||||||
c.Dir, _ = os.Getwd() // #nosec
|
c.Dir, _ = os.Getwd() // #nosec
|
||||||
log.Println("[wd:", c.Dir, "]")
|
log.Println("[wd:", c.Dir, "]")
|
||||||
c.Stdout = conn
|
c.Stdout = conn
|
||||||
stdErrBuffer := new(bytes.Buffer)
|
stdErrBuffer := new(bytes.Buffer)
|
||||||
|
if captureStderr {
|
||||||
c.Stderr = stdErrBuffer
|
c.Stderr = stdErrBuffer
|
||||||
|
} else {
|
||||||
|
c.Stderr = os.Stderr
|
||||||
|
}
|
||||||
|
|
||||||
// Start the command (no pty)
|
// Start the command (no pty)
|
||||||
err = c.Start() // returns immediately
|
err = c.Start() // returns immediately
|
||||||
|
@ -320,10 +368,12 @@ func doCopyMode(conn *xsnet.Conn, remoteDest bool, files string, rec *xs.Session
|
||||||
// an ExitStatus() method with the same signature.
|
// an ExitStatus() method with the same signature.
|
||||||
if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
|
if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
|
||||||
exitStatus = uint32(status.ExitStatus())
|
exitStatus = uint32(status.ExitStatus())
|
||||||
|
if captureStderr {
|
||||||
fmt.Print(stdErrBuffer)
|
fmt.Print(stdErrBuffer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// send CSOExitStatus to inform remote (server) end cp is done
|
// send CSOExitStatus to inform remote (server) end cp is done
|
||||||
log.Println("Sending local exitStatus:", exitStatus)
|
log.Println("Sending local exitStatus:", exitStatus)
|
||||||
r := make([]byte, 4)
|
r := make([]byte, 4)
|
||||||
|
@ -612,6 +662,8 @@ func main() {
|
||||||
|
|
||||||
var copySrc []byte
|
var copySrc []byte
|
||||||
var copyDst string
|
var copyDst string
|
||||||
|
var copyQuiet bool
|
||||||
|
var copyLimitBPS uint
|
||||||
|
|
||||||
var authCookie string
|
var authCookie string
|
||||||
var chaffEnabled bool
|
var chaffEnabled bool
|
||||||
|
@ -649,6 +701,8 @@ func main() {
|
||||||
shellMode = true
|
shellMode = true
|
||||||
flag.Usage = usageShell
|
flag.Usage = usageShell
|
||||||
} else {
|
} else {
|
||||||
|
flag.BoolVar(©Quiet, "q", false, "do not output progress bar during copy")
|
||||||
|
flag.UintVar(©LimitBPS, "L", 8589934592, "copy max rate in bytes per sec")
|
||||||
flag.Usage = usageCp
|
flag.Usage = usageCp
|
||||||
}
|
}
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
@ -932,7 +986,7 @@ func main() {
|
||||||
launchTuns(&conn, remoteHost, tunSpecStr)
|
launchTuns(&conn, remoteHost, tunSpecStr)
|
||||||
doShellMode(isInteractive, &conn, oldState, rec)
|
doShellMode(isInteractive, &conn, oldState, rec)
|
||||||
} else { // copyMode
|
} else { // copyMode
|
||||||
s, _ := doCopyMode(&conn, pathIsDest, fileArgs, rec) // nolint: errcheck,gosec
|
s, _ := doCopyMode(&conn, pathIsDest, fileArgs, copyQuiet, copyLimitBPS, rec) // nolint: errcheck,gosec
|
||||||
rec.SetStatus(s)
|
rec.SetStatus(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue