// Copyright 2019 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package disklayout

import (
	"gvisor.dev/gvisor/pkg/marshal"
)

// Extents were introduced in ext4 and provide huge performance gains in terms
// data locality and reduced metadata block usage. Extents are organized in
// extent trees. The root node is contained in inode.BlocksRaw.
//
// Terminology:
//   - Physical Block:
//       Filesystem data block which is addressed normally wrt the entire
//       filesystem (addressed with 48 bits).
//
//   - File Block:
//       Data block containing *only* file data and addressed wrt to the file
//       with only 32 bits. The (i)th file block contains file data from
//       byte (i * sb.BlockSize()) to ((i+1) * sb.BlockSize()).

const (
	// ExtentHeaderSize is the size of the header of an extent tree node.
	ExtentHeaderSize = 12

	// ExtentEntrySize is the size of an entry in an extent tree node.
	// This size is the same for both leaf and internal nodes.
	ExtentEntrySize = 12

	// ExtentMagic is the magic number which must be present in the header.
	ExtentMagic = 0xf30a
)

// ExtentEntryPair couples an in-memory ExtendNode with the ExtentEntry that
// points to it. We want to cache these structs in memory to avoid repeated
// disk reads.
//
// Note: This struct itself does not represent an on-disk struct.
type ExtentEntryPair struct {
	// Entry points to the child node on disk.
	Entry ExtentEntry
	// Node points to child node in memory. Is nil if the current node is a leaf.
	Node *ExtentNode
}

// ExtentNode represents an extent tree node. For internal nodes, all Entries
// will be ExtendIdxs. For leaf nodes, they will all be Extents.
//
// Note: This struct itself does not represent an on-disk struct.
type ExtentNode struct {
	Header  ExtentHeader
	Entries []ExtentEntryPair
}

// ExtentEntry represents an extent tree node entry. The entry can either be
// an ExtentIdx or Extent itself. This exists to simplify navigation logic.
type ExtentEntry interface {
	marshal.Marshallable

	// FileBlock returns the first file block number covered by this entry.
	FileBlock() uint32

	// PhysicalBlock returns the child physical block that this entry points to.
	PhysicalBlock() uint64
}

// ExtentHeader emulates the ext4_extent_header struct in ext4. Each extent
// tree node begins with this and is followed by `NumEntries` number of:
//   - Extent         if `Depth` == 0
//   - ExtentIdx      otherwise
//
// +marshal
type ExtentHeader struct {
	// Magic in the extent magic number, must be 0xf30a.
	Magic uint16

	// NumEntries indicates the number of valid entries following the header.
	NumEntries uint16

	// MaxEntries that could follow the header. Used while adding entries.
	MaxEntries uint16

	// Height represents the distance of this node from the farthest leaf. Please
	// note that Linux incorrectly calls this `Depth` (which means the distance
	// of the node from the root).
	Height uint16
	_      uint32
}

// ExtentIdx emulates the ext4_extent_idx struct in ext4. Only present in
// internal nodes. Sorted in ascending order based on FirstFileBlock since
// Linux does a binary search on this. This points to a block containing the
// child node.
//
// +marshal
type ExtentIdx struct {
	FirstFileBlock uint32
	ChildBlockLo   uint32
	ChildBlockHi   uint16
	_              uint16
}

// Compiles only if ExtentIdx implements ExtentEntry.
var _ ExtentEntry = (*ExtentIdx)(nil)

// FileBlock implements ExtentEntry.FileBlock.
func (ei *ExtentIdx) FileBlock() uint32 {
	return ei.FirstFileBlock
}

// PhysicalBlock implements ExtentEntry.PhysicalBlock. It returns the
// physical block number of the child block.
func (ei *ExtentIdx) PhysicalBlock() uint64 {
	return (uint64(ei.ChildBlockHi) << 32) | uint64(ei.ChildBlockLo)
}

// Extent represents the ext4_extent struct in ext4. Only present in leaf
// nodes. Sorted in ascending order based on FirstFileBlock since Linux does a
// binary search on this. This points to an array of data blocks containing the
// file data. It covers `Length` data blocks starting from `StartBlock`.
//
// +marshal
type Extent struct {
	FirstFileBlock uint32
	Length         uint16
	StartBlockHi   uint16
	StartBlockLo   uint32
}

// Compiles only if Extent implements ExtentEntry.
var _ ExtentEntry = (*Extent)(nil)

// FileBlock implements ExtentEntry.FileBlock.
func (e *Extent) FileBlock() uint32 {
	return e.FirstFileBlock
}

// PhysicalBlock implements ExtentEntry.PhysicalBlock. It returns the
// physical block number of the first data block this extent covers.
func (e *Extent) PhysicalBlock() uint64 {
	return (uint64(e.StartBlockHi) << 32) | uint64(e.StartBlockLo)
}