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