//go:build !wasm

// Copyright (c) HashiCorp, Inc
// SPDX-License-Identifier: MPL-2.0

package metadb

import (
	
	
	
	
	

	

	
)

const (
	// FileName is the default file name for the bolt db file.
	FileName = "wal-meta.db"

	// *Bucket are the names used for internal bolt buckets
	MetaBucket   = "wal-meta"
	StableBucket = "stable"

	// We just need one key for now so use the byte 'm' for meta arbitrarily.
	MetaKey = "m"
)

var (
	// ErrUnintialized is returned when any call is made before Load has opened
	// the DB file.
	ErrUnintialized = errors.New("uninitialized")
)

// BoltMetaDB implements types.MetaStore using BoltDB as a reliable persistent
// store. See repo README for reasons for this design choice and performance
// implications.
type BoltMetaDB struct {
	dir string
	db  *bbolt.DB
}

func ( *BoltMetaDB) ( string) error {
	if .dir != "" && .dir !=  {
		return fmt.Errorf("can't load dir %s, already open in dir %s", , .dir)
	}
	if .db != nil {
		return nil
	}

	 := filepath.Join(, FileName)

	 := func() error {
		,  := bbolt.Open(, 0644, nil)
		if  != nil {
			return fmt.Errorf("failed to open %s: %w", FileName, )
		}
		.db = 
		.dir = 
		return nil
	}

	// BoltDB can get stuck in invalid states if we crash while it's initializing.
	// We can't distinguish those as safe to just wipe it and start again because
	// we don't know for sure if it's failing due to bad init or later corruption
	// (which would loose data if we just wipe and start over). So to ensure
	// initial creation of the WAL is as crash-safe as possible we will manually
	// detect we have an atomic init procedure:
	//  1. Check if file exits already. If yes, skip init and just open it.
	//  2. Delete any existing DB file with tmp name
	//  3. Creat a new BoltDB that is empty and has the buckets with a temp name.
	//  4. Once that's committed, rename to final name and Fsync parent dir
	,  := os.Stat()
	if  == nil {
		// File exists, just open it
		return ()
	}
	if !errors.Is(, os.ErrNotExist) {
		// Unknown err just return that
		return fmt.Errorf("failed to stat %s: %w", FileName, )
	}

	// File doesn't exist, initialize a new DB in a crash-safe way
	if  := safeInitBoltDB();  != nil {
		return fmt.Errorf("failed initializing meta DB: %w", )
	}

	// All good, now open it!
	return ()
}

func safeInitBoltDB( string) error {
	 := filepath.Join(, FileName+".tmp")

	// Delete any old attempts to init that were unsuccessful
	if  := os.RemoveAll();  != nil {
		return 
	}

	// Open bolt DB at tmp file name
	,  := bbolt.Open(, 0644, nil)
	if  != nil {
		return 
	}

	,  := .Begin(true)
	defer .Rollback()

	if  != nil {
		return 
	}
	_,  = .CreateBucket([]byte(MetaBucket))
	if  != nil {
		return 
	}
	_,  = .CreateBucket([]byte(StableBucket))
	if  != nil {
		return 
	}
	if  := .Commit();  != nil {
		return 
	}
	// Close the file ready to rename into place and re-open. This probably isn't
	// necessary but it make it easier to reason about this code path being
	// totally separate from the common case.
	if  := .Close();  != nil {
		return 
	}

	// We created the DB OK. Now rename it to the final name.
	if  := os.Rename(, filepath.Join(, FileName));  != nil {
		return 
	}

	// And Fsync that parent dir to make sure the new new file with it's new name
	// is persisted!
	,  := os.Open()
	if  != nil {
		return 
	}
	 = .Sync()
	 := .Close()
	if  != nil {
		return 
	}
	return 
}

// Load loads the existing persisted state. If there is no existing state
// implementations are expected to create initialize new storage and return an
// empty state.
func ( *BoltMetaDB) ( string) (types.PersistentState, error) {
	var  types.PersistentState

	if  := .ensureOpen();  != nil {
		return , 
	}

	,  := .db.Begin(false)
	if  != nil {
		return , 
	}
	defer .Rollback()
	 := .Bucket([]byte(MetaBucket))

	// We just need one key for now so use the byte 'm' for meta arbitrarily.
	 := .Get([]byte(MetaKey))
	if  == nil {
		// This is valid it's an "empty" log that will be initialized by the WAL.
		return , nil
	}

	if  := json.Unmarshal(, &);  != nil {
		return , fmt.Errorf("%w: failed to parse persisted state: %s", types.ErrCorrupt, )
	}
	return , nil
}

// CommitState must atomically replace all persisted metadata in the current
// store with the set provided. It must not return until the data is persisted
// durably and in a crash-safe way otherwise the guarantees of the WAL will be
// compromised. The WAL will only ever call this in a single thread at one
// time and it will never be called concurrently with Load however it may be
// called concurrently with Get/SetStable operations.
func ( *BoltMetaDB) ( types.PersistentState) error {
	if .db == nil {
		return ErrUnintialized
	}

	,  := json.Marshal()
	if  != nil {
		return fmt.Errorf("failed to encode persisted state: %w", )
	}

	,  := .db.Begin(true)
	if  != nil {
		return 
	}
	defer .Rollback()
	 := .Bucket([]byte(MetaBucket))

	if  := .Put([]byte(MetaKey), );  != nil {
		return 
	}

	return .Commit()
}

// Close implements io.Closer
func ( *BoltMetaDB) () error {
	if .db == nil {
		return nil
	}
	 := .db.Close()
	.db = nil
	return 
}