Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
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
@breznak

This comment has been minimized.

Copy link

@breznak breznak commented Sep 16, 2016

👍 saved my day (and ass/data) today! :) Do you plan to turn it into a small repo or package it, so it doesnt get lost?

@breznak

This comment has been minimized.

Copy link

@breznak breznak commented Sep 16, 2016

Q1: does it recover only delete, or all files in given directory?
Q2: is there a possibility to speed the script up (quit early safely)? It recovered most of my files on (16/173) and now I'm just sitting here..is it safe to kill the process early?
Thanks!

@Jipok

This comment has been minimized.

Copy link

@Jipok Jipok commented Nov 13, 2017

Thank you! It saved me! For some reason, the file began to occupy 0 bytes. This script is the only thing that helped to restore.

@MetaPhaze

This comment has been minimized.

Copy link

@MetaPhaze MetaPhaze commented Jan 10, 2018

doesn't seem to work.

@Omnicraft

This comment has been minimized.

Copy link

@Omnicraft Omnicraft commented Mar 6, 2018

This script totally saved my movie collection folder!

@fjf2002

This comment has been minimized.

Copy link

@fjf2002 fjf2002 commented Apr 2, 2018

You are sorting byte-addresses descending instead of generation ids descending, aren't you?

@MerloRodrigo

This comment has been minimized.

Copy link

@MerloRodrigo MerloRodrigo commented Sep 30, 2018

Wow! perfect. It's was a beatful work. Thanks a lot

@Payden-Pringle

This comment has been minimized.

Copy link

@Payden-Pringle Payden-Pringle commented Dec 28, 2018

This script is successfully creating files that appear to be the correct size, but they cannot be opened or read correctly with the relevant programs. Not sure what could be wrong here, as the script seems to be working?

@splurben

This comment has been minimized.

Copy link

@splurben splurben commented Apr 26, 2019

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

This comment has been minimized.

Copy link

@minhng99 minhng99 commented Oct 13, 2019

Perfect!

@jamalroger

This comment has been minimized.

Copy link

@jamalroger jamalroger commented Mar 1, 2020

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

This comment has been minimized.

Copy link

@cpbotha 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

This comment has been minimized.

Copy link

@StarterX4 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

This comment has been minimized.

Copy link

@StarterX4 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

This comment has been minimized.

Copy link

@janos666 janos666 commented Oct 12, 2020

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

@kiptanoi

This comment has been minimized.

Copy link

@kiptanoi kiptanoi commented Dec 14, 2020

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

This comment has been minimized.

Copy link

@endolith 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

This comment has been minimized.

Copy link

@endolith 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

This comment has been minimized.

Copy link

@mgutt 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

This comment has been minimized.

Copy link

@illwieckz 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

This comment has been minimized.

Copy link

@mgutt 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

This comment has been minimized.

Copy link

@illwieckz 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?

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