257 lines
4.2 KiB
Go
257 lines
4.2 KiB
Go
// 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 <base> [ <path> ]
|
|
|
|
ls file:///mnt/nfs /foo
|
|
|
|
copy file from/to directory:
|
|
cp <src base> <src> <dst base> <dst>
|
|
|
|
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 <base> <file>
|
|
|
|
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, nil, 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, nil, true)
|
|
if err != nil {
|
|
return
|
|
}
|
|
defer sfs.Close()
|
|
dfs, err := parse.FS(dst, dk, nil, 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, nil, true)
|
|
if err != nil {
|
|
return
|
|
}
|
|
defer fs.Close()
|
|
return fs.Remove(path)
|
|
}
|