github.com/transparency-dev/armored-witness-applet@v0.1.1/trusted_applet/internal/storage/slots/slots.go (about) 1 // Copyright 2022 The Armored Witness Applet authors. All Rights Reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package slots 16 17 import ( 18 "errors" 19 "fmt" 20 "sync" 21 22 "k8s.io/klog/v2" 23 ) 24 25 // Geometry describes the physical layout of a Partition and its slots on the 26 // underlying storage. 27 type Geometry struct { 28 // Start identifies the address of first block which is part of a partition. 29 Start uint 30 // Length is the number of blocks covered by this partition. 31 // i.e. [Start, Start+Length) is the range of blocks covered by this partition. 32 Length uint 33 // SlotLengths is an ordered list containing the lengths of the slot(s) 34 // allocated within this partition. 35 // For obvious reasons, great care must be taken if, once data has been written 36 // to one or more slots, the values specified in this list at the time the data 37 // was written are changed. 38 SlotLengths []uint 39 } 40 41 // Validate checks that the geometry is self-consistent. 42 func (g Geometry) Validate() error { 43 t := uint(0) 44 for _, l := range g.SlotLengths { 45 t += l 46 } 47 if t > g.Length { 48 return fmt.Errorf("invalid geometry: total slot length (%d blocks) exceeds overall length (%d blocks)", t, g.Length) 49 } 50 return nil 51 } 52 53 // Partition describes the extent and layout of a single contiguous region of 54 // underlying block storage. 55 type Partition struct { 56 // dev provides the device-specific read/write functionality. 57 dev BlockReaderWriter 58 59 // slots describes the layout of the slot(s) stored within this partition. 60 slots []Slot 61 } 62 63 // OpenPartition returns a partition struct for accessing the slots described by the given 64 // geometry using the provided read/write methods. 65 func OpenPartition(rw BlockReaderWriter, geo Geometry) (*Partition, error) { 66 if err := geo.Validate(); err != nil { 67 return nil, err 68 } 69 70 ret := &Partition{ 71 dev: rw, 72 } 73 74 b := geo.Start 75 for _, l := range geo.SlotLengths { 76 ret.slots = append(ret.slots, Slot{ 77 start: b, 78 length: l, 79 }) 80 b += l 81 } 82 83 return ret, nil 84 } 85 86 // Erase destroys the data stored in all slots configured in this partition. 87 // WARNING: Data Loss! 88 func (p *Partition) Erase() error { 89 klog.Info("Erasing partition") 90 borked := false 91 for i := range p.slots { 92 if err := p.eraseSlot(i); err != nil { 93 klog.Warningf("Failed to erase slot %d: %v", i, err) 94 } 95 } 96 if borked { 97 return errors.New("failed to erase one or more slots in partition") 98 } 99 return nil 100 } 101 102 func (p *Partition) eraseSlot(i int) error { 103 p.slots[i].mu.Lock() 104 defer p.slots[i].mu.Unlock() 105 106 // Invalidate journal since we're erasing data from underneath it. 107 p.slots[i].journal = nil 108 109 klog.Infof("Erasing partition slot %d @ block %d len %d blocks", i, p.slots[i].start, p.slots[i].length) 110 length := p.slots[i].length * p.dev.BlockSize() 111 start := p.slots[i].start 112 b := make([]byte, length) 113 if _, err := p.dev.WriteBlocks(start, b); err != nil { 114 return fmt.Errorf("slot %d occupying blocks [%d, %d): %v", i, start, start+length, err) 115 } 116 return nil 117 } 118 119 // Open opens the specified slot, returns an error if the slot is out of bounds. 120 func (p *Partition) Open(slot uint) (*Slot, error) { 121 if l := uint(len(p.slots)); slot >= l { 122 return nil, fmt.Errorf("invalid slot %d (partition has %d slots)", slot, l) 123 } 124 s := &p.slots[slot] 125 klog.V(2).Infof("Opening slot %d", slot) 126 if err := s.Open(p.dev); err != nil { 127 klog.V(2).Infof("Failed to open slot %d: %v", slot, err) 128 } 129 130 return s, nil 131 } 132 133 // NumSlots returns the number of slots configured in this partition. 134 func (p *Partition) NumSlots() int { 135 return len(p.slots) 136 } 137 138 // Slot represents the current data in a slot. 139 type Slot struct { 140 // mu guards access to this Slot. 141 mu sync.RWMutex 142 143 // start and length define the on-storage blocks assigned to this journal: 144 // [start, start+length). 145 start, length uint 146 147 // journal is the underlying journal used to store the data in this slot. 148 // if it's nil, it hasn't yet been opened and will be opened upon first 149 // access. 150 journal *Journal 151 } 152 153 // Open prepares the slot for use. 154 // This method is idempotent and will not return an error if called multiple times. 155 func (s *Slot) Open(dev BlockReaderWriter) error { 156 s.mu.Lock() 157 defer s.mu.Unlock() 158 159 if s.journal != nil { 160 return nil 161 } 162 j, err := OpenJournal(dev, s.start, s.length) 163 if err != nil { 164 return fmt.Errorf("failed to open journal: %v", err) 165 } 166 s.journal = j 167 return nil 168 } 169 170 // Read returns the last data successfully written to the slot, along with a token 171 // which can be used with CheckAndWrite. 172 func (s *Slot) Read() ([]byte, uint32, error) { 173 s.mu.RLock() 174 defer s.mu.RUnlock() 175 return s.journal.current.Data, s.journal.current.Revision, nil 176 } 177 178 // Write writes the provided data to the slot. 179 // Upon successful completion, this data will be returned by future calls to Read 180 // until another successful Write call is mode. 181 // If the call to Write fails, future calls to Read will return the previous 182 // successfully written data, if any. 183 func (s *Slot) Write(p []byte) error { 184 s.mu.Lock() 185 defer s.mu.Unlock() 186 return s.journal.Update(p) 187 } 188 189 // CheckAndWrite behaves like Write, with the exception that it will immediately 190 // return an error if the slot has been successfully written to since the Read call 191 // which produced the passed-in token. 192 func (s *Slot) CheckAndWrite(token uint32, p []byte) error { 193 s.mu.Lock() 194 defer s.mu.Unlock() 195 if s.journal.current.Revision != token { 196 return errors.New("invalid token, slot updated since then") 197 } 198 return s.journal.Update(p) 199 }