zblk.go 11.8 KB
Newer Older
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
1 2
// Copyright (C) 2018-2019  Nexedi SA and Contributors.
//                          Kirill Smelkov <kirr@nexedi.com>
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
//
// This program is free software: you can Use, Study, Modify and Redistribute
// it under the terms of the GNU General Public License version 3, or (at your
// option) any later version, as published by the Free Software Foundation.
//
// You can also Link and Combine this program with other software covered by
// the terms of any of the Free Software licenses or any of the Open Source
// Initiative approved licenses and Convey the resulting work. Corresponding
// source of such a combination shall include the source code for all other
// software used.
//
// This program is distributed WITHOUT ANY WARRANTY; without even the implied
// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
//
// See COPYING file for full licensing terms.
// See https://www.nexedi.com/licensing for rationale and options.

package main
// ZBlk* + ZBigFile loading

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
23

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
// module: "wendelin.bigfile.file_zodb"
//
// ZBigFile
//	.blksize	xint
//	.blktab		LOBtree{}  blk -> ZBlk*(blkdata)
//
// ZBlk0 (aliased as ZBlk)
//	str with trailing '\0' removed.
//
// ZBlk1
//	.chunktab	IOBtree{}  offset -> ZData(chunk)
//
// ZData
//	str (chunk)

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
39
import (
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
40
	"context"
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
41
	"fmt"
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
42
	"log"
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
43
	"reflect"
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
44
	"sort"
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
45
	"sync"
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
46
	"syscall"
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
47 48

	"golang.org/x/sync/errgroup"
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
49

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
50
	"lab.nexedi.com/kirr/go123/mem"
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
51
	"lab.nexedi.com/kirr/go123/xerr"
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
52 53
	"lab.nexedi.com/kirr/neo/go/zodb"
	"lab.nexedi.com/kirr/neo/go/zodb/btree"
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
54
	pickle "github.com/kisielk/og-rek"
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
55 56

	"./internal/pycompat"
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
57 58
)

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
59 60
// zBlk is the interface that every ZBlk* block implements internally.
type zBlk interface {
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
61 62
	// loadBlkData loads from database and returns data block stored by this ZBlk.
	//
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
63 64
	// If returned data size is less than the block size of containing ZBigFile,
	// the block trailing is assumed to be trailing \0.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
65
	loadBlkData(ctx context.Context) ([]byte, error)
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
66 67 68 69 70 71

	// bindZFile associates ZBlk as being used by zfile to store block #blk.
	//
	// A ZBlk may be bound to several blocks inside one file, and to
	// several files.
	//
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
72
	// The information is preserved even when ZBlk comes to ghost
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
73 74
	// state, but is lost if ZBlk is garbage collected.
	//
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
75 76
	// it is safe to call multiple bindZFile simultaneously.
	// it is not safe to call bindZFile and boundTo simultaneously.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
77 78 79 80 81 82
	//
	// XXX link to overview.
	bindZFile(zfile *ZBigFile, blk int64)

	// XXX unbindZFile
	// XXX zfile -> bind map for it
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
83 84 85 86 87 88 89

	// blkBoundTo returns ZBlk association with zfile(s)/#blk(s).
	//
	// The association returned is that was previously set by bindZFile.
	//
	// blkBoundTo must not be called simultaneously wrt bindZFile.
	blkBoundTo() map[*ZBigFile]SetI64
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
90 91
}

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
92 93
// module of Wendelin ZODB py objects
const zwendelin = "wendelin.bigfile.file_zodb"
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
94

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
95 96 97 98 99 100 101
// ---- zBlkBase ----

// zBlkBase provides common functionality to implement ZBlk* -> zfile, #blk binding.
//
// The data stored by zBlkBase is transient - it is _not_ included into
// persistent state.
type zBlkBase struct {
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
102
	bindMu  sync.Mutex           // used only for binding to support multiple loaders
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
103
	inzfile map[*ZBigFile]SetI64 // {} zfile -> set(#blk)
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
104 105
}

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
106 107
// bindZFile implements zBlk.
func (zb *zBlkBase) bindZFile(zfile *ZBigFile, blk int64) {
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
108 109
	zb.bindMu.Lock()
	defer zb.bindMu.Unlock()
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
110

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
111
	blkmap, ok := zb.inzfile[zfile]
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
112
	if !ok {
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
113
		blkmap = make(SetI64, 1)
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
114 115 116
		if zb.inzfile == nil {
			zb.inzfile = make(map[*ZBigFile]SetI64)
		}
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
117
		zb.inzfile[zfile] = blkmap
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
118
	}
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
119
	blkmap.Add(blk)
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
120 121
}

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
122 123 124 125 126
// blkBoundTo implementss zBlk.
func (zb *zBlkBase) blkBoundTo() map[*ZBigFile]SetI64 {
	return zb.inzfile
}

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
127
// ---- ZBlk0 ----
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
128

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
129 130
// ZBlk0 mimics ZBlk0 from python.
type ZBlk0 struct {
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
131
	zBlkBase
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
132
	zodb.Persistent
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
133

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
134
	// XXX py source uses bytes(buf) but on python2 it still results in str
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
135 136
	blkdata string
}
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
137

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
138 139 140
type zBlk0State ZBlk0 // hide state methods from public API

func (zb *zBlk0State) DropState() {
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
141 142
	zb.blkdata = ""
}
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
143

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
144
func (zb *zBlk0State) PySetState(pystate interface{}) error {
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
145 146
	blkdata, ok := pystate.(string)
	if !ok {
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
147
		return fmt.Errorf("expect str; got %s", typeOf(pystate))
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
148 149
	}

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
150
	log.Printf("ZBlk0.PySetState #%d", len(blkdata))
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
151 152
	zb.blkdata = blkdata
	return nil
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
153 154
}

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
155
func (zb *ZBlk0) loadBlkData(ctx context.Context) ([]byte, error) {
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
156 157 158 159 160 161 162 163 164 165 166
	// XXX err ctx

	err := zb.PActivate(ctx)
	if err != nil {
		return nil, err
	}
	defer zb.PDeactivate()

	return mem.Bytes(zb.blkdata), nil
}

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
167
// ---- ZBlk1 ---
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
168

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
169 170
// ZData mimics ZData from python.
type ZData struct {
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
171
	zBlkBase
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
172
	zodb.Persistent
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
173

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
174
	// XXX py source uses bytes(buf) but on python2 it still results in str
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
175 176
	data string
}
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
177

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
178 179 180
type zDataState ZData // hide state methods from public API

func (zd *zDataState) DropState() {
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
181 182
	zd.data = ""
}
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
183

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
184
func (zd *zDataState) PySetState(pystate interface{}) error {
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
185
	//log.Printf("ZData.PySetState")
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
186 187
	data, ok := pystate.(string)
	if !ok {
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
188
		return fmt.Errorf("expect str; got %s", typeOf(pystate))
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
189 190
	}

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
191 192 193
	zd.data = data
	return nil
}
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
194

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
195 196
// ZBlk1 mimics ZBlk1 from python.
type ZBlk1 struct {
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
197
	zodb.Persistent
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
198

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
199
	chunktab *btree.IOBTree // {} offset -> ZData(chunk)
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
200
}
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
201

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
202 203 204
type zBlk1State ZBlk1 // hide state methods from public API

func (zb *zBlk1State) DropState() {
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
205 206
	zb.chunktab = nil
}
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
207

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
208
func (zb *zBlk1State) PySetState(pystate interface{}) error {
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
209
	log.Printf("ZBlk1.PySetState")
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
210
	chunktab, ok := pystate.(*btree.IOBTree)
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
211
	if !ok {
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
212
		return fmt.Errorf("expect IOBTree; got %s", typeOf(pystate))
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
213 214
	}

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
215 216 217 218
	zb.chunktab = chunktab
	return nil
}

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
219
func (zb *ZBlk1) loadBlkData(ctx context.Context) ([]byte, error) {
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
220 221
	// XXX errctx

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
222 223 224 225 226 227
	err := zb.PActivate(ctx)
	if err != nil {
		return nil, err
	}
	defer zb.PDeactivate()

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
228 229 230 231 232 233
	// get to all ZData objects; activate them and build
	//
	//	{} offset -> ZData
	//
	// with all ZData being live.
	var mu sync.Mutex
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
234
	chunktab := make(map[int32]*ZData)
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
235

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
236
	// on return deactivate all ZData objects loaded in chunktab
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
237 238 239 240 241 242 243
	defer func() {
		for _, zd := range chunktab {
			zd.PDeactivate()
		}
	}()


Kirill Smelkov's avatar
.  
Kirill Smelkov committed
244 245
	wg, ctx := errgroup.WithContext(ctx)

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
246
	// loadZData loads 1 ZData object into chunktab and leaves it activated.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
247
	loadZData := func(offset int32, zd *ZData) error {
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
248 249 250 251
		err := zd.PActivate(ctx)
		if err != nil {
			return err
		}
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
252 253
		// no PDeactivate, zd remains live

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
254
		//fmt.Printf("@%d -> zdata #%s (%d)\n", offset, zd.POid(), len(zd.data))
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
255 256 257 258 259 260 261
		mu.Lock()
		defer mu.Unlock()

		chunktab[offset] = zd
		return nil
	}

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
262
	// loadBucket loads all ZData objects from leaf BTree bucket.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
263
	loadBucket := func(b *btree.IOBucket) error {
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
264 265 266 267 268 269
		err := b.PActivate(ctx)
		if err != nil {
			return err
		}
		defer b.PDeactivate()

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
270 271
		// go through all bucket key/v -> chunktab

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
272 273 274
		// XXX off	 < 0		!ok
		// XXX off + len > blksize	!ok

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
275
		//fmt.Printf("\nbucket: %v\n\n", b.Entryv())
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
276

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
277 278 279
		for _, e := range b.Entryv() {
			zd, ok := e.Value().(*ZData)
			if !ok {
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
280
				return fmt.Errorf("!ZData (%s)", typeOf(e.Value()))
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
281 282
			}

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
283
			offset := e.Key()
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
284
			wg.Go(func() error {
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
285
				return loadZData(offset, zd)
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
286 287
			})
		}
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
288 289 290 291

		return nil
	}

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
292
	// loadBTree spawns loading of all BTree children.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
293 294
	var loadBTree func(t *btree.IOBTree) error
	loadBTree = func(t *btree.IOBTree) error {
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
295 296 297 298 299 300
		err := t.PActivate(ctx)
		if err != nil {
			return err
		}
		defer t.PDeactivate()

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
301
		//fmt.Printf("\nbtree: %v\n\n", t.Entryv())
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
302

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
303 304
		for _, e := range t.Entryv() {
			switch child := e.Child().(type) {
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
305
			case *btree.IOBTree:
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
306
				wg.Go(func() error {
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
307
					return loadBTree(child)
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
308 309
				})

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
310
			case *btree.IOBucket:
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
311 312 313 314 315
				wg.Go(func() error {
					return loadBucket(child)
				})

			default:
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
316
				panic(fmt.Sprintf("IOBTree has %s child", typeOf(child)))
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
317 318 319 320 321 322
			}
		}

		return nil
	}

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
323 324 325 326
	wg.Go(func() error {
		return loadBTree(zb.chunktab)
	})

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
327
	err = wg.Wait()
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
328 329 330
	if err != nil {
		return nil, err // XXX err ctx
	}
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
331

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
332 333 334
	// empty .chunktab -> ø
	if len(chunktab) == 0 {
		return nil, nil
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
335 336
	}

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
337
	// glue all chunks from chunktab
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
338
	offv := make([]int32, 0, len(chunktab)) // ↑
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
339 340 341 342 343 344 345
	for off := range(chunktab) {
		offv = append(offv, off)
	}
	sort.Slice(offv, func(i, j int) bool {
		return offv[i] < offv[j]
	})

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
346 347
	//fmt.Printf("#chunktab: %d\n", len(chunktab))
	//fmt.Printf("offv: %v\n", offv)
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
348

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
349 350 351 352

        // find out whole blk len via inspecting tail chunk
	tailStart := offv[len(offv)-1]
	tailChunk := chunktab[tailStart]
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
353
	blklen    := tailStart + int32(len(tailChunk.data))	// XXX overflow?
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
354 355 356 357 358 359

        // whole buffer initialized as 0 + tail_chunk
	blkdata := make([]byte, blklen)
	copy(blkdata[tailStart:], tailChunk.data)

        // go through all chunks besides tail and extract them
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
360
	stop := int32(0)
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
361 362
	for _, start := range offv[:len(offv)-1] {
		chunk := chunktab[start]
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
363
		if !(start >= stop) {	// verify chunks don't overlap
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
364
			return nil, fmt.Errorf("!(start >= stop)")	// XXX
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
365
		}
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
366
		if !(start + int32(len(chunk.data)) <= int32(len(blkdata))) {	// XXX overflow?
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
367
			return nil, fmt.Errorf("blkdata overrun")	// XXX
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
368
		}
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
369
		stop = start + int32(len(chunk.data))	// XXX overflow?
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
370
		copy(blkdata[start:], chunk.data)
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
371
	}
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
372

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
373
	return blkdata, nil
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
374 375
}

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
376 377 378 379 380

// ----------------------------------------

// ZBigFile mimics ZBigFile from python.
type ZBigFile struct {
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
381
	zodb.Persistent
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
382 383

	blksize	int64
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
384
	blktab  *btree.LOBTree	// {}  blk -> ZBlk*(blkdata)
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
385
}
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
386

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
387 388
type zBigFileState ZBigFile // hide state methods from public API

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
389

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
390
// DropState implements zodb.Stateful.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
391
func (bf *zBigFileState) DropState() {
Kirill Smelkov's avatar
Kirill Smelkov committed
392
	bf.blksize = 0
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
393 394 395
	bf.blktab  = nil
}

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
396
// PySetState implements zodb.PyStateful.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
397
func (bf *zBigFileState) PySetState(pystate interface{}) (err error) {
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
398 399 400 401 402
	// ZBigFile
	//	.blksize	xint
	//	.blktab		LOBtree{}  blk -> ZBlk*(blkdata)
	//
	// state: (.blksize, .blktab)
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
403

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
404
	t, ok := pystate.(pickle.Tuple)
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
405
	if !ok {
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
406
		return fmt.Errorf("expect [2](); got %s", typeOf(pystate))
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
407 408 409
	}
	if len(t) != 2 {
		return fmt.Errorf("expect [2](); got [%d]()", len(t))
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
410 411
	}

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
412
	blksize, ok := pycompat.Int64(t[0])
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
413
	if !ok {
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
414
		return fmt.Errorf("blksize: expect integer; got %s", typeOf(t[0]))
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
415
	}
Kirill Smelkov's avatar
Kirill Smelkov committed
416 417 418
	if blksize <= 0 {
		return fmt.Errorf("blksize: must be > 0; got %d", blksize)
	}
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
419

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
420
	blktab, ok := t[1].(*btree.LOBTree)
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
421
	if !ok {
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
422
		return fmt.Errorf("blktab: expect LOBTree; got %s", typeOf(t[1]))
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
423
	}
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
424

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
425
	bf.blksize = blksize
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
426 427
	bf.blktab  = blktab
	return nil
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
428
}
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
429

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449
// LoadBlk loads data for file block #blk.
//
// XXX better load into user-provided buf?
func (bf *ZBigFile) LoadBlk(ctx context.Context, blk int64) (_ []byte, err error) {
	defer xerr.Contextf(&err, "bigfile %s: loadblk %d", bf.POid(), blk)

	err = bf.PActivate(ctx)
	if err != nil {
		return nil, err
	}
	defer bf.PDeactivate()

	xzblk, ok, err := bf.blktab.Get(ctx, blk)
	if err != nil {
		return nil, err
	}
	if !ok {
		return make([]byte, bf.blksize), nil
	}

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
450
	zblk, ok := xzblk.(zBlk)
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
451 452 453 454 455 456 457 458 459
	if !ok {
		return nil, fmt.Errorf("expect ZBlk*; got %s", typeOf(xzblk))
	}

	blkdata, err := zblk.loadBlkData(ctx)
	if err != nil {
		return nil, err
	}

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
460 461
	l := int64(len(blkdata))
	if l > bf.blksize {
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
462 463 464
		return nil, fmt.Errorf("invalid blk: size = %d (> blksize = %d)", l, bf.blksize)
	}

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
465 466 467 468 469 470 471
	// append trailing \0 to data to reach .blksize
	if l < bf.blksize {
		d := make([]byte, bf.blksize)
		copy(d, blkdata)
		blkdata = d
	}

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
472 473 474
	zblk.bindZFile(bf, blk)

	//log.Printf("ZBigFile.loadblk(%d) -> %dB", blk, len(blkdata))
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
475 476 477
	return blkdata, nil
}

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503
// Size returns whole file size.
func (bf *ZBigFile) Size(ctx context.Context) (_ int64, err error) {
	defer xerr.Contextf(&err, "bigfile %s: size", bf.POid())

	err = bf.PActivate(ctx)
	if err != nil {
		return 0, err
	}
	defer bf.PDeactivate()

	tailblk, ok, err := bf.blktab.MaxKey(ctx)
	if err != nil {
		return 0, err
	}
	if !ok {
		return 0, nil
	}

	size := (tailblk + 1) * bf.blksize
	if size / bf.blksize != tailblk + 1 {
		return 0, syscall.EFBIG // overflow
	}

	return size, nil
}

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
504

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
505 506 507
// ----------------------------------------

func init() {
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
508 509 510 511 512
	t := reflect.TypeOf
	zodb.RegisterClass(zwendelin + ".ZBlk0",    t(ZBlk0{}),    t(zBlk0State{}))
	zodb.RegisterClass(zwendelin + ".ZBlk1",    t(ZBlk1{}),    t(zBlk1State{}))
	zodb.RegisterClass(zwendelin + ".ZData",    t(ZData{}),    t(zDataState{}))
	zodb.RegisterClass(zwendelin + ".ZBigFile", t(ZBigFile{}), t(zBigFileState{}))
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
513 514 515

	// backward compatibility
	zodb.RegisterClassAlias(zwendelin + ".ZBlk", zwendelin + ".ZBlk0")
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
516
}