layout.go (143 lines of code) (raw):

// Licensed to Elasticsearch B.V. under one or more contributor // license agreements. See the NOTICE file distributed with // this work for additional information regarding copyright // ownership. Elasticsearch B.V. licenses this file to you 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 txfile import ( "fmt" "hash/fnv" "reflect" "unsafe" bin "github.com/urso/go-bin" ) // on disk page layout for writing and parsing // primitive types: type ( u8 = bin.U8le u16 = bin.U16le u32 = bin.U32le u64 = bin.U64le i8 = bin.I8le i16 = bin.I16le i32 = bin.I32le i64 = bin.I64le pgID u64 ) // Special page at beginning of file. // A file holds to meta pages at the beginning of the file. A metaPage is // updated after a write transaction has been completed. On error during // transactions or when updating the metaPage, the old metaPage will still be // valid, technically ignoring all contents written by the transactions active // while the program/id did crash/fail. type metaPage struct { magic u32 version u32 pageSize u32 maxSize u64 // maximum file size flags u32 root pgID // ID of first page to look for data. txid u64 // page transaction ID freelist pgID // pointer to user area freelist wal pgID // write-ahead-log root dataEndMarker pgID // end marker of user-area page metaEndMarker pgID // file end marker metaTotal u64 // total number of pages in meta area checksum u32 } type metaBuf [unsafe.Sizeof(metaPage{})]byte const ( metaFlagPrealloc = 1 << 0 // indicates the complete file has been preallocated ) type listPage struct { next pgID // pointer to next entry count u32 // number of entries in current page } type freePage = listPage type walPage = listPage const ( metaPageHeaderSize = int(unsafe.Sizeof(metaPage{})) listPageHeaderSize = int(unsafe.Sizeof(listPage{})) walPageHeaderSize = int(unsafe.Sizeof(walPage{})) freePageHeaderSize = int(unsafe.Sizeof(freePage{})) ) const magic uint32 = 0xBEA77AEB const version uint32 = 1 func init() { checkPacked := func(t reflect.Type) { off := uintptr(0) for i := 0; i < t.NumField(); i++ { f := t.Field(i) if f.Offset != off { panic(fmt.Sprintf("field %v offset mismatch (expected=%v, actual=%v)", f.Name, off, f.Offset)) } off += f.Type.Size() } } // check compiler really generates packed structes. Required, so file can be // accesed from within different architectures + checksum based on raw bytes // contents are correct. checkPacked(reflect.TypeOf(metaPage{})) checkPacked(reflect.TypeOf(freePage{})) checkPacked(reflect.TypeOf(walPage{})) } func castMetaPage(b []byte) (p *metaPage) { castPageTo(&p, b); return } func (m *metaPage) Init(flags uint32, pageSize uint32, maxSize uint64) { m.magic.Set(magic) m.version.Set(version) m.pageSize.Set(pageSize) m.maxSize.Set(maxSize) m.flags.Set(flags) m.root.Set(0) m.freelist.Set(0) m.wal.Set(0) m.dataEndMarker.Set(0) } func (m *metaPage) Finalize() { m.checksum.Set(m.computeChecksum()) } func (m *metaPage) Validate() reason { if m.magic.Get() != magic { return errOf(InvalidMetaPage).report("invalid magic number") } if m.version.Get() != version { return errOf(InvalidMetaPage).report("invalid version number") } if m.checksum.Get() != m.computeChecksum() { return errOf(InvalidMetaPage).report("checksum mismatch") } return nil } func (b *metaBuf) cast() *metaPage { return castMetaPage((*b)[:]) } func (m *metaPage) computeChecksum() uint32 { h := fnv.New32a() type metaHashContent [unsafe.Offsetof(metaPage{}.checksum)]byte contents := *(*metaHashContent)(unsafe.Pointer(m)) _, _ = h.Write(contents[:]) return h.Sum32() } func (id *pgID) Len() int { return id.access().Len() } func (id *pgID) Get() PageID { return PageID(id.access().Get()) } func (id *pgID) Set(v PageID) { id.access().Set(uint64(v)) } func (id *pgID) access() *u64 { return (*u64)(id) } func castU8(b []byte) (u *u8) { mapMem(&u, b); return } func castU16(b []byte) (u *u16) { mapMem(&u, b); return } func castU32(b []byte) (u *u32) { mapMem(&u, b); return } func castU64(b []byte) (u *u64) { mapMem(&u, b); return } func castListPage(b []byte) (node *listPage, data []byte) { if castPageTo(&node, b); node != nil { data = b[unsafe.Sizeof(listPage{}):] } return } func castFreePage(b []byte) (node *freePage, data []byte) { return castListPage(b) } func castWalPage(b []byte) (node *walPage, data []byte) { return castListPage(b) } func mapMem(to interface{}, b []byte) { bin.UnsafeCastStruct(to, b) } func castPageTo(to interface{}, b []byte) { mapMem(to, b) } func traceMetaPage(meta *metaPage) { traceln("meta page:") traceln(" version:", meta.version.Get()) traceln(" pagesize:", meta.pageSize.Get()) traceln(" maxsize:", meta.maxSize.Get()) traceln(" root:", meta.root.Get()) traceln(" txid:", meta.txid.Get()) traceln(" freelist:", meta.freelist.Get()) traceln(" wal:", meta.wal.Get()) traceln(" data end:", meta.dataEndMarker.Get()) traceln(" meta end:", meta.metaEndMarker.Get()) traceln(" meta total:", meta.metaTotal.Get()) traceln(" checksum:", meta.checksum.Get()) }