btrfscue icon indicating copy to clipboard operation
btrfscue copied to clipboard

6 GB metadata.db but no files? :-(

Open hopeseekr opened this issue 2 years ago • 13 comments

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...

hopeseekr avatar Nov 09 '21 23:11 hopeseekr

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.

cblichmann avatar Nov 10 '21 11:11 cblichmann

Linux 5.4 something... the current LTS in Arch...

hopeseekr avatar Nov 18 '21 05:11 hopeseekr

Happened to me too, but I don't remember which kernel version created that BTRFS partition... :frowning_face:

giacomoferretti avatar Apr 24 '23 17:04 giacomoferretti

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

giacomoferretti avatar Apr 26 '23 09:04 giacomoferretti

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.

dfcamara avatar May 11 '23 16:05 dfcamara

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?

dfcamara avatar May 11 '23 16:05 dfcamara

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.

giacomoferretti avatar May 27 '23 21:05 giacomoferretti

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

dfcamara avatar May 28 '23 12:05 dfcamara

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.

Dikakus avatar Jul 14 '23 06:07 Dikakus

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.

so...

yunginnanet avatar Sep 23 '23 07:09 yunginnanet

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)
	}
}

giacomoferretti avatar Sep 23 '23 08:09 giacomoferretti

Check https://github.com/giacomoferretti/btrfscue-metadata-extract for a full source.

giacomoferretti avatar Sep 23 '23 08:09 giacomoferretti

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

thomas725 avatar Dec 12 '23 12:12 thomas725