INTRO

BTRFS RESTORE: runs thru the specified filesystem and tries to restore files and folders. Useful if the filesystem is not mountable. Here we will explore using different regular expression to dump out specific part (folders or files) as sometimes we dont want to extract the full FS.

Here are the BTRFS RESTORE options that are going to be used: -v verbose (shows files & folders), -F to continously hit yes on looping thru large files (so that big files are extracted), -D for dry run / dry dumping, so you can just see what happens without any write operations happening, -i to ignore encountered errors (important as we might be dumping out a corrupt filesystem), -o to overwrite files that are there. -o and -F are useful because it removes user interaction with the “btrfs restore” and you can just leave it running until it finishes, which is alot more script friendly (script friends = less user interaction or better yet no user interaction whilst script runneth)

First lets look at a regular “btrfs restore” operation that dumps out all of the files inside the /dev/md127 filesystem to a /USB. First we run a dry run to see what is dumped out. Also in this example “btrfs restore” is more complex as we had to look at a none current tree location (notice -t 10669166821376) because the most recent one probably didnt return any files or folders in the dump (the most recent one is done with -t #)

# dry dumping of everything at tree location 10669166821376 (got the tree location from running "btrfs-find-root /dev/md127" - see link below). dry run meaning no files or folders are dumped, it just shows you what will be dumped.
ionice -c3 -n7 nice -n19 btrfs restore -t 10669166821376 -v -F -D -i -o /dev/md127 /dev/null
# restoring dumping to USB
ionice -c3 -n7 nice -n19 btrfs restore -t 10669166821376 -v -F -i -o /dev/md127 /USB

Imagine we wanted a specific folder now. Imagine /dev/md127 is usually mounted to /data. And we wanted the /data/Backup folder. Backup can be a folder or a subvolume. Eitherway the commands would be identical, and the procedure would look like this (TIP: I would still start without a path-regex dry dump as seen above and then try a dry dump with a path regex):

# dry restoring with path regex (only dumping the "Backup" subvolume/folder)
ionice -c3 -n7 nice -n19 btrfs restore -t 10669166821376 -v -F -D -i -o --path-regex '^/(|Backup(|/.*))$' /dev/md127 /dev/null
# restoring with path regular expressions (only dumping the "Backup" subvolume/folder)
ionice -c3 -n7 nice -n19 btrfs restore -t 10669166821376 -v -F -i -o --path-regex '^/(|Backup(|/.*))$' /dev/md127 /USB

Now if the FS is not too corrupt and the latest tree location works. Then try it without -t #. Ideally I should of put this at the top, as you should always try to get out the FS at the most current tree location – and then if that doesnt work try to get it at other tree locations (see this article: btrfs restore from other tree locations).

# restoring from latest tree location (leave out the -t #)
# dry restoring with path regex (only dumping the "Backup" subvolume/folder)
ionice -c3 -n7 nice -n19 btrfs restore -v -F -D -i -o --path-regex '^/(|Backup(|/.*))$' /dev/md127 /dev/null
# restoring with path regular expressions (only dumping the "Backup" subvolume/folder)
ionice -c3 -n7 nice -n19 btrfs restore -v -F -i -o --path-regex '^/(|Backup(|/.*))$' /dev/md127 /USB

OTHER EXAMPLES WITH DIFFERENT REGULAR EXPRESSION TO GET DIFFERENT FOLDERS

NOTE: there is a couple of ways to restore a folder or a file. Also make sure to escape special characters:

## 1 folder: ## which folder? /Backup321/
--path-regex '^/(|Backup321(|/.*))$'
--path-regex '^/(|Backup321.*)$'

## 2 folder: ## which folder? /Backup321/DRIVE/
--path-regex '^/(|Backup321(|/DRIVE(|/.*)))$'
--path-regex '^/(|Backup321(|/DRIVE.*))$'

## 3 folder: ## which folder? /7zArchive/OurPC321/AUTOCAD/
--path-regex '^/(|7zArchive(|/OurPC321(|/AUTOCAD.*)))$'
--path-regex '^/(|7zArchive(|/OurPC321(|/AUTOCAD(|/.*))))$'

## 4 folders (with spaces): ## which folder? /7zArchive/OurPC321/AUTOCAD/ZZF Team/
--path-regex '^/(|7zArchive(|/OurPC321(|/AUTOCAD(|/ZZF Team.*))))$'
--path-regex '^/(|7zArchive(|/OurPC321(|/AUTOCAD(|/ZZF Team(|/.*)))))$'
NOTE: its optional to escape the spaces

## 5 folder (with spaces): ## which folder? /7zArchive/OurPC321/AUTOCAD/ZZF Team/Washington/
--path-regex '^/(|7zArchive(|/OurPC321(|/AUTOCAD(|/ZZF Team(|/Washington.*)))))$'
--path-regex '^/(|7zArchive(|/OurPC321(|/AUTOCAD(|/ZZF Team(|/Washington(|.*))))))$'
--path-regex '^/(|7zArchive(|/OurPC321(|/AUTOCAD(|/ZZF\ Team(|/Washington.*)))))$'
--path-regex '^/(|7zArchive(|/OurPC321(|/AUTOCAD(|/ZZF\ Team(|/Washington(|.*))))))$'
NOTE: its optional to escape the spaces

## file: ## which file? /7zArchive/OurPC321/OutlookFolder/Personnel/Fios/Smithonian, Gary.docx
--path-regex '^/(|7zArchive(|/OurPC321(|/OutlookFolder(|/Personnel(|/Fios(|/Smithonian, Gary.docx))))))$'

## another file with special char, parenthesis and curly braces: ## which file? /Backup/Looper1 (BAI1)/D_VOL-b002-i089.spi_{12345A02-B328-470E-ADF1-500000156E83}.tmp
--path-regex '^/(|Backup(|/Looper1 \(BAI1\)(|/D_VOL-b002-i089.spi_\{12345A02-B328-470E-ADF1-500000156E83\}.tmp)))$'
NOTE: () and {} need to be escaped

## folder with special char, parenthesis: ## which folder? /Backup/Looper1 (BAI1)/
--path-regex '^/(|Backup(|/Looper1 \(BAI1\).*))$'
--path-regex '^/(|Backup(|/Looper1 \(BAI1\)(|.*)))$'

## folder with dot: ## which folder? /ESXi/.snapshots/25
--path-regex '^/(|ESXi(|/.snapshots(|/25.*)))$'
--path-regex '^/(|ESXi(|/.snapshots(|/25(|.*))))$'

 

NOTE: no need to escape dots, but need to escape (), also no need to escape spaces (its optional to escape dots and spaces). These would of also worked:

--path-regex '^/(|7zArchive(|/OurPC321(|/OutlookFolder(|/Personnel(|/Fios(|/Smithonian,\ Gary.docx))))))$'
--path-regex '^/(|ESXi(|/\.snapshots(|/25(|\.*))))$'

HOW TO BUILD PATH REGEX STEPS:

* get the folder or filepath path from the root of the filesystem (remove the mount point, and remove the left hand / and if its a folder remove the right hand /)… so /data/Backup/Files/ , would turn into Backup/Files , since /data is the mount point and we also dont want the first & last slash in /Backup/Files/ as the rest of the steps take care of those)
* At the beginning, left end, put ^/(|
* Before each / slash put (|
* Count up how many occurances of (| you have and call that number parcount
* At the right most end put ) parcount times
* At the right most end put $
* If its a folder that your extracting (so you want everything inside it recursively), locate the left most unescaped ), and to the left of it put either: (|.*)  or .*
— so ))))))  becomes (|.*)))))))  <- prefered
— or ))))))  becomes .*))))))
— man page and examples online use (|.*) , to be more accurate I would (|.*)  because its more specific, and .*  is more general and might find more results than you need
* Or if its a folder that your extracting (you want everything inside it recursively), located the left most unescaped ), and right before it put
* which characters to escape with a backslash \? http://stackoverflow.com/questions/399078/what-special-characters-must-be-escaped-in-regular-expressions
* Surround whole thing with single quotes ‘whole path regex’

NOTE: the above would be easy to script out ‘./build-btrfs-path-regex.sh “/data/Backup/Files”‘

TIP:
* you dont need to escape spaces, but if you do it will not hurt the results
* first run a dry dump to see if your capturing what you want, as the dry dump operation is really quick

SO WHICH CHARACTERS TO ESCAPE?

From link above: For PCRE, and most other so-called Perl-compatible flavors, escape these outside character classes:
.^$*+?()[{\| 
and these inside character classes:
^-]\ 
Obivously in our cases we didnt need to escape . so thats probably the only exception from the list above.
Also if you accidentally escape }, thats fine, notice { is in the list but } is not
The point is, try doing a dry run first to see if you get what you want and then do a normal run. Or test every possible condition and let me know a better list to put here under the “SO WHICH CHARACTERS TO ESCAPE?”

WHY IS THE REGULAR EXPRESSION SO COMPLICATED?

So that BTRFS RESTORE doesnt only restore the files, but also the folders. So that you dont have to “mkdir” the full paths for everything before hand.
For example, using either of these:

--path-regex '^/(|7zArchive(|/OurPC321(|/OutlookFolder(|/Personnel(|/Fios(|/Smithonian, Gary.docx))))))$'
--path-regex '^/(|7zArchive(|/OurPC321(|/OutlookFolder(|/Personnel(|/Fios(|/Smithonian,\ Gary.docx))))))$'

You get these restoring messages:

Restoring /dev/null/7zArchive
Restoring /dev/null/7zArchive/OurPC321
Restoring /dev/null/7zArchive/OurPC321/OutlookFolder
Restoring /dev/null/7zArchive/OurPC321/OutlookFolder/Personnel
Restoring /dev/null/7zArchive/OurPC321/OutlookFolder/Personnel/Fios
Restoring /dev/null/7zArchive/OurPC321/OutlookFolder/Personnel/Fios/Smithonian, Gary.docx

Ignore The /dev/null (its showing up because im using a dry run and dumping to /dev/null – yes its a double nothing, a dry run that we are dumping to the nothingness which /dev/null)
Notice how it first restores the folders

As an example, lets look at this:

--path-regex '^/(|Backup321(|/DRIVE(|/.*)))$'
--path-regex '^/(|Backup321(|/DRIVE.*))$'

Which dumps

Restoring /dev/null/Backup321
Restoring /dev/null/Backup321/DRIVE
Restoring /dev/null/Backup321/DRIVE/0000003.CTF
Restoring /dev/null/Backup321/DRIVE/0000004.CTF
Restoring /dev/null/Backup321/DRIVE/0000011.CTF
Restoring /dev/null/Backup321/DRIVE/0000012.CTF

Now look at what happens if I remove the .*  or (|/.*) , they both become:

--path-regex '^/(|Backup321(|/DRIVE))$'

And now it only restores:

Restoring /dev/null/Backup321
Restoring /dev/null/Backup321/DRIVE

So the intericate regular expression of ‘^/(|Backup321(|/DRIVE.*))$’  or ‘^/(|Backup321(|/DRIVE(|/.*)))$’
Evaluate true for /Backup321  so that Backup321 folder is created and for /Backup321/DRIVE  so that DRIVE folder is created in Backup321  and for /Backup321/DRIVE/*  so that all of the files and folders inside it are dumped.

4 thoughts on “Btrfs Restore Path-regex (regular expressions) how to/examples

  1. Thanks for this well documented and helpful article.

    From what I can tell this uses the ERE extended regular expression form of regex?

    I’ve been having a lot of difficulty figuring out the correct syntax to make an or statement. Is it possible to for example make a statement that would allow you to restore 2 or 3 paths at one time?

    For example so you could restore both /Backup321/ as well as /Folder2/ ?
    Or perhaps restore all 3 folders, /Backup321/ and /Folder2/subfolder1 and /Folder2/subfolder2 ?

  2. What an AMAZING article! Had I found this 30 min ago, I’d have still my data.

    Thank you so much! =)

    1. Glad to help. Wish I still remembered this stuff, its been ages. Haven’t dabbled with btrfs in a while. I just recall even if the fs gets really mucked up, usually its possible as a last resort to mount it up read only to backup your data.

Leave a Reply

Your email address will not be published. Required fields are marked *