feat: Add restore command which combines behaviour of find and recovery restore commands
This commit is contained in:
@@ -34,74 +34,95 @@ var recoveryRestoreCmd = &cobra.Command{
|
||||
boil.DebugMode = true
|
||||
}
|
||||
|
||||
f, isRegular, err := openTapeReadOnly(viper.GetString(tapeFlag))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
return restoreFromRecordAndBlock(
|
||||
viper.GetString(tapeFlag),
|
||||
viper.GetInt(recordSizeFlag),
|
||||
viper.GetInt(recordFlag),
|
||||
viper.GetInt(blockFlag),
|
||||
viper.GetString(dstFlag),
|
||||
viper.GetBool(previewFlag),
|
||||
true,
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
var tr *tar.Reader
|
||||
if isRegular {
|
||||
// Seek to record and block
|
||||
if _, err := f.Seek(int64((viper.GetInt(recordSizeFlag)*controllers.BlockSize*viper.GetInt(recordFlag))+viper.GetInt(blockFlag)*controllers.BlockSize), io.SeekStart); err != nil {
|
||||
return err
|
||||
}
|
||||
func restoreFromRecordAndBlock(
|
||||
tape string,
|
||||
recordSize int,
|
||||
record int,
|
||||
block int,
|
||||
dst string,
|
||||
preview bool,
|
||||
showHeader bool,
|
||||
) error {
|
||||
f, isRegular, err := openTapeReadOnly(tape)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
tr = tar.NewReader(f)
|
||||
} else {
|
||||
// Seek to record
|
||||
if err := controllers.SeekToRecordOnTape(f, int32(viper.GetInt(recordFlag))); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Seek to block
|
||||
br := bufio.NewReaderSize(f, controllers.BlockSize*viper.GetInt(recordSizeFlag))
|
||||
if _, err := br.Read(make([]byte, viper.GetInt(blockFlag)*controllers.BlockSize)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tr = tar.NewReader(br)
|
||||
}
|
||||
|
||||
hdr, err := tr.Next()
|
||||
if err != nil {
|
||||
var tr *tar.Reader
|
||||
if isRegular {
|
||||
// Seek to record and block
|
||||
if _, err := f.Seek(int64((recordSize*controllers.BlockSize*record)+block*controllers.BlockSize), io.SeekStart); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tr = tar.NewReader(f)
|
||||
} else {
|
||||
// Seek to record
|
||||
if err := controllers.SeekToRecordOnTape(f, int32(record)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Seek to block
|
||||
br := bufio.NewReaderSize(f, controllers.BlockSize*recordSize)
|
||||
if _, err := br.Read(make([]byte, block*controllers.BlockSize)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tr = tar.NewReader(br)
|
||||
}
|
||||
|
||||
hdr, err := tr.Next()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if showHeader {
|
||||
if err := formatting.PrintCSV(formatting.TARHeaderCSV); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := formatting.PrintCSV(formatting.GetTARHeaderAsCSV(int64(viper.GetInt(recordFlag)), int64(viper.GetInt(blockFlag)), hdr)); err != nil {
|
||||
if err := formatting.PrintCSV(formatting.GetTARHeaderAsCSV(int64(record), int64(block), hdr)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if !preview {
|
||||
if dst == "" {
|
||||
dst = filepath.Base(hdr.Name)
|
||||
}
|
||||
|
||||
if hdr.Typeflag == tar.TypeDir {
|
||||
return os.MkdirAll(dst, hdr.FileInfo().Mode())
|
||||
}
|
||||
|
||||
dstFile, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE, hdr.FileInfo().Mode())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !viper.GetBool(previewFlag) {
|
||||
dst := viper.GetString(dstFlag)
|
||||
if dst == "" {
|
||||
dst = filepath.Base(hdr.Name)
|
||||
}
|
||||
|
||||
if hdr.Typeflag == tar.TypeDir {
|
||||
return os.MkdirAll(dst, hdr.FileInfo().Mode())
|
||||
}
|
||||
|
||||
dstFile, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE, hdr.FileInfo().Mode())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := dstFile.Truncate(0); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := io.Copy(dstFile, tr); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := dstFile.Truncate(0); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
if _, err := io.Copy(dstFile, tr); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
||||
124
cmd/stbak/cmd/restore.go
Normal file
124
cmd/stbak/cmd/restore.go
Normal file
@@ -0,0 +1,124 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"context"
|
||||
"database/sql"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/pojntfx/stfs/pkg/converters"
|
||||
models "github.com/pojntfx/stfs/pkg/db/sqlite/models/metadata"
|
||||
"github.com/pojntfx/stfs/pkg/formatting"
|
||||
"github.com/pojntfx/stfs/pkg/persisters"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/volatiletech/sqlboiler/v4/boil"
|
||||
)
|
||||
|
||||
const (
|
||||
flattenFlag = "flatten"
|
||||
)
|
||||
|
||||
var restoreCmd = &cobra.Command{
|
||||
Use: "restore",
|
||||
Aliases: []string{"r"},
|
||||
Short: "Restore a file or directory",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if err := viper.BindPFlags(cmd.PersistentFlags()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if viper.GetBool(verboseFlag) {
|
||||
boil.DebugMode = true
|
||||
}
|
||||
|
||||
metadataPersister := persisters.NewMetadataPersister(viper.GetString(metadataFlag))
|
||||
if err := metadataPersister.Open(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
headersToRestore := []*models.Header{}
|
||||
src := strings.TrimSuffix(viper.GetString(srcFlag), "/")
|
||||
dbhdr, err := metadataPersister.GetHeader(context.Background(), src)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
src = src + "/"
|
||||
|
||||
dbhdr, err = metadataPersister.GetHeader(context.Background(), src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
headersToRestore = append(headersToRestore, dbhdr)
|
||||
|
||||
// If the header refers to a directory, get it's children
|
||||
if dbhdr.Typeflag == tar.TypeDir {
|
||||
dbhdrs, err := metadataPersister.GetHeaderChildren(context.Background(), src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
headersToRestore = append(headersToRestore, dbhdrs...)
|
||||
}
|
||||
|
||||
for i, dbhdr := range headersToRestore {
|
||||
if i == 0 {
|
||||
if err := formatting.PrintCSV(formatting.TARHeaderCSV); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
hdr, err := converters.DBHeaderToTarHeader(dbhdr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := formatting.PrintCSV(formatting.GetTARHeaderAsCSV(dbhdr.Record, dbhdr.Block, hdr)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dst := dbhdr.Name
|
||||
if viper.GetString(dstFlag) != "" {
|
||||
if viper.GetBool(flattenFlag) {
|
||||
dst = viper.GetString(dstFlag)
|
||||
} else {
|
||||
dst = filepath.Join(viper.GetString(dstFlag), strings.TrimPrefix(dst, viper.GetString(srcFlag)))
|
||||
|
||||
if strings.TrimSuffix(dst, "/") == strings.TrimSuffix(viper.GetString(dstFlag), "/") {
|
||||
dst = filepath.Join(dst, path.Base(dbhdr.Name)) // Append the name so we don't overwrite
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := restoreFromRecordAndBlock(
|
||||
viper.GetString(tapeFlag),
|
||||
viper.GetInt(recordSizeFlag),
|
||||
int(dbhdr.Record),
|
||||
int(dbhdr.Block),
|
||||
dst,
|
||||
false,
|
||||
false,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
restoreCmd.PersistentFlags().IntP(recordSizeFlag, "e", 20, "Amount of 512-bit blocks per record")
|
||||
restoreCmd.PersistentFlags().StringP(srcFlag, "s", "", "File or directory to restore")
|
||||
restoreCmd.PersistentFlags().StringP(dstFlag, "d", "", "File or directory restore to (archived name by default)")
|
||||
restoreCmd.PersistentFlags().BoolP(flattenFlag, "f", false, "Ignore the folder hierarchy on the tape or tar file")
|
||||
|
||||
viper.AutomaticEnv()
|
||||
|
||||
rootCmd.AddCommand(restoreCmd)
|
||||
}
|
||||
Reference in New Issue
Block a user