freezer_table_test.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640
  1. // Copyright 2019 The go-ethereum Authors
  2. // This file is part of the go-ethereum library.
  3. //
  4. // The go-ethereum library is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU Lesser General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // The go-ethereum library is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU Lesser General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU Lesser General Public License
  15. // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
  16. package rawdb
  17. import (
  18. "bytes"
  19. "fmt"
  20. "math/rand"
  21. "os"
  22. "path/filepath"
  23. "testing"
  24. "time"
  25. "github.com/ethereum/go-ethereum/metrics"
  26. )
  27. func init() {
  28. rand.Seed(time.Now().Unix())
  29. }
  30. // Gets a chunk of data, filled with 'b'
  31. func getChunk(size int, b int) []byte {
  32. data := make([]byte, size)
  33. for i := range data {
  34. data[i] = byte(b)
  35. }
  36. return data
  37. }
  38. // TestFreezerBasics test initializing a freezertable from scratch, writing to the table,
  39. // and reading it back.
  40. func TestFreezerBasics(t *testing.T) {
  41. t.Parallel()
  42. // set cutoff at 50 bytes
  43. f, err := newCustomTable(os.TempDir(),
  44. fmt.Sprintf("unittest-%d", rand.Uint64()),
  45. metrics.NewMeter(), metrics.NewMeter(), metrics.NewGauge(), 50, true)
  46. if err != nil {
  47. t.Fatal(err)
  48. }
  49. defer f.Close()
  50. // Write 15 bytes 255 times, results in 85 files
  51. for x := 0; x < 255; x++ {
  52. data := getChunk(15, x)
  53. f.Append(uint64(x), data)
  54. }
  55. //print(t, f, 0)
  56. //print(t, f, 1)
  57. //print(t, f, 2)
  58. //
  59. //db[0] = 000000000000000000000000000000
  60. //db[1] = 010101010101010101010101010101
  61. //db[2] = 020202020202020202020202020202
  62. for y := 0; y < 255; y++ {
  63. exp := getChunk(15, y)
  64. got, err := f.Retrieve(uint64(y))
  65. if err != nil {
  66. t.Fatal(err)
  67. }
  68. if !bytes.Equal(got, exp) {
  69. t.Fatalf("test %d, got \n%x != \n%x", y, got, exp)
  70. }
  71. }
  72. // Check that we cannot read too far
  73. _, err = f.Retrieve(uint64(255))
  74. if err != errOutOfBounds {
  75. t.Fatal(err)
  76. }
  77. }
  78. // TestFreezerBasicsClosing tests same as TestFreezerBasics, but also closes and reopens the freezer between
  79. // every operation
  80. func TestFreezerBasicsClosing(t *testing.T) {
  81. t.Parallel()
  82. // set cutoff at 50 bytes
  83. var (
  84. fname = fmt.Sprintf("basics-close-%d", rand.Uint64())
  85. rm, wm, sg = metrics.NewMeter(), metrics.NewMeter(), metrics.NewGauge()
  86. f *freezerTable
  87. err error
  88. )
  89. f, err = newCustomTable(os.TempDir(), fname, rm, wm, sg, 50, true)
  90. if err != nil {
  91. t.Fatal(err)
  92. }
  93. // Write 15 bytes 255 times, results in 85 files
  94. for x := 0; x < 255; x++ {
  95. data := getChunk(15, x)
  96. f.Append(uint64(x), data)
  97. f.Close()
  98. f, err = newCustomTable(os.TempDir(), fname, rm, wm, sg, 50, true)
  99. if err != nil {
  100. t.Fatal(err)
  101. }
  102. }
  103. defer f.Close()
  104. for y := 0; y < 255; y++ {
  105. exp := getChunk(15, y)
  106. got, err := f.Retrieve(uint64(y))
  107. if err != nil {
  108. t.Fatal(err)
  109. }
  110. if !bytes.Equal(got, exp) {
  111. t.Fatalf("test %d, got \n%x != \n%x", y, got, exp)
  112. }
  113. f.Close()
  114. f, err = newCustomTable(os.TempDir(), fname, rm, wm, sg, 50, true)
  115. if err != nil {
  116. t.Fatal(err)
  117. }
  118. }
  119. }
  120. // TestFreezerRepairDanglingHead tests that we can recover if index entries are removed
  121. func TestFreezerRepairDanglingHead(t *testing.T) {
  122. t.Parallel()
  123. rm, wm, sg := metrics.NewMeter(), metrics.NewMeter(), metrics.NewGauge()
  124. fname := fmt.Sprintf("dangling_headtest-%d", rand.Uint64())
  125. { // Fill table
  126. f, err := newCustomTable(os.TempDir(), fname, rm, wm, sg, 50, true)
  127. if err != nil {
  128. t.Fatal(err)
  129. }
  130. // Write 15 bytes 255 times
  131. for x := 0; x < 255; x++ {
  132. data := getChunk(15, x)
  133. f.Append(uint64(x), data)
  134. }
  135. // The last item should be there
  136. if _, err = f.Retrieve(0xfe); err != nil {
  137. t.Fatal(err)
  138. }
  139. f.Close()
  140. }
  141. // open the index
  142. idxFile, err := os.OpenFile(filepath.Join(os.TempDir(), fmt.Sprintf("%s.ridx", fname)), os.O_RDWR, 0644)
  143. if err != nil {
  144. t.Fatalf("Failed to open index file: %v", err)
  145. }
  146. // Remove 4 bytes
  147. stat, err := idxFile.Stat()
  148. if err != nil {
  149. t.Fatalf("Failed to stat index file: %v", err)
  150. }
  151. idxFile.Truncate(stat.Size() - 4)
  152. idxFile.Close()
  153. // Now open it again
  154. {
  155. f, err := newCustomTable(os.TempDir(), fname, rm, wm, sg, 50, true)
  156. if err != nil {
  157. t.Fatal(err)
  158. }
  159. // The last item should be missing
  160. if _, err = f.Retrieve(0xff); err == nil {
  161. t.Errorf("Expected error for missing index entry")
  162. }
  163. // The one before should still be there
  164. if _, err = f.Retrieve(0xfd); err != nil {
  165. t.Fatalf("Expected no error, got %v", err)
  166. }
  167. }
  168. }
  169. // TestFreezerRepairDanglingHeadLarge tests that we can recover if very many index entries are removed
  170. func TestFreezerRepairDanglingHeadLarge(t *testing.T) {
  171. t.Parallel()
  172. rm, wm, sg := metrics.NewMeter(), metrics.NewMeter(), metrics.NewGauge()
  173. fname := fmt.Sprintf("dangling_headtest-%d", rand.Uint64())
  174. { // Fill a table and close it
  175. f, err := newCustomTable(os.TempDir(), fname, rm, wm, sg, 50, true)
  176. if err != nil {
  177. t.Fatal(err)
  178. }
  179. // Write 15 bytes 255 times
  180. for x := 0; x < 0xff; x++ {
  181. data := getChunk(15, x)
  182. f.Append(uint64(x), data)
  183. }
  184. // The last item should be there
  185. if _, err = f.Retrieve(f.items - 1); err != nil {
  186. t.Fatal(err)
  187. }
  188. f.Close()
  189. }
  190. // open the index
  191. idxFile, err := os.OpenFile(filepath.Join(os.TempDir(), fmt.Sprintf("%s.ridx", fname)), os.O_RDWR, 0644)
  192. if err != nil {
  193. t.Fatalf("Failed to open index file: %v", err)
  194. }
  195. // Remove everything but the first item, and leave data unaligned
  196. // 0-indexEntry, 1-indexEntry, corrupt-indexEntry
  197. idxFile.Truncate(indexEntrySize + indexEntrySize + indexEntrySize/2)
  198. idxFile.Close()
  199. // Now open it again
  200. {
  201. f, err := newCustomTable(os.TempDir(), fname, rm, wm, sg, 50, true)
  202. if err != nil {
  203. t.Fatal(err)
  204. }
  205. // The first item should be there
  206. if _, err = f.Retrieve(0); err != nil {
  207. t.Fatal(err)
  208. }
  209. // The second item should be missing
  210. if _, err = f.Retrieve(1); err == nil {
  211. t.Errorf("Expected error for missing index entry")
  212. }
  213. // We should now be able to store items again, from item = 1
  214. for x := 1; x < 0xff; x++ {
  215. data := getChunk(15, ^x)
  216. f.Append(uint64(x), data)
  217. }
  218. f.Close()
  219. }
  220. // And if we open it, we should now be able to read all of them (new values)
  221. {
  222. f, _ := newCustomTable(os.TempDir(), fname, rm, wm, sg, 50, true)
  223. for y := 1; y < 255; y++ {
  224. exp := getChunk(15, ^y)
  225. got, err := f.Retrieve(uint64(y))
  226. if err != nil {
  227. t.Fatal(err)
  228. }
  229. if !bytes.Equal(got, exp) {
  230. t.Fatalf("test %d, got \n%x != \n%x", y, got, exp)
  231. }
  232. }
  233. }
  234. }
  235. // TestSnappyDetection tests that we fail to open a snappy database and vice versa
  236. func TestSnappyDetection(t *testing.T) {
  237. t.Parallel()
  238. rm, wm, sg := metrics.NewMeter(), metrics.NewMeter(), metrics.NewGauge()
  239. fname := fmt.Sprintf("snappytest-%d", rand.Uint64())
  240. // Open with snappy
  241. {
  242. f, err := newCustomTable(os.TempDir(), fname, rm, wm, sg, 50, true)
  243. if err != nil {
  244. t.Fatal(err)
  245. }
  246. // Write 15 bytes 255 times
  247. for x := 0; x < 0xff; x++ {
  248. data := getChunk(15, x)
  249. f.Append(uint64(x), data)
  250. }
  251. f.Close()
  252. }
  253. // Open without snappy
  254. {
  255. f, err := newCustomTable(os.TempDir(), fname, rm, wm, sg, 50, false)
  256. if err != nil {
  257. t.Fatal(err)
  258. }
  259. if _, err = f.Retrieve(0); err == nil {
  260. f.Close()
  261. t.Fatalf("expected empty table")
  262. }
  263. }
  264. // Open with snappy
  265. {
  266. f, err := newCustomTable(os.TempDir(), fname, rm, wm, sg, 50, true)
  267. if err != nil {
  268. t.Fatal(err)
  269. }
  270. // There should be 255 items
  271. if _, err = f.Retrieve(0xfe); err != nil {
  272. f.Close()
  273. t.Fatalf("expected no error, got %v", err)
  274. }
  275. }
  276. }
  277. func assertFileSize(f string, size int64) error {
  278. stat, err := os.Stat(f)
  279. if err != nil {
  280. return err
  281. }
  282. if stat.Size() != size {
  283. return fmt.Errorf("error, expected size %d, got %d", size, stat.Size())
  284. }
  285. return nil
  286. }
  287. // TestFreezerRepairDanglingIndex checks that if the index has more entries than there are data,
  288. // the index is repaired
  289. func TestFreezerRepairDanglingIndex(t *testing.T) {
  290. t.Parallel()
  291. rm, wm, sg := metrics.NewMeter(), metrics.NewMeter(), metrics.NewGauge()
  292. fname := fmt.Sprintf("dangling_indextest-%d", rand.Uint64())
  293. { // Fill a table and close it
  294. f, err := newCustomTable(os.TempDir(), fname, rm, wm, sg, 50, true)
  295. if err != nil {
  296. t.Fatal(err)
  297. }
  298. // Write 15 bytes 9 times : 150 bytes
  299. for x := 0; x < 9; x++ {
  300. data := getChunk(15, x)
  301. f.Append(uint64(x), data)
  302. }
  303. // The last item should be there
  304. if _, err = f.Retrieve(f.items - 1); err != nil {
  305. f.Close()
  306. t.Fatal(err)
  307. }
  308. f.Close()
  309. // File sizes should be 45, 45, 45 : items[3, 3, 3)
  310. }
  311. // Crop third file
  312. fileToCrop := filepath.Join(os.TempDir(), fmt.Sprintf("%s.0002.rdat", fname))
  313. // Truncate third file: 45 ,45, 20
  314. {
  315. if err := assertFileSize(fileToCrop, 45); err != nil {
  316. t.Fatal(err)
  317. }
  318. file, err := os.OpenFile(fileToCrop, os.O_RDWR, 0644)
  319. if err != nil {
  320. t.Fatal(err)
  321. }
  322. file.Truncate(20)
  323. file.Close()
  324. }
  325. // Open db it again
  326. // It should restore the file(s) to
  327. // 45, 45, 15
  328. // with 3+3+1 items
  329. {
  330. f, err := newCustomTable(os.TempDir(), fname, rm, wm, sg, 50, true)
  331. if err != nil {
  332. t.Fatal(err)
  333. }
  334. if f.items != 7 {
  335. f.Close()
  336. t.Fatalf("expected %d items, got %d", 7, f.items)
  337. }
  338. if err := assertFileSize(fileToCrop, 15); err != nil {
  339. t.Fatal(err)
  340. }
  341. }
  342. }
  343. func TestFreezerTruncate(t *testing.T) {
  344. t.Parallel()
  345. rm, wm, sg := metrics.NewMeter(), metrics.NewMeter(), metrics.NewGauge()
  346. fname := fmt.Sprintf("truncation-%d", rand.Uint64())
  347. { // Fill table
  348. f, err := newCustomTable(os.TempDir(), fname, rm, wm, sg, 50, true)
  349. if err != nil {
  350. t.Fatal(err)
  351. }
  352. // Write 15 bytes 30 times
  353. for x := 0; x < 30; x++ {
  354. data := getChunk(15, x)
  355. f.Append(uint64(x), data)
  356. }
  357. // The last item should be there
  358. if _, err = f.Retrieve(f.items - 1); err != nil {
  359. t.Fatal(err)
  360. }
  361. f.Close()
  362. }
  363. // Reopen, truncate
  364. {
  365. f, err := newCustomTable(os.TempDir(), fname, rm, wm, sg, 50, true)
  366. if err != nil {
  367. t.Fatal(err)
  368. }
  369. defer f.Close()
  370. f.truncate(10) // 150 bytes
  371. if f.items != 10 {
  372. t.Fatalf("expected %d items, got %d", 10, f.items)
  373. }
  374. // 45, 45, 45, 15 -- bytes should be 15
  375. if f.headBytes != 15 {
  376. t.Fatalf("expected %d bytes, got %d", 15, f.headBytes)
  377. }
  378. }
  379. }
  380. // TestFreezerRepairFirstFile tests a head file with the very first item only half-written.
  381. // That will rewind the index, and _should_ truncate the head file
  382. func TestFreezerRepairFirstFile(t *testing.T) {
  383. t.Parallel()
  384. rm, wm, sg := metrics.NewMeter(), metrics.NewMeter(), metrics.NewGauge()
  385. fname := fmt.Sprintf("truncationfirst-%d", rand.Uint64())
  386. { // Fill table
  387. f, err := newCustomTable(os.TempDir(), fname, rm, wm, sg, 50, true)
  388. if err != nil {
  389. t.Fatal(err)
  390. }
  391. // Write 80 bytes, splitting out into two files
  392. f.Append(0, getChunk(40, 0xFF))
  393. f.Append(1, getChunk(40, 0xEE))
  394. // The last item should be there
  395. if _, err = f.Retrieve(f.items - 1); err != nil {
  396. t.Fatal(err)
  397. }
  398. f.Close()
  399. }
  400. // Truncate the file in half
  401. fileToCrop := filepath.Join(os.TempDir(), fmt.Sprintf("%s.0001.rdat", fname))
  402. {
  403. if err := assertFileSize(fileToCrop, 40); err != nil {
  404. t.Fatal(err)
  405. }
  406. file, err := os.OpenFile(fileToCrop, os.O_RDWR, 0644)
  407. if err != nil {
  408. t.Fatal(err)
  409. }
  410. file.Truncate(20)
  411. file.Close()
  412. }
  413. // Reopen
  414. {
  415. f, err := newCustomTable(os.TempDir(), fname, rm, wm, sg, 50, true)
  416. if err != nil {
  417. t.Fatal(err)
  418. }
  419. if f.items != 1 {
  420. f.Close()
  421. t.Fatalf("expected %d items, got %d", 0, f.items)
  422. }
  423. // Write 40 bytes
  424. f.Append(1, getChunk(40, 0xDD))
  425. f.Close()
  426. // Should have been truncated down to zero and then 40 written
  427. if err := assertFileSize(fileToCrop, 40); err != nil {
  428. t.Fatal(err)
  429. }
  430. }
  431. }
  432. // TestFreezerReadAndTruncate tests:
  433. // - we have a table open
  434. // - do some reads, so files are open in readonly
  435. // - truncate so those files are 'removed'
  436. // - check that we did not keep the rdonly file descriptors
  437. func TestFreezerReadAndTruncate(t *testing.T) {
  438. t.Parallel()
  439. rm, wm, sg := metrics.NewMeter(), metrics.NewMeter(), metrics.NewGauge()
  440. fname := fmt.Sprintf("read_truncate-%d", rand.Uint64())
  441. { // Fill table
  442. f, err := newCustomTable(os.TempDir(), fname, rm, wm, sg, 50, true)
  443. if err != nil {
  444. t.Fatal(err)
  445. }
  446. // Write 15 bytes 30 times
  447. for x := 0; x < 30; x++ {
  448. data := getChunk(15, x)
  449. f.Append(uint64(x), data)
  450. }
  451. // The last item should be there
  452. if _, err = f.Retrieve(f.items - 1); err != nil {
  453. t.Fatal(err)
  454. }
  455. f.Close()
  456. }
  457. // Reopen and read all files
  458. {
  459. f, err := newCustomTable(os.TempDir(), fname, rm, wm, sg, 50, true)
  460. if err != nil {
  461. t.Fatal(err)
  462. }
  463. if f.items != 30 {
  464. f.Close()
  465. t.Fatalf("expected %d items, got %d", 0, f.items)
  466. }
  467. for y := byte(0); y < 30; y++ {
  468. f.Retrieve(uint64(y))
  469. }
  470. // Now, truncate back to zero
  471. f.truncate(0)
  472. // Write the data again
  473. for x := 0; x < 30; x++ {
  474. data := getChunk(15, ^x)
  475. if err := f.Append(uint64(x), data); err != nil {
  476. t.Fatalf("error %v", err)
  477. }
  478. }
  479. f.Close()
  480. }
  481. }
  482. func TestOffset(t *testing.T) {
  483. t.Parallel()
  484. rm, wm, sg := metrics.NewMeter(), metrics.NewMeter(), metrics.NewGauge()
  485. fname := fmt.Sprintf("offset-%d", rand.Uint64())
  486. { // Fill table
  487. f, err := newCustomTable(os.TempDir(), fname, rm, wm, sg, 40, true)
  488. if err != nil {
  489. t.Fatal(err)
  490. }
  491. // Write 6 x 20 bytes, splitting out into three files
  492. f.Append(0, getChunk(20, 0xFF))
  493. f.Append(1, getChunk(20, 0xEE))
  494. f.Append(2, getChunk(20, 0xdd))
  495. f.Append(3, getChunk(20, 0xcc))
  496. f.Append(4, getChunk(20, 0xbb))
  497. f.Append(5, getChunk(20, 0xaa))
  498. f.Close()
  499. }
  500. // Now crop it.
  501. {
  502. // delete files 0 and 1
  503. for i := 0; i < 2; i++ {
  504. p := filepath.Join(os.TempDir(), fmt.Sprintf("%v.%04d.rdat", fname, i))
  505. if err := os.Remove(p); err != nil {
  506. t.Fatal(err)
  507. }
  508. }
  509. // Read the index file
  510. p := filepath.Join(os.TempDir(), fmt.Sprintf("%v.ridx", fname))
  511. indexFile, err := os.OpenFile(p, os.O_RDWR, 0644)
  512. if err != nil {
  513. t.Fatal(err)
  514. }
  515. indexBuf := make([]byte, 7*indexEntrySize)
  516. indexFile.Read(indexBuf)
  517. // Update the index file, so that we store
  518. // [ file = 2, offset = 4 ] at index zero
  519. tailId := uint32(2) // First file is 2
  520. itemOffset := uint32(4) // We have removed four items
  521. zeroIndex := indexEntry{
  522. filenum: tailId,
  523. offset: itemOffset,
  524. }
  525. buf := zeroIndex.marshallBinary()
  526. // Overwrite index zero
  527. copy(indexBuf, buf)
  528. // Remove the four next indices by overwriting
  529. copy(indexBuf[indexEntrySize:], indexBuf[indexEntrySize*5:])
  530. indexFile.WriteAt(indexBuf, 0)
  531. // Need to truncate the moved index items
  532. indexFile.Truncate(indexEntrySize * (1 + 2))
  533. indexFile.Close()
  534. }
  535. // Now open again
  536. checkPresent := func(numDeleted uint64) {
  537. f, err := newCustomTable(os.TempDir(), fname, rm, wm, sg, 40, true)
  538. if err != nil {
  539. t.Fatal(err)
  540. }
  541. // It should allow writing item 6
  542. f.Append(numDeleted+2, getChunk(20, 0x99))
  543. // It should be fine to fetch 4,5,6
  544. if got, err := f.Retrieve(numDeleted); err != nil {
  545. t.Fatal(err)
  546. } else if exp := getChunk(20, 0xbb); !bytes.Equal(got, exp) {
  547. t.Fatalf("expected %x got %x", exp, got)
  548. }
  549. if got, err := f.Retrieve(numDeleted + 1); err != nil {
  550. t.Fatal(err)
  551. } else if exp := getChunk(20, 0xaa); !bytes.Equal(got, exp) {
  552. t.Fatalf("expected %x got %x", exp, got)
  553. }
  554. if got, err := f.Retrieve(numDeleted + 2); err != nil {
  555. t.Fatal(err)
  556. } else if exp := getChunk(20, 0x99); !bytes.Equal(got, exp) {
  557. t.Fatalf("expected %x got %x", exp, got)
  558. }
  559. // It should error at 0, 1,2,3
  560. for i := numDeleted - 1; i > numDeleted-10; i-- {
  561. if _, err := f.Retrieve(i); err == nil {
  562. t.Fatal("expected err")
  563. }
  564. }
  565. }
  566. checkPresent(4)
  567. // Now, let's pretend we have deleted 1M items
  568. {
  569. // Read the index file
  570. p := filepath.Join(os.TempDir(), fmt.Sprintf("%v.ridx", fname))
  571. indexFile, err := os.OpenFile(p, os.O_RDWR, 0644)
  572. if err != nil {
  573. t.Fatal(err)
  574. }
  575. indexBuf := make([]byte, 3*indexEntrySize)
  576. indexFile.Read(indexBuf)
  577. // Update the index file, so that we store
  578. // [ file = 2, offset = 1M ] at index zero
  579. tailId := uint32(2) // First file is 2
  580. itemOffset := uint32(1000000) // We have removed 1M items
  581. zeroIndex := indexEntry{
  582. offset: itemOffset,
  583. filenum: tailId,
  584. }
  585. buf := zeroIndex.marshallBinary()
  586. // Overwrite index zero
  587. copy(indexBuf, buf)
  588. indexFile.WriteAt(indexBuf, 0)
  589. indexFile.Close()
  590. }
  591. checkPresent(1000000)
  592. }
  593. // TODO (?)
  594. // - test that if we remove several head-files, aswell as data last data-file,
  595. // the index is truncated accordingly
  596. // Right now, the freezer would fail on these conditions:
  597. // 1. have data files d0, d1, d2, d3
  598. // 2. remove d2,d3
  599. //
  600. // However, all 'normal' failure modes arising due to failing to sync() or save a file should be
  601. // handled already, and the case described above can only (?) happen if an external process/user
  602. // deletes files from the filesystem.