// 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 }) }
// 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 }
// 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 }) }
// 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 }
// Set stores value under partition, key in Store and returns an error if any. func (s *Store) Set(partition uint32, key, value []byte) (err error) { return storage.Mutate(s.accessor, func() (err error) { lenK := len(key) var h = newFNV1a() h.writeUint32(partition) h.write(key) var ptrbuf = make([]byte, s.PtrBytes) hdelta := s.hdelta(h.hash(s.HashWidth)) if _, err = s.accessor.ReadAt(ptrbuf, hdelta); err != nil { return } handle := s.getHandle(ptrbuf) if handle == 0 { // no collision, not set before if handle, err = s.Store.New(s.compose(0, partition, key, value)); err != nil { return } return s.setHandle(handle, hdelta) } // collision or overwrite existing var chunk []byte for { if chunk, err = s.Store.Get(handle); err != nil { return } if len(chunk) < s.PtrBytes+8 { return &falloc.ECorrupted{s.accessor.Name(), int64(handle) << 4} } rdoff := s.PtrBytes rdpartition := uint32(chunk[rdoff])<<24 | uint32(chunk[rdoff+1])<<16 | uint32(chunk[rdoff+2])<<8 | uint32(chunk[rdoff+3]) rdoff += 4 if rdpartition == partition { rdLenK := int(chunk[rdoff])<<8 | int(chunk[rdoff+1]) rdoff += 4 if rdLenK == lenK { // chunk key length OK if rdoff+lenK > len(chunk) { return &falloc.ECorrupted{ s.accessor.Name(), int64(handle) << 4, } } if bytes.Compare(key, chunk[rdoff:rdoff+lenK]) == 0 { // hit, overwrite rdoff += lenK next := s.getHandle(chunk) return s.Store.Set( handle, s.compose(next, partition, key, value), ) } } } next := s.getHandle(chunk) if next == 0 { // collision, not set before if next, err = s.Store.New(s.compose(0, partition, key, value)); err != nil { return } s.putHandle(next, chunk) // link return s.Store.Set(handle, chunk) // write back updated chunk } handle = next } panic("unreachable") }) }