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  }