Skip to content

Instantly share code, notes, and snippets.

@tidwall

tidwall/main.go Secret

Created April 1, 2021 17:30
Show Gist options
  • Save tidwall/e823bcb75558c2a590cc17227d630ee1 to your computer and use it in GitHub Desktop.
Save tidwall/e823bcb75558c2a590cc17227d630ee1 to your computer and use it in GitHub Desktop.
Partial write could corrupt BuntDB database
package main
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/tidwall/buntdb"
)
const VolumeName = "/Volumes/SMALL" // Tiny Volume
const VolumeSize = 0x40000000 // 1 GB
const ValueSize = 0xA00000 // 10 MB
func main() {
// Create a very small volume.
// I created a 1GB volume at /Volumes/SMALL
// Delete the previous "data.db", if exists
if err := os.RemoveAll(filepath.Join(VolumeName, "data.db")); err != nil {
panic(err)
}
// create an blank file that is half the size of the volume
println("* write blank file")
if err := ioutil.WriteFile(filepath.Join(VolumeName, "blank"),
make([]byte, VolumeSize/2), 0600); err != nil {
panic(err)
}
// Open the database
println("* open database")
db, err := buntdb.Open(filepath.Join(VolumeName, "data.db"))
if err != nil {
panic(err)
}
// disable background auto shrinking
var cfg buntdb.Config
if err := db.ReadConfig(&cfg); err != nil {
panic(err)
}
cfg.AutoShrinkDisabled = true
if err := db.SetConfig(cfg); err != nil {
panic(err)
}
// create a very big value. One that could not possiblly be written in a
// single syscall, but much smaller than the 'blank' file size
value := strings.Repeat("*", ValueSize) // 10MB
failCount := 0
// start writing transactions to disk
println("* write transactions")
for i := 0; ; i++ {
err := db.Update(func(tx *buntdb.Tx) error {
key := fmt.Sprintf("key:%d", i)
_, _, err := tx.Set(key, value, nil)
return err
})
if err != nil {
// A failure is probably because the file system ran out of
// disk space. It's possible that this was a partial write leaving
// the database in a potentially corrupted state.
failCount++
if failCount == 2 {
println("* fail twice")
println("* finished writing")
break
}
println("* fail once")
// Let's delete the 'blank' file and continue writing.
println("* delete blank file")
err := os.RemoveAll(filepath.Join(VolumeName, "blank"))
if err != nil {
panic(err)
}
println("* write more transactions")
}
}
println("* close database")
if err := db.Close(); err != nil {
panic(err)
}
println("* reopen database")
db, err = buntdb.Open(filepath.Join(VolumeName, "data.db"))
if err != nil {
panic(err)
}
// disable background auto shrinking
if err := db.ReadConfig(&cfg); err != nil {
panic(err)
}
cfg.AutoShrinkDisabled = true
if err := db.SetConfig(cfg); err != nil {
panic(err)
}
if err := db.Close(); err != nil {
panic(err)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment