You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
130 lines
3.4 KiB
130 lines
3.4 KiB
// tera-recovery-request-tool creates and inspects the exact 2 KiB BCB record
|
|
// used by the target recovery protocol. It never writes a target block device.
|
|
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"flag"
|
|
"fmt"
|
|
"os"
|
|
|
|
"tera-clone/services/tera-api/internal/update/recoveryrequest"
|
|
)
|
|
|
|
func main() {
|
|
if len(os.Args) < 2 {
|
|
usage()
|
|
}
|
|
var err error
|
|
switch os.Args[1] {
|
|
case "create":
|
|
err = create(os.Args[2:])
|
|
case "clear":
|
|
err = clear(os.Args[2:])
|
|
case "inspect":
|
|
err = inspect(os.Args[2:])
|
|
default:
|
|
usage()
|
|
}
|
|
if err != nil {
|
|
fmt.Fprintln(os.Stderr, "tera-recovery-request-tool:", err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
func usage() {
|
|
fmt.Fprintln(os.Stderr, "usage: tera-recovery-request-tool {create|clear|inspect} [flags]")
|
|
os.Exit(2)
|
|
}
|
|
|
|
func clear(arguments []string) error {
|
|
flags := flag.NewFlagSet("clear", flag.ContinueOnError)
|
|
output := flags.String("output", "", "new 2048-byte cleared BCB record path")
|
|
if err := flags.Parse(arguments); err != nil {
|
|
return err
|
|
}
|
|
if *output == "" {
|
|
return errors.New("output is required")
|
|
}
|
|
file, err := os.OpenFile(*output, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0o600)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
encoded := recoveryrequest.Clear()
|
|
written, writeErr := file.Write(encoded)
|
|
syncErr := file.Sync()
|
|
closeErr := file.Close()
|
|
if writeErr != nil || written != recoveryrequest.Size {
|
|
_ = os.Remove(*output)
|
|
return fmt.Errorf("write cleared BCB record: wrote %d of %d bytes: %w", written, recoveryrequest.Size, writeErr)
|
|
}
|
|
if syncErr != nil {
|
|
_ = os.Remove(*output)
|
|
return syncErr
|
|
}
|
|
if closeErr != nil {
|
|
_ = os.Remove(*output)
|
|
return closeErr
|
|
}
|
|
fmt.Printf("created cleared BCB record: %s (misc offset %d)\n", *output, recoveryrequest.Offset)
|
|
return nil
|
|
}
|
|
|
|
func create(arguments []string) error {
|
|
flags := flag.NewFlagSet("create", flag.ContinueOnError)
|
|
operationID := flags.String("operation-id", "", "validated update operation id")
|
|
output := flags.String("output", "", "new 2048-byte BCB record path")
|
|
if err := flags.Parse(arguments); err != nil {
|
|
return err
|
|
}
|
|
if *output == "" {
|
|
return errors.New("output is required")
|
|
}
|
|
encoded, err := recoveryrequest.Encode(*operationID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
file, err := os.OpenFile(*output, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0o600)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
written, writeErr := file.Write(encoded)
|
|
syncErr := file.Sync()
|
|
closeErr := file.Close()
|
|
if writeErr != nil || written != recoveryrequest.Size {
|
|
_ = os.Remove(*output)
|
|
return fmt.Errorf("write BCB record: wrote %d of %d bytes: %w", written, recoveryrequest.Size, writeErr)
|
|
}
|
|
if syncErr != nil {
|
|
_ = os.Remove(*output)
|
|
return syncErr
|
|
}
|
|
if closeErr != nil {
|
|
_ = os.Remove(*output)
|
|
return closeErr
|
|
}
|
|
fmt.Printf("created recovery BCB record: %s (misc offset %d)\n", *output, recoveryrequest.Offset)
|
|
return nil
|
|
}
|
|
|
|
func inspect(arguments []string) error {
|
|
flags := flag.NewFlagSet("inspect", flag.ContinueOnError)
|
|
input := flags.String("input", "", "2048-byte BCB record path")
|
|
if err := flags.Parse(arguments); err != nil {
|
|
return err
|
|
}
|
|
data, err := os.ReadFile(*input)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
message, err := recoveryrequest.Decode(data)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return json.NewEncoder(os.Stdout).Encode(map[string]any{
|
|
"valid": true, "command": recoveryrequest.Command,
|
|
"operation_id": message.OperationID, "misc_offset": recoveryrequest.Offset,
|
|
"record_bytes": recoveryrequest.Size,
|
|
})
|
|
}
|
|
|