| 1 | package host |
| 2 | |
| 3 | import ( |
| 4 | "encoding/json" |
| 5 | "os" |
| 6 | "strings" |
| 7 | |
| 8 | "github.com/carlosframework/platform/internal/release" |
| 9 | "github.com/carlosframework/platform/internal/rootstate" |
| 10 | ) |
| 11 | |
| 12 | func recoveryResultKey(a rollbackAttempt) string { |
| 13 | return rollbackHostKey(a.Host) + "." + a.ID + ".recovery" |
| 14 | } |
| 15 | |
| 16 | func recoveryHistoryOutcome(a rollbackAttempt) (release.HistoryRecovery, error) { |
| 17 | if !a.Independent || a.History == nil || a.HistoryBinding.Host != a.Host || a.History.ID != a.ID || len(a.ID) != 32 { |
| 18 | return release.HistoryRecovery{}, os.ErrPermission |
| 19 | } |
| 20 | return release.HistoryRecovery{Binding: a.HistoryBinding, Attempt: *a.History, Settled: a.Settled, Failed: a.Failed, Recovering: a.Recovering, Verified: a.Verified}, nil |
| 21 | } |
| 22 | |
| 23 | func (c rootRestartChecks) copyRecoveryHistory(h *release.RootHistory, binding release.HistoryBinding) ([]release.HistoryRecovery, error) { |
| 24 | if c.attempts == nil || h == nil { |
| 25 | return nil, os.ErrPermission |
| 26 | } |
| 27 | s, err := c.attempts() |
| 28 | if err != nil { |
| 29 | return nil, err |
| 30 | } |
| 31 | defer s.Close() |
| 32 | names, err := s.Names() |
| 33 | if err != nil { |
| 34 | return nil, err |
| 35 | } |
| 36 | var unresolved []release.HistoryRecovery |
| 37 | for _, name := range names { |
| 38 | if !strings.HasSuffix(name, ".attempt") && !strings.HasSuffix(name, ".recovery") { |
| 39 | continue |
| 40 | } |
| 41 | raw, err := s.Read(name) |
| 42 | if err != nil { |
| 43 | return nil, err |
| 44 | } |
| 45 | var a rollbackAttempt |
| 46 | if len(raw) > rootstate.MaxRecord || json.Unmarshal(raw, &a) != nil || (name != rollbackJournalKey(a.Host) && name != recoveryResultKey(a)) { |
| 47 | return nil, os.ErrPermission |
| 48 | } |
| 49 | if !a.Independent { |
| 50 | continue |
| 51 | } |
| 52 | r, err := recoveryHistoryOutcome(a) |
| 53 | if err != nil { |
| 54 | return nil, err |
| 55 | } |
| 56 | if r.Binding.Account != binding.Account || r.Binding.App != binding.App { |
| 57 | continue |
| 58 | } |
| 59 | if err := s.Write(name, raw); err != nil { |
| 60 | return nil, err |
| 61 | } |
| 62 | if err := h.RetainRecovery(r); err != nil { |
| 63 | return nil, err |
| 64 | } |
| 65 | if !r.Settled || (!r.Failed && !r.Recovering && !r.Verified) { |
| 66 | unresolved = append(unresolved, r) |
| 67 | } |
| 68 | } |
| 69 | return unresolved, nil |
| 70 | } |
| 71 | |
| 72 | func (c rootRestartChecks) retainRecoveryHistory(h *release.RootHistory, binding release.HistoryBinding) error { |
| 73 | unresolved, err := c.copyRecoveryHistory(h, binding) |
| 74 | if err != nil { |
| 75 | return err |
| 76 | } |
| 77 | return checkUnresolvedRecoveries(h, unresolved) |
| 78 | } |
| 79 | |
| 80 | func checkUnresolvedRecoveries(h *release.RootHistory, unresolved []release.HistoryRecovery) error { |
| 81 | for _, r := range unresolved { |
| 82 | if !r.Settled { |
| 83 | return os.ErrPermission |
| 84 | } |
| 85 | retired, err := h.RecoveryRetired(r) |
| 86 | if err != nil { |
| 87 | return err |
| 88 | } |
| 89 | if !retired { |
| 90 | return os.ErrPermission |
| 91 | } |
| 92 | } |
| 93 | return nil |
| 94 | } |
| 95 | |
| 96 | func (c rootRestartChecks) mintRefusals(h *release.RootHistory, binding release.HistoryBinding) ([]release.Refusal, error) { |
| 97 | if err := c.retainRecoveryHistory(h, binding); err != nil { |
| 98 | return nil, err |
| 99 | } |
| 100 | refs, err := h.MintRefusals(binding) |
| 101 | if err != nil { |
| 102 | return nil, err |
| 103 | } |
| 104 | if len(refs) > 256 { |
| 105 | return nil, os.ErrPermission |
| 106 | } |
| 107 | return refs, nil |
| 108 | } |
| 109 | |