/*
 * SPDX-FileCopyrightText: © 2017-2025 Istari Digital, Inc.
 * SPDX-License-Identifier: Apache-2.0
 */

package badger

import (
	
	
	
	
	
	
	
	
	
	
	

	

	
	
	
)

// Manifest represents the contents of the MANIFEST file in a Badger store.
//
// The MANIFEST file describes the startup state of the db -- all LSM files and what level they're
// at.
//
// It consists of a sequence of ManifestChangeSet objects.  Each of these is treated atomically,
// and contains a sequence of ManifestChange's (file creations/deletions) which we use to
// reconstruct the manifest at startup.
type Manifest struct {
	Levels []levelManifest
	Tables map[uint64]TableManifest

	// Contains total number of creation and deletion changes in the manifest -- used to compute
	// whether it'd be useful to rewrite the manifest.
	Creations int
	Deletions int
}

func createManifest() Manifest {
	 := make([]levelManifest, 0)
	return Manifest{
		Levels: ,
		Tables: make(map[uint64]TableManifest),
	}
}

// levelManifest contains information about LSM tree levels
// in the MANIFEST file.
type levelManifest struct {
	Tables map[uint64]struct{} // Set of table id's
}

// TableManifest contains information about a specific table
// in the LSM tree.
type TableManifest struct {
	Level       uint8
	KeyID       uint64
	Compression options.CompressionType
}

// manifestFile holds the file pointer (and other info) about the manifest file, which is a log
// file we append to.
type manifestFile struct {
	fp        *os.File
	directory string

	// The external magic number used by the application running badger.
	externalMagic uint16

	// We make this configurable so that unit tests can hit rewrite() code quickly
	deletionsRewriteThreshold int

	// Guards appends, which includes access to the manifest field.
	appendLock sync.Mutex

	// Used to track the current state of the manifest, used when rewriting.
	manifest Manifest

	// Used to indicate if badger was opened in InMemory mode.
	inMemory bool
}

const (
	// ManifestFilename is the filename for the manifest file.
	ManifestFilename                  = "MANIFEST"
	manifestRewriteFilename           = "MANIFEST-REWRITE"
	manifestDeletionsRewriteThreshold = 10000
	manifestDeletionsRatio            = 10
)

// asChanges returns a sequence of changes that could be used to recreate the Manifest in its
// present state.
func ( *Manifest) () []*pb.ManifestChange {
	 := make([]*pb.ManifestChange, 0, len(.Tables))
	for ,  := range .Tables {
		 = append(, newCreateChange(, int(.Level), .KeyID, .Compression))
	}
	return 
}

func ( *Manifest) ( Options) Manifest {
	 := pb.ManifestChangeSet{Changes: .asChanges()}
	 := createManifest()
	y.Check(applyChangeSet(&, &, ))
	return 
}

// openOrCreateManifestFile opens a Badger manifest file if it exists, or creates one if
// doesn’t exists.
func openOrCreateManifestFile( Options) (
	 *manifestFile,  Manifest,  error) {
	if .InMemory {
		return &manifestFile{inMemory: true}, Manifest{}, nil
	}
	return helpOpenOrCreateManifestFile(.Dir, .ReadOnly, .ExternalMagicVersion,
		manifestDeletionsRewriteThreshold, )
}

func helpOpenOrCreateManifestFile( string,  bool,  uint16,
	 int,  Options) (*manifestFile, Manifest, error) {

	 := filepath.Join(, ManifestFilename)
	var  y.Flags
	if  {
		 |= y.ReadOnly
	}
	,  := y.OpenExistingFile(, ) // We explicitly sync in addChanges, outside the lock.
	if  != nil {
		if !os.IsNotExist() {
			return nil, Manifest{}, 
		}
		if  {
			return nil, Manifest{}, fmt.Errorf("no manifest found, required for read-only db")
		}
		 := createManifest()
		, ,  := helpRewrite(, &, )
		if  != nil {
			return nil, Manifest{}, 
		}
		y.AssertTrue( == 0)
		 := &manifestFile{
			fp:                        ,
			directory:                 ,
			externalMagic:             ,
			manifest:                  .clone(),
			deletionsRewriteThreshold: ,
		}
		return , , nil
	}

	, ,  := ReplayManifestFile(, , )
	if  != nil {
		_ = .Close()
		return nil, Manifest{}, 
	}

	if ! {
		// Truncate file so we don't have a half-written entry at the end.
		if  := .Truncate();  != nil {
			_ = .Close()
			return nil, Manifest{}, 
		}
	}
	if _,  = .Seek(0, io.SeekEnd);  != nil {
		_ = .Close()
		return nil, Manifest{}, 
	}

	 := &manifestFile{
		fp:                        ,
		directory:                 ,
		externalMagic:             ,
		manifest:                  .clone(),
		deletionsRewriteThreshold: ,
	}
	return , , nil
}

func ( *manifestFile) () error {
	if .inMemory {
		return nil
	}
	return .fp.Close()
}

// addChanges writes a batch of changes, atomically, to the file.  By "atomically" that means when
// we replay the MANIFEST file, we'll either replay all the changes or none of them.  (The truth of
// this depends on the filesystem -- some might append garbage data if a system crash happens at
// the wrong time.)
func ( *manifestFile) ( []*pb.ManifestChange,  Options) error {
	if .inMemory {
		return nil
	}
	 := pb.ManifestChangeSet{Changes: }
	,  := proto.Marshal(&)
	if  != nil {
		return 
	}

	// Maybe we could use O_APPEND instead (on certain file systems)
	.appendLock.Lock()
	defer .appendLock.Unlock()
	if  := applyChangeSet(&.manifest, &, );  != nil {
		return 
	}
	// Rewrite manifest if it'd shrink by 1/10 and it's big enough to care
	if .manifest.Deletions > .deletionsRewriteThreshold &&
		.manifest.Deletions > manifestDeletionsRatio*(.manifest.Creations-.manifest.Deletions) {
		if  := .rewrite();  != nil {
			return 
		}
	} else {
		var  [8]byte
		binary.BigEndian.PutUint32([0:4], uint32(len()))
		binary.BigEndian.PutUint32([4:8], crc32.Checksum(, y.CastagnoliCrcTable))
		 = append([:], ...)
		if ,  := .fp.Write();  != nil {
			return 
		}
	}

	return syncFunc(.fp)
}

// this function is saved here to allow injection of fake filesystem latency at test time.
var syncFunc = func( *os.File) error { return .Sync() }

// Has to be 4 bytes.  The value can never change, ever, anyway.
var magicText = [4]byte{'B', 'd', 'g', 'r'}

// The magic version number. It is allocated 2 bytes, so it's value must be <= math.MaxUint16
const badgerMagicVersion = 8

func helpRewrite( string,  *Manifest,  uint16) (*os.File, int, error) {
	 := filepath.Join(, manifestRewriteFilename)
	// We explicitly sync.
	,  := y.OpenTruncFile(, false)
	if  != nil {
		return nil, 0, 
	}

	// magic bytes are structured as
	// +---------------------+-------------------------+-----------------------+
	// | magicText (4 bytes) | externalMagic (2 bytes) | badgerMagic (2 bytes) |
	// +---------------------+-------------------------+-----------------------+

	y.AssertTrue(badgerMagicVersion <= math.MaxUint16)
	 := make([]byte, 8)
	copy([0:4], magicText[:])
	binary.BigEndian.PutUint16([4:6], )
	binary.BigEndian.PutUint16([6:8], badgerMagicVersion)

	 := len(.Tables)
	 := .asChanges()
	 := pb.ManifestChangeSet{Changes: }

	,  := proto.Marshal(&)
	if  != nil {
		.Close()
		return nil, 0, 
	}
	var  [8]byte
	binary.BigEndian.PutUint32([0:4], uint32(len()))
	binary.BigEndian.PutUint32([4:8], crc32.Checksum(, y.CastagnoliCrcTable))
	 = append(, [:]...)
	 = append(, ...)
	if ,  := .Write();  != nil {
		.Close()
		return nil, 0, 
	}
	if  := .Sync();  != nil {
		.Close()
		return nil, 0, 
	}

	// In Windows the files should be closed before doing a Rename.
	if  = .Close();  != nil {
		return nil, 0, 
	}
	 := filepath.Join(, ManifestFilename)
	if  := os.Rename(, );  != nil {
		return nil, 0, 
	}
	,  = y.OpenExistingFile(, 0)
	if  != nil {
		return nil, 0, 
	}
	if ,  := .Seek(0, io.SeekEnd);  != nil {
		.Close()
		return nil, 0, 
	}
	if  := syncDir();  != nil {
		.Close()
		return nil, 0, 
	}

	return , , nil
}

// Must be called while appendLock is held.
func ( *manifestFile) () error {
	// In Windows the files should be closed before doing a Rename.
	if  := .fp.Close();  != nil {
		return 
	}
	, ,  := helpRewrite(.directory, &.manifest, .externalMagic)
	if  != nil {
		return 
	}
	.fp = 
	.manifest.Creations = 
	.manifest.Deletions = 0

	return nil
}

type countingReader struct {
	wrapped *bufio.Reader
	count   int64
}

func ( *countingReader) ( []byte) ( int,  error) {
	,  = .wrapped.Read()
	.count += int64()
	return
}

func ( *countingReader) () ( byte,  error) {
	,  = .wrapped.ReadByte()
	if  == nil {
		.count++
	}
	return
}

var (
	errBadMagic    = errors.New("manifest has bad magic")
	errBadChecksum = errors.New("manifest has checksum mismatch")
)

// ReplayManifestFile reads the manifest file and constructs two manifest objects.  (We need one
// immutable copy and one mutable copy of the manifest.  Easiest way is to construct two of them.)
// Also, returns the last offset after a completely read manifest entry -- the file must be
// truncated at that point before further appends are made (if there is a partial entry after
// that).  In normal conditions, truncOffset is the file size.
func ( *os.File,  uint16,  Options) (Manifest, int64, error) {
	 := countingReader{wrapped: bufio.NewReader()}

	var  [8]byte
	if ,  := io.ReadFull(&, [:]);  != nil {
		return Manifest{}, 0, errBadMagic
	}
	if !bytes.Equal([0:4], magicText[:]) {
		return Manifest{}, 0, errBadMagic
	}

	 := y.BytesToU16([4:6])
	 := y.BytesToU16([6:8])

	if  != badgerMagicVersion {
		return Manifest{}, 0,
			//nolint:lll
			fmt.Errorf("manifest has unsupported version: %d (we support %d).\n"+
				"Please see https://github.com/dgraph-io/badger/blob/main/docs/troubleshooting.md#i-see-manifest-has-unsupported-version-x-we-support-y-error"+
				" on how to fix this",
				, badgerMagicVersion)
	}
	if  !=  {
		return Manifest{}, 0,
			fmt.Errorf("cannot open DB because the external magic number doesn't match, "+
				"expected: %d, version present in manifest: %d", , )
	}

	,  := .Stat()
	if  != nil {
		return Manifest{}, 0, 
	}

	 := createManifest()
	var  int64
	for {
		 = .count
		var  [8]byte
		,  := io.ReadFull(&, [:])
		if  != nil {
			if  == io.EOF ||  == io.ErrUnexpectedEOF {
				break
			}
			return Manifest{}, 0, 
		}
		 := y.BytesToU32([0:4])
		// Sanity check to ensure we don't over-allocate memory.
		if  > uint32(.Size()) {
			return Manifest{}, 0, fmt.Errorf(
				"Buffer length: %d greater than file size: %d. Manifest file might be corrupted",
				, .Size())
		}
		var  = make([]byte, )
		if ,  := io.ReadFull(&, );  != nil {
			if  == io.EOF ||  == io.ErrUnexpectedEOF {
				break
			}
			return Manifest{}, 0, 
		}
		if crc32.Checksum(, y.CastagnoliCrcTable) != y.BytesToU32([4:8]) {
			return Manifest{}, 0, errBadChecksum
		}

		var  pb.ManifestChangeSet
		if  := proto.Unmarshal(, &);  != nil {
			return Manifest{}, 0, 
		}

		if  := applyChangeSet(&, &, );  != nil {
			return Manifest{}, 0, 
		}
	}

	return , , nil
}

func applyManifestChange( *Manifest,  *pb.ManifestChange,  Options) error {
	switch .Op {
	case pb.ManifestChange_CREATE:
		if ,  := .Tables[.Id];  {
			return fmt.Errorf("MANIFEST invalid, table %d exists", .Id)
		}
		.Tables[.Id] = TableManifest{
			Level:       uint8(.Level),
			KeyID:       .KeyId,
			Compression: options.CompressionType(.Compression),
		}
		for len(.Levels) <= int(.Level) {
			.Levels = append(.Levels, levelManifest{make(map[uint64]struct{})})
		}
		.Levels[.Level].Tables[.Id] = struct{}{}
		.Creations++
	case pb.ManifestChange_DELETE:
		,  := .Tables[.Id]
		if ! {
			.Warningf("MANIFEST delete: table %d has already been removed", .Id)
			for ,  := range .Levels {
				delete(.Tables, .Id)
			}
		} else {
			delete(.Levels[.Level].Tables, .Id)
			delete(.Tables, .Id)
		}
		.Deletions++
	default:
		return fmt.Errorf("MANIFEST file has invalid manifestChange op")
	}
	return nil
}

// This is not a "recoverable" error -- opening the KV store fails because the MANIFEST file is
// just plain broken.
func applyChangeSet( *Manifest,  *pb.ManifestChangeSet,  Options) error {
	for ,  := range .Changes {
		if  := applyManifestChange(, , );  != nil {
			return 
		}
	}
	return nil
}

func newCreateChange(
	 uint64,  int,  uint64,  options.CompressionType) *pb.ManifestChange {
	return &pb.ManifestChange{
		Id:    ,
		Op:    pb.ManifestChange_CREATE,
		Level: uint32(),
		KeyId: ,
		// Hard coding it, since we're supporting only AES for now.
		EncryptionAlgo: pb.EncryptionAlgo_aes,
		Compression:    uint32(),
	}
}

func newDeleteChange( uint64) *pb.ManifestChange {
	return &pb.ManifestChange{
		Id: ,
		Op: pb.ManifestChange_DELETE,
	}
}