git clone https://orangeshoelaces.net/git/rscp.git
Author: Vasily Kolobkov on 06/21/2017
Committer: Vasily Kolobkov on 06/21/2017
Be humble naming things
main.go | 539 --------
rscp.go | 539 ++++++++
2 files changed, 539 insertions(+), 539 deletions(-)
diff --git a/main.go b/main.go
deleted file mode 100644
index b5dfe70..0000000
--- a/main.go
+++ /dev/null
@@ -1,539 +0,0 @@
-package main
-
-import (
- "errors"
- "flag"
- "fmt"
- "io"
- "io/ioutil"
- "os"
- "path"
- "strings"
- "syscall"
-)
-
-const (
- S_IWUSR = 00200
- S_IRWXU = 00700
- S_ISUID = 04000
- S_ISGID = 02000
-)
-
-var (
- iamSource = flag.Bool("f", false, "Run in source mode")
- iamSink = flag.Bool("t", false, "Run in sink mode")
- bwLimit = flag.Int("l", 0, "Limit the bandwidth, specified in Kbit/s")
- iamRecursive = flag.Bool("r", false, "Copy directoires recursively following any symlinks")
- targetDir = flag.Bool("d", false, "Target should be a directory")
- preserveAttrs = flag.Bool("p", false, "Preserve modification and access times and mode from original file")
-
- protocolErr = FatalError("protocol error")
-)
-
-func main() {
- flag.Parse()
- var args = flag.Args()
-
- var validMode = (*iamSource || *iamSink) && !(*iamSource && *iamSink)
- var validArgc = (*iamSource && len(args) > 0) || (*iamSink && len(args) == 1)
-
- if !validMode || !validArgc {
- usage()
- }
-
- var err error
-
- if *iamSource {
- err = source(args)
- } else {
- err = sink(args[0], false)
- }
-
- if err != nil {
- fmt.Fprintln(os.Stderr, err)
- os.Exit(1)
- }
-}
-
-func source(paths []string) error {
- if err := ack(); err != nil {
- return err
- }
-
- var sendErrs []error
- for _, path := range paths {
- if err := send(path); isFatal(err) {
- return err
- } else if err != nil {
- sendErrs = append(sendErrs, err)
- }
- }
-
- if len(sendErrs) > 0 {
- return AccError{sendErrs}
- }
- return nil
-}
-
-func sink(path string, recur bool) error {
- var errs []error
- var times *FileTimes
-
- if *targetDir {
- if st, err := os.Stat(path); err != nil {
- return teeError(FatalError(err.Error()))
- } else if !st.IsDir() {
- return teeError(FatalError(path + ": is not a directory"))
- }
- }
-
- fmt.Fprint(os.Stdout, "\x00")
-
- for first := true; ; first = false {
- prefix := []byte{0}
- if _, err := os.Stdin.Read(prefix); err != nil {
- if err == io.EOF {
- break
- }
- return FatalError(err.Error())
- }
- line, err := readLine()
- if err != nil {
- return FatalError(err.Error())
- }
-
- switch prefix[0] {
- case '\x01':
- errs = append(errs, errors.New(line))
-
- case '\x02':
- return FatalError(line)
-
- case 'E':
- if !recur {
- return teeError(protocolErr)
- }
- fmt.Fprint(os.Stdout, "\x00")
-
- case 'T':
- if times == nil {
- times = new(FileTimes)
- }
- if n, err := fmt.Sscanf(line, "%d %d %d %d",
- ×.Mtime.Sec, ×.Mtime.Usec,
- ×.Atime.Sec, ×.Atime.Usec); err != nil {
-
- return teeError(FatalError(err.Error()))
- } else if n != 4 {
- return teeError(protocolErr)
- }
- fmt.Fprint(os.Stdout, "\x00")
-
- case 'D':
- if err := sinkDir(path, line, times); isFatal(err) {
- return err
- } else if err != nil {
- errs = append(errs, err)
- }
- times = nil
-
- case 'C':
- if err := sinkFile(path, line, times); isFatal(err) {
- return err
- } else if err != nil {
- errs = append(errs, err)
- }
- times = nil
-
- default:
- err := protocolErr
- if first {
- compLine := append([]byte{prefix[0]}, line...)
- err = FatalError(string(compLine))
- }
- return teeError(err)
- }
- }
-
- if len(errs) > 0 {
- return AccError{errs}
- }
- return nil
-}
-
-func sinkDir(parent, line string, times *FileTimes) error {
- if !*iamRecursive {
- return teeError(FatalError("received directory without -r flag"))
- }
-
- perm, _, name, err := parseSubj(line)
- if err != nil {
- return teeError(FatalError(err.Error()))
- }
-
- name = path.Join(parent, name)
-
- resetPerm, err := prepareDir(name, perm)
- if err != nil {
- return teeError(err)
- }
-
- var errs []error
- if err := sink(name, true); isFatal(err) {
- return err
- } else if err != nil {
- errs = append(errs, err)
- }
-
- var pendErrs []error
- if times != nil {
- t := []syscall.Timeval{times.Atime, times.Mtime}
- if err := syscall.Utimes(name, t); err != nil {
- pendErrs = append(pendErrs, err)
- }
- }
- if resetPerm {
- if err := os.Chmod(name, perm); err != nil {
- pendErrs = append(pendErrs, err)
- }
- }
- if len(pendErrs) > 0 {
- errs = append(errs, pendErrs...)
- sendError(AccError{pendErrs})
- }
-
- if len(errs) > 0 {
- return AccError{errs}
- }
- return nil
-}
-
-func sinkFile(name, line string, times *FileTimes) error {
- perm, size, subj, err := parseSubj(line)
- if err != nil {
- return teeError(FatalError(err.Error()))
- }
-
- exists := false
- if st, err := os.Stat(name); err == nil {
- exists = true
- if st.IsDir() {
- name = path.Join(name, subj)
- }
- }
-
- f, err := os.OpenFile(name, os.O_WRONLY|os.O_CREATE, perm|S_IWUSR)
- if err != nil {
- return teeError(err)
- }
- defer f.Close() /* will sync explicitly */
-
- st, err := f.Stat()
- if err != nil {
- return teeError(err)
- }
-
- fmt.Fprint(os.Stdout, "\x00")
-
- var pendErrs []error
- if wr, err := io.Copy(f, io.LimitReader(os.Stdin, size)); err != nil {
- if _, err := io.Copy(ioutil.Discard, io.LimitReader(os.Stdin, size-wr)); err != nil {
- return teeError(FatalError(err.Error()))
- }
- pendErrs = append(pendErrs, err)
- }
-
- if !exists || st.Mode().IsRegular() {
- if err := f.Truncate(size); err != nil {
- pendErrs = append(pendErrs, err)
- }
- }
- if err := f.Sync(); err != nil {
- pendErrs = append(pendErrs, err)
- }
- if *preserveAttrs || !exists {
- if err := f.Chmod(perm); err != nil {
- pendErrs = append(pendErrs, err)
- }
- }
- if times != nil {
- if err := syscall.Utimes(name, []syscall.Timeval{times.Atime, times.Mtime}); err != nil {
- pendErrs = append(pendErrs, err)
- }
- }
-
- ackErr := ack()
- if isFatal(ackErr) {
- return ackErr
- }
-
- var sentErr error
- if len(pendErrs) > 0 {
- sentErr = AccError{pendErrs}
- sendError(sentErr)
- } else {
- fmt.Fprint(os.Stdout, "\x00")
- }
-
- if ackErr != nil {
- return AccError{append(pendErrs, ackErr)}
- }
- return sentErr
-}
-
-func prepareDir(name string, perm os.FileMode) (bool, error) {
- resetPerm := false
- if st, err := os.Stat(name); err == nil {
- if !st.IsDir() {
- return resetPerm, errors.New(name + ": is not a directory")
- }
- if *preserveAttrs {
- if err := os.Chmod(name, perm); err != nil {
- return resetPerm, err
- }
- }
- } else if os.IsNotExist(err) {
- if err := os.Mkdir(name, perm|S_IRWXU); err != nil {
- return resetPerm, err
- }
- resetPerm = true
- } else {
- return resetPerm, err
- }
- return resetPerm, nil
-}
-
-func send(name string) error {
- f, err := os.Open(name)
- if err != nil {
- return teeError(err)
- }
- defer f.Close()
-
- st, err := f.Stat()
- if err != nil {
- return teeError(err)
- }
- name = st.Name()
-
- switch st.Mode() & os.ModeType {
- case 0: /* regular file */
- break
- case os.ModeDir:
- if *iamRecursive {
- return sendDir(f, st)
- }
- return teeError(errors.New(name + ": is a directory"))
- default:
- return teeError(errors.New(name + ": not a regular file"))
- }
-
- if *preserveAttrs {
- if err := sendAttr(st); err != nil {
- return err
- }
- }
-
- fmt.Fprintf(os.Stdout, "C%04o %d %s\n", toPosixPerm(st.Mode()), st.Size(), name)
- if err := ack(); err != nil {
- return err
- }
-
- if sent, err := io.Copy(os.Stdout, f); err != nil {
- patch := io.LimitReader(ConstReader(0), st.Size()-sent)
- if _, err := io.Copy(os.Stdout, patch); err != nil {
- return FatalError(err.Error())
- }
- if err := ack(); err != nil {
- return err
- }
- return teeError(err)
- }
-
- fmt.Fprint(os.Stdout, "\x00")
- return ack()
-}
-
-func sendDir(dir *os.File, st os.FileInfo) error {
- content, err := dir.Readdirnames(0)
- if err != nil {
- return teeError(err)
- }
-
- if *preserveAttrs {
- if err := sendAttr(st); err != nil {
- return err
- }
- }
-
- fmt.Fprintf(os.Stdout, "D%04o %d %s\n", toPosixPerm(st.Mode()), 0, st.Name())
- if err := ack(); err != nil {
- return err
- }
-
- var sendErrs []error
- for _, entry := range content {
- if err := send(path.Join(dir.Name(), entry)); err != nil {
- if _, ok := err.(FatalError); ok {
- return err
- }
- sendErrs = append(sendErrs, err)
- }
- }
-
- fmt.Fprintf(os.Stdout, "E\n")
- ackErr := ack()
- if isFatal(ackErr) {
- return ackErr
- }
-
- if len(sendErrs) > 0 {
- return AccError{sendErrs}
- }
- return ackErr
-}
-
-func parseSubj(line string) (perm os.FileMode, size int64, name string, err error) {
- n := 0
- pperm := 0
- if n, err = fmt.Sscanf(line, "%o %d %s", &pperm, &size, &name); err != nil {
- return
- } else if n != 3 {
- err = protocolErr
- return
- }
- perm = toStdPerm(pperm)
- if name == ".." || strings.ContainsRune(name, '/') {
- err = FatalError(name + ": invalid name")
- }
- return
-}
-
-func sendAttr(st os.FileInfo) error {
- mtime := st.ModTime().Unix()
- atime := int64(0)
-
- if sysStat, ok := st.Sys().(*syscall.Stat_t); ok {
- atime, _ = sysStat.Atim.Unix()
- }
-
- fmt.Fprintf(os.Stdout, "T%d 0 %d 0\n", mtime, atime)
- return ack()
-}
-
-func ack() error {
- kind := []byte{0}
- if _, err := os.Stdin.Read(kind); err != nil {
- return FatalError(err.Error())
- }
- if kind[0] == 0 {
- return nil
- }
-
- l, err := readLine()
- if err != nil {
- return FatalError(err.Error())
- }
-
- switch kind[0] {
- case 1:
- return errors.New(l)
- case 2:
- return FatalError(l)
- default:
- return protocolErr
- }
-}
-
-func teeError(err error) error {
- sendError(err)
- return err
-}
-
-func sendError(err error) {
- fmt.Fprintf(os.Stdout, "\x01%s\n", strings.Replace(err.Error(), "\n", "; ", -1))
-}
-
-func readLine() (string, error) {
- l := make([]byte, 0, 64)
- ch := []byte{0}
-
- for {
- if _, err := os.Stdin.Read(ch); err != nil {
- return "", err
- } else {
- if ch[0] == '\n' {
- break
- }
- l = append(l, ch[0])
- }
- }
-
- return string(l), nil
-}
-
-func toPosixPerm(perm os.FileMode) int {
- pp := perm & os.ModePerm
- if perm&os.ModeSetuid != 0 {
- pp |= S_ISUID
- }
- if perm&os.ModeSetgid != 0 {
- pp |= S_ISGID
- }
- return int(pp)
-}
-
-func toStdPerm(posixPerm int) os.FileMode {
- perm := os.FileMode(posixPerm) & os.ModePerm
- if posixPerm&S_ISUID != 0 {
- perm |= os.ModeSetuid
- }
- if posixPerm&S_ISGID != 0 {
- perm |= os.ModeSetgid
- }
- return perm
-}
-
-func usage() {
- fmt.Fprintf(os.Stderr, "Usage: rscp -f [-pr] [-l limit] file1 ...\n"+
- " rscp -t [-prd] [-l limit] directory\n")
- flag.PrintDefaults()
- os.Exit(1)
-}
-
-type FileTimes struct {
- Atime syscall.Timeval
- Mtime syscall.Timeval
-}
-
-type FatalError string
-
-func (e FatalError) Error() string {
- return string(e)
-}
-
-func isFatal(err error) bool {
- _, isFatal := err.(FatalError)
- return isFatal
-}
-
-type AccError struct {
- Errors []error
-}
-
-func (e AccError) Error() string {
- ve := []interface{}{}
- for _, err := range e.Errors {
- ve = append(ve, err)
- }
- return fmt.Sprintln(ve...)
-}
-
-type ConstReader byte
-
-func (c ConstReader) Read(b []byte) (int, error) {
- for i, _ := range b {
- b[i] = byte(c)
- }
- return len(b), nil
-}
diff --git a/rscp.go b/rscp.go
new file mode 100644
index 0000000..b5dfe70
--- /dev/null
+++ b/rscp.go
@@ -0,0 +1,539 @@
+package main
+
+import (
+ "errors"
+ "flag"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "os"
+ "path"
+ "strings"
+ "syscall"
+)
+
+const (
+ S_IWUSR = 00200
+ S_IRWXU = 00700
+ S_ISUID = 04000
+ S_ISGID = 02000
+)
+
+var (
+ iamSource = flag.Bool("f", false, "Run in source mode")
+ iamSink = flag.Bool("t", false, "Run in sink mode")
+ bwLimit = flag.Int("l", 0, "Limit the bandwidth, specified in Kbit/s")
+ iamRecursive = flag.Bool("r", false, "Copy directoires recursively following any symlinks")
+ targetDir = flag.Bool("d", false, "Target should be a directory")
+ preserveAttrs = flag.Bool("p", false, "Preserve modification and access times and mode from original file")
+
+ protocolErr = FatalError("protocol error")
+)
+
+func main() {
+ flag.Parse()
+ var args = flag.Args()
+
+ var validMode = (*iamSource || *iamSink) && !(*iamSource && *iamSink)
+ var validArgc = (*iamSource && len(args) > 0) || (*iamSink && len(args) == 1)
+
+ if !validMode || !validArgc {
+ usage()
+ }
+
+ var err error
+
+ if *iamSource {
+ err = source(args)
+ } else {
+ err = sink(args[0], false)
+ }
+
+ if err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ os.Exit(1)
+ }
+}
+
+func source(paths []string) error {
+ if err := ack(); err != nil {
+ return err
+ }
+
+ var sendErrs []error
+ for _, path := range paths {
+ if err := send(path); isFatal(err) {
+ return err
+ } else if err != nil {
+ sendErrs = append(sendErrs, err)
+ }
+ }
+
+ if len(sendErrs) > 0 {
+ return AccError{sendErrs}
+ }
+ return nil
+}
+
+func sink(path string, recur bool) error {
+ var errs []error
+ var times *FileTimes
+
+ if *targetDir {
+ if st, err := os.Stat(path); err != nil {
+ return teeError(FatalError(err.Error()))
+ } else if !st.IsDir() {
+ return teeError(FatalError(path + ": is not a directory"))
+ }
+ }
+
+ fmt.Fprint(os.Stdout, "\x00")
+
+ for first := true; ; first = false {
+ prefix := []byte{0}
+ if _, err := os.Stdin.Read(prefix); err != nil {
+ if err == io.EOF {
+ break
+ }
+ return FatalError(err.Error())
+ }
+ line, err := readLine()
+ if err != nil {
+ return FatalError(err.Error())
+ }
+
+ switch prefix[0] {
+ case '\x01':
+ errs = append(errs, errors.New(line))
+
+ case '\x02':
+ return FatalError(line)
+
+ case 'E':
+ if !recur {
+ return teeError(protocolErr)
+ }
+ fmt.Fprint(os.Stdout, "\x00")
+
+ case 'T':
+ if times == nil {
+ times = new(FileTimes)
+ }
+ if n, err := fmt.Sscanf(line, "%d %d %d %d",
+ ×.Mtime.Sec, ×.Mtime.Usec,
+ ×.Atime.Sec, ×.Atime.Usec); err != nil {
+
+ return teeError(FatalError(err.Error()))
+ } else if n != 4 {
+ return teeError(protocolErr)
+ }
+ fmt.Fprint(os.Stdout, "\x00")
+
+ case 'D':
+ if err := sinkDir(path, line, times); isFatal(err) {
+ return err
+ } else if err != nil {
+ errs = append(errs, err)
+ }
+ times = nil
+
+ case 'C':
+ if err := sinkFile(path, line, times); isFatal(err) {
+ return err
+ } else if err != nil {
+ errs = append(errs, err)
+ }
+ times = nil
+
+ default:
+ err := protocolErr
+ if first {
+ compLine := append([]byte{prefix[0]}, line...)
+ err = FatalError(string(compLine))
+ }
+ return teeError(err)
+ }
+ }
+
+ if len(errs) > 0 {
+ return AccError{errs}
+ }
+ return nil
+}
+
+func sinkDir(parent, line string, times *FileTimes) error {
+ if !*iamRecursive {
+ return teeError(FatalError("received directory without -r flag"))
+ }
+
+ perm, _, name, err := parseSubj(line)
+ if err != nil {
+ return teeError(FatalError(err.Error()))
+ }
+
+ name = path.Join(parent, name)
+
+ resetPerm, err := prepareDir(name, perm)
+ if err != nil {
+ return teeError(err)
+ }
+
+ var errs []error
+ if err := sink(name, true); isFatal(err) {
+ return err
+ } else if err != nil {
+ errs = append(errs, err)
+ }
+
+ var pendErrs []error
+ if times != nil {
+ t := []syscall.Timeval{times.Atime, times.Mtime}
+ if err := syscall.Utimes(name, t); err != nil {
+ pendErrs = append(pendErrs, err)
+ }
+ }
+ if resetPerm {
+ if err := os.Chmod(name, perm); err != nil {
+ pendErrs = append(pendErrs, err)
+ }
+ }
+ if len(pendErrs) > 0 {
+ errs = append(errs, pendErrs...)
+ sendError(AccError{pendErrs})
+ }
+
+ if len(errs) > 0 {
+ return AccError{errs}
+ }
+ return nil
+}
+
+func sinkFile(name, line string, times *FileTimes) error {
+ perm, size, subj, err := parseSubj(line)
+ if err != nil {
+ return teeError(FatalError(err.Error()))
+ }
+
+ exists := false
+ if st, err := os.Stat(name); err == nil {
+ exists = true
+ if st.IsDir() {
+ name = path.Join(name, subj)
+ }
+ }
+
+ f, err := os.OpenFile(name, os.O_WRONLY|os.O_CREATE, perm|S_IWUSR)
+ if err != nil {
+ return teeError(err)
+ }
+ defer f.Close() /* will sync explicitly */
+
+ st, err := f.Stat()
+ if err != nil {
+ return teeError(err)
+ }
+
+ fmt.Fprint(os.Stdout, "\x00")
+
+ var pendErrs []error
+ if wr, err := io.Copy(f, io.LimitReader(os.Stdin, size)); err != nil {
+ if _, err := io.Copy(ioutil.Discard, io.LimitReader(os.Stdin, size-wr)); err != nil {
+ return teeError(FatalError(err.Error()))
+ }
+ pendErrs = append(pendErrs, err)
+ }
+
+ if !exists || st.Mode().IsRegular() {
+ if err := f.Truncate(size); err != nil {
+ pendErrs = append(pendErrs, err)
+ }
+ }
+ if err := f.Sync(); err != nil {
+ pendErrs = append(pendErrs, err)
+ }
+ if *preserveAttrs || !exists {
+ if err := f.Chmod(perm); err != nil {
+ pendErrs = append(pendErrs, err)
+ }
+ }
+ if times != nil {
+ if err := syscall.Utimes(name, []syscall.Timeval{times.Atime, times.Mtime}); err != nil {
+ pendErrs = append(pendErrs, err)
+ }
+ }
+
+ ackErr := ack()
+ if isFatal(ackErr) {
+ return ackErr
+ }
+
+ var sentErr error
+ if len(pendErrs) > 0 {
+ sentErr = AccError{pendErrs}
+ sendError(sentErr)
+ } else {
+ fmt.Fprint(os.Stdout, "\x00")
+ }
+
+ if ackErr != nil {
+ return AccError{append(pendErrs, ackErr)}
+ }
+ return sentErr
+}
+
+func prepareDir(name string, perm os.FileMode) (bool, error) {
+ resetPerm := false
+ if st, err := os.Stat(name); err == nil {
+ if !st.IsDir() {
+ return resetPerm, errors.New(name + ": is not a directory")
+ }
+ if *preserveAttrs {
+ if err := os.Chmod(name, perm); err != nil {
+ return resetPerm, err
+ }
+ }
+ } else if os.IsNotExist(err) {
+ if err := os.Mkdir(name, perm|S_IRWXU); err != nil {
+ return resetPerm, err
+ }
+ resetPerm = true
+ } else {
+ return resetPerm, err
+ }
+ return resetPerm, nil
+}
+
+func send(name string) error {
+ f, err := os.Open(name)
+ if err != nil {
+ return teeError(err)
+ }
+ defer f.Close()
+
+ st, err := f.Stat()
+ if err != nil {
+ return teeError(err)
+ }
+ name = st.Name()
+
+ switch st.Mode() & os.ModeType {
+ case 0: /* regular file */
+ break
+ case os.ModeDir:
+ if *iamRecursive {
+ return sendDir(f, st)
+ }
+ return teeError(errors.New(name + ": is a directory"))
+ default:
+ return teeError(errors.New(name + ": not a regular file"))
+ }
+
+ if *preserveAttrs {
+ if err := sendAttr(st); err != nil {
+ return err
+ }
+ }
+
+ fmt.Fprintf(os.Stdout, "C%04o %d %s\n", toPosixPerm(st.Mode()), st.Size(), name)
+ if err := ack(); err != nil {
+ return err
+ }
+
+ if sent, err := io.Copy(os.Stdout, f); err != nil {
+ patch := io.LimitReader(ConstReader(0), st.Size()-sent)
+ if _, err := io.Copy(os.Stdout, patch); err != nil {
+ return FatalError(err.Error())
+ }
+ if err := ack(); err != nil {
+ return err
+ }
+ return teeError(err)
+ }
+
+ fmt.Fprint(os.Stdout, "\x00")
+ return ack()
+}
+
+func sendDir(dir *os.File, st os.FileInfo) error {
+ content, err := dir.Readdirnames(0)
+ if err != nil {
+ return teeError(err)
+ }
+
+ if *preserveAttrs {
+ if err := sendAttr(st); err != nil {
+ return err
+ }
+ }
+
+ fmt.Fprintf(os.Stdout, "D%04o %d %s\n", toPosixPerm(st.Mode()), 0, st.Name())
+ if err := ack(); err != nil {
+ return err
+ }
+
+ var sendErrs []error
+ for _, entry := range content {
+ if err := send(path.Join(dir.Name(), entry)); err != nil {
+ if _, ok := err.(FatalError); ok {
+ return err
+ }
+ sendErrs = append(sendErrs, err)
+ }
+ }
+
+ fmt.Fprintf(os.Stdout, "E\n")
+ ackErr := ack()
+ if isFatal(ackErr) {
+ return ackErr
+ }
+
+ if len(sendErrs) > 0 {
+ return AccError{sendErrs}
+ }
+ return ackErr
+}
+
+func parseSubj(line string) (perm os.FileMode, size int64, name string, err error) {
+ n := 0
+ pperm := 0
+ if n, err = fmt.Sscanf(line, "%o %d %s", &pperm, &size, &name); err != nil {
+ return
+ } else if n != 3 {
+ err = protocolErr
+ return
+ }
+ perm = toStdPerm(pperm)
+ if name == ".." || strings.ContainsRune(name, '/') {
+ err = FatalError(name + ": invalid name")
+ }
+ return
+}
+
+func sendAttr(st os.FileInfo) error {
+ mtime := st.ModTime().Unix()
+ atime := int64(0)
+
+ if sysStat, ok := st.Sys().(*syscall.Stat_t); ok {
+ atime, _ = sysStat.Atim.Unix()
+ }
+
+ fmt.Fprintf(os.Stdout, "T%d 0 %d 0\n", mtime, atime)
+ return ack()
+}
+
+func ack() error {
+ kind := []byte{0}
+ if _, err := os.Stdin.Read(kind); err != nil {
+ return FatalError(err.Error())
+ }
+ if kind[0] == 0 {
+ return nil
+ }
+
+ l, err := readLine()
+ if err != nil {
+ return FatalError(err.Error())
+ }
+
+ switch kind[0] {
+ case 1:
+ return errors.New(l)
+ case 2:
+ return FatalError(l)
+ default:
+ return protocolErr
+ }
+}
+
+func teeError(err error) error {
+ sendError(err)
+ return err
+}
+
+func sendError(err error) {
+ fmt.Fprintf(os.Stdout, "\x01%s\n", strings.Replace(err.Error(), "\n", "; ", -1))
+}
+
+func readLine() (string, error) {
+ l := make([]byte, 0, 64)
+ ch := []byte{0}
+
+ for {
+ if _, err := os.Stdin.Read(ch); err != nil {
+ return "", err
+ } else {
+ if ch[0] == '\n' {
+ break
+ }
+ l = append(l, ch[0])
+ }
+ }
+
+ return string(l), nil
+}
+
+func toPosixPerm(perm os.FileMode) int {
+ pp := perm & os.ModePerm
+ if perm&os.ModeSetuid != 0 {
+ pp |= S_ISUID
+ }
+ if perm&os.ModeSetgid != 0 {
+ pp |= S_ISGID
+ }
+ return int(pp)
+}
+
+func toStdPerm(posixPerm int) os.FileMode {
+ perm := os.FileMode(posixPerm) & os.ModePerm
+ if posixPerm&S_ISUID != 0 {
+ perm |= os.ModeSetuid
+ }
+ if posixPerm&S_ISGID != 0 {
+ perm |= os.ModeSetgid
+ }
+ return perm
+}
+
+func usage() {
+ fmt.Fprintf(os.Stderr, "Usage: rscp -f [-pr] [-l limit] file1 ...\n"+
+ " rscp -t [-prd] [-l limit] directory\n")
+ flag.PrintDefaults()
+ os.Exit(1)
+}
+
+type FileTimes struct {
+ Atime syscall.Timeval
+ Mtime syscall.Timeval
+}
+
+type FatalError string
+
+func (e FatalError) Error() string {
+ return string(e)
+}
+
+func isFatal(err error) bool {
+ _, isFatal := err.(FatalError)
+ return isFatal
+}
+
+type AccError struct {
+ Errors []error
+}
+
+func (e AccError) Error() string {
+ ve := []interface{}{}
+ for _, err := range e.Errors {
+ ve = append(ve, err)
+ }
+ return fmt.Sprintln(ve...)
+}
+
+type ConstReader byte
+
+func (c ConstReader) Read(b []byte) (int, error) {
+ for i, _ := range b {
+ b[i] = byte(c)
+ }
+ return len(b), nil
+}