Commit 5d775923 authored by Kirill Smelkov's avatar Kirill Smelkov

.

parent bf88c4f5
...@@ -364,14 +364,15 @@ package main ...@@ -364,14 +364,15 @@ package main
// and a client that wants @rev data will get @rev data, even if it was this // and a client that wants @rev data will get @rev data, even if it was this
// "old" client that triggered the pagefault(~). // "old" client that triggered the pagefault(~).
// //
// XXX 8) serving read from @<rev>/data + zconn(s) for historical state
//
//
// (*) see notes.txt -> "Notes on OS pagecache control" // (*) see notes.txt -> "Notes on OS pagecache control"
// (+) see notes.txt -> "Invalidations to wcfs clients are delayed until block access" // (+) see notes.txt -> "Invalidations to wcfs clients are delayed until block access"
// (~) see notes.txt -> "Changing mmapping while under pagefault is possible" // (~) see notes.txt -> "Changing mmapping while under pagefault is possible"
// (^) see notes.txt -> "Client cannot be ptraced while under pagefault" // (^) see notes.txt -> "Client cannot be ptraced while under pagefault"
// (%) no need to keep track of ZData - ZBlk1 is always marked as changed on blk data change. // (%) no need to keep track of ZData - ZBlk1 is always marked as changed on blk data change.
// //
// XXX 8) serving read from @<rev>/data + zconn(s) for historical state
//
// XXX For every ZODB connection a dedicated read-only transaction is maintained. // XXX For every ZODB connection a dedicated read-only transaction is maintained.
import ( import (
...@@ -418,37 +419,39 @@ type Root struct { ...@@ -418,37 +419,39 @@ type Root struct {
// ZODB connections for @<rev>/ // ZODB connections for @<rev>/
zrevMu sync.Mutex zrevMu sync.Mutex
zrevTab map[zodb.Tid]*ZConn zrevTab map[zodb.Tid]*ZConn
// XXX include?
// // {} rev -> @<rev>/
// mu sync.Mutex
// revTab map[zodb.Tid]*Rev
} }
// /bigfile/ - served by BigFileRoot. // /(head|<rev>)/ - served by Head. XXX separate Rev?
type BigFileRoot struct { type Head struct {
nodefs.Node nodefs.Node
// bigfile, at, watch, etc - all implicitly linked to by fs
// {} oid -> <bigfileX>/
mu sync.Mutex
tab map[zodb.Oid]*BigFileDir
} }
// /bigfile/<bigfileX>/ - served by BigFileDir. // /head/watch - served by Watch.
type BigFileDir struct { type Watch struct {
nodefs.Node nodefs.Node
oid zodb.Oid // oid of ZBigFile
// head/ is implicitly linked to by fs
// {} rev -> @<rev>/ bigfile snapshot // TODO
mu sync.Mutex
revTab map[zodb.Tid]*BigFileRev
} }
// /bigfile/<bigfileX>/(head|<rev>)/ - served by BigFileRev. // /(head|<rev>)/bigfile/ - served by BigFileDir.
type BigFileRev struct { type BigFileDir struct {
nodefs.Node nodefs.Node
// data, at, invalidations, etc - all implicitly linked to by fs
// {} oid -> <bigfileX>
mu sync.Mutex
tab map[zodb.Oid]*BigFile
} }
// /bigfile/<bigfileX>/(head|<rev>)/* - internally served by BigFile. // /(head|<rev>)/bigfile/<bigfileX> - served by BigFile.
type BigFile struct { type BigFile struct {
nodefs.Node
// this BigFile views ZODB via zconn // this BigFile views ZODB via zconn
zconn *ZConn zconn *ZConn
...@@ -463,18 +466,9 @@ type BigFile struct { ...@@ -463,18 +466,9 @@ type BigFile struct {
// TODO -> δFtail // TODO -> δFtail
// lastChange zodb.Tid // last change to whole bigfile as of .zconn.At view // lastChange zodb.Tid // last change to whole bigfile as of .zconn.At view
}
// /bigfile/<bigfileX>/(head|<rev>)/data - served by BigFileData.
type BigFileData struct {
nodefs.Node
bigfile *BigFile
// inflight loadings of ZBigFile from ZODB. // inflight loadings of ZBigFile from ZODB.
// successfull load results are kept here until blkdata is put into OS pagecache. // successfull load results are kept here until blkdata is put into OS pagecache.
//
// XXX -> BigFile ?
loadMu sync.Mutex loadMu sync.Mutex
loading map[int64]*blkLoadState // #blk -> {... blkdata} loading map[int64]*blkLoadState // #blk -> {... blkdata}
...@@ -524,6 +518,7 @@ func (cc *zodbCacheControl) WantEvict(obj zodb.IPersistent) bool { ...@@ -524,6 +518,7 @@ func (cc *zodbCacheControl) WantEvict(obj zodb.IPersistent) bool {
return false return false
} }
/*
// zwatcher watches for ZODB changes. // zwatcher watches for ZODB changes.
// see "4) when we receive an invalidation message from ZODB ..." // see "4) when we receive an invalidation message from ZODB ..."
func (r *Root) zwatcher(ctx context.Context) (err error) { func (r *Root) zwatcher(ctx context.Context) (err error) {
...@@ -550,7 +545,7 @@ func (r *Root) zhandle1(zevent zodb.WatchEvent) { ...@@ -550,7 +545,7 @@ func (r *Root) zhandle1(zevent zodb.WatchEvent) {
defer r.zheadMu.Unlock() defer r.zheadMu.Unlock()
//toinvalidate := map[*ZBigFile]SetI64{} // {} zfile -> set(#blk) //toinvalidate := map[*ZBigFile]SetI64{} // {} zfile -> set(#blk)
toinvalidate := map[*BigFileData]SetI64{} // {} zfile -> set(#blk) toinvalidate := map[*BigFile]SetI64{} // {} zfile -> set(#blk)
// zevent = (tid^, []oid) // zevent = (tid^, []oid)
for _, oid := range zevent.Changev { for _, oid := range zevent.Changev {
...@@ -601,7 +596,7 @@ func (r *Root) zhandle1(zevent zodb.WatchEvent) { ...@@ -601,7 +596,7 @@ func (r *Root) zhandle1(zevent zodb.WatchEvent) {
// invalidateBlk invalidates 1 file block. XXX // invalidateBlk invalidates 1 file block. XXX
// XXX see "4.4) for all file/blk to in invalidate we do" // XXX see "4.4) for all file/blk to in invalidate we do"
func (f *BigFileData) invalidateBlk(ctx context.Context, blk int64) error { func (f *BigFile) invalidateBlk(ctx context.Context, blk int64) error {
fsconn := f.root().fsconn fsconn := f.root().fsconn
off := blk*blksize off := blk*blksize
...@@ -630,20 +625,19 @@ func (f *BigFileData) invalidateBlk(ctx context.Context, blk int64) error { ...@@ -630,20 +625,19 @@ func (f *BigFileData) invalidateBlk(ctx context.Context, blk int64) error {
panic("TODO") panic("TODO")
} }
*/
// ---------------------------------------- // ----------------------------------------
// /bigfile -> Mkdir receives client request to create /bigfile/<bigfileX>. // /(head/<rev>)/bigfile/ -> Lookup receives client request to create (head|<rev>)/bigfile/<bigfileX>.
// func (bfdir *BigFileDir) Lookup(out *fuse.Attr, name string, fctx *fuse.Context) (*nodefs.Inode, fuse.Status) {
// It creates <bigfileX>/head/* along the way. inode, err := bfdir.lookup(out, name, fctx) // XXX reorder out?
func (bfroot *BigFileRoot) Mkdir(name string, mode uint32, fctx *fuse.Context) (*nodefs.Inode, fuse.Status) {
inode, err := bfroot.mkdir(name, fctx) // XXX ok to ignore mode?
return inode, err2LogStatus(err) return inode, err2LogStatus(err)
} }
func (bfroot *BigFileRoot) mkdir(name string, fctx *fuse.Context) (_ *nodefs.Inode, err error) { func (bfdir *BigFileDir) lookup(out *fuse.Attr, name string, fctx *fuse.Context) (_ *nodefs.Inode, err error) {
defer xerr.Contextf(&err, "/bigfile: mkdir %q", name) defer xerr.Contextf(&err, "/bigfile: lookup %q", name)
oid, err := zodb.ParseOid(name) oid, err := zodb.ParseOid(name)
if err != nil { if err != nil {
...@@ -651,139 +645,100 @@ func (bfroot *BigFileRoot) mkdir(name string, fctx *fuse.Context) (_ *nodefs.Ino ...@@ -651,139 +645,100 @@ func (bfroot *BigFileRoot) mkdir(name string, fctx *fuse.Context) (_ *nodefs.Ino
} }
// check to see if dir(oid) is already there // check to see if dir(oid) is already there
bfroot.mu.Lock() bfdir.mu.Lock()
_, already := bfroot.tab[oid] f, already := bfdir.tab[oid]
bfroot.mu.Unlock() bfdir.mu.Unlock()
if already { if already {
return nil, syscall.EEXIST // XXX fill out
return f.Inode(), nil
} }
// not there - without bfroot lock proceed to open BigFile from ZODB // not there - without bfdir lock proceed to open BigFile from ZODB
bf, err := bigopen(asctx(fctx), groot.zhead, oid) f, err = bigopen(asctx(fctx), groot.zhead, oid) // XXX zhead -> head|rev.zconn
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer func() {
if err != nil {
bf.Close()
}
}()
// relock bfroot and either mkdir or EEXIST if the directory was maybe // relock bfdir and either register f or, if the file was maybe
// simultanously created while we were not holding bfroot.mu // simultanously created while we were not holding bfdir.mu, return that.
bfroot.mu.Lock() bfdir.mu.Lock()
_, already = bfroot.tab[oid] f2, already := bfdir.tab[oid]
if already { if already {
bfroot.mu.Unlock() bfdir.mu.Unlock()
return nil, syscall.EEXIST f.Close()
} return f2.Inode(), nil // XXX fill out
bfdir := &BigFileDir{
Node: nodefs.NewDefaultNode(),
oid: oid,
revTab: make(map[zodb.Tid]*BigFileRev),
}
bfhead := &BigFileRev{
Node: nodefs.NewDefaultNode(),
}
bfdata := &BigFileData{
Node: nodefs.NewDefaultNode(),
bigfile: bf,
loading: make(map[int64]*blkLoadState),
} }
bfroot.tab[oid] = bfdir bfdir.tab[oid] = f
bfroot.mu.Unlock() bfdir.mu.Unlock()
// mkdir takes filesystem treeLock - do it outside bfroot.mu // mkfile takes filesystem treeLock - do it outside bfdir.mu
mkdir(bfroot, name, bfdir) mkfile(bfdir, name, f)
mkdir(bfdir, "head", bfhead)
mkfile(bfhead, "data", bfdata)
mkfile(bfhead, "at", NewSmallFile(bf.readAt)) // TODO mtime(at) = tidtime(at)
// XXX mkfile(bh, "invalidations", bh.inv)
return bfdir.Inode(), nil // XXX fill out
return f.Inode(), nil
} }
// XXX do we need to support rmdir? (probably no) // XXX do we need to support unlink? (probably no)
// /bigfile/<bigfileX> -> Mkdir receives client request to create @<tid>/. // / -> Mkdir receives client request to create @<rev>/.
func (bfdir *BigFileDir) Mkdir(name string, mode uint32, fctx *fuse.Context) (*nodefs.Inode, fuse.Status) { func (root *Root) Mkdir(name string, mode uint32, fctx *fuse.Context) (*nodefs.Inode, fuse.Status) {
inode, err := bfdir.mkdir(name, fctx) // XXX ok to ignore mode? inode, err := root.mkdir(name, fctx) // XXX ok to ignore mode?
return inode, err2LogStatus(err) return inode, err2LogStatus(err)
} }
func (bfdir *BigFileDir) mkdir(name string, fctx *fuse.Context) (_ *nodefs.Inode, err error) { func (root *Root) mkdir(name string, fctx *fuse.Context) (_ *nodefs.Inode, err error) {
defer xerr.Contextf(&err, "/bigfile/%s: mkdir %q", bfdir.oid, name) defer xerr.Contextf(&err, "/: mkdir %q", name)
var tid zodb.Tid var rev zodb.Tid
ok := false ok := false
if strings.HasPrefix(name, "@") { if strings.HasPrefix(name, "@") {
tid, err = zodb.ParseTid(name[1:]) rev, err = zodb.ParseTid(name[1:])
ok = (err == nil) ok = (err == nil)
} }
if !ok { if !ok {
return nil, eINVALf("not @tid") return nil, eINVALf("not @rev")
} }
// check to see if dir(tid) is already there // check to see if dir(rev) is already there
bfdir.mu.Lock() root.zrevMu.Lock()
_, already := bfdir.revTab[tid] _, already := root.zrevTab[rev]
bfdir.mu.Unlock() root.zrevMu.Unlock()
if already { if already {
return nil, syscall.EEXIST return nil, syscall.EEXIST
} }
// not there - without bfdir lock proceed to open BigFile @tid view of ZODB // not there - without zrevMu lock proceed to open @rev view of ZODB
ctx := asctx(fctx) ctx := asctx(fctx)
zconnRev, err := groot.zopenAt(ctx, tid) zconnRev, err := groot.zopenAt(ctx, rev)
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer zconnRev.Release() defer zconnRev.Release() // XXX ok?
bf, err := bigopen(ctx, zconnRev, bfdir.oid) // relock root and either mkdir or EEXIST if the directory was maybe
if err != nil { // simultanously created while we were not holding zrevMu.
return nil, err root.zrevMu.Lock()
} _, already = root.zrevTab[rev]
defer func() {
if err != nil {
bf.Close()
}
}()
// relock bfdir and either mkdir or EEXIST if the directory was maybe
// simultanously created while we were not holding bfroot.mu
bfdir.mu.Lock()
_, already = bfdir.revTab[tid]
if already { if already {
bfdir.mu.Unlock() root.zrevMu.Unlock()
return nil, syscall.EEXIST return nil, syscall.EEXIST
} }
bfrev := &BigFileRev{ revDir := &Head{ // XXX -> Rev ?
Node: nodefs.NewDefaultNode(), Node: nodefs.NewDefaultNode(),
} }
revdata := &BigFileData{ root.zrevTab[rev] = zconnRev
Node: nodefs.NewDefaultNode(), root.zrevMu.Unlock()
bigfile: bf,
loading: make(map[int64]*blkLoadState),
}
bfdir.revTab[tid] = bfrev // mkdir takes filesystem treeLock - do it outside zrevMu.
bfdir.mu.Unlock() mkdir(root, name, revDir)
// mkdir takes filesystem treeLock - do it outside bfroot.mu
mkdir(bfdir, name, bfrev)
mkfile(bfrev, "data", revdata)
return bfrev.Inode(), nil return revDir.Inode(), nil
} }
...@@ -844,30 +799,28 @@ func bigopen(ctx context.Context, zconn *ZConn, oid zodb.Oid) (_ *BigFile, err e ...@@ -844,30 +799,28 @@ func bigopen(ctx context.Context, zconn *ZConn, oid zodb.Oid) (_ *BigFile, err e
} }
// Close release all resources of BigFile. // Close release all resources of BigFile.
func (bf *BigFile) Close() error { func (f *BigFile) Close() error {
bf.zbf.PDeactivate() f.zbf.PDeactivate()
bf.zbf = nil f.zbf = nil
bf.zconn.Release() f.zconn.Release()
bf.zconn = nil f.zconn = nil
return nil return nil
} }
// /bigfile/<bigfileX>/head/data -> Getattr serves stat. // /(head|<rev>)/bigfile/<bigfileX> -> Getattr serves stat.
func (bfdata *BigFileData) GetAttr(out *fuse.Attr, _ nodefs.File, fctx *fuse.Context) fuse.Status { func (f *BigFile) GetAttr(out *fuse.Attr, _ nodefs.File, fctx *fuse.Context) fuse.Status {
// XXX locking // XXX locking
bf := bfdata.bigfile
out.Mode = fuse.S_IFREG | 0444 out.Mode = fuse.S_IFREG | 0444
out.Size = uint64(bf.zbfSize) out.Size = uint64(f.zbfSize)
// .Blocks // .Blocks
// .Blksize // .Blksize
// FIXME lastChange should cover all bigfile data, not only ZBigFile itself // FIXME lastChange should cover all bigfile data, not only ZBigFile itself
//mtime := &bfdata.lastChange.Time().Time //mtime := &f.lastChange.Time().Time
lastChange := bf.zbf.PSerial() lastChange := f.zbf.PSerial()
mtime := lastChange.Time().Time mtime := lastChange.Time().Time
out.SetTimes(/*atime=*/nil, /*mtime=*/&mtime, /*ctime=*/&mtime) out.SetTimes(/*atime=*/nil, /*mtime=*/&mtime, /*ctime=*/&mtime)
...@@ -876,17 +829,16 @@ func (bfdata *BigFileData) GetAttr(out *fuse.Attr, _ nodefs.File, fctx *fuse.Con ...@@ -876,17 +829,16 @@ func (bfdata *BigFileData) GetAttr(out *fuse.Attr, _ nodefs.File, fctx *fuse.Con
} }
// /bigfile/<bigfileX>/head/data -> Read serves reading bigfile data. // /(head|<rev>)/bigfile/<bigfileX> -> Read serves reading bigfile data.
func (bfdata *BigFileData) Read(_ nodefs.File, dest []byte, off int64, fctx *fuse.Context) (fuse.ReadResult, fuse.Status) { func (f *BigFile) Read(_ nodefs.File, dest []byte, off int64, fctx *fuse.Context) (fuse.ReadResult, fuse.Status) {
// XXX locking // XXX locking
bf := bfdata.bigfile zbf := f.zbf
zbf := bf.zbf
// cap read request to file size // cap read request to file size
end := off + int64(len(dest)) // XXX overflow? end := off + int64(len(dest)) // XXX overflow?
if end > bf.zbfSize { if end > f.zbfSize {
end = bf.zbfSize end = f.zbfSize
} }
if end <= off { if end <= off {
// XXX off >= size -> EINVAL? (but when size=0 kernel issues e.g. [0 +4K) read) // XXX off >= size -> EINVAL? (but when size=0 kernel issues e.g. [0 +4K) read)
...@@ -903,7 +855,7 @@ func (bfdata *BigFileData) Read(_ nodefs.File, dest []byte, off int64, fctx *fus ...@@ -903,7 +855,7 @@ func (bfdata *BigFileData) Read(_ nodefs.File, dest []byte, off int64, fctx *fus
dest = make([]byte, aend - aoff) // ~> [aoff:aend) in file dest = make([]byte, aend - aoff) // ~> [aoff:aend) in file
// XXX better ctx = transaction.PutIntoContext(ctx, txn) // XXX better ctx = transaction.PutIntoContext(ctx, txn)
ctx, cancel := xcontext.Merge(asctx(fctx), bf.zconn.txnCtx) ctx, cancel := xcontext.Merge(asctx(fctx), f.zconn.txnCtx)
defer cancel() defer cancel()
// read/load all block(s) in parallel // read/load all block(s) in parallel
...@@ -914,7 +866,7 @@ func (bfdata *BigFileData) Read(_ nodefs.File, dest []byte, off int64, fctx *fus ...@@ -914,7 +866,7 @@ func (bfdata *BigFileData) Read(_ nodefs.File, dest []byte, off int64, fctx *fus
wg.Go(func() error { wg.Go(func() error {
δ := blkoff-aoff // blk position in dest δ := blkoff-aoff // blk position in dest
//log.Infof("readBlk #%d dest[%d:+%d]", blk, δ, zbf.blksize) //log.Infof("readBlk #%d dest[%d:+%d]", blk, δ, zbf.blksize)
return bfdata.readBlk(ctx, blk, dest[δ:δ+zbf.blksize]) return f.readBlk(ctx, blk, dest[δ:δ+zbf.blksize])
}) })
} }
...@@ -929,23 +881,23 @@ func (bfdata *BigFileData) Read(_ nodefs.File, dest []byte, off int64, fctx *fus ...@@ -929,23 +881,23 @@ func (bfdata *BigFileData) Read(_ nodefs.File, dest []byte, off int64, fctx *fus
// readBlk serves Read to read 1 ZBlk #blk into destination buffer. // readBlk serves Read to read 1 ZBlk #blk into destination buffer.
// //
// see "6) when we receive a FUSE read(#blk) request ..." in overview. // see "7) when we receive a FUSE read(#blk) request ..." in overview.
// //
// len(dest) == blksize. // len(dest) == blksize.
func (bfdata *BigFileData) readBlk(ctx context.Context, blk int64, dest []byte) error { func (f *BigFile) readBlk(ctx context.Context, blk int64, dest []byte) error {
// XXX errctx? // XXX errctx?
// XXX locking // XXX locking
// check if someone else is already loading this block // check if someone else is already loading this block
bfdata.loadMu.Lock() f.loadMu.Lock()
loading, already := bfdata.loading[blk] loading, already := f.loading[blk]
if !already { if !already {
loading = &blkLoadState{ loading = &blkLoadState{
ready: make(chan struct{}), ready: make(chan struct{}),
} }
bfdata.loading[blk] = loading f.loading[blk] = loading
} }
bfdata.loadMu.Unlock() f.loadMu.Unlock()
// if it is already loading - just wait for it // if it is already loading - just wait for it
if already { if already {
...@@ -963,14 +915,14 @@ func (bfdata *BigFileData) readBlk(ctx context.Context, blk int64, dest []byte) ...@@ -963,14 +915,14 @@ func (bfdata *BigFileData) readBlk(ctx context.Context, blk int64, dest []byte)
// noone was loading - we became reponsible to load this block // noone was loading - we became reponsible to load this block
zbf := bfdata.bigfile.zbf zbf := f.zbf
blkdata, err := zbf.LoadBlk(ctx, blk) // XXX -> +blkrevmax1 blkdata, err := zbf.LoadBlk(ctx, blk) // XXX -> +blkrevmax1
loading.blkdata = blkdata loading.blkdata = blkdata
loading.err = err loading.err = err
close(loading.ready) close(loading.ready)
// XXX before loading.ready? // XXX before loading.ready?
blkrevmax2, _ := bfdata.bigfile.δFtail.LastRevOf(blk, zbf.PJar().At()) blkrevmax2, _ := f.δFtail.LastRevOf(blk, zbf.PJar().At())
//revmax := min(blkrevmax1, blkrevmax2) //revmax := min(blkrevmax1, blkrevmax2)
revmax := blkrevmax2 revmax := blkrevmax2
_ = revmax _ = revmax
...@@ -979,7 +931,7 @@ func (bfdata *BigFileData) readBlk(ctx context.Context, blk int64, dest []byte) ...@@ -979,7 +931,7 @@ func (bfdata *BigFileData) readBlk(ctx context.Context, blk int64, dest []byte)
// XXX remmapping // XXX remmapping
// XXX -> own func? // XXX -> own func?
// XXX locking // XXX locking
for _, mapping := range bfdata.mappings { for _, mapping := range f.mappings {
if revmax <= mapping.at || !mapping.blkrange.in(blk) { if revmax <= mapping.at || !mapping.blkrange.in(blk) {
continue // do nothing continue // do nothing
} }
...@@ -1001,9 +953,9 @@ func (bfdata *BigFileData) readBlk(ctx context.Context, blk int64, dest []byte) ...@@ -1001,9 +953,9 @@ func (bfdata *BigFileData) readBlk(ctx context.Context, blk int64, dest []byte)
// data loaded with error - cleanup .loading // data loaded with error - cleanup .loading
if loading.err != nil { if loading.err != nil {
bfdata.loadMu.Lock() f.loadMu.Lock()
delete(bfdata.loading, blk) delete(f.loading, blk)
bfdata.loadMu.Unlock() f.loadMu.Unlock()
return err return err
} }
...@@ -1028,11 +980,11 @@ func (bfdata *BigFileData) readBlk(ctx context.Context, blk int64, dest []byte) ...@@ -1028,11 +980,11 @@ func (bfdata *BigFileData) readBlk(ctx context.Context, blk int64, dest []byte)
// XXX locking - invalidation must make sure this workers are finished. // XXX locking - invalidation must make sure this workers are finished.
// XXX if direct-io: don't touch pagecache // XXX if direct-io: don't touch pagecache
st := gfsconn.FileNotifyStoreCache(bfdata.Inode(), blk*zbf.blksize, blkdata) st := gfsconn.FileNotifyStoreCache(f.Inode(), blk*zbf.blksize, blkdata)
bfdata.loadMu.Lock() f.loadMu.Lock()
delete(bfdata.loading, blk) delete(f.loading, blk)
bfdata.loadMu.Unlock() f.loadMu.Unlock()
if st == fuse.OK { if st == fuse.OK {
return return
...@@ -1137,9 +1089,8 @@ func main() { ...@@ -1137,9 +1089,8 @@ func main() {
// add entries to / // add entries to /
mkfile(root, ".wcfs", NewStaticFile([]byte(zurl))) mkfile(root, ".wcfs", NewStaticFile([]byte(zurl)))
mkdir(root, "bigfile", &BigFileRoot{ mkdir(root, "head", &Head{
Node: nodefs.NewDefaultNode(), Node: nodefs.NewDefaultNode(),
tab: make(map[zodb.Oid]*BigFileDir),
}) })
// TODO handle autoexit // TODO handle autoexit
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment