...

Source file src/go.etcd.io/bbolt/internal/guts_cli/guts_cli.go

Documentation: go.etcd.io/bbolt/internal/guts_cli

     1  package guts_cli
     2  
     3  // Low level access to pages / data-structures of the bbolt file.
     4  
     5  // TODO(ptab): Merge with bbolt/page file that should get ported to internal.
     6  
     7  import (
     8  	"errors"
     9  	"fmt"
    10  	"io"
    11  	"os"
    12  	"unsafe"
    13  )
    14  
    15  var (
    16  	// ErrCorrupt is returned when a checking a data file finds errors.
    17  	ErrCorrupt = errors.New("invalid value")
    18  )
    19  
    20  // PageHeaderSize represents the size of the bolt.Page header.
    21  const PageHeaderSize = 16
    22  
    23  // Represents a marker value to indicate that a file (Meta Page) is a Bolt DB.
    24  const magic uint32 = 0xED0CDAED
    25  
    26  // DO NOT EDIT. Copied from the "bolt" package.
    27  const maxAllocSize = 0xFFFFFFF
    28  
    29  // DO NOT EDIT. Copied from the "bolt" package.
    30  const (
    31  	branchPageFlag   = 0x01
    32  	leafPageFlag     = 0x02
    33  	metaPageFlag     = 0x04
    34  	freelistPageFlag = 0x10
    35  )
    36  
    37  // DO NOT EDIT. Copied from the "bolt" package.
    38  const bucketLeafFlag = 0x01
    39  
    40  // DO NOT EDIT. Copied from the "bolt" package.
    41  type Pgid uint64
    42  
    43  // DO NOT EDIT. Copied from the "bolt" package.
    44  type txid uint64
    45  
    46  // DO NOT EDIT. Copied from the "bolt" package.
    47  type Meta struct {
    48  	magic    uint32
    49  	version  uint32
    50  	pageSize uint32
    51  	flags    uint32
    52  	root     Bucket
    53  	freelist Pgid
    54  	pgid     Pgid // High Water Mark (id of next added Page if the file growths)
    55  	txid     txid
    56  	checksum uint64
    57  }
    58  
    59  func LoadPageMeta(buf []byte) *Meta {
    60  	return (*Meta)(unsafe.Pointer(&buf[PageHeaderSize]))
    61  }
    62  
    63  func (m *Meta) RootBucket() *Bucket {
    64  	return &m.root
    65  }
    66  
    67  func (m *Meta) Txid() uint64 {
    68  	return uint64(m.txid)
    69  }
    70  
    71  func (m *Meta) Print(w io.Writer) {
    72  	fmt.Fprintf(w, "Version:    %d\n", m.version)
    73  	fmt.Fprintf(w, "Page Size:  %d bytes\n", m.pageSize)
    74  	fmt.Fprintf(w, "Flags:      %08x\n", m.flags)
    75  	fmt.Fprintf(w, "Root:       <pgid=%d>\n", m.root.root)
    76  	fmt.Fprintf(w, "Freelist:   <pgid=%d>\n", m.freelist)
    77  	fmt.Fprintf(w, "HWM:        <pgid=%d>\n", m.pgid)
    78  	fmt.Fprintf(w, "Txn ID:     %d\n", m.txid)
    79  	fmt.Fprintf(w, "Checksum:   %016x\n", m.checksum)
    80  	fmt.Fprintf(w, "\n")
    81  }
    82  
    83  // DO NOT EDIT. Copied from the "bolt" package.
    84  type Bucket struct {
    85  	root     Pgid
    86  	sequence uint64
    87  }
    88  
    89  const bucketHeaderSize = int(unsafe.Sizeof(Bucket{}))
    90  
    91  func LoadBucket(buf []byte) *Bucket {
    92  	return (*Bucket)(unsafe.Pointer(&buf[0]))
    93  }
    94  
    95  func (b *Bucket) String() string {
    96  	return fmt.Sprintf("<pgid=%d,seq=%d>", b.root, b.sequence)
    97  }
    98  
    99  func (b *Bucket) RootPage() Pgid {
   100  	return b.root
   101  }
   102  
   103  func (b *Bucket) InlinePage(v []byte) *Page {
   104  	return (*Page)(unsafe.Pointer(&v[bucketHeaderSize]))
   105  }
   106  
   107  // DO NOT EDIT. Copied from the "bolt" package.
   108  type Page struct {
   109  	id       Pgid
   110  	flags    uint16
   111  	count    uint16
   112  	overflow uint32
   113  	ptr      uintptr
   114  }
   115  
   116  func LoadPage(buf []byte) *Page {
   117  	return (*Page)(unsafe.Pointer(&buf[0]))
   118  }
   119  
   120  func (p *Page) FreelistPageCount() int {
   121  	// Check for overflow and, if present, adjust actual element count.
   122  	if p.count == 0xFFFF {
   123  		return int(((*[maxAllocSize]Pgid)(unsafe.Pointer(&p.ptr)))[0])
   124  	} else {
   125  		return int(p.count)
   126  	}
   127  }
   128  
   129  func (p *Page) FreelistPagePages() []Pgid {
   130  	// Check for overflow and, if present, adjust starting index.
   131  	idx := 0
   132  	if p.count == 0xFFFF {
   133  		idx = 1
   134  	}
   135  	return (*[maxAllocSize]Pgid)(unsafe.Pointer(&p.ptr))[idx:p.FreelistPageCount()]
   136  }
   137  
   138  func (p *Page) Overflow() uint32 {
   139  	return p.overflow
   140  }
   141  
   142  func (p *Page) String() string {
   143  	return fmt.Sprintf("ID: %d, Type: %s, count: %d, overflow: %d", p.id, p.Type(), p.count, p.overflow)
   144  }
   145  
   146  // DO NOT EDIT. Copied from the "bolt" package.
   147  
   148  // TODO(ptabor): Make the page-types an enum.
   149  func (p *Page) Type() string {
   150  	if (p.flags & branchPageFlag) != 0 {
   151  		return "branch"
   152  	} else if (p.flags & leafPageFlag) != 0 {
   153  		return "leaf"
   154  	} else if (p.flags & metaPageFlag) != 0 {
   155  		return "meta"
   156  	} else if (p.flags & freelistPageFlag) != 0 {
   157  		return "freelist"
   158  	}
   159  	return fmt.Sprintf("unknown<%02x>", p.flags)
   160  }
   161  
   162  func (p *Page) Count() uint16 {
   163  	return p.count
   164  }
   165  
   166  func (p *Page) Id() Pgid {
   167  	return p.id
   168  }
   169  
   170  // DO NOT EDIT. Copied from the "bolt" package.
   171  func (p *Page) LeafPageElement(index uint16) *LeafPageElement {
   172  	n := &((*[0x7FFFFFF]LeafPageElement)(unsafe.Pointer(&p.ptr)))[index]
   173  	return n
   174  }
   175  
   176  // DO NOT EDIT. Copied from the "bolt" package.
   177  func (p *Page) BranchPageElement(index uint16) *BranchPageElement {
   178  	return &((*[0x7FFFFFF]BranchPageElement)(unsafe.Pointer(&p.ptr)))[index]
   179  }
   180  
   181  func (p *Page) SetId(target Pgid) {
   182  	p.id = target
   183  }
   184  
   185  func (p *Page) SetCount(target uint16) {
   186  	p.count = target
   187  }
   188  
   189  func (p *Page) SetOverflow(target uint32) {
   190  	p.overflow = target
   191  }
   192  
   193  // DO NOT EDIT. Copied from the "bolt" package.
   194  type BranchPageElement struct {
   195  	pos   uint32
   196  	ksize uint32
   197  	pgid  Pgid
   198  }
   199  
   200  // DO NOT EDIT. Copied from the "bolt" package.
   201  func (n *BranchPageElement) Key() []byte {
   202  	buf := (*[maxAllocSize]byte)(unsafe.Pointer(n))
   203  	return buf[n.pos : n.pos+n.ksize]
   204  }
   205  
   206  func (n *BranchPageElement) PgId() Pgid {
   207  	return n.pgid
   208  }
   209  
   210  // DO NOT EDIT. Copied from the "bolt" package.
   211  type LeafPageElement struct {
   212  	flags uint32
   213  	pos   uint32
   214  	ksize uint32
   215  	vsize uint32
   216  }
   217  
   218  // DO NOT EDIT. Copied from the "bolt" package.
   219  func (n *LeafPageElement) Key() []byte {
   220  	buf := (*[maxAllocSize]byte)(unsafe.Pointer(n))
   221  	return buf[n.pos : n.pos+n.ksize]
   222  }
   223  
   224  // DO NOT EDIT. Copied from the "bolt" package.
   225  func (n *LeafPageElement) Value() []byte {
   226  	buf := (*[maxAllocSize]byte)(unsafe.Pointer(n))
   227  	return buf[n.pos+n.ksize : n.pos+n.ksize+n.vsize]
   228  }
   229  
   230  func (n *LeafPageElement) IsBucketEntry() bool {
   231  	return n.flags&uint32(bucketLeafFlag) != 0
   232  }
   233  
   234  func (n *LeafPageElement) Bucket() *Bucket {
   235  	if n.IsBucketEntry() {
   236  		return LoadBucket(n.Value())
   237  	} else {
   238  		return nil
   239  	}
   240  }
   241  
   242  // ReadPage reads Page info & full Page data from a path.
   243  // This is not transactionally safe.
   244  func ReadPage(path string, pageID uint64) (*Page, []byte, error) {
   245  	// Find Page size.
   246  	pageSize, hwm, err := ReadPageAndHWMSize(path)
   247  	if err != nil {
   248  		return nil, nil, fmt.Errorf("read Page size: %s", err)
   249  	}
   250  
   251  	// Open database file.
   252  	f, err := os.Open(path)
   253  	if err != nil {
   254  		return nil, nil, err
   255  	}
   256  	defer f.Close()
   257  
   258  	// Read one block into buffer.
   259  	buf := make([]byte, pageSize)
   260  	if n, err := f.ReadAt(buf, int64(pageID*pageSize)); err != nil {
   261  		return nil, nil, err
   262  	} else if n != len(buf) {
   263  		return nil, nil, io.ErrUnexpectedEOF
   264  	}
   265  
   266  	// Determine total number of blocks.
   267  	p := LoadPage(buf)
   268  	if p.id != Pgid(pageID) {
   269  		return nil, nil, fmt.Errorf("error: %w due to unexpected Page id: %d != %d", ErrCorrupt, p.id, pageID)
   270  	}
   271  	overflowN := p.overflow
   272  	if overflowN >= uint32(hwm)-3 { // we exclude 2 Meta pages and the current Page.
   273  		return nil, nil, fmt.Errorf("error: %w, Page claims to have %d overflow pages (>=hwm=%d). Interrupting to avoid risky OOM", ErrCorrupt, overflowN, hwm)
   274  	}
   275  
   276  	// Re-read entire Page (with overflow) into buffer.
   277  	buf = make([]byte, (uint64(overflowN)+1)*pageSize)
   278  	if n, err := f.ReadAt(buf, int64(pageID*pageSize)); err != nil {
   279  		return nil, nil, err
   280  	} else if n != len(buf) {
   281  		return nil, nil, io.ErrUnexpectedEOF
   282  	}
   283  	p = LoadPage(buf)
   284  	if p.id != Pgid(pageID) {
   285  		return nil, nil, fmt.Errorf("error: %w due to unexpected Page id: %d != %d", ErrCorrupt, p.id, pageID)
   286  	}
   287  
   288  	return p, buf, nil
   289  }
   290  
   291  func WritePage(path string, pageBuf []byte) error {
   292  	page := LoadPage(pageBuf)
   293  	pageSize, _, err := ReadPageAndHWMSize(path)
   294  	if err != nil {
   295  		return err
   296  	}
   297  	expectedLen := pageSize * (uint64(page.Overflow()) + 1)
   298  	if expectedLen != uint64(len(pageBuf)) {
   299  		return fmt.Errorf("WritePage: len(buf):%d != pageSize*(overflow+1):%d", len(pageBuf), expectedLen)
   300  	}
   301  	f, err := os.OpenFile(path, os.O_WRONLY, 0)
   302  	if err != nil {
   303  		return err
   304  	}
   305  	defer f.Close()
   306  	_, err = f.WriteAt(pageBuf, int64(page.Id())*int64(pageSize))
   307  	return err
   308  }
   309  
   310  // ReadPageAndHWMSize reads Page size and HWM (id of the last+1 Page).
   311  // This is not transactionally safe.
   312  func ReadPageAndHWMSize(path string) (uint64, Pgid, error) {
   313  	// Open database file.
   314  	f, err := os.Open(path)
   315  	if err != nil {
   316  		return 0, 0, err
   317  	}
   318  	defer f.Close()
   319  
   320  	// Read 4KB chunk.
   321  	buf := make([]byte, 4096)
   322  	if _, err := io.ReadFull(f, buf); err != nil {
   323  		return 0, 0, err
   324  	}
   325  
   326  	// Read Page size from metadata.
   327  	m := LoadPageMeta(buf)
   328  	if m.magic != magic {
   329  		return 0, 0, fmt.Errorf("the Meta Page has wrong (unexpected) magic")
   330  	}
   331  	return uint64(m.pageSize), Pgid(m.pgid), nil
   332  }
   333  
   334  // GetRootPage returns the root-page (according to the most recent transaction).
   335  func GetRootPage(path string) (root Pgid, activeMeta Pgid, err error) {
   336  	_, buf0, err0 := ReadPage(path, 0)
   337  	if err0 != nil {
   338  		return 0, 0, err0
   339  	}
   340  	m0 := LoadPageMeta(buf0)
   341  	_, buf1, err1 := ReadPage(path, 1)
   342  	if err1 != nil {
   343  		return 0, 1, err1
   344  	}
   345  	m1 := LoadPageMeta(buf1)
   346  	if m0.txid < m1.txid {
   347  		return m1.root.root, 1, nil
   348  	} else {
   349  		return m0.root.root, 0, nil
   350  	}
   351  }
   352  

View as plain text