Skip to content

Instantly share code, notes, and snippets.

@Changaco
Last active March 27, 2024 21:50
Star You must be signed in to star a gist
Save Changaco/45f8d171027ea2655d74 to your computer and use it in GitHub Desktop.
btrfs-undelete
#!/bin/bash
# btrfs-undelete
# Copyright (C) 2013 Jörg Walter <info@syntax-k.de>
# This program is free software; you can redistribute it and/or modify it under
# the term of the GNU General Public License as published by the Free Software
# Foundation; either version 2 of the License, or any later version.
if [ ! -b "$1" -o -z "$2" -o -z "$3" ]; then
echo "Usage: $0 <dev> <file/dir> <dest>" 1>&2
echo
echo "This program tries to recover the most recent version of the"
echo "given file or directory (recursively)"
echo
echo "<dev> must not be mounted, otherwise this program may appear"
echo "to work but find nothing."
echo
echo "<file/dir> must be specified relative to the filesystem root,"
echo "obviously. It may contain * and ? as wildcards, but in that"
echo "case, empty files might be 'recovered'. If <file/dir> is a"
echo "single file name, this program tries to recover the most"
echo "recent non-empty version of the file."
echo
echo "<dest> must be a writable directory with enough free space"
echo "to hold the files you're trying to restore."
exit 1
fi
dev="$1"
file="$2"
file="${file#/}"
file="${file%/}"
regex="${file//\\/\\\\}"
# quote regex special characters
regex="${regex//./\.}"
regex="${regex//+/\+}"
regex="${regex//|/\|}"
regex="${regex//(/\(}"
regex="${regex//)/\)}"
regex="${regex//\[/\[}"
regex="${regex//]/\]}"
regex="${regex//\{/\{}"
regex="${regex//\}/\}}"
# treat shell wildcards specially
regex="${regex//\*/.*}"
regex="${regex//\?/.}"
# extract number of slashes in order to get correct number of closing parens
slashes="${regex//[^\/]/}"
# build final regex
regex="^/(|${regex//\//(|/}(|/.*${slashes//?/)}))\$"
roots="$(mktemp --tmpdir btrfs-undelete.roots.XXXXX)"
out="$(mktemp --tmpdir="$3" -d btrfs-undelete.XXXXX)"
cd $out
trap "rm $roots" EXIT
trap "rm -r $out &> /dev/null; exit 1" SIGINT
echo -ne "Searching roots..."
btrfs-find-root -a "$dev" 2>&1 \
| grep ^Well \
| sed -r -e 's/Well block ([0-9]+).*/\1/' \
| sort -rn >$roots || exit 1
echo
i=0
max="$(wc -l <$roots)"
while read id; do
((i+=1))
echo -e "Trying root $id... ($i/$max)"
btrfs restore -t $id --path-regex "$regex" "$dev" . &>/dev/null
if [ "$?" = 0 ]; then
found=$(find . -type f ! -size 0c | wc -l)
if [ $found -gt 0 ]; then
echo "Recovered $found non-empty file(s) into $out"
exit 0
fi
find . -type f -size 0c -exec echo "Found {} but it's empty" \; -delete
fi
done <$roots
rm -r $out
echo "Didn't find '$file'"
exit 1
@splurben
Copy link

This worked perfectly, better than I ever could have expected, for me. The files I was recovering had only been deleted about 2 minutes before I used the script and I was able to unmount the drive 10 seconds after I accidentally deleted (rm -r) the files.

@minhng99
Copy link

Perfect!

@jamalroger
Copy link

I try to restore my file but i got this errors

recover.sh: 32: Bad substitution

command
sudo sh recover.sh /dev/sda /home/jamal/project/work_payments/* /home/jamal/restore/

@cpbotha
Copy link

cpbotha commented Mar 18, 2020

Thank you very much for this script! Did something silly with dvc remove on a directory containing some files that were not committed yet, and your script was able to recover the files.

@StarterX4
Copy link

StarterX4 commented Apr 21, 2020

[root@doadgrz starterx4]# /home/starterx4/Pulpit/btrfs-undelete.sh /dev/sdb3 * /mnt/xir/~Btrfs/un/
mktemp: failed to create directory via template ‘akt2.png/btrfs-undelete.XXXXX’: Not a directory
Searching roots...
Trying root 53695479808... (1/33)
Recovered 24085 non-empty file(s) into
[root@doadgrz starterx4]#

And then the destination directory is empty. What's happening?

@StarterX4
Copy link

StarterX4 commented Apr 21, 2020

Ah ok, there must be "/" instead of "".
Altough, i tried to restore files manually, but my filesystem must be really messed up.

[root@doadgrz tmp]# btrfs restore -t 7303168 /dev/sdb3 /mnt/xir/~Btrfs/un/
parent transid verify failed on 7303168 wanted 99464 found 91953
parent transid verify failed on 7303168 wanted 99464 found 91953
Ignoring transid failure
WARNING: could not setup extent tree, skipping it
Couldn't setup device tree
Could not open root, trying backup super
parent transid verify failed on 7303168 wanted 99464 found 91953
parent transid verify failed on 7303168 wanted 99464 found 91953
Ignoring transid failure
WARNING: could not setup extent tree, skipping it
Couldn't setup device tree
Could not open root, trying backup super
parent transid verify failed on 7303168 wanted 99464 found 91953
parent transid verify failed on 7303168 wanted 99464 found 91953
Ignoring transid failure
WARNING: could not setup extent tree, skipping it
Couldn't setup device tree
Could not open root, trying backup super
[root@doadgrz tmp]#

@janos666
Copy link

find-root now needs the -a option to list more than one roots

@kiptanoi
Copy link

Can anyone tell me how to use this?
Do I need to download something? Where to put the downloaded stuff?
And if I want to have back deleted files from this folder: "Dokument"
That have this info:
Location: "/media/jonas/0e362876:data"
Filesystem: "btrfs"
Mounted on: "/media/jonas/0e362876:data"
Mounted from: "/dev/md127"

And I want to save data collected by this code here:
Name: "nas_tmp"
Location: "/media/jonas/M.2 - Recover"
Mounted on: "/media/jonas/M.2 - Recover"
Mounted from: "/dev/nvmeOn1p6"

How can I the use this "btrfs-undelete" with my info?

@endolith
Copy link

endolith commented Jan 19, 2021

Can you write some examples with different <file/dir> formats? wildcards, etc? No matter what format I enter, it says "Didn't find ..."

@endolith
Copy link

endolith commented Jan 19, 2021

OH IT NEEDS TO BE RUN AS ROOT using sudo. Then it says

...
Trying root 14769717477376... (192/213)
Trying root 14769717379072... (193/213)
Trying root 14769716805632... (194/213)
Trying root 14769716510720... (195/213)
...

But still doesn't find anything, even with a very broad search like sudo ./btrfs-undelete /dev/sdc "/Foldername/*" ~/restore which is a folder that definitely still exists on that volume...

Edit: Nevermind it somehow re-mounted itself, so it wasn't working even though it looked like it was. The above command does work! Yayyyy

@mgutt
Copy link

mgutt commented Jan 23, 2021

Does this script really work unattended? Because I executed the restore command manually and I needed to confirm a "looping a lot" question:

btrfs restore -i /dev/nvme1n1p1 /mnt/disk2/nvme1n1p1_restore
No valid Btrfs found on /dev/nvme1n1p1
Could not open root, trying backup super
We seem to be looping a lot on /mnt/disk2/nvme1n1p1_restore/domains/Win10/vdisk1.img, do you want to keep going on ? (y/N/a)

I was not able to find a flag which allowed unattended execution?!

@illwieckz
Copy link

illwieckz commented Jan 24, 2021

@mgutt, the restore command you did is not exactly the same as the one used in the script, anyway if you want to avoid answering y indefinitely you can do that:

yes | btrfs restore -i /dev/nvme1n1p1 /mnt/disk2/nvme1n1p1_restore

@mgutt
Copy link

mgutt commented Jan 24, 2021

I know. Its only a hint. Maybe the same can happen for the btrfs-undelete script, so it become stuck?!

Regarding your piping idea. I think "a" for "all" would be better. But does it return "a" + "enter", which is needed? (I can't test it anymore)

PS
I hit "N" because the file was not important for me. Anyhow, it was restored ^^

@illwieckz
Copy link

illwieckz commented Jan 24, 2021

To always answer a you can do:

yes a | btrfs restore -i /dev/nvme1n1p1 /mnt/disk2/nvme1n1p1_restore

Anyhow, it was restored ^^

Maybe it's incomplete?

@Fr-Dae
Copy link

Fr-Dae commented Mar 9, 2021

(Lubuntu 18.04x64)

hello, I would like to have some pressision on your script

To execute it, I have to copy and paste it into a text editor

saved it under the .sh extension and make it executable right?

and

I have rescement I deleted by mistake a very important folder on my secondary hard drive (/ dev / sda3) an HDD
The file contained only zip that I want to receive
/dev/sda3 = /mnt/38b05da3-7068-45b9-bc0a-0b944f15487f

A friend recommended me the following monate options to make the disk read alone thus avoiding the old data, for limiting the loss.
nosuid,nodev,nofail,x-gvfs-show,noatime,nodiratime,ro
that right for you ?

I plugged in an external hard drive, because my systeme ssd are prety small
/dev/sdc1 = /media/dae-rog/data-backup

My question is, are your tools like?
Where is it asking for data once executed?
Or do I manually be edited?

Dae#5125 on discord

@Fogapod
Copy link

Fogapod commented Apr 11, 2021

Thank you

@alvarlagerlof
Copy link

Thank you so much! This saved me 12 days.

@idreamerhx
Copy link

amazing works! saved my monthes!

@kulak
Copy link

kulak commented Jul 17, 2023

When working with subvolumes make sure to include subvolume data. For example, to extract tree of files from @data subvolume and user /home/accident use command:

 btrfs-undelete /dev/mapper/container '@data/home/accident' /tmp/extraction-location

This will include all fines under /home/accident.

@sirus20x6
Copy link

" must not be mounted, otherwise this program may appear
to work but find nothing."

can I unmount dev on a running system?

@sirus20x6
Copy link

sirus20x6 commented Oct 26, 2023

oh I get it. please change the wording of dev to source device

@sirus20x6
Copy link

can this also find the next most recent deleted file? I think I got most of what I wanted back except for a few lines that had been touched more recently

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment