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.

131 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,
})
}