diff options
| -rw-r--r-- | internal/store/data.go | 10 | ||||
| -rw-r--r-- | internal/store/header.go | 3 | ||||
| -rw-r--r-- | internal/store/segment.go | 27 | ||||
| -rw-r--r-- | internal/store/store.go | 79 |
4 files changed, 86 insertions, 33 deletions
diff --git a/internal/store/data.go b/internal/store/data.go index 34a0f261..380481a0 100644 --- a/internal/store/data.go +++ b/internal/store/data.go @@ -23,7 +23,8 @@ func entryEncode(w io.Writer, s *hst.State) error { } } -// entryDecodeHeader calls entryReadHeader, returning [hst.AppError] for a non-nil error. +// entryDecodeHeader calls entryReadHeader, returning [hst.AppError] for a +// non-nil error. func entryDecodeHeader(r io.Reader) (hst.Enablements, error) { if et, err := entryReadHeader(r); err != nil { return 0, &hst.AppError{Step: "decode state header", Err: err} @@ -44,11 +45,14 @@ func entryDecode(r io.Reader, p *hst.State) (hst.Enablements, error) { } else if err = p.Config.Validate(hst.VAllowInsecure); err != nil { return et, err } else if p.Enablements.Unwrap() != et { - return et, &hst.AppError{Step: "validate state enablement", Err: os.ErrInvalid, + return et, &hst.AppError{ + Step: "validate state enablement", + Err: os.ErrInvalid, Msg: fmt.Sprintf( "state entry %s has unexpected enablement byte %#x, %#x", p.ID.String(), byte(p.Enablements.Unwrap()), byte(et), - )} + ), + } } else { return et, nil } diff --git a/internal/store/header.go b/internal/store/header.go index 5b5d73be..0e5a92a2 100644 --- a/internal/store/header.go +++ b/internal/store/header.go @@ -60,7 +60,8 @@ func (e *EntrySizeError) Error() string { return "state entry file " + strconv.Quote(e.Name) + " is too short" } -// entryCheckFile checks whether [os.FileInfo] refers to a file that might hold [hst.State]. +// entryCheckFile checks whether [os.FileInfo] refers to a file that might hold +// [hst.State]. func entryCheckFile(fi os.FileInfo) error { if fi.IsDir() { return syscall.EISDIR diff --git a/internal/store/segment.go b/internal/store/segment.go index 06849749..32c8a141 100644 --- a/internal/store/segment.go +++ b/internal/store/segment.go @@ -92,8 +92,14 @@ func (eh *EntryHandle) Load(v *hst.State) (hst.Enablements, error) { if v != nil { et, err = entryDecode(f, v) if err == nil && v.ID != eh.ID { - err = &hst.AppError{Step: "validate state identifier", Err: os.ErrInvalid, - Msg: fmt.Sprintf("state entry %s has unexpected id %s", eh.ID.String(), v.ID.String())} + err = &hst.AppError{ + Step: "validate state identifier", + Err: os.ErrInvalid, + Msg: fmt.Sprintf( + "state entry %s has unexpected id %s", + eh.ID.String(), v.ID.String(), + ), + } } } else { et, err = entryDecodeHeader(f) @@ -127,7 +133,10 @@ type Handle struct { // A non-nil error returned by Lock is of type [hst.AppError]. func (h *Handle) Lock() (unlock func(), err error) { if unlock, err = h.fileMu.Lock(); err != nil { - return nil, &hst.AppError{Step: "acquire lock on store segment " + strconv.Itoa(h.Identity), Err: err} + return nil, &hst.AppError{ + Step: "acquire lock on store segment " + strconv.Itoa(h.Identity), + Err: err, + } } return } @@ -174,8 +183,11 @@ func (h *Handle) Entries() (iter.Seq[*EntryHandle], int, error) { // this should never happen if ent.IsDir() { - eh.DecodeErr = &hst.AppError{Step: step, - Err: errors.New("unexpected directory " + strconv.Quote(ent.Name()) + " in store")} + eh.DecodeErr = &hst.AppError{ + Step: step, + Err: errors.New("unexpected directory " + + strconv.Quote(ent.Name()) + " in store"), + } goto out } @@ -186,7 +198,10 @@ func (h *Handle) Entries() (iter.Seq[*EntryHandle], int, error) { // this either indicates a serious bug or external interference if err := eh.ID.UnmarshalText([]byte(ent.Name())); err != nil { - eh.DecodeErr = &hst.AppError{Step: "decode store segment entry", Err: err} + eh.DecodeErr = &hst.AppError{ + Step: "decode store segment entry", + Err: err, + } goto out } diff --git a/internal/store/store.go b/internal/store/store.go index 3604521c..4383d55f 100644 --- a/internal/store/store.go +++ b/internal/store/store.go @@ -1,4 +1,5 @@ -// Package store implements cross-process state tracking for hakurei container instances. +// Package store provides storage for hakurei instance states, safe for +// concurrent and cross-process use. package store import ( @@ -15,11 +16,15 @@ import ( "hakurei.app/internal/lockedfile" ) -// MutexName is the pathname of the file backing [lockedfile.Mutex] of a [Store] and [Handle]. +// MutexName is the pathname of the file backing [lockedfile.Mutex] of a [Store] +// and [Handle]. const MutexName = "lock" -// A Store keeps track of [hst.State] via a well-known filesystem accessible to all hakurei priv-side processes. -// Access to store data and related resources are synchronised on a per-segment basis via [Handle]. +// A Store keeps track of [hst.State] via a well-known filesystem accessible to +// all hakurei priv-side processes. +// +// Access to store data and related resources are synchronised on a per-segment +// basis via [Handle]. type Store struct { // Pathname of directory that the store is rooted in. base *check.Absolute @@ -28,8 +33,10 @@ type Store struct { handles sync.Map // Inter-process mutex to synchronise operations against the entire store. - // Held during List and when initialising previously unknown identities during Do. - // Must not be accessed directly. Callers should use the bigLock method instead. + // + // Held during List and when initialising previously unknown identities + // during Do. Must not be accessed directly. Callers should use the bigLock + // method instead. fileMu *lockedfile.Mutex // For creating the base directory. @@ -43,11 +50,17 @@ type Store struct { func (s *Store) bigLock() (unlock func(), err error) { s.mkdirOnce.Do(func() { s.mkdirErr = os.MkdirAll(s.base.String(), 0700) }) if s.mkdirErr != nil { - return nil, &hst.AppError{Step: "create state store directory", Err: s.mkdirErr} + return nil, &hst.AppError{ + Step: "create state store directory", + Err: s.mkdirErr, + } } if unlock, err = s.fileMu.Lock(); err != nil { - return nil, &hst.AppError{Step: "acquire lock on the state store", Err: err} + return nil, &hst.AppError{ + Step: "acquire lock on the state store", + Err: err, + } } return } @@ -73,7 +86,10 @@ func (s *Store) Handle(identity int) (*Handle, error) { if err != nil && !errors.Is(err, fs.ErrExist) { // handle methods will likely return ENOENT s.handles.CompareAndDelete(identity, h) - return nil, &hst.AppError{Step: "create store segment directory", Err: err} + return nil, &hst.AppError{ + Step: "create store segment directory", + Err: err, + } } } return h, nil @@ -88,8 +104,9 @@ type SegmentIdentity struct { } // Segments returns an iterator over all [SegmentIdentity] known to the [Store]. -// To obtain a [Handle] on a segment, caller must then call [Store.Handle]. -// A non-nil error returned by segments is of type [hst.AppError]. +// +// To obtain a [Handle] on a segment, caller must then call [Store.Handle]. A +// non-nil error returned by segments is of type [hst.AppError]. func (s *Store) Segments() (iter.Seq[SegmentIdentity], int, error) { // read directory contents, should only contain storeMutexName and identity var entries []os.DirEntry @@ -102,7 +119,10 @@ func (s *Store) Segments() (iter.Seq[SegmentIdentity], int, error) { unlock() if err != nil && !errors.Is(err, os.ErrNotExist) { - return nil, -1, &hst.AppError{Step: "read store segments", Err: err} + return nil, -1, &hst.AppError{ + Step: "read store segments", + Err: err, + } } } @@ -126,19 +146,25 @@ func (s *Store) Segments() (iter.Seq[SegmentIdentity], int, error) { } // this should never happen - si.Err = &hst.AppError{Step: step, Err: syscall.ENOTDIR, - Msg: "skipped non-directory entry " + strconv.Quote(ent.Name())} + si.Err = &hst.AppError{ + Step: step, Err: syscall.ENOTDIR, + Msg: "skipped non-directory entry " + strconv.Quote(ent.Name()), + } goto out } // failure paths either indicates a serious bug or external interference if v, err := strconv.Atoi(ent.Name()); err != nil { - si.Err = &hst.AppError{Step: step, Err: err, - Msg: "skipped non-identity entry " + strconv.Quote(ent.Name())} + si.Err = &hst.AppError{ + Step: step, Err: err, + Msg: "skipped non-identity entry " + strconv.Quote(ent.Name()), + } goto out } else if v < hst.IdentityStart || v > hst.IdentityEnd { - si.Err = &hst.AppError{Step: step, Err: syscall.ERANGE, - Msg: "skipped out of bounds entry " + strconv.Itoa(v)} + si.Err = &hst.AppError{ + Step: step, Err: syscall.ERANGE, + Msg: "skipped out of bounds entry " + strconv.Itoa(v), + } goto out } else { si.Identity = v @@ -152,9 +178,11 @@ func (s *Store) Segments() (iter.Seq[SegmentIdentity], int, error) { }, l, nil } -// All returns a non-reusable iterator over all [EntryHandle] known to this [Store]. -// Callers must call copyError after completing iteration and handle the error accordingly. -// A non-nil error returned by copyError is of type [hst.AppError]. +// All returns a non-reusable iterator over all [EntryHandle] known to this +// [Store]. +// +// Callers must call copyError after completing iteration and handle the error +// accordingly. A non-nil error returned by copyError is of type [hst.AppError]. func (s *Store) All() (entries iter.Seq[*EntryHandle], copyError func() error) { var savedErr error return func(yield func(*EntryHandle) bool) { @@ -197,7 +225,12 @@ func (s *Store) All() (entries iter.Seq[*EntryHandle], copyError func() error) { } // New returns the address of a new instance of [Store]. -// Multiple instances of [Store] rooted in the same directory is possible, but unsupported. +// +// Multiple instances of [Store] rooted in the same directory is possible, but +// unsupported. func New(base *check.Absolute) *Store { - return &Store{base: base, fileMu: lockedfile.MutexAt(base.Append(MutexName).String())} + return &Store{ + base: base, + fileMu: lockedfile.MutexAt(base.Append(MutexName).String()), + } } |
