github.com/scottcagno/storage@v1.8.0/pkg/_junk/bfile/bfile.go (about) 1 package bfile 2 3 import ( 4 "encoding/binary" 5 "errors" 6 "fmt" 7 "io" 8 "math" 9 "os" 10 "path/filepath" 11 ) 12 13 const ( 14 sectorSize = 512 // 512 B 15 blockSize = 8 * sectorSize // 4 KB 16 chunkSize = 8 * blockSize // 32 KB 17 extentSize = 8 * chunkSize // 256 KB 18 19 headerSize = 12 20 recordMaxSize = blockSize * math.MaxUint16 21 asciiUnitSeparator = 0x1F 22 statusFree = uint16(asciiUnitSeparator >> 0 << 10) 23 statusActive = uint16(asciiUnitSeparator >> 1 << 10) 24 statusDeleted = uint16(asciiUnitSeparator >> 2 << 10) 25 ) 26 27 var ( 28 ErrBadOffsetAlignment = errors.New("bad offset; not correctly aligned") 29 ErrOffsetOutOfBounds = errors.New("bad offset; out of bounds") 30 ErrDataTooLarge = errors.New("data exceeds max record size") 31 ErrFileClosed = errors.New("file is closed") 32 ErrScannerSkip = errors.New("skip to next place with scanner/iterator") 33 ) 34 35 // align block aligns the provided size 36 func align(size int64) int64 { 37 if size > 0 { 38 return ((size + 2) + blockSize - 1) &^ (blockSize - 1) 39 } 40 return blockSize 41 } 42 43 // at takes a block position and converts it to a block offset 44 func at(n int) int64 { 45 return int64(n) * blockSize 46 } 47 48 // getOffset returns a block aligned offset for the provided position 49 func getOffset(pos int, max int64) (int64, error) { 50 // calculate the 51 offset := int64(pos * blockSize) 52 // return error if offset is not block aligned 53 if offset%blockSize != 0 { 54 return -1, ErrBadOffsetAlignment 55 } 56 // return error if offset is larger than max 57 if offset > max { 58 return -1, ErrOffsetOutOfBounds 59 } 60 // otherwise, return offset 61 return offset, nil 62 } 63 64 /* 65 * To save space, the header could always be adjusted as such: 66 * 67 * // header size would be reduced in half (12 down to 6 bytes) 68 * type header struct { 69 * status uint8 // 70 * blocks uint8 // max block count of 255 71 * length uint16 // max length (aka record size) of 65535 bytes 72 * padding uint16 // max padding of 65535 bytes (also, padding could be removed) 73 * } 74 * 75 */ 76 77 // header represents a record header 78 type header struct { 79 status uint16 // status of the record 80 blocks uint16 // blocks used by the record 81 length uint32 // length of the record data 82 padding uint32 // padding is the extra unused bytes in the block 83 } 84 85 // readHeader reads and fills out a header from the provided io.Reader 86 func (hdr *header) readHeader(r io.Reader) (int, error) { 87 // make buffer for record header 88 buf := make([]byte, headerSize) 89 // read in entire record header 90 n, err := r.Read(buf) 91 if err != nil { 92 return n, err 93 } 94 // decode status 95 hdr.status = binary.LittleEndian.Uint16(buf[0:2]) 96 // decode blocks 97 hdr.blocks = binary.LittleEndian.Uint16(buf[2:4]) 98 // decode length 99 hdr.length = binary.LittleEndian.Uint32(buf[4:8]) 100 // decode padding 101 hdr.padding = binary.LittleEndian.Uint32(buf[8:12]) 102 // return bytes read and nil 103 return n, nil 104 } 105 106 // readHeaderAt reads and fills out a header from the provided io.ReaderAt 107 func (hdr *header) readHeaderAt(r io.ReaderAt, off int64) (int, error) { 108 // make buffer for record header 109 buf := make([]byte, headerSize) 110 // read in entire record header 111 n, err := r.ReadAt(buf, off) 112 if err != nil { 113 return n, err 114 } 115 // decode status 116 hdr.status = binary.LittleEndian.Uint16(buf[0:2]) 117 // decode blocks 118 hdr.blocks = binary.LittleEndian.Uint16(buf[2:4]) 119 // decode length 120 hdr.length = binary.LittleEndian.Uint32(buf[4:8]) 121 // decode padding 122 hdr.padding = binary.LittleEndian.Uint32(buf[8:12]) 123 // return bytes read and nil 124 return n, nil 125 } 126 127 // writeHeader writes a header to the provided io.Writer 128 func (hdr *header) writeHeader(w io.Writer) (int, error) { 129 // make buffer to encode record header into 130 buf := make([]byte, headerSize) 131 // encode status 132 binary.LittleEndian.PutUint16(buf[0:2], hdr.status) 133 // encode blocks 134 binary.LittleEndian.PutUint16(buf[2:4], hdr.blocks) 135 // encode length 136 binary.LittleEndian.PutUint32(buf[4:8], hdr.length) 137 // encode padding 138 binary.LittleEndian.PutUint32(buf[8:12], hdr.padding) 139 // write record header 140 return w.Write(buf) 141 } 142 143 // writeHeaderAt writes a header to the provided io.WriterAt 144 func (hdr *header) writeHeaderAt(w io.WriterAt, offset int64) (int, error) { 145 // make buffer to encode record header into 146 buf := make([]byte, headerSize) 147 // encode status 148 binary.LittleEndian.PutUint16(buf[0:2], hdr.status) 149 // encode blocks 150 binary.LittleEndian.PutUint16(buf[2:4], hdr.blocks) 151 // encode length 152 binary.LittleEndian.PutUint32(buf[4:8], hdr.length) 153 // encode padding 154 binary.LittleEndian.PutUint32(buf[8:12], hdr.padding) 155 // write record header at 156 return w.WriteAt(buf, offset) 157 } 158 159 // record represents a data record on disk 160 type record struct { 161 *header // record header 162 data []byte // record data 163 } 164 165 // makeRecord creates and returns a new record with the provided data 166 func makeRecord(d []byte) (*record, error) { 167 // calc "overhead" 168 overhead := headerSize + int64(len(d)) 169 // get aligned size 170 size := align(overhead) 171 // error check 172 if size > recordMaxSize { 173 return nil, ErrDataTooLarge 174 } 175 // create record 176 rd := &record{ 177 // create and fill record header 178 header: &header{ 179 status: statusActive, 180 blocks: uint16(size / blockSize), 181 length: uint32(len(d)), 182 padding: uint32(size - overhead), 183 }, 184 data: d, 185 } 186 // return record 187 return rd, nil 188 } 189 190 // String is a stringer method for a record 191 func (rec *record) String() string { 192 s := fmt.Sprintf("record:\n") 193 s += fmt.Sprintf("\theader:\n") 194 s += fmt.Sprintf("\t\tstatus: %d\n", rec.header.status) 195 s += fmt.Sprintf("\t\tblocks: %d\n", rec.header.blocks) 196 s += fmt.Sprintf("\t\tlength: %d\n", rec.header.length) 197 s += fmt.Sprintf("\t\tpadding: %d\n", rec.header.padding) 198 s += fmt.Sprintf("\tdata: %s\n", rec.data) 199 return s 200 } 201 202 // readRecord reads and fills out a record from the provided io.Reader 203 func (rec *record) readRecord(r io.Reader) error { 204 // create record header 205 hdr := new(header) 206 // read the record header 207 _, err := hdr.readHeader(r) 208 if err != nil { 209 return err 210 } 211 // fill out the record and allocate space to read in data 212 rec.header = hdr 213 rec.data = make([]byte, hdr.length) 214 // read data into the record 215 _, err = r.Read(rec.data) 216 if err != nil { 217 return err 218 } 219 return nil 220 } 221 222 // readRecordAt reads and fills out a record from the provided io.ReaderAt 223 func (rec *record) readRecordAt(r io.ReaderAt, offset int64) error { 224 // create record header 225 hdr := new(header) 226 // read the record header 227 n, err := hdr.readHeaderAt(r, offset) 228 if err != nil { 229 return err 230 } 231 // fill out the record and allocate space to read in data 232 rec.header = hdr 233 rec.data = make([]byte, hdr.length) 234 // read data into the record 235 _, err = r.ReadAt(rec.data, offset+int64(n)) 236 if err != nil { 237 return err 238 } 239 return nil 240 } 241 242 // writeRecord writes a record to the provided io.Writer 243 func (rec *record) writeRecord(w io.Writer) error { 244 // write the record header 245 _, err := rec.header.writeHeader(w) 246 if err != nil { 247 return err 248 } 249 // write the record data 250 _, err = w.Write(rec.data) 251 if err != nil { 252 return err 253 } 254 // return nil error 255 return nil 256 } 257 258 // writeRecordAt writes a record to the provided io.WriterAt 259 func (rec *record) writeRecordAt(w io.WriterAt, offset int64) error { 260 // write the record header 261 n, err := rec.header.writeHeaderAt(w, offset) 262 if err != nil { 263 return err 264 } 265 // write the record data 266 _, err = w.WriteAt(rec.data, offset+int64(n)) 267 if err != nil { 268 return err 269 } 270 // return nil error 271 return nil 272 } 273 274 // bfile represents a block file 275 type bfile struct { 276 path string // path is the path in which the file pointer is pointing to 277 size int64 // size reports the current file size 278 open bool // open reports true if the file is open 279 index []int64 // index is the record offset index 280 fp *os.File 281 } 282 283 // openBFile opens and returns a new or existing bfile 284 func openBFile(name string) (*bfile, error) { 285 // sanitize base path 286 path, err := filepath.Abs(name) 287 if err != nil { 288 return nil, err 289 } 290 // sanitize any path separators 291 path = filepath.ToSlash(path) 292 // get dir 293 dir, _ := filepath.Split(path) 294 // create any directories if they are not there 295 err = os.MkdirAll(dir, os.ModeDir) 296 if err != nil { 297 return nil, err 298 } 299 // open file 300 f, err := os.OpenFile(path, os.O_CREATE, 0666) 301 if err != nil { 302 return nil, err 303 } 304 // get file size 305 fi, err := f.Stat() 306 if err != nil { 307 return nil, err 308 } 309 // setup new blockFile 310 bf := &bfile{ 311 path: path, 312 size: fi.Size(), 313 open: true, 314 index: make([]int64, 0), 315 fp: f, 316 } 317 // call init 318 err = bf.init() 319 if err != nil { 320 return nil, err 321 } 322 // return blockFile 323 return bf, nil 324 } 325 326 // init initializes the bfile 327 func (bf *bfile) init() error { 328 // init offset 329 var offset int64 330 // iterate and fill out 331 for { 332 // create record header 333 hdr := new(header) 334 // read the record header 335 n, err := hdr.readHeaderAt(bf.fp, offset) 336 if err != nil { 337 if err == io.EOF { 338 break 339 } 340 return err 341 } 342 // add record offset to index 343 bf.index = append(bf.index, offset) 344 // increment offset 345 offset += int64(uint32(n) + hdr.length + hdr.padding) 346 // keep on iterating till we reach the end of the file 347 } 348 // no errors to return 349 return nil 350 } 351 352 // check performs basic error checking before a call to read, readAt, write or writeAt 353 func (bf *bfile) check(off int64) error { 354 // check offset alignment 355 if off != -1 && off%blockSize != 0 { 356 return ErrBadOffsetAlignment 357 } 358 // check offset bounds 359 if off != -1 && off > bf.size { 360 return ErrOffsetOutOfBounds 361 } 362 // check to make sure file is open 363 if !bf.open { 364 return ErrFileClosed 365 } 366 return nil 367 } 368 369 // read attempts to read and return the data from the next record in the file. 370 // After a successful call to read, the file pointer is advanced ahead to the 371 // offset of the start of the next record. It returns an error if the call fails 372 // for any reason. 373 func (bf *bfile) read() ([]byte, error) { 374 // error check 375 err := bf.check(-1) 376 if err != nil { 377 return nil, err 378 } 379 // allocate new record to read into 380 rec := new(record) 381 // read record 382 err = rec.readRecord(bf.fp) 383 if err != nil { 384 return nil, err 385 } 386 // skip to the next record offset 387 _, err = bf.fp.Seek(int64(rec.padding), io.SeekCurrent) 388 if err != nil { 389 return nil, err 390 } 391 // return record data 392 return rec.data, nil 393 } 394 395 // readRaw attempts to read and return the actual record located in the file. 396 // After a successful call to read, the file pointer is advanced ahead to the 397 // offset of the start of the next record. It returns an error if the call fails 398 // for any reason. 399 func (bf *bfile) readRaw() (*record, error) { 400 // error check 401 err := bf.check(-1) 402 if err != nil { 403 return nil, err 404 } 405 // allocate new record to read into 406 rec := new(record) 407 // read record 408 err = rec.readRecord(bf.fp) 409 if err != nil { 410 return nil, err 411 } 412 // skip to the next record offset 413 _, err = bf.fp.Seek(int64(rec.padding), io.SeekCurrent) 414 if err != nil { 415 return nil, err 416 } 417 // return record data 418 return rec, nil 419 } 420 421 // readAt attempts to read and return the data from the next record in the file 422 // located at the provided offset. The file pointer is not advanced in a readAt 423 // call. It returns an error if the call fails for any reason. 424 func (bf *bfile) readAt(off int64) ([]byte, error) { 425 // error check 426 err := bf.check(off) 427 if err != nil { 428 return nil, err 429 } 430 // allocate new record to read into 431 rec := new(record) 432 // read record 433 err = rec.readRecordAt(bf.fp, off) 434 if err != nil { 435 return nil, err 436 } 437 // return record data 438 return rec.data, nil 439 } 440 441 // readAtIndex attempts to read and return the data from the next record in the file 442 // located at the provided index key. The file pointer is not advanced in a readAt 443 // call. It returns an error if the call fails for any reason. 444 func (bf *bfile) readAtIndex(i int) ([]byte, error) { 445 // get offset from index 446 off := bf.index[i] 447 // error check 448 err := bf.check(off) 449 if err != nil { 450 return nil, err 451 } 452 // allocate new record to read into 453 rec := new(record) 454 // read record 455 err = rec.readRecordAt(bf.fp, off) 456 if err != nil { 457 return nil, err 458 } 459 // return record data 460 return rec.data, nil 461 } 462 463 // write takes the provided data and creates a new record. It then attempts to 464 // write the record to disk. After a successful write the file pointer is advanced 465 // ahead to the offset of the start of the next record; it will also return the 466 // offset in the underlying file where the beginning of the record was written. 467 // It returns an error if the call fails for any reason. 468 func (bf *bfile) write(data []byte) (int64, error) { 469 // error check 470 err := bf.check(-1) 471 if err != nil { 472 return -1, err 473 } 474 // create record from provided data 475 rec, err := makeRecord(data) 476 if err != nil { 477 return -1, err 478 } 479 // get offset to return later 480 off, err := bf.fp.Seek(0, io.SeekCurrent) 481 if err != nil { 482 return -1, err 483 } 484 // write the record to disk 485 err = rec.writeRecord(bf.fp) 486 if err != nil { 487 return -1, err 488 } 489 // advance the file pointer to the next alignment offset 490 _, err = bf.fp.Seek(int64(rec.padding), io.SeekCurrent) 491 if err != nil { 492 return -1, err 493 } 494 // add write to size 495 bf.size += int64(rec.blocks) * blockSize 496 // return offset where record was written 497 return off, nil 498 } 499 500 // writeAt takes the provided data and creates a new record. It then attempts to 501 // write the record to disk using the provided offset. The function will not advance 502 // the file pointer, and it will not return the offset of where the record was 503 // written to in the underlying file (because it was provided.) It returns an error 504 // if the call fails for any reason. 505 func (bf *bfile) writeAt(data []byte, off int64) error { 506 // error check 507 err := bf.check(off) 508 if err != nil { 509 return err 510 } 511 // create record from provided data 512 rec, err := makeRecord(data) 513 if err != nil { 514 return err 515 } 516 // write the record to disk 517 err = rec.writeRecordAt(bf.fp, off) 518 if err != nil { 519 return err 520 } 521 return nil 522 } 523 524 // rewind moves the file pointer back to the beginning 525 func (bf *bfile) rewind() error { 526 if !bf.open { 527 return ErrFileClosed 528 } 529 _, err := bf.fp.Seek(0, io.SeekStart) 530 if err != nil { 531 return err 532 } 533 return nil 534 } 535 536 // seek calls the seek operation of the underlying file pointer 537 func (bf *bfile) seek(offset int64, whence int) error { 538 if !bf.open { 539 return ErrFileClosed 540 } 541 _, err := bf.fp.Seek(offset, whence) 542 if err != nil { 543 return err 544 } 545 return nil 546 } 547 548 // count returns the offset index count (which should always match the record count 549 func (bf *bfile) count() int { 550 return len(bf.index) 551 } 552 553 // sync calls Sync on the underlying *os.File 554 func (bf *bfile) sync() error { 555 if !bf.open { 556 return ErrFileClosed 557 } 558 err := bf.fp.Sync() 559 if err != nil { 560 return err 561 } 562 return nil 563 } 564 565 // close calls Sync and then Close on the underlying *os.File 566 func (bf *bfile) close() error { 567 if !bf.open { 568 return ErrFileClosed 569 } 570 err := bf.fp.Sync() 571 if err != nil { 572 return err 573 } 574 err = bf.fp.Close() 575 if err != nil { 576 return err 577 } 578 return nil 579 }