// Copyright (C) 2022 Marius Schellenberger package main import ( "cachefs/pkg/provider" "cachefs/pkg/provider/parse" "encoding/hex" "errors" "flag" "fmt" "io" "math" "os" "path/filepath" "strings" "sync/atomic" "time" ) var ( progress bool ) const gib = 1024 * 1024 * 1024 const ( srcKey = "SRC_KEY" dstKey = "DST_KEY" ) func fatal(err error) { fmt.Println(err) os.Exit(1) } func getKey(key string) (b []byte, err error) { k := os.Getenv(key) if k == "" { return } b, err = hex.DecodeString(k) if err != nil { err = fmt.Errorf("error decoding %s: %w", key, err) } return } func main() { flag.Usage = func() { fmt.Fprintf(flag.CommandLine.Output(), "Usage of %s:\n", os.Args[0]) fmt.Fprintf(flag.CommandLine.Output(), ` optional environment variables: `+srcKey+` (source encryption key) `+dstKey+` (destination encryption key) list files and directories (uses `+srcKey+`): ls [ ] ls file:///mnt/nfs /foo copy file from/to directory: cp cp file:///mnt/nfs foo.txt file:///mnt/cache bar.txt cp file:///mnt/cache fizz.txt sftp:///home/user fizz.txt remove file (uses `+srcKey+`): rm rm file:///mnt/nfs /buzz.txt options -p show copy progress `) os.Exit(1) } flag.BoolVar(&progress, "p", false, "show copy progress") flag.Parse() args := flag.Args() if len(args) < 2 { flag.Usage() } var err error switch args[0] { case "ls": err = ls(args[1:]) case "cp": if len(args) < 5 { flag.Usage() } err = cp(args[1:]) case "rm": if len(args) < 3 { flag.Usage() } err = rm(args[1:]) } if err != nil { fatal(err) } } func ls(args []string) (err error) { k, err := getKey(srcKey) if err != nil { return } fs, err := parse.FS(args[0], k, true) if err != nil { return } defer fs.Close() path := "" if len(args) == 2 { path = args[1] } f, err := fs.Open(path) if err != nil { return } defer f.Close() fis, err := f.Readdir(0) if err != nil { return } for _, fi := range fis { p := filepath.Join(fs.Root(), path, fi.Name()) if fi.IsDir() { fmt.Println(fi.Mode().String(), fi.Size(), p+"/") continue } fmt.Println(fi.Mode().String(), fi.Size(), p) } return } func cp(args []string) (err error) { src := args[0] sp := args[1] dst := args[2] dp := args[3] sk, err := getKey(srcKey) if err != nil { return } dk, err := getKey(dstKey) if err != nil { return } sfs, err := parse.FS(src, sk, true) if err != nil { return } defer sfs.Close() dfs, err := parse.FS(dst, dk, true) if err != nil { return } defer dfs.Close() sf, err := sfs.Open(sp) if err != nil { return } defer sf.Close() if i := strings.LastIndex(dp, "/"); i > 0 { dir := dp[:i] _, err = dfs.Stat(dir) if errors.Is(err, os.ErrNotExist) { err = dfs.MkdirAll(dir, 0o755) if err != nil { return } } } df, err := dfs.OpenFile(dp, os.O_RDWR|os.O_CREATE, 0o644) if err != nil { return } defer df.Close() if progress { fi, err := sf.Stat() if err != nil { return err } r := &progressReader{ f: sf, done: make(chan struct{}), size: fi.Size(), } go r.Progress() _, err = io.Copy(df, r) close(r.done) r.Print() fmt.Println() } else { _, err = io.Copy(df, sf) } return } type progressReader struct { f provider.File done chan struct{} size int64 n int64 } func (p *progressReader) Read(b []byte) (n int, err error) { n, err = p.f.Read(b) atomic.AddInt64(&p.n, int64(n)) return } func (p *progressReader) ReadAt(b []byte, off int64) (n int, err error) { n, err = p.f.ReadAt(b, off) atomic.AddInt64(&p.n, int64(n)) return } func (p *progressReader) Print() { n := atomic.LoadInt64(&p.n) percent := int(math.Round(float64(n) / float64(p.size) * 100)) fmt.Printf("progress: %d / %d %d%%\r", n, p.size, percent) } func (p *progressReader) Progress() { for { select { case <-p.done: return case <-time.After(time.Millisecond * 500): } p.Print() } } func rm(args []string) (err error) { src := args[0] path := args[1] k, err := getKey(srcKey) if err != nil { return } fs, err := parse.FS(src, k, true) if err != nil { return } defer fs.Close() return fs.Remove(path) }