mirror of
https://github.com/octoleo/syncthing.git
synced 2025-01-09 17:53:59 +00:00
677 lines
17 KiB
Go
677 lines
17 KiB
Go
// Copyright (c) 2011 CZ.NIC z.s.p.o. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
// blame: jnml, labs.nic.cz
|
|
|
|
/*
|
|
|
|
This is an mostly (WIP) conforming implementation of the "specs" in docs.go.
|
|
|
|
The main incompletness is support for only one kind of FTL, though this table kind is still per "specs".
|
|
|
|
*/
|
|
|
|
package falloc
|
|
|
|
import (
|
|
"bytes"
|
|
"github.com/cznic/fileutil/storage"
|
|
"sync"
|
|
)
|
|
|
|
// Handle is a reference to a block in a file/store.
|
|
// Handle is an uint56 wrapped in an in64, i.e. the most significant byte must be always zero.
|
|
type Handle int64
|
|
|
|
// Put puts the 7 least significant bytes of h into b. The MSB of h should be zero.
|
|
func (h Handle) Put(b []byte) {
|
|
for ofs := 6; ofs >= 0; ofs-- {
|
|
b[ofs] = byte(h)
|
|
h >>= 8
|
|
}
|
|
}
|
|
|
|
// Get gets the 7 least significant bytes of h from b. The MSB of h is zeroed.
|
|
func (h *Handle) Get(b []byte) {
|
|
var x Handle
|
|
for ofs := 0; ofs <= 6; ofs++ {
|
|
x = x<<8 | Handle(b[ofs])
|
|
}
|
|
*h = x
|
|
}
|
|
|
|
// File is a file/store with space allocation/deallocation support.
|
|
type File struct {
|
|
f storage.Accessor
|
|
atoms int64 // current file size in atom units
|
|
canfree int64 // only blocks >= canfree can be subject to Free()
|
|
freetab [3857]int64 // freetab[0] is unused, freetab[1] is size 1 ptr, freetab[2] is size 2 ptr, ...
|
|
rwm sync.RWMutex
|
|
}
|
|
|
|
func (f *File) read(b []byte, off int64) {
|
|
if n, err := f.f.ReadAt(b, off); n != len(b) {
|
|
panic(&ERead{f.f.Name(), off, err})
|
|
}
|
|
}
|
|
|
|
func (f *File) write(b []byte, off int64) {
|
|
if n, err := f.f.WriteAt(b, off); n != len(b) {
|
|
panic(&EWrite{f.f.Name(), off, err})
|
|
}
|
|
}
|
|
|
|
var ( // R/O
|
|
hdr = []byte{0x0f, 0xf1, 0xc1, 0xa1, 0xfe, 0xa5, 0x1b, 0x1e, 0, 0, 0, 0, 0, 0, 2, 0} // free lists table @2
|
|
empty = make([]byte, 16)
|
|
zero = []byte{0}
|
|
zero7 = make([]byte, 7)
|
|
)
|
|
|
|
// New returns a new File backed by store or an error if any.
|
|
// Any existing data in store are discarded.
|
|
func New(store storage.Accessor) (f *File, err error) {
|
|
f = &File{f: store}
|
|
return f, storage.Mutate(store, func() (err error) {
|
|
if err = f.f.Truncate(0); err != nil {
|
|
return &ECreate{f.f.Name(), err}
|
|
}
|
|
|
|
if _, err = f.Alloc(hdr[1:]); err != nil { //TODO internal panicking versions of the exported fns.
|
|
return
|
|
}
|
|
|
|
if _, err = f.Alloc(nil); err != nil { // (empty) root @1
|
|
return
|
|
}
|
|
|
|
b := make([]byte, 3856*14)
|
|
for i := 1; i <= 3856; i++ {
|
|
Handle(i).Put(b[(i-1)*14:])
|
|
}
|
|
if _, err = f.Alloc(b); err != nil {
|
|
return
|
|
}
|
|
|
|
f.canfree = f.atoms
|
|
return
|
|
})
|
|
}
|
|
|
|
// Open returns a new File backed by store or an error if any.
|
|
// Store already has to be in a valid format.
|
|
func Open(store storage.Accessor) (f *File, err error) {
|
|
defer func() {
|
|
if e := recover(); e != nil {
|
|
f = nil
|
|
err = e.(error)
|
|
}
|
|
}()
|
|
|
|
fi, err := store.Stat()
|
|
if err != nil {
|
|
panic(&EOpen{store.Name(), err})
|
|
}
|
|
|
|
fs := fi.Size()
|
|
if fs&0xf != 0 {
|
|
panic(&ESize{store.Name(), fi.Size()})
|
|
}
|
|
|
|
f = &File{f: store, atoms: fs >> 4}
|
|
b := make([]byte, len(hdr))
|
|
f.read(b, 0)
|
|
if !bytes.Equal(b, hdr) {
|
|
panic(&EHeader{store.Name(), b, append([]byte{}, hdr...)})
|
|
}
|
|
|
|
var atoms int64
|
|
b, atoms = f.readUsed(2)
|
|
f.canfree = atoms + 2
|
|
ofs := 0
|
|
var size, p Handle
|
|
for ofs < len(b) {
|
|
size.Get(b[ofs:])
|
|
ofs += 7
|
|
p.Get(b[ofs:])
|
|
ofs += 7
|
|
if sz, pp := int64(size), int64(p); size == 0 || size > 3856 || (pp != 0 && pp < f.canfree) || pp<<4 > fs-16 {
|
|
panic(&EFreeList{store.Name(), sz, pp})
|
|
}
|
|
|
|
f.freetab[size] = int64(p)
|
|
}
|
|
return
|
|
}
|
|
|
|
// Accessor returns the File's underlying Accessor.
|
|
func (f *File) Accessor() storage.Accessor {
|
|
return f.f
|
|
}
|
|
|
|
// Close closes f and returns an error if any.
|
|
func (f *File) Close() (err error) {
|
|
return storage.Mutate(f.Accessor(), func() (err error) {
|
|
if err = f.f.Close(); err != nil {
|
|
err = &EClose{f.f.Name(), err}
|
|
}
|
|
return
|
|
})
|
|
}
|
|
|
|
// Root returns the handle of the DB root (top level directory, ...).
|
|
func (f *File) Root() Handle {
|
|
return 1
|
|
}
|
|
|
|
func (f *File) readUsed(atom int64) (content []byte, atoms int64) {
|
|
b, redirected := make([]byte, 7), false
|
|
redir:
|
|
ofs := atom << 4
|
|
f.read(b[:1], ofs)
|
|
switch pre := b[0]; {
|
|
default:
|
|
panic(&ECorrupted{f.f.Name(), ofs})
|
|
case pre == 0x00: // Empty block
|
|
case pre >= 1 && pre <= 237: // Short
|
|
content = make([]byte, pre)
|
|
f.read(content, ofs+1)
|
|
case pre >= 0xee && pre <= 0xfb: // Short esc
|
|
content = make([]byte, 15+16*(pre-0xee))
|
|
f.read(content, ofs+1)
|
|
content[len(content)-1] += 0xfe
|
|
case pre == 0xfc: // Long
|
|
f.read(b[:2], ofs+1)
|
|
n := int(b[0])<<8 + int(b[1])
|
|
switch {
|
|
default:
|
|
panic(&ECorrupted{f.f.Name(), ofs + 1})
|
|
case n >= 238 && n <= 61680: // Long non esc
|
|
content = make([]byte, n)
|
|
f.read(content, ofs+3)
|
|
case n >= 61681: // Long esc
|
|
content = make([]byte, 13+16*(n-0xf0f1))
|
|
f.read(content, ofs+3)
|
|
content[len(content)-1] += 0xfe
|
|
}
|
|
case pre == 0xfd: // redir
|
|
if redirected {
|
|
panic(&ECorrupted{f.f.Name(), ofs})
|
|
}
|
|
|
|
f.read(b[:7], ofs+1)
|
|
(*Handle)(&atom).Get(b)
|
|
redirected = true
|
|
goto redir
|
|
}
|
|
return content, rq2Atoms(len(content))
|
|
}
|
|
|
|
func (f *File) writeUsed(b []byte, atom int64) {
|
|
n := len(b)
|
|
switch ofs, atoms, endmark := atom<<4, rq2Atoms(n), true; {
|
|
default:
|
|
panic("internal error")
|
|
case n == 0:
|
|
f.write(empty, ofs)
|
|
case n <= 237:
|
|
if (n+1)&0xf == 0 { // content end == atom end
|
|
if v := b[n-1]; v >= 0xfe { // escape
|
|
pre := []byte{byte((16*0xee + n - 15) >> 4)}
|
|
f.write(pre, ofs)
|
|
f.write(b[:n-1], ofs+1)
|
|
f.write([]byte{v - 0xfe}, ofs+atoms<<4-1)
|
|
return
|
|
}
|
|
endmark = false
|
|
}
|
|
// non esacpe
|
|
pre := []byte{byte(n)}
|
|
f.write(pre, ofs)
|
|
f.write(b, ofs+1)
|
|
if endmark {
|
|
f.write(zero, ofs+atoms<<4-1) // last block byte <- used block
|
|
}
|
|
case n > 237 && n <= 61680:
|
|
if (n+3)&0xf == 0 { // content end == atom end
|
|
if v := b[n-1]; v >= 0xfe { // escape
|
|
x := (16*0xf0f1 + n - 13) >> 4
|
|
pre := []byte{0xFC, byte(x >> 8), byte(x)}
|
|
f.write(pre, ofs)
|
|
f.write(b[:n-1], ofs+3)
|
|
f.write([]byte{v - 0xfe}, ofs+atoms<<4-1)
|
|
return
|
|
}
|
|
endmark = false
|
|
}
|
|
// non esacpe
|
|
pre := []byte{0xfc, byte(n >> 8), byte(n)}
|
|
f.write(pre, ofs)
|
|
f.write(b, ofs+3)
|
|
if endmark {
|
|
f.write(zero, ofs+atoms<<4-1) // last block byte <- used block
|
|
}
|
|
}
|
|
}
|
|
|
|
func rq2Atoms(rqbytes int) (rqatoms int64) {
|
|
if rqbytes > 237 {
|
|
rqbytes += 2
|
|
}
|
|
return int64(rqbytes>>4 + 1)
|
|
}
|
|
|
|
func (f *File) extend(b []byte) (handle int64) {
|
|
handle = f.atoms
|
|
f.writeUsed(b, handle)
|
|
f.atoms += rq2Atoms(len(b))
|
|
return
|
|
}
|
|
|
|
// Alloc stores b in a newly allocated space and returns its handle and an error if any.
|
|
func (f *File) Alloc(b []byte) (handle Handle, err error) {
|
|
err = storage.Mutate(f.Accessor(), func() (err error) {
|
|
rqAtoms := rq2Atoms(len(b))
|
|
if rqAtoms > 3856 {
|
|
return &EBadRequest{f.f.Name(), len(b)}
|
|
}
|
|
|
|
for foundsize, foundp := range f.freetab[rqAtoms:] {
|
|
if foundp != 0 {
|
|
// this works only for the current unique sizes list (except the last item!)
|
|
size := int64(foundsize) + rqAtoms
|
|
handle = Handle(foundp)
|
|
if size == 3856 {
|
|
buf := make([]byte, 7)
|
|
f.read(buf, int64(handle)<<4+15)
|
|
(*Handle)(&size).Get(buf)
|
|
}
|
|
f.delFree(int64(handle), size)
|
|
if rqAtoms < size {
|
|
f.addFree(int64(handle)+rqAtoms, size-rqAtoms)
|
|
}
|
|
f.writeUsed(b, int64(handle))
|
|
return
|
|
}
|
|
}
|
|
|
|
handle = Handle(f.extend(b))
|
|
return
|
|
})
|
|
return
|
|
}
|
|
|
|
// checkLeft returns the atom size of a free bleck left adjacent to block @atom.
|
|
// If that block is not free the returned size is 0.
|
|
func (f *File) checkLeft(atom int64) (size int64) {
|
|
if atom <= f.canfree {
|
|
return
|
|
}
|
|
|
|
b := make([]byte, 7)
|
|
fp := atom << 4
|
|
f.read(b[:1], fp-1)
|
|
switch last := b[0]; {
|
|
case last <= 0xfd:
|
|
// used block
|
|
case last == 0xfe:
|
|
f.read(b, fp-8)
|
|
(*Handle)(&size).Get(b)
|
|
case last == 0xff:
|
|
size = 1
|
|
}
|
|
return
|
|
}
|
|
|
|
// getInfo returns the block @atom type and size.
|
|
func (f *File) getInfo(atom int64) (pref byte, size int64) {
|
|
b := make([]byte, 7)
|
|
fp := atom << 4
|
|
f.read(b[:1], fp)
|
|
switch pref = b[0]; {
|
|
case pref == 0: // Empty used
|
|
size = 1
|
|
case pref >= 1 && pref <= 237: // Short
|
|
size = rq2Atoms(int(pref))
|
|
case pref >= 0xee && pref <= 0xfb: // Short esc
|
|
size = rq2Atoms(15 + 16*int(pref-0xee))
|
|
case pref == 0xfc: // Long
|
|
f.read(b[:2], fp+1)
|
|
n := int(b[0])<<8 + int(b[1])
|
|
switch {
|
|
default:
|
|
panic(&ECorrupted{f.f.Name(), fp + 1})
|
|
case n >= 238 && n <= 61680: // Long non esc
|
|
size = rq2Atoms(n)
|
|
case n >= 61681: // Long esc
|
|
size = rq2Atoms(13 + 16*(n-0xf0f1))
|
|
}
|
|
case pref == 0xfd: // reloc
|
|
size = 1
|
|
case pref == 0xfe:
|
|
f.read(b, fp+15)
|
|
(*Handle)(&size).Get(b)
|
|
case pref == 0xff:
|
|
size = 1
|
|
}
|
|
return
|
|
}
|
|
|
|
// getSize returns the atom size of the block @atom and wheter it is free.
|
|
func (f *File) getSize(atom int64) (size int64, isFree bool) {
|
|
var typ byte
|
|
typ, size = f.getInfo(atom)
|
|
isFree = typ >= 0xfe
|
|
return
|
|
}
|
|
|
|
// checkRight returns the atom size of a free bleck right adjacent to block @atom,atoms.
|
|
// If that block is not free the returned size is 0.
|
|
func (f *File) checkRight(atom, atoms int64) (size int64) {
|
|
if atom+atoms >= f.atoms {
|
|
return
|
|
}
|
|
|
|
if sz, free := f.getSize(atom + atoms); free {
|
|
size = sz
|
|
}
|
|
return
|
|
}
|
|
|
|
// delFree removes the atoms@atom free block from the free block list
|
|
func (f *File) delFree(atom, atoms int64) {
|
|
b := make([]byte, 15)
|
|
size := int(atoms)
|
|
if n := len(f.freetab); atoms >= int64(n) {
|
|
size = n - 1
|
|
}
|
|
fp := atom << 4
|
|
f.read(b[1:], fp+1)
|
|
var prev, next Handle
|
|
prev.Get(b[1:])
|
|
next.Get(b[8:])
|
|
|
|
switch {
|
|
case prev == 0 && next != 0:
|
|
next.Put(b)
|
|
f.write(b[:7], int64(32+3+7+(size-1)*14))
|
|
f.write(zero7, int64(next)<<4+1)
|
|
f.freetab[size] = int64(next)
|
|
case prev != 0 && next == 0:
|
|
f.write(zero7, int64(prev)<<4+8)
|
|
case prev != 0 && next != 0:
|
|
prev.Put(b)
|
|
f.write(b[:7], int64(next)<<4+1)
|
|
next.Put(b)
|
|
f.write(b[:7], int64(prev)<<4+8)
|
|
default: // prev == 0 && next == 0:
|
|
f.write(zero7, int64(32+3+7+(size-1)*14))
|
|
f.freetab[size] = 0
|
|
}
|
|
}
|
|
|
|
// addFree adds atoms@atom to the free block lists and marks it free.
|
|
func (f *File) addFree(atom, atoms int64) {
|
|
b := make([]byte, 7)
|
|
size := int(atoms)
|
|
if n := len(f.freetab); atoms >= int64(n) {
|
|
size = n - 1
|
|
}
|
|
head := f.freetab[size]
|
|
if head == 0 { // empty list
|
|
f.makeFree(0, atom, atoms, 0)
|
|
Handle(atom).Put(b)
|
|
f.write(b, int64(32+3+7+(size-1)*14))
|
|
f.freetab[size] = atom
|
|
return
|
|
}
|
|
|
|
Handle(atom).Put(b)
|
|
f.write(b, head<<4+1) // head.prev = atom
|
|
f.makeFree(0, atom, atoms, head) // atom.next = head
|
|
f.write(b, int64(32+3+7+(size-1)*14))
|
|
f.freetab[size] = atom
|
|
}
|
|
|
|
// makeFree sets up the content of a free block atoms@atom, fills the prev and next links.
|
|
func (f *File) makeFree(prev, atom, atoms, next int64) {
|
|
b := make([]byte, 23)
|
|
fp := atom << 4
|
|
if atoms == 1 {
|
|
b[0] = 0xff
|
|
Handle(prev).Put(b[1:])
|
|
Handle(next).Put(b[8:])
|
|
b[15] = 0xff
|
|
f.write(b[:16], fp)
|
|
return
|
|
}
|
|
|
|
b[0] = 0xfe
|
|
Handle(prev).Put(b[1:])
|
|
Handle(next).Put(b[8:])
|
|
Handle(atoms).Put(b[15:])
|
|
f.write(b[:22], fp)
|
|
b[22] = 0xfe
|
|
f.write(b[15:], fp+atoms<<4-8)
|
|
}
|
|
|
|
// Read reads and return the data associated with handle and an error if any.
|
|
// Passing an invalid handle to Read may return invalid data without error.
|
|
// It's like getting garbage via passing an invalid pointer to C.memcopy().
|
|
func (f *File) Read(handle Handle) (b []byte, err error) {
|
|
defer func() {
|
|
if e := recover(); e != nil {
|
|
b = nil
|
|
err = e.(error)
|
|
}
|
|
}()
|
|
|
|
switch handle {
|
|
case 0:
|
|
panic(ENullHandle(f.f.Name()))
|
|
case 2:
|
|
panic(&EHandle{f.f.Name(), handle})
|
|
default:
|
|
b, _ = f.readUsed(int64(handle))
|
|
}
|
|
return
|
|
}
|
|
|
|
// Free frees space associated with handle and returns an error if any. Passing an invalid
|
|
// handle to Free or reusing handle afterwards will probably corrupt the database or provide
|
|
// invalid data on Read. It's like corrupting memory via passing an invalid pointer to C.free()
|
|
// or reusing that pointer.
|
|
func (f *File) Free(handle Handle) (err error) {
|
|
return storage.Mutate(f.Accessor(), func() (err error) {
|
|
atom := int64(handle)
|
|
atoms, isFree := f.getSize(atom)
|
|
if isFree || atom < f.canfree {
|
|
return &EHandle{f.f.Name(), handle}
|
|
}
|
|
|
|
leftFree, rightFree := f.checkLeft(atom), f.checkRight(atom, atoms)
|
|
switch {
|
|
case leftFree != 0 && rightFree != 0:
|
|
f.delFree(atom-leftFree, leftFree)
|
|
f.delFree(atom+atoms, rightFree)
|
|
f.addFree(atom-leftFree, leftFree+atoms+rightFree)
|
|
case leftFree != 0 && rightFree == 0:
|
|
f.delFree(atom-leftFree, leftFree)
|
|
if atom+atoms == f.atoms { // the left free neighbour and this block together are an empy tail
|
|
f.atoms = atom - leftFree
|
|
f.f.Truncate(f.atoms << 4)
|
|
return
|
|
}
|
|
|
|
f.addFree(atom-leftFree, leftFree+atoms)
|
|
case leftFree == 0 && rightFree != 0:
|
|
f.delFree(atom+atoms, rightFree)
|
|
f.addFree(atom, atoms+rightFree)
|
|
default: // leftFree == 0 && rightFree == 0
|
|
if atom+atoms < f.atoms { // isolated inner block
|
|
f.addFree(atom, atoms)
|
|
return
|
|
}
|
|
|
|
f.f.Truncate(atom << 4) // isolated tail block, shrink file
|
|
f.atoms = atom
|
|
}
|
|
return
|
|
})
|
|
}
|
|
|
|
// Realloc reallocates space associted with handle to acomodate b, returns the newhandle
|
|
// newly associated with b and an error if any. If keepHandle == true then Realloc guarantees
|
|
// newhandle == handle even if the new data are larger then the previous content associated
|
|
// with handle. If !keepHandle && newhandle != handle then reusing handle will probably corrupt
|
|
// the database.
|
|
// The above effects are like corrupting memory/data via passing an invalid pointer to C.realloc().
|
|
func (f *File) Realloc(handle Handle, b []byte, keepHandle bool) (newhandle Handle, err error) {
|
|
err = storage.Mutate(f.Accessor(), func() (err error) {
|
|
switch handle {
|
|
case 0, 2:
|
|
return &EHandle{f.f.Name(), handle}
|
|
case 1:
|
|
keepHandle = true
|
|
}
|
|
newhandle = handle
|
|
atom, newatoms := int64(handle), rq2Atoms(len(b))
|
|
if newatoms > 3856 {
|
|
return &EBadRequest{f.f.Name(), len(b)}
|
|
}
|
|
|
|
typ, oldatoms := f.getInfo(atom)
|
|
switch {
|
|
default:
|
|
return &ECorrupted{f.f.Name(), atom << 4}
|
|
case typ <= 0xfc: // non relocated used block
|
|
switch {
|
|
case newatoms == oldatoms: // in place replace
|
|
f.writeUsed(b, atom)
|
|
case newatoms < oldatoms: // in place shrink
|
|
rightFree := f.checkRight(atom, oldatoms)
|
|
if rightFree > 0 { // right join
|
|
f.delFree(atom+oldatoms, rightFree)
|
|
}
|
|
f.addFree(atom+newatoms, oldatoms+rightFree-newatoms)
|
|
f.writeUsed(b, atom)
|
|
case newatoms > oldatoms:
|
|
if rightFree := f.checkRight(atom, oldatoms); rightFree > 0 && newatoms <= oldatoms+rightFree {
|
|
f.delFree(atom+oldatoms, rightFree)
|
|
if newatoms < oldatoms+rightFree {
|
|
f.addFree(atom+newatoms, oldatoms+rightFree-newatoms)
|
|
}
|
|
f.writeUsed(b, atom)
|
|
return
|
|
}
|
|
|
|
if !keepHandle {
|
|
f.Free(Handle(atom))
|
|
newhandle, err = f.Alloc(b)
|
|
return
|
|
}
|
|
|
|
// reloc
|
|
newatom, e := f.Alloc(b)
|
|
if e != nil {
|
|
return e
|
|
}
|
|
|
|
buf := make([]byte, 16)
|
|
buf[0] = 0xfd
|
|
Handle(newatom).Put(buf[1:])
|
|
f.Realloc(Handle(atom), buf[1:], true)
|
|
f.write(buf[:1], atom<<4)
|
|
}
|
|
case typ == 0xfd: // reloc
|
|
var target Handle
|
|
buf := make([]byte, 7)
|
|
f.read(buf, atom<<4+1)
|
|
target.Get(buf)
|
|
switch {
|
|
case newatoms == 1:
|
|
f.writeUsed(b, atom)
|
|
f.Free(target)
|
|
default:
|
|
if rightFree := f.checkRight(atom, 1); rightFree > 0 && newatoms <= 1+rightFree {
|
|
f.delFree(atom+1, rightFree)
|
|
if newatoms < 1+rightFree {
|
|
f.addFree(atom+newatoms, 1+rightFree-newatoms)
|
|
}
|
|
f.writeUsed(b, atom)
|
|
f.Free(target)
|
|
return
|
|
}
|
|
|
|
newtarget, e := f.Realloc(Handle(target), b, false)
|
|
if e != nil {
|
|
return e
|
|
}
|
|
|
|
if newtarget != target {
|
|
Handle(newtarget).Put(buf)
|
|
f.write(buf, atom<<4+1)
|
|
}
|
|
}
|
|
}
|
|
return
|
|
})
|
|
return
|
|
}
|
|
|
|
// Lock locks f for writing. If the lock is already locked for reading or writing,
|
|
// Lock blocks until the lock is available. To ensure that the lock eventually becomes available,
|
|
// a blocked Lock call excludes new readers from acquiring the lock.
|
|
func (f *File) Lock() {
|
|
f.rwm.Lock()
|
|
}
|
|
|
|
// RLock locks f for reading. If the lock is already locked for writing or there is a writer
|
|
// already waiting to release the lock, RLock blocks until the writer has released the lock.
|
|
func (f *File) RLock() {
|
|
f.rwm.RLock()
|
|
}
|
|
|
|
// Unlock unlocks f for writing. It is a run-time error if f is not locked for writing on entry to Unlock.
|
|
//
|
|
// As with Mutexes, a locked RWMutex is not associated with a particular goroutine.
|
|
// One goroutine may RLock (Lock) f and then arrange for another goroutine to RUnlock (Unlock) it.
|
|
func (f *File) Unlock() {
|
|
f.rwm.Unlock()
|
|
}
|
|
|
|
// RUnlock undoes a single RLock call; it does not affect other simultaneous readers.
|
|
// It is a run-time error if f is not locked for reading on entry to RUnlock.
|
|
func (f *File) RUnlock() {
|
|
f.rwm.RUnlock()
|
|
}
|
|
|
|
// LockedAlloc wraps Alloc in a Lock/Unlock pair.
|
|
func (f *File) LockedAlloc(b []byte) (handle Handle, err error) {
|
|
f.Lock()
|
|
defer f.Unlock()
|
|
return f.Alloc(b)
|
|
}
|
|
|
|
// LockedFree wraps Free in a Lock/Unlock pair.
|
|
func (f *File) LockedFree(handle Handle) (err error) {
|
|
f.Lock()
|
|
defer f.Unlock()
|
|
return f.Free(handle)
|
|
}
|
|
|
|
// LockedRead wraps Read in a RLock/RUnlock pair.
|
|
func (f *File) LockedRead(handle Handle) (b []byte, err error) {
|
|
f.RLock()
|
|
defer f.RUnlock()
|
|
return f.Read(handle)
|
|
}
|
|
|
|
// LockedRealloc wraps Realloc in a Lock/Unlock pair.
|
|
func (f *File) LockedRealloc(handle Handle, b []byte, keepHandle bool) (newhandle Handle, err error) {
|
|
f.Lock()
|
|
defer f.Unlock()
|
|
return f.Realloc(handle, b, keepHandle)
|
|
}
|