btrfscue
btrfscue copied to clipboard
6 GB metadata.db but no files? :-(
root@rescue /mnt/omg # ls -lh
total 6.2G
-r--r--r-- 1 root root 6.2G Nov 9 14:09 metadata
drwxr-xr-x 1 root root 224 Sep 13 16:49 rescue
root@rescue /mnt/omg # ls rescue/
root@rescue /mnt/omg #
Am I screwed? I'm trying to recover file names. Photorec has recovered 150 GB of data but it has no file names...
I have not touched this repo in quite a while. Do you happen to know the kernel version that created the filesystem or that was last used to access it? That'd help me to check whether there were and on-disk changes that the tool doesn't understand.
Linux 5.4 something... the current LTS in Arch...
Happened to me too, but I don't remember which kernel version created that BTRFS partition... :frowning_face:
Ok, it doesn't work even when I create a new BTRFS partition. BTW, this repo is still helpful because I can retrieve some files using the inline data in the metadata.db using an external program.
truncate -s 1G tmp.img
mkfs.btrfs -f tmp.img
mkdir tmp_mount
mount tmp.img tmp_mount
mkdir tmp_mount/folder
touch tmp_mount/folder/file
umount tmp_mount
btrfscue identify tmp.img
btrfscue recon --id UUID_FSID --metadata metadata.db tmp.img
btrfscue --metadata metadata.db mount tmp.img tmp_mount
I'm having the same isssue. The filesystem was last acessed using Fedora Linux 37, kernel 6.2.14-200.fc37.x86_64. Now running btrfscue on Fedora Linux 38, kernel 6.2.14-300.fc38.x86_64.
Ok, it doesn't work even when I create a new BTRFS partition. BTW, this repo is still helpful because I can retrieve some files using the inline data in the metadata.db using an external program.
truncate -s 1G tmp.img mkfs.btrfs -f tmp.img mkdir tmp_mount mount tmp.img tmp_mount mkdir tmp_mount/folder touch tmp_mount/folder/file umount tmp_mount btrfscue identify tmp.img btrfscue recon --id UUID_FSID --metadata metadata.db tmp.img btrfscue --metadata metadata.db mount tmp.img tmp_mount
What external program did you use to access metadata.db contents? What type of file is metadata.db?
metadata.db
is a bbolt
database.
I didn't recover much from it, only small files.
At least I could export the list of files (lost files), and with photorec
recovered the majority of it.
The external program I was referring to is a script that I made. Let me recover it and I will share it here.
Thanks. During installation of Fedora Linux 38 I inadvertently deleted the format of my second disk (NVMe). This disk contains one partition formatted with Btrfs using all the disk space without a partition table. I was able to recover my disk (all file data and metadata) using btrfs-select-super utility to overwrite the primary superblock with a backup copy.
sudo btrfs-select-super -s 2 /dev/nvme1n1
Hi. Same issue here:
Linux raspberrypi 6.1.21-v8+ #1642 SMP PREEMPT Mon Apr 3 17:24:16 BST 2023 aarch64 GNU/Linux
Using mount option with a 4.9GB metadata file I only can see a metadata file and a void "rescue" directory.
metadata.db
is abbolt
database.I didn't recover much from it, only small files. At least I could export the list of files (lost files), and with
photorec
recovered the majority of it.The external program I was referring to is a script that I made. Let me recover it and I will share it here.
so...
I don't remember which one I used, but it's probably this:
package main
import (
"encoding/gob"
"encoding/hex"
"errors"
"fmt"
"io"
"log"
"os"
"path"
"strings"
bolt "go.etcd.io/bbolt"
"github.com/giacomoferretti/bbolt-dump/internal/btrfs"
"github.com/giacomoferretti/bbolt-dump/internal/btrfscue"
)
type Inode struct {
Generation uint64
Inode uint64
ParentInode uint64
Name string
IsFile bool
FullPath string
Size uint64
}
var inodeData []Inode
func createDir(inode Inode) {
f := path.Join(os.Args[1], inode.FullPath)
if !inode.IsFile {
os.MkdirAll(f, os.ModePerm)
}
}
func createFile(inode Inode, data []byte) {
f := path.Join(os.Args[1], inode.FullPath)
log.Printf("[GEN:%v] Writing %v bytes to %v...", inode.Generation, inode.Size, f)
if inode.IsFile {
if err := os.WriteFile(f, data, 0644); err != nil {
log.Fatal(err)
}
}
}
func readFileFromDisk(offset, length uint64) []byte {
fmt.Fprintf(os.Stderr, "Reading offset %v and length %v\n", offset, length)
f, err := os.Open(os.Args[4])
if err != nil {
log.Fatal(err)
}
defer f.Close()
_, err = f.Seek(int64(offset), io.SeekStart)
if err != nil {
log.Fatal(err)
}
ret := make([]byte, length)
_, err = f.Read(ret)
if err != nil {
log.Fatal(err)
}
return ret
}
func findInode(inode uint64) (Inode, error) {
for _, v := range inodeData {
if v.Inode == inode {
return v, nil
}
}
return Inode{}, errors.New("inode not found")
}
func dbProcessEntry(key, value []byte) error {
btrfsKeyV2 := btrfscue.BtrfscueParseDbKey(key)
btrfsKey, data := btrfscue.BtrfscueParseDbValue(value)
// Print data
dataHex := hex.EncodeToString(data)
dataSanitized := string(data)
dataSanitized = strings.Replace(dataSanitized, "\n", "\\x0a", -1)
dataSanitized = strings.Replace(dataSanitized, "\b", "\\x0b", -1)
dataPrint := ""
if btrfsKey.Size > 0 {
dataPrint = fmt.Sprintf(" %s, %s", dataSanitized, dataHex)
}
if btrfsKeyV2.Type == 108 {
fmt.Printf("[G:%v O:%v ID:%v OF1:%v T:%v OF2:%v S:%v]%s\n", btrfsKeyV2.Generation, btrfsKeyV2.Owner, btrfsKeyV2.ObjectID, btrfsKeyV2.Offset, btrfsKeyV2.Type, btrfsKey.Offset, btrfsKey.Size, dataPrint)
// BTRFS_EXTENT_DATA_KEY
inode := btrfsKeyV2.ObjectID
offset := btrfsKeyV2.Offset
file_extent_item := btrfs.ParseFileExtentItem(data)
targetInode, err := findInode(inode)
if err != nil {
log.Fatal(err)
}
// Inline data
if file_extent_item.Type == 0 {
// Check length
if file_extent_item.RamBytes != uint64(len(data[21:])) {
log.Fatal("WRONG LENGTH ON EXTENT_DATA")
}
if btrfsKeyV2.Generation == targetInode.Generation {
createFile(targetInode, data[21:])
}
} else if file_extent_item.Type == 1 {
file_extent_item_disk := btrfs.ParseFileExtentItemDisk(data[21:])
fmt.Printf(" └─ FILE_EXTENT_ITEM = %v\n", file_extent_item_disk)
if btrfsKeyV2.Generation == targetInode.Generation {
createFile(targetInode, readFileFromDisk(file_extent_item_disk.DiskBytenr, targetInode.Size))
}
}
fmt.Printf(" └─ BTRFS_EXTENT_DATA_KEY - INODE:%v OFFSET:%v = %v\n", inode, offset, file_extent_item)
}
return nil
}
func main() {
// Check arguments
if len(os.Args) != 5 {
fmt.Fprintf(os.Stderr, "Usage: %v <output_folder> <output.bin> <metadata.db> <disk.img>\n", os.Args[0])
os.Exit(1)
}
// GOB DECODE
dataFile, err := os.Open(os.Args[2])
if err != nil {
log.Fatal(err)
}
dataDecoder := gob.NewDecoder(dataFile)
err = dataDecoder.Decode(&inodeData)
if err != nil {
log.Fatal(err)
}
dataFile.Close()
// Create folder
for _, v := range inodeData {
if !v.IsFile {
createDir(v)
}
}
// Open bbolt database
db, err := bolt.Open(os.Args[3], 0600, &bolt.Options{ReadOnly: true})
if err != nil {
log.Fatalln(err)
}
defer db.Close()
// Process all entries on "index"
err = db.View(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte("index"))
b.ForEach(dbProcessEntry)
return nil
})
if err != nil {
log.Fatalln(err)
}
}
Check https://github.com/giacomoferretti/btrfscue-metadata-extract for a full source.
same issue here, had hoped to gain access to my data using this project but btrfscue ls
doesn't produce any output, and mount only shows me
- my ~20gb metadata.db file (without the .db extension inside the mount directory)
- and an empty "rescue" folder.
UPDATE: I found a much simpler and nicer way to recover my data from my broken btrfs disk:
mount -t btrfs -o ro,rescue=all /dev/disk /mnt/
by reading through the mount options here: https://btrfs.readthedocs.io/en/latest/Administration.html#mount-options