Crazy Man mi-a trimis traducerea acestui articol pe care m-a rugat să-l postez aici. Traducerea aparține lui crazy man, Adrian Vesa și Seba Popity.
Carlo Wood, Mar 2008

Introducere

I se întâmplă fiecăruia mai devreme sau mai târziu: o fracţiune de secundă după ce ai apăsat Enter realizezi greşeala, dar este deja prea târziu. Tocmai ai şters un fişier sau director valoros pentru care nu este rezervă (backup). Sau poate există o rezervă, dar e veche de o lună… şi, şocat, vezi ultima lună fugind prin faţa ochilor şi realizezi dureros ce trebuie să refaci …

Din fericire, iţi aduci aminte că niciodată fişierele nu sunt cu adevărat şterse, cel mult suprascrise de un nou conţinut. Astfel, remontezi discul doar în citire. Şi acum?
Dacă căutaţi pe Google “undelete ext3″, aproape în fiecare articol găsit vor fi utilizatori care întreabă dacă e posibil iar răspunsul este acelaşi: NU
Cele mai citate pagini vin chiar de pe pagina de întrebări ext3 FAQ

Q: Cum îmi pot recupera fişierele şterse de pe o partiţie ext3?
De fapt, nu se poate! Astfel a răspuns unul din dezvoltatori, Andreas Dilger:

Pentru a avea certitudinea că ext3 poate relua în siguranţă o deconectare după un crash, goleşte de fapt block pointerii din inode, în timp ce ext2 doar marchează aceste blocuri ca nefolosite in bitmap-ul blocului, marchează inode-ul ca “şters” şi nu modifică block pointerii.

Singura ta speranţă este “grep” pentru a recupera parţial fişierele care au fost şterse şi să speri la cât mai mult.
Totuşi, nu este chiar tot adevărat. Toate informaţiile pot fi încă acolo, la fel şi block pointerii. Este doar mai puţin probabil ca sunt încă acolo (de pe ext2), deoarece acestea trebuie să fie recuperate de la jurnal. Pe deasupra, meta datele sunt mai puţin coerent legate de datele reale astfel încât algoritmii heuristici sunt necesari pentru a recupera lucrurile. De fiecare dată când un fişier este accesat, se schimba Acces Time , şi inode-ul lui este scris pe disc, împreună cu alţi 31 de inodes care rezidă în acelaşi bloc. Când se întâmplă asta, o copie a acelui bloc este scrisă în jurnal. De aici inainte, dacă partiţia nu este destul de mare în comparaţie cu jurnalul, şi dacă cel puţin ai accesat recent fişierele pe care vrei să le recuperezi, ar putea fi posibil să recuperezi block pointerii din jurnal .

Pe 7 februarie 2008, mi-am şers din greseală tot directorul home: peste 3 GB de date şterse cu rm -rf. Singura rezervă avută era din iunie 2007. Neputinţa de a reface datele era inacceptabilă. Astfel, am ignorat tot ce mi s-a spus şi am început să învăţ cum funcţionează un sistem ext3 şi exact ce anume se întâmplă când sunt şterse fişierele…
3 săptămâni şi aproape 5000 de linii de cod mai târziu recuperasem toate fişierele de pe disc.

Ce trebuie să ştiţi înainte de a începe

Programul pe care l-am scris presupune o selecţie a fişierelor recent şterse (imediat după ultimul unmount). Nu tratează fişierele de pe un sistem de fişiere corupt ci doar fişierele şterse accidental.

Totuşi, programul este în faza beta: dezvoltarea programului a fost făcută ca un hack, doar cu scopul de a îmi recupera datele. După ce mi-am recuperat datele am continuat dezvoltarea programului pentru încă o lună la adresarea bug-urilor care nu au apărut în cazul meu, dar per total programul nu este atât de robust pe cât ar putea fi. Astfel, e posibil ca lucrurile să nu meargă „ca pe roate” şi pentru voi. Am dotat programul cu asserts, care creează oportunitatea ca în cazul în care ceva nu merge cum trebuie programul să se oprească şi să nu mai încerce recuperarea. În acest caz va trebui să săpaţi mai adânc, ca să terminaţi voi programul, ca sa spun aşa.

Programul are nevoie doar de acces în citire pe sistemul de fişiere pe care au fost fişierele şterse: el nu încearcă să recupereze fişierele. În schimb, vă permite să faceţi o copie a fişierelor şterse şi le scrie într-un director proaspăt creat în directorul curent (care , desigur, ar trebui să fie pe un sistem de fişiere diferit). Toate căile sunt relative cu root-ul partiţiei, thus— daca analizaţi o partiţie /dev/md5 care a fost montată /home, atunci /home este necunoscut de partiţie şi de program deci nu e parte din cale (path). În schimb, o cale va fi ceva de genul “carlo/c++/foo.cc” , fără primul slash. Root-ul partiţiei (/home din exemplu) este şirul gol , nu ‘/’.

Numele programului, ext3grep l-am pus astfel deoarece planuiam să scriu un program mai inteligent care să fie în stare să reconstruiască fişierele căutând după blocuri care arată identic cu blocurile căutate (pe baza unui backup mai vechi sau a altor reguli). Particula grep din nume a fost pusă anticipând că citatul din partea dezvoltatorilor ex3 era adevărat: mă pregăteam pentru a lucra cu seturi de blocuri, fiecare set corespunzând unui tipar de căutare şi încărcate cu probabilităţi, asupra cărora cineva ar trebui să lucreze cu seturi de operatori pentru a reduce numărul de blocuri şi să le atribuie fişierelor aranjându-le în ordine. Oricum nimic de genul acesta nu a fost folosit. Totuşi, am păstrat numele ext3grep deoarece cineva poate doreşte să adauge o adevărată funcţionalitate grep programului (în acest moment funcţiile grep sunt limitate la şiruri fixe, listând numerele de bloc asemănătoare pe o ieşire standard).

Cum stochează EXT3 fişierele?

Mărimea blocurilor

Conţinutul fişierelor este stocat în blocuri continue de 4096 de bytes (mărimea actuală depinde de parametrii liniei de comandă dată pentru mke2fs când a fost creat sistemul de fişiere prima dată şi poate fi 1024, 2048 sau 4096 de bytes). Un hard disc este un “block device”, adică fiecare intrare / ieşire este în fucţie de aceste blocuri; o persoană poate doar citi / scrie un număr integral de blocuri odată. Această nu înseamnă neapărat că mărimea minimă a unui fragment continuu de fişier este aceeaşi (deşi poate fi doar mai mică), dar în practică este. De fapt, programul nu va funcţiona dacă mărimea fragmentelor nu este aceeaşi cu mărimea blocurilor.

Mărimea actuală a blocului precum şi mărimea actuală a fragmentelor sunt stocate într-un superbloc şi pot fi găsite cu opţiunea --superblock. De exemplu ,

$ ext3grep $IMAGE --superblock | grep 'size:'
Block size: 4096
Fragment size: 4096

Aici IMAGE este o variabilă de sistem care a fost setată ca nume pentru dispozitivul (sau o copie a acestuia făcută cu dd) partiţiei care conţine sistemul de fişiere. De exemplu, /dev/sdd2 ( în general, oricare nume de dispozitiv returnat de comanda df sub numele de “Filesystem”). În mod normal doar root poate citi direct dispozitivele, dar puteţi (temporar) să le faceţi citibile de către voi, sau să faceţi o imagine backup cu dd. Ţineţi minte că, de exemplu, /dev/sdd NU este o partiţie (observaţi digitul lipsă ) şi nu va conţine date folositoare pentru scopul nostru.

Întreaga partiţie este impărţită într-un număr de blocuri integral, începând numărătoarea de la 0. Astfel, dacă doriţi vreodată să faceţi o copie a blocului cu numărul N trebuie să faceţi astfel:

$ dd if=$IMAGE bs=4096 count=1 skip=$N of=block.$N

unde N ia valoare de la 0 până la (dar nu incluzând şi) numărul de blocuri stocate în superbloc. De exemplu,

$ ext3grep $IMAGE --superblock | grep 'Blocks count:'
Blocks count: 2441824

Având oricare număr de bloc, se pot tipări informaţii despre el folosind opţiunea --block în linia de comandă. De exemplu,
$ ext3grep $IMAGE --ls --block 600
[...]
Group: 0
Block 600 is Allocated. It's inside the inode table of group 0
(inodes [1 - 33>).

$ ext3grep $IMAGE --ls --block 1109
[...]
Group: 0

Block 1109 is a directory. The block is Allocated

.-- File type in dir_entry (r=regular file, d=directory, l=symlink)
| .-- D: Deleted ; R: Reallocated
Indx Next | Inode | Deletion time Mode File name
==========+==========+----------------data-from-inode------+-----------+=========
0 1 d 2 drwxr-xr-x .
1 end d 2 drwxr-xr-x ..
2 3 d 11 D 1202351093 Thu Feb 7 03:24:53 2008 drwxr-xr-x lost+found
3 end d 195457 D 1202352103 Thu Feb 7 03:41:43 2008 drwxr-xr-x carlo

Superblocul

Superblocul nu este chiar un bloc. Mărimea lui este întotdeauna de 1024 de bytes şi primul superbloc începe la multiplu de 1024. Deci, dacă mărimea blocului este 1024 atunci superblocul este blocul 1, dar dacă mărimea blocului este 2048 sau 4096 atunci superblocul este o parte din blocul 0. Există multiple copii de rezervă altundeva pe disc. ext3grep presupune că primul superbloc nu este corupt şi nu încearcă să găsească sau să citească copiile de rezervă.

Se poate citi conţinutul primului superbloc folosind dd astfel:

$ dd if=$IMAGE bs=1024 skip=1 count=1 of=superblock

Semnificaţia fiecărui byte este dată în tabelul 1.

Tabel 1. Superblocuri
Bytes type Descriere
0 .. 3 __le32 Inodes total
4 .. 7 __le32 Blocuri total
8 .. 11 __le32 Blocuri rezervate total
12 .. 15 __le32 Blocuri libere total
16 .. 19 __le32 Inodes liberi total
20 .. 23 __le32 Primul bloc de date
24 .. 27 __le32 Mărime bloc
28 .. 31 __le32 Mărime fragment
32 .. 35 __le32 Număr de blocuri per grup
36 .. 39 __le32 Număr de fragmente per grup
40 .. 43 __le32 Număr de inodes per grup
44 .. 47 __le32 Ora montării
48 .. 51 __le32 Ora scrierii
52 .. 53 __le16 Montări total
54 .. 55 __le16 Maximal mount count
56 .. 57 __le16 Semnătura magică
58 .. 59 __le16 Stare sistem de fişiere
60 .. 61 __le16 Comportament la descoperire erori
62 .. 63 __le16 Nivel minim revizie
64 .. 67 __le32 Data ultimei verificări
68 .. 71 __le32 Timp maxim între verificări
72 .. 75 __le32 OS
76 .. 79 __le32 Nivel revizie
80 .. 81 __le16 UID iniţial pentru blockuri rezervate
82 .. 83 __le16 GID iniţial pentru blockuri libere
84 .. 87 __le32 Primul inode nerezervat
88 .. 89 __le16 Mărimea structurii inode
90 .. 91 __le16 Numărul de grupuri de blocuri pentru acest superbloc
92 .. 95 __le32 Set de opţiuni compatibile
96 .. 99 __le32 Set de opţiuni incompatibile
100 .. 103 __le32 Set de opţiuni compatibile doar în citire
104 .. 119 __u8[16] 128-bit uuid pentru volum
120 .. 135 char[16] Numele volumului
136 .. 199 char[64] Directorul în care a fost montat ultima oară
200 .. 203 __le32 Pentru compresie
204 __u8 Număr de blocuri pentru care se va încerca realocarea
205 __u8 Număr de prealocare pentru directoare
206 .. 207 __le16 Descriptor per grup pentru creştere online
208 .. 223 __u8[16] uuid pentru superblocul jurnal
224 .. 227 __le32 Numărul inode pentru fişierul jurnal
228 .. 231 __le32 Numărul dispozitivului pentru fişierul jurnal
232 .. 235 __le32 Începutul listei de inodes care se vor şterge
236 .. 251 __le32[4] HTREE hash seed
252 __u8 Versiunea iniţială de hash folosită
253 .. 255 Rezervat
256 .. 259 __le32 Opţiuni iniţiale de montare
260 .. 263 __le32 Primul grup metabloc
264 .. 1023 Rezervat

Structura C ( C-struct ) pentru superbloc este dată în fişierul header /usr/include/linux/ext3_fs.h şi a fost folosită pentru a crea tabelul 1. Datele întregilor nealocaţi sunt stocate pe disc în format Little Endian. Într-un format Little Endian procesoarele gen Intel x86, care înseamnă că __le32 este de fapt uint32_t şi __le16 este egal cu uint16_t.

Grupurile

Fiecare sistem de fişiere ext3 este împărţit în grupuri, cu un număr fix de blocuri per grup, cu excepţia ultimului grup care conţine blocurile rămase. Numărul de blocuri per grup este dat în superbloc.

$ ext3grep $IMAGE --superblock | grep 'Blocks per group'
# Blocks per group: 32768

Fiecare grup foloseşte un bloc ca hartă pentru a ţine evidenţa cărui bloc din grup îi este alocat (folosit). Astfel pot fi cel mult 4096 * 8 = 32768 blocuri normale per grup.

Alt bloc este folosit ca hartă pentru numărul de inodes alocat. Inodes sunt structuri de date de 128 de bytes (ele pot fi extinse teoretic; mărimea reală este dată tot în superbloc) care sunt stocate într-un tabel (4096 / 128 = 32 inodes per bloc) în fiecare grup. Având cel mult 32768 bits în hartă, putem trage concluzia că vor fi cel mult 32768 inodes per grup şi astfel 32768 / 32 = 1024 blocuri în tabela inode pentru fiecare grup. Dimensiunea reală a tabelei inode este dată de numărul real de inodes per grup, care este şi el stocat în supergrup.

$ ext3grep $IMAGE --superblock | egrep 'Size of inode|inodes per group'
Number of inodes per group: 16288
Size of inode structure: 128

Numerele de bloc atât pentru hartă cât şi pentru începutul tabelei inode este dat în “tabela descriptoare a grupului “, care se găseşte în blocul următor superblocului; deci bloc1 sau bloc2 în funcţie de mărimea blocului. Această tabelă de decriptare constă din o serie de structuri consecutive ext3_group_desc definite şi ele în /usr/include/linux/ext3_fs.h, conform tabelului 2.

Tabel 2. O descriptoare de grup
Bytes type Descriere
0 .. 3 __le32 Blocul hartă pentru blocuri – Blocks bitmap block
4 .. 7 __le32 Blocul hartă pentru inodes – Inodes bitmap block
8 .. 11 __le32 Blocul tabelei inodes – Inodes table block
12 .. 13 __le16 Total blocuri libere – Free blocks count
14 .. 15 __le16 Total inodes liberi-Free inodes count
16 .. 17 __le16 Directoare total – Directories count
18 .. 31 Rezervat

Datorită faptului că dimensiunea acestei structuri este o putere de 2, 32 de bytes, se potriveşte exact unui număr întreg de descriptoare într-un bloc. Astfel tabela este continuă chiar dacă este întinsă pe mai multe blocuri. Ţineţi minte că un bloc de 4096 bytes este deja capabil să ţină 128 de descriptoare de grup, fiecare putând stoca 32768 de blocuri / mdash; aşadar doar o partiţie mai mare de 16GB va folosi mai mult de un bloc pentru tabela de descriptoare de grup.

Conţinutul tabelei este tipărit folosind ext3grep dacă nu sunt specificate alte acţiuni sau un grup în linia de comandă. De exemplu,
$ ext3grep $IMAGE
No action specified; implying --superblock.
[...]
Number of groups: 75
Group 0: block bitmap at 598, inodes bitmap at 599, inode table at 600
4 free blocks, 16278 free inodes, 1 used directory
Group 1: block bitmap at 33366, inodes bitmap at 33367, inode table at
33368
30510 free blocks, 16288 free inodes, 0 used directory
[...]
Group 74: block bitmap at 2424832, inodes bitmap at 2424833, inode table
at 2424834
16481 free blocks, 16288 free inodes, 0 used directory
[...]

Inodes

Inodes din tabelul de inodes al fiecărui grup conţin metadate pentru fiecare tip de date pe care îl poate stoca sistemul de fişiere. Acest tip poate fi un link simbolic, caz în care doar inodeul este suficient, poate fi un director, un fişier, un FIFO, un socket UNIX etc. În cazul fişierelor şi directoarelor datele reale sunt stocate în blocurile sistemului de fişiere din exteriorul inodeului. Primele 12 numere de blocuri sunt stocate în inode, în cazul în care este nevoie de mai multe blocuri inodeul indică un bloc indirect: un bloc cu mai multe numere de blocuri care conţin date. Astfel inodeul poate stoca un bloc indirect dublu sau triplu. Structura unui inode este dată în tabelul 3.

Tabel 3. Un inode
Bytes type Descriere
0 .. 1 __le16 Mod de fişier
2 .. 3 __le16 Low 16 bits of Owner uid
4 .. 7 __le32 Mărime in bytes
8 .. 11 __le32 Access time
12 .. 15 __le32 Ora creării
16 .. 19 __le32 Ora modificării
20 .. 23 __le32 Ora ştergerii
24 .. 25 __le16 Low 16 bits of Group Id
26 .. 27 __le16 Total Linkuri
28 .. 31 __le32 Blocuri total
32 .. 35 __le32 File flags
36 .. 39 linux1 OS dependent 1
40 .. 99 __le32[15] Pointers to blocks
100 .. 103 __le32 Versiune fişier (for NFS)
104 .. 107 __le32 File ACL
108 .. 111 __le32 Directory ACL
112 .. 115 __le32 Fragment address
116 .. 127 linux2 OS dependent 2

Structura C a unui inode struct ext3_inode, este dată în fişierul header /usr/include/linux/ext3_fs.h şi a fost utilizată pentru a crea tabelul3. Acelaşi fişier header mai defineşte un număr de constante sub forma unui macros care poate fi folosit pentru accesarea datelor. De exemplu, componenta de structură stocată de la byte 40 până la 99 este i_block, mărimea ei este EXT3_N_BLOCKS 32-bit numere de blocuri. i_block[EXT3_IND_BLOCK] indică către (conţine numărul de bloc pentru ) un bloc indirect dacă acesta există, i_block[EXT3_DIND_BLOCK] către un bloc indirect dublu şi i_block[EXT3_TIND_BLOCK] către un bloc indirect triplu. În mod normal, fiecare constantă are un macro al ei, se poate analiza fişierul header pentru mai multe detalii. ext3grep foloseşte i_reserved2 pentru a stoca numărul inodeului, astfel că tipărirea structurii unui ext3_inode în gdb arată care inode este el de fapt.

Superblocul indică câte inodes sunt în total şi câte inodes sunt per grup. Aceasta ne permite să calculăm numărul de grupuri. Deoarece inodesurile sunt stocate în tabela lor respectivă pentru grup, prima dată trebuie determinat grupul căruia îi aparţine numărul de inodes. Deoarece inodes încep a fi numărate de la 1, formula de conversie a unui număr de inodes în numărul de grup căruia îi aparţine este:

grup = (număr_inode  - 1) / număr_de_inodes_per_grup

Aceasta ne dă tabela inodes corectă. Pentru aflarea index-ului inodes din acestă tabelă extragem numărul primului inodes din tabelă din numărul inodesului nostru:

index = număr_inode  - (grup * număr_de_inodes_per_grup + 1)

Observaţi că acest index determină şi bitul corespunzător din harta inodes.

Atfel, grupurile au fost făcute transparente: fiecare inodes poate fi adresat cu un număr din intervalul continuu [1, număr_de_inodes], unde număr_de_inodes este dat de :

$ ext3grep $IMAGE --superblock | grep 'Inodes count'
Inodes count: 1221600

În unele cazuri e posibil să doriţi să ştiţi care bloc din sistemul de fişiere aparţine tabelei de inodes care stochează un anumit inodes. Aceasta se poate face folosind în linia de comandă opţiunea --inode-to-block, de exemplu:

$ ext3grep $IMAGE --inode-to-block 2
[...]
Inode 2 resides in block 600 at offset 0x80.

Inodesul cu numărul 2 (macro EXT3_ROOT_INO în ext3_fs.h) este întotdeauna folosit pentru root-ul partiţiei: tipul lui este un director. Din toate celelalte inodes speciale folosim doar EXT3_JOURNAL_INO (numărul 8).

Având numărul inodesului se poate afişa conţinutul lui folosind ext3grep, astfel:

$ ext3grep $IMAGE --inode 2 --print
Number of groups: 75
Loading group metadata... done
[...]

Hex dump of inode 2:
0000 | ed 41 00 00 00 10 00 00 97 6f aa 47 08 6c aa 47 | .A.......o.G.l.G
0010 | 08 6c aa 47 00 00 00 00 00 00 02 00 08 00 00 00 | .l.G............
0020 | 00 00 00 00 02 00 00 00 55 04 00 00 00 00 00 00 | ........U.......
0030 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................
0040 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................
0050 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................
0060 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................
0070 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................

Inode is Allocated
Group: 0
Generation Id: 0
uid / gid: 0 / 0
mode: drwxr-xr-x
size: 4096
num of links: 2
sectors: 8 (--> 0 indirect blocks).

Inode Times:
Accessed:       1202352023 = Thu Feb  7 03:40:23 2008
File Modified:  1202351112 = Thu Feb  7 03:25:12 2008
Inode Modified: 1202351112 = Thu Feb  7 03:25:12 2008
Deletion time:  0

Direct Blocks: 1109
[...]
Inode 2 is directory "".
Directory block 1109:
          .-- File type in dir_entry (r=regular file, d=directory, l=symlink)
          |          .-- D: Deleted ; R: Reallocated
Indx Next |  Inode   | Deletion time                        Mode        File name
==========+==========+----------------data-from-inode------+-----------+=========
   0    1 d       2                                         drwxr-xr-x  .
   1  end d       2                                         drwxr-xr-x  ..
   2    3 d      11  D 1202351093 Thu Feb  7 03:24:53 2008  drwxr-xr-x  lost+found
   3  end d  195457  D 1202352103 Thu Feb  7 03:41:43 2008  drwxr-xr-x  carlo

După cum observaţi, ext3grep prima dată se descarcă conţinutul hexazecimal al tabelei inodes; apoi se interpretează şi se afişează membrii de structură terminându-se cu linia Direct Blocks: 1109. Apoi detectează că acest bloc este un director (care mai poate fi observat în câmpul MODE al inodesului) şi apoi continuă cu listarea acestui bloc ca director.

Fişierele normale

Dacă un inodes reprezintă un fişier normal, atunci blocurile la care se referă conţin doar datele fişierului. Dacă mărimea unui fişier nu este un număr întreg de ori mărimea blocului, atunci bytesii în exces din ultimul bloc vor fi scrişi cu 0 (cel puţin în Linux).

Legături simbolice

Valoarea unui link simbolic este un şir: cărarea (path) până la ţintă. Lungimea şirului este dată în i_size. Dacă i_blocks este zero, atunci i_block NU conţine numere de blocuri ci este folosit pentru stocarea efectivă a şirului. Oricum, dacă numele ţintei este mai lung decât suportă i_block, atunci i_blocks va fi non-zero şi i_block[0] va indica un bloc care conţine numele ţintei.

Directoarele

Dacă un inodes reprezintă un director atunci blocurile lui sunt legături singulare către structurile de date ale ext3_dir_entry_2. Fiecare bloc se conţine pe el: nu sunt entry point pentru directoare în exteriorul lui. Primul bloc începe întotdeauna cu intrarea directoarelor „.” şi „..”

Tabel 4. A Directory Entry
Bytes type Descriere
0 .. 3 __le32 Număr Inodes
4 .. 5 __le16 Lungime intrare director
6 __u8 Lungime nume
7 __u8 Tip fişier
8 char[] Fişier, link simbolic sau nume director

Folosind opţiunile --ls --inode $N, ext3grep afişează conţinutul fiecărui bloc de director pentru inodesul N. De exemplu, pentru a lista directorul rădăcină (root) al unei partiţii:

$ ext3grep $IMAGE --ls --inode 2
Number of groups: 75
Loading group metadata... done
Minimum / maximum journal block: 1115 / 35026
Loading journal descriptors... done
Journal transaction 4381435 wraps around, some data blocks might have been
 lost of this transaction.
Number of descriptors in journal: 30258; min / max sequence numbers:
 4379495 / 4382264
Inode is Allocated
Loading md5.ext3grep.stage2... done
The first block of the directory is 1109.
Inode 2 is directory "".
Directory block 1109:
          .-- File type in dir_entry (r=regular file, d=directory, l=symlink)
          |          .-- D: Deleted ; R: Reallocated
Indx Next |  Inode   | Deletion time                        Mode        File name
==========+==========+----------------data-from-inode------+-----------+=========
   0    1 d       2                                         drwxr-xr-x  .
   1  end d       2                                         drwxr-xr-x  ..
   2    3 d      11  D 1202351093 Thu Feb  7 03:24:53 2008  drwxr-xr-x  lost+found
   3  end d  195457  D 1202352103 Thu Feb  7 03:41:43 2008  drwxr-xr-x  carlo

Aşadar, se va folosi ext3grep --ls --inode 195457 pentru a lista directorul carlo, şi aşa mai departe.

Observaţi că ext3grep afişează toate intrările din director, şterse sau nu. Există 2 posibilităţi pentru a vedea dacă un director este şters: prima, inodesul lui va avea un Deletion Time diferit de 0, a doua – intrările directorului pot fi scoase din lista de legătură fiind omise; “Directory Entry Length”, bytes 4 şi 5 ai fiecărei intrări din director, în mod normal, indică intrarea următoare sau byte-ul care urmează blocul dacă nu sunt alte intrări de directoare. În afişarea făcută de ext2grep adresa intrărilor de directoare a fost înlocuită cu un index artifical (în prima coloană) iar “Directory Entry Length” este înlocuită cu coloana numită Next, care ori indică următoarea intrare ori conţine end când nu mai sunt alte intrări de directoare. În exemplul de mai sus 0 este prima intrare, 1 este următoarea şi ultima. Intrările cu indexul 2 şi 3 sunt omise. Totuşi încă se poate observa că intrarea 2 indica intrarea 3. De fapt, intrările 2 şi 3 sunt şterse în acelaşi timp schimbând “Directory Entry Length” a intrării 1 astfel încât să nu mai indice intrarea 2 ci sfârşitul blocului .

Deoarece ext3grep afişează şi intrările şterse este foarte posibil ca aceleaşi intrări să fie făcute de mai multe ori. În cazuri particulare, dacă un fişier este mutat, o dublură rămâne şi încă poate fi văzută. De exemplu,

$ ext3grep $IMAGE --ls --inode 195457 | grep '\.viminfo$'
   7    8 r  201434  D 1202351096 Thu Feb  7 03:24:56 2008  rrw-r--r--  .viminfo
  18   19 r  195995  D 1202351097 Thu Feb  7 03:24:57 2008  rrw-------  .viminfo
  18   19 r  195994  D 1202351111 Thu Feb  7 03:25:11 2008  rrw-r--r--  .viminfo
  18   19 r  195994  D 1202351111 Thu Feb  7 03:25:11 2008  rrw-r--r--  .viminfo
  18   19 r  195995  D 1202351097 Thu Feb  7 03:24:57 2008  rrw-------  .viminfo
  18   19 r  195994  D 1202351111 Thu Feb  7 03:25:11 2008  rrw-r--r--  .viminfo
  18   19 r  195994  D 1202351111 Thu Feb  7 03:25:11 2008  rrw-r--r--  .viminfo
  18   19 r  195995  D 1202351097 Thu Feb  7 03:24:57 2008  rrw-------  .viminfo
  18   19 r  195994  D 1202351111 Thu Feb  7 03:25:11 2008  rrw-r--r--  .viminfo
  18   19 r  195994  D 1202351111 Thu Feb  7 03:25:11 2008  rrw-r--r--  .viminfo
  18   19 r  195995  D 1202351097 Thu Feb  7 03:24:57 2008  rrw-------  .viminfo
  18   19 r  195995  D 1202351097 Thu Feb  7 03:24:57 2008  rrw-------  .viminfo
  18   19 r  195994  D 1202351111 Thu Feb  7 03:25:11 2008  rrw-r--r--  .viminfo
  18   19 r  195995  D 1202351097 Thu Feb  7 03:24:57 2008  rrw-------  .viminfo
  18   19 r  195994  D 1202351111 Thu Feb  7 03:25:11 2008  rrw-r--r--  .viminfo
  18   19 r  195995  D 1202351097 Thu Feb  7 03:24:57 2008  rrw-------  .viminfo
  18   19 r  195994  D 1202351111 Thu Feb  7 03:25:11 2008  rrw-r--r--  .viminfo
  18   19 r  195995  D 1202351097 Thu Feb  7 03:24:57 2008  rrw-------  .viminfo
  18   19 r  195994  D 1202351111 Thu Feb  7 03:25:11 2008  rrw-r--r--  .viminfo
  18   19 r  195994  D 1202351111 Thu Feb  7 03:25:11 2008  rrw-r--r--  .viminfo
  18   19 r  195994  D 1202351111 Thu Feb  7 03:25:11 2008  rrw-r--r--  .viminfo
  18   19 r  195995  D 1202351097 Thu Feb  7 03:24:57 2008  rrw-------  .viminfo
  18   19 r  195995  D 1202351097 Thu Feb  7 03:24:57 2008  rrw-------  .viminfo
  18   19 r  195995  D 1202351097 Thu Feb  7 03:24:57 2008  rrw-------  .viminfo
  18   19 r  195995  D 1202351097 Thu Feb  7 03:24:57 2008  rrw-------  .viminfo
  18   19 r  195995  D 1202351097 Thu Feb  7 03:24:57 2008  rrw-------  .viminfo
  18   19 r  197221  D 1202351110 Thu Feb  7 03:25:10 2008  rrw-r--r--  .viminfo
  18   19 r  197221  D 1202351110 Thu Feb  7 03:25:10 2008  rrw-r--r--  .viminfo
  18   19 r  197221  D 1202351110 Thu Feb  7 03:25:10 2008  rrw-r--r--  .viminfo
  18   19 r  197221  D 1202351110 Thu Feb  7 03:25:10 2008  rrw-r--r--  .viminfo
  17   19 r  197221  D 1202351110 Thu Feb  7 03:25:10 2008  rrw-r--r--  .viminfo
  17   19 r  197221  D 1202351110 Thu Feb  7 03:25:10 2008  rrw-r--r--  .viminfo
  17   19 r  197221  D 1202351110 Thu Feb  7 03:25:10 2008  rrw-r--r--  .viminfo
  18   19 r  195993  D 1202351097 Thu Feb  7 03:24:57 2008  rrw-------  .viminfo
  18   19 r  195981  D 1202351097 Thu Feb  7 03:24:57 2008  rrw-r--r--  .viminfo
  18   19 r  195993  D 1202351097 Thu Feb  7 03:24:57 2008  rrw-------  .viminfo
  18   19 r  195981  D 1202351097 Thu Feb  7 03:24:57 2008  rrw-r--r--  .viminfo
  18   19 r  195993  D 1202351097 Thu Feb  7 03:24:57 2008  rrw-------  .viminfo
  18   19 r  195987  D 1202351097 Thu Feb  7 03:24:57 2008  rrw-------  .viminfo
  18   19 r  195993  D 1202351097 Thu Feb  7 03:24:57 2008  rrw-------  .viminfo
  18   19 r  195994  D 1202351111 Thu Feb  7 03:25:11 2008  rrw-r--r--  .viminfo
  18   19 r  195993  D 1202351097 Thu Feb  7 03:24:57 2008  rrw-------  .viminfo
  18   19 r  195994  D 1202351111 Thu Feb  7 03:25:11 2008  rrw-r--r--  .viminfo
  18   19 r  195993  D 1202351097 Thu Feb  7 03:24:57 2008  rrw-------  .viminfo
  18   19 r  195994  D 1202351111 Thu Feb  7 03:25:11 2008  rrw-r--r--  .viminfo
  18   19 r  195993  D 1202351097 Thu Feb  7 03:24:57 2008  rrw-------  .viminfo
  18   19 r  195994  D 1202351111 Thu Feb  7 03:25:11 2008  rrw-r--r--  .viminfo
  18   19 r  195993  D 1202351097 Thu Feb  7 03:24:57 2008  rrw-------  .viminfo
  18   19 r  195994  D 1202351111 Thu Feb  7 03:25:11 2008  rrw-r--r--  .viminfo
  18   19 r  195993  D 1202351097 Thu Feb  7 03:24:57 2008  rrw-------  .viminfo
  18   19 r  195994  D 1202351111 Thu Feb  7 03:25:11 2008  rrw-r--r--  .viminfo
  18   19 r  195993  D 1202351097 Thu Feb  7 03:24:57 2008  rrw-------  .viminfo
  18   19 r  195994  D 1202351111 Thu Feb  7 03:25:11 2008  rrw-r--r--  .viminfo
  18   19 r  195995  D 1202351097 Thu Feb  7 03:24:57 2008  rrw-------  .viminfo
  18   19 r  195994  D 1202351111 Thu Feb  7 03:25:11 2008  rrw-r--r--  .viminfo
  18   19 r  195995  D 1202351097 Thu Feb  7 03:24:57 2008  rrw-------  .viminfo
  18   19 r  195994  D 1202351111 Thu Feb  7 03:25:11 2008  rrw-r--r--  .viminfo
  18   19 r  195995  D 1202351097 Thu Feb  7 03:24:57 2008  rrw-------  .viminfo
  18   19 r  195993  D 1202351097 Thu Feb  7 03:24:57 2008  rrw-------  .viminfo
  17   19 r  197221  D 1202351110 Thu Feb  7 03:25:10 2008  rrw-r--r--  .viminfo

Pentru a înţelege acestea, trebuie remarcat că

Întâi, aceste intrări dublate apar mai ales datorită blocurilor director dublate, care se observă deja din numărul de index al intrărilor: dacă ar fi toate din acelaşi bloc atunci toate numerele de index ar fi diferite. Desigur, fără a piping rezultatul la grep ar fi clar că fiecare intrare aparţine altui bloc director, dar acel rezultat este prea mare pentru a fi aratat aici.

În al doiela rând, trebuie să observaţi că doar numărul de inode, tipul de fişier din a 3-a coloană şi numele fişierului sunt datele din intrarea de director. Timpul ştergerii şi coloana de Mod sunt extrase din data curentă din inodeul corespunzator. Aşadar, acel inode ar fi putut fi refolosit acum mult timp de alt fişier iar datele conţinute nu ar mai fi legate de intrările acestui director. Acesta este clar cazul exemplului de mai sus deoarece este sigur faptul că acele fişiere .viminfo nu au fost şterse în aceeaşi zi. În câteva cazuri se poate detecta că un inode a fost realocat (refolosit): dacă este încă în uz (dar nu mai poate fi de această intrare de director ştearsă), sau când tipul de fişier din inode diferă de tipul de fişier din intrarea de director. În aceste cazuri a 5-a coloană arată un R în loc de D şi conţinutul inodeului nu este afişat. Astfel, deoarece aceste intrări arată puţine informaţii despre utilizare, ele sunt în mod normal eliminate. Dacă doriţi să vedeţi intrările cu inodeurile realocate cunoscute trebuie să adăugaţi la linia de comandă opţiunea --reallocated. Mai mult decât atât, uneori numărul inodeului din intrarea de director este scris cu 0. Aceste intrări sunt în mod evident nefolositoare şi eliminate şi ele. Pentru a le afişa se foloseşte în linia de comandă optiunea --zeroed-inodes.

Există posibilitatea filtrării rezultatelor afişate de --ls. O prezentare generală a filtrelor disponibile este dată de rezultatul optiunii --help :

$ ext3grep $IMAGE --help
[...]
Filters:
  --group grp            Only process group 'grp'.
  --directory            Only process directory inodes.
  --after dtime          Only entries deleted on or after 'dtime'.
  --before dtime         Only entries deleted before 'dtime'.
  --deleted              Only show/process deleted entries.
  --allocated            Only show/process allocated inodes/blocks.
  --unallocated          Only show/process unallocated inodes/blocks.
  --reallocated          Do not suppress entries with reallocated inodes.
                         Inodes are considered 'reallocated' if the entry
                         is deleted but the inode is allocated, but also when
                         the file type in the dir entry and the inode are
                         different.
  --zeroed-inodes        Do not suppress entries with zeroed inodes. Linked
                         entries are always shown, regardless of this option.
  --depth depth          Process directories recursively up till a depth
                         of 'depth'.
[...]

Pentru a putea determian valorile importante pentru --after şi --before a fost adăugată acţiunea --histogram=dtime . Această opţiune a linie de comandă face ca ext3grep să listeze o histogramă a timpului faţă de numărul inodeurilor şterse. Dacă ştergeţi un număr mare de fişiere odată, de exemplu cu rm -rf, atunci ar trebui să fie simplu calculul unei ferestre de timp în care a avut loc ştergerea. De exemplu, aici am mărit o bucată din dezastrul personal în care am şters cu puţin peste 50 000 de fişiere din directorul Home:

$ ext3grep $IMAGE --histogram=dtime --after=1202351086 --before=1202351129
Only show/process deleted entries if they are deleted on or after Thu Feb  7 03:24:46 2008 and before Thu Feb  7 03:25:29 2008.

Number of groups: 75
Minimum / maximum journal block: 1115 / 35026
Loading journal descriptors... done
Journal transaction 4381435 wraps around, some data blocks might have been lost of this transaction.
Number of descriptors in journal: 30258; min / max sequence numbers: 4379495 / 4382264

Only show/process deleted entries if they are deleted on or after 1202351086 and before 1202351129.
Only showing deleted entries.
Thu Feb  7 03:24:46 2008  1202351086        0
Thu Feb  7 03:24:47 2008  1202351087        1
Thu Feb  7 03:24:48 2008  1202351088        0
Thu Feb  7 03:24:49 2008  1202351089        0
Thu Feb  7 03:24:50 2008  1202351090        0
Thu Feb  7 03:24:51 2008  1202351091        0
Thu Feb  7 03:24:52 2008  1202351092        0
Thu Feb  7 03:24:53 2008  1202351093      705 ==============
Thu Feb  7 03:24:54 2008  1202351094     1698 ==================================
Thu Feb  7 03:24:55 2008  1202351095     2320 ===============================================
Thu Feb  7 03:24:56 2008  1202351096     3652 ==========================================================================
Thu Feb  7 03:24:57 2008  1202351097     3332 ===================================================================
Thu Feb  7 03:24:58 2008  1202351098     2014 =========================================
Thu Feb  7 03:24:59 2008  1202351099     1160 =======================
Thu Feb  7 03:25:00 2008  1202351100     4188 =====================================================================================
Thu Feb  7 03:25:01 2008  1202351101     2480 ==================================================
Thu Feb  7 03:25:02 2008  1202351102     1945 =======================================
Thu Feb  7 03:25:03 2008  1202351103     1471 ==============================
Thu Feb  7 03:25:04 2008  1202351104     2724 =======================================================
Thu Feb  7 03:25:05 2008  1202351105     3090 ===============================================================
Thu Feb  7 03:25:06 2008  1202351106     3360 ====================================================================
Thu Feb  7 03:25:07 2008  1202351107     4902 ====================================================================================================
Thu Feb  7 03:25:08 2008  1202351108      698 ==============
Thu Feb  7 03:25:09 2008  1202351109     1612 ================================
Thu Feb  7 03:25:10 2008  1202351110     4547 ============================================================================================
Thu Feb  7 03:25:11 2008  1202351111     2651 ======================================================
Thu Feb  7 03:25:12 2008  1202351112     1513 ==============================
Thu Feb  7 03:25:13 2008  1202351113        0
Thu Feb  7 03:25:14 2008  1202351114        0
Thu Feb  7 03:25:15 2008  1202351115        0
Thu Feb  7 03:25:16 2008  1202351116        0
Thu Feb  7 03:25:17 2008  1202351117        1
Thu Feb  7 03:25:18 2008  1202351118        0
Thu Feb  7 03:25:19 2008  1202351119        0
Thu Feb  7 03:25:20 2008  1202351120        0
Thu Feb  7 03:25:21 2008  1202351121        0
Thu Feb  7 03:25:22 2008  1202351122        0
Thu Feb  7 03:25:23 2008  1202351123        0
Thu Feb  7 03:25:24 2008  1202351124        0
Thu Feb  7 03:25:25 2008  1202351125        0
Thu Feb  7 03:25:26 2008  1202351126        0
Thu Feb  7 03:25:27 2008  1202351127        0
Thu Feb  7 03:25:28 2008  1202351128        0
Thu Feb  7 03:25:29 2008  1202351129
Totals:
1202351086 - 1202351128    50064

Este important setarea unei valori bune pentru --after înainte de a recupera toate fişierele, sau vor fi recuperate prea multe fişiere.

Jurnalul

Jurnalul este un fişier format dintr-un număr fix de blocuri. Inodeul lui este EXT3_JOURNAL_INO, care de obicei este 8. Inodeul actual poate fi şi el găsit în superbloc:

$ ext3grep $IMAGE --superblock | grep 'Inode number of journal file'
Inode number of journal file: 8

şi mărimea i se poate afla listând inodeul 8:

$ ext3grep $IMAGE --print --inode 8
Number of groups: 75
Loading group metadata... done
Minimum / maximum journal block: 1115 / 35026
Loading journal descriptors... done
Journal transaction 4381435 wraps around, some data blocks might have been lost of this transaction.
Number of descriptors in journal: 30258; min / max sequence numbers: 4379495 / 4382264

Hex dump of inode 8:
0000 | 80 81 00 00 00 00 00 08 00 00 00 00 62 07 57 46 | ............b.WF
0010 | 62 07 57 46 00 00 00 00 00 00 01 00 10 01 04 00 | b.WF............
0020 | 00 00 00 00 08 00 00 00 5b 04 00 00 5c 04 00 00 | ........[...\...
0030 | 5d 04 00 00 5e 04 00 00 5f 04 00 00 60 04 00 00 | ]...^..._...`...
0040 | 61 04 00 00 62 04 00 00 63 04 00 00 64 04 00 00 | a...b...c...d...
0050 | 65 04 00 00 66 04 00 00 67 04 00 00 68 08 00 00 | e...f...g...h...
0060 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................
0070 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................

Inode is Allocated
Group: 0
Generation Id: 0
uid / gid: 0 / 0
mode: rrw-------
size: 134217728
num of links: 1
sectors: 262416 (--> 34 indirect blocks).

Inode Times:
Accessed:       0
File Modified:  1180108642 = Fri May 25 17:57:22 2007
Inode Modified: 1180108642 = Fri May 25 17:57:22 2007
Deletion time:  0

Direct Blocks: 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126
Indirect Block: 1127
Double Indirect Block: 2152

unde puteţi observa că dimensiunea jurnalului meu este de 134217728 bytes, sau 32768 de blocuri. Primele 12 blocuri sunt afişate direct în inode: blocurile 1115 – 1126. Apoi un bloc indirect este plasat în 1127. Acest bloc indirect poate conţine 1024 de numere de blocuri fiecare urmând direct blocul indirect (1128 – 2151). Apoi inodeul indică un bloc indirect dublu care conţine 31 de numere de blocuri pentru blocuri indirecte adiţionale. Numărul total de blocuri indirecte (duble / triple) este calculat să fie 34 (ştiind că un sector are 512 bytes). Aşadar, dacă totul ar fi stocat continuu, ultimul block al jurnalului ar fi 1115 + 32768 + 34 – 1 = 33916. Totuşi, jurnalul nu se încadrează în întregime în grupul 0, astfel ultimele ultimele blocuri se găsesc în grupul 1 şi headerul grupului 1 (cel mai degrabă tabela de inodeuri) este inserat undeva între blocurile jurnalului făcând ultimul bloc să fie 35025. . Pe deasupra, pot exista blocuri rele oriunde în interior. Astfel, modul corect de apropiere a jurnalului este în termenii de numere de blocuri jurnal – ‘journal block numbers’.

Primul bloc al fişierului jurnal (blocul 1115 din exemplul de mai sus) conţine superblocul jurnalului. Structura lui este definită în /usr/include/linux/jbd.h ca fiind journal_superblock_t. Ea poate fi afişată folosind :

$ ext3grep $IMAGE --journal --superblock
Journal Super Block:

Signature: 0x3225106840
Block type: Superblock version 2
Sequence Number: 0
Journal block size: 4096
Number of journal blocks: 32768
Journal block where the journal actually starts: 1
Sequence number of first transaction: 4382265
Journal block of first transaction: 0
Error number: 0
Compatible Features: 0
Incompatible features: 1
Read only compatible features: 0
Journal UUID: 0xe3 0x88 0xd9 0x09 0x94 0xca 0x43 0x95 0x9b 0x53 0xac 0x2c 0xd8 0xe0 0x3d 0x25
Number of file systems using journal: 1
Location of superblock copy: 0
Max journal blocks per transaction: 0
Max file system blocks per transaction: 0
IDs of all file systems using the journal:
1. 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00

Minimum / maximum journal block: 1115 / 35026
Loading journal descriptors... done
Journal transaction 4381435 wraps around, some data blocks might have been lost of this transaction.
Number of descriptors in journal: 30258; min / max sequence numbers: 4379495 / 4382264

Aici se poate observa că jurnalul începe de fapt la Journal Block Number 1, şi ultimul bloc este Journal Block Number 32768. Acestea nu sunt deci la fel cu numere de bloc ale sistemului de fişiere. Se pot afla numerele de bloc reale, astfel

$ ext3grep $IMAGE --journal --journal-block 1
[...]
Group: 0
Block 1116 belongs to the journal.
[...]

care arată că Journal Block Number 1 este blocul 1116 din sistemul de fişiere.

Jurnalul este umplut cu “Transactions” care au un număr secvenţial crescător. Dacă sfârşitul jurnalului este atins, scriind continuu de la început, umplând în jur. If the end of the journal is reached, writing continuous at the start, wrapping around. Aşadar, dacă un sistem de fişiere este demontat curat, atunci la următoarea montare scrierea începe mereu de la început (cred).

O singură tranzacţie constă din una sau mai multe “Descriptors”. Ultimul descriptor al tranzacţiei este un “Commit Block”, semnalând că tranzacţia s+a terminat cu succes iar datele din descriptori anteriori au fost scrise pe disc. Mai există încă 2 tipuri de descriptori: blocuri de revocare şi blocuri conţinând “tags”. Un bloc de revocare este umplut cu numere de blocuri care ar trebui să fie (sau sunt) nealocate de această tranzacţie. Un tag este o structură care atribuie blocuri secvenţiale de jurnal (nu blocuri ale sistemului de fişiere) la blocurile sistemului de fişiere: următoarele blocuri de jurnal conţin datele care ar fi trebuit să fie scrise (au fost scrise) în blocul dar de pe sistemul de fişiere.

Aceasta face ca “tags” în particular, interesante pentru noi: ele conţin copii ale datelor care au fost scrise pe disc în trecut, incluzând inodeurile vechi.

Exemplu de recuperare manuală

În exemplul următor vom recupera manual un fişier mic. Rezultatul afişat este dat doar parţial pentru a economisi spaţiu şi a face exemplul cât mai citibil.

Folosind ext3grep $IMAGE --ls --inode găsim numele fişierului pe care dorim să îl recuperăm:

$ ext3grep $IMAGE --ls --inode 2 | grep carlo
   3  end d  195457  D 1202352103 Thu Feb  7 03:41:43 2008  drwxr-xr-x  carlo

$ ext3grep $IMAGE --ls --inode 195457 | grep ' bin$' | head -n 1
  34   35 d  309540  D 1202352104 Thu Feb  7 03:41:44 2008  drwxr-xr-x  bin

$ ext3grep $IMAGE --ls --inode 309540 | grep start_azureus
   9   10 r  309631  D 1202351093 Thu Feb  7 03:24:53 2008  rrwxr-xr-x  start_azureus

evident, inode numărul 309631 este şters şi nu avem numere de bloc pentru acest fişier:

$ ext3grep $IMAGE --print --inode 309631
[...]
Inode is Unallocated
Group: 19
Generation Id: 2771183319
uid / gid: 1000 / 1000
mode: rrwxr-xr-x
size: 0
num of links: 0
sectors: 0 (--> 0 indirect blocks).

Inode Times:
Accessed:       1202350961 = Thu Feb  7 03:22:41 2008
File Modified:  1202351093 = Thu Feb  7 03:24:53 2008
Inode Modified: 1202351093 = Thu Feb  7 03:24:53 2008
Deletion time:  1202351093 = Thu Feb  7 03:24:53 2008

Direct Blocks:

Aşadar, vom încerca să căutăm o copie mai veche a lui în jurnal. Mai întâi, găsim blocul sistemului de fişiere care conţine acest inode:

$ ext3grep $IMAGE --inode-to-block 309631 | grep resides
Inode 309631 resides in block 622598 at offset 0xf00.

Apoi găsim toţi descriptorii jurnalului care se referă la blocul 622598:

$ ext3grep $IMAGE --journal --block 622598
[...]
Journal descriptors referencing block 622598:
4381294 26582
4381311 28693
4381313 28809
4381314 28814
4381321 29308
4381348 30676
4381349 30986
4381350 31299
4381374 32718
4381707 1465
4381709 2132
4381755 2945
4381961 4606
4382098 6073
4382137 6672
4382138 7536
4382139 7984
4382140 8931

Aceasta înseamnă că tranzacţia cu numărul secvenţial 4381294 are o copie a blocului 622598 în blocul 26582 şi asa mai departe. Cel mai mare număr de secvenţă de jos, ar trebui să fie ultimele date scrise pe disc şi deci blocul 8931 ar trebui să fie identic cu blocul curent 622598. Pentru a găsi ultima copie neştearsă trebuie pornit de jos în sus.

dacă încercaţi scrierea unui astfel de bloc, ext3grep recunoaşte că este un bloc dintr-o tabelă de inodes şi va afişa conţinutul tuturor celor 32 de inodes din ea. Noi dorim să vedem doar inode cu numărul 309631; astfel că folosimt un grep inteligent:

$ ext3grep $IMAGE --print --block 8931 | grep -A15 'Inode 309631'
--------------Inode 309631-----------------------
Generation Id: 2771183319
uid / gid: 1000 / 1000
mode: rrwxr-xr-x
size: 0
num of links: 0
sectors: 0 (--> 0 indirect blocks).

Inode Times:
Accessed:       1202350961 = Thu Feb  7 03:22:41 2008
File Modified:  1202351093 = Thu Feb  7 03:24:53 2008
Inode Modified: 1202351093 = Thu Feb  7 03:24:53 2008
Deletion time:  1202351093 = Thu Feb  7 03:24:53 2008

Direct Blocks:

Acesta este într-adevăr identic cu ce am văzut în blocul 622598. Apoi urmează să ne uităm la numerele secvenţiale mai mici până când găsim unul cu 0 Deletion time. Primul găsit (de jos în sus) este blocul 6073:

$ ext3grep $IMAGE --print --block 6073 | grep -A15 'Inode 309631'
--------------Inode 309631-----------------------
Generation Id: 2771183319
uid / gid: 1000 / 1000
mode: rrwxr-xr-x
size: 40
num of links: 1
sectors: 8 (--> 0 indirect blocks).

Inode Times:
Accessed:       1202350961 = Thu Feb  7 03:22:41 2008
File Modified:  1189688692 = Thu Sep 13 15:04:52 2007
Inode Modified: 1189688692 = Thu Sep 13 15:04:52 2007
Deletion time:  0

Direct Blocks: 645627

Ce este mai sus este automat generat şi poate fi făcut mult mai repede folosind în linia de comandă opţiunea --show-journal-inodes. Această opţiune va găsi blocul de care aparţine inodeul, apoi găseşte toate copiile ale acelui bloc în jurnal, şi afişează în cele din urmă doar inodeul cerut din fiecare bloc gasit (fiecare conţinând 32 de inoduri, după cum ştiţi) eliminând dublurile:

$ ext3grep $IMAGE --show-journal-inodes 309631
Number of groups: 75
Minimum / maximum journal block: 1115 / 35026
Loading journal descriptors... done
Journal transaction 4381435 wraps around, some data blocks might have been lost of this transaction.
Number of descriptors in journal: 30258; min / max sequence numbers: 4379495 / 4382264
Copies of inode 309631 found in the journal:

--------------Inode 309631-----------------------
Generation Id: 2771183319
uid / gid: 1000 / 1000
mode: rrwxr-xr-x
size: 0
num of links: 0
sectors: 0 (--> 0 indirect blocks).

Inode Times:
Accessed:       1202350961 = Thu Feb  7 03:22:41 2008
File Modified:  1202351093 = Thu Feb  7 03:24:53 2008
Inode Modified: 1202351093 = Thu Feb  7 03:24:53 2008
Deletion time:  1202351093 = Thu Feb  7 03:24:53 2008

Direct Blocks:

--------------Inode 309631-----------------------
Generation Id: 2771183319
uid / gid: 1000 / 1000
mode: rrwxr-xr-x
size: 40
num of links: 1
sectors: 8 (--> 0 indirect blocks).

Inode Times:
Accessed:       1202350961 = Thu Feb  7 03:22:41 2008
File Modified:  1189688692 = Thu Sep 13 15:04:52 2007
Inode Modified: 1189688692 = Thu Sep 13 15:04:52 2007
Deletion time:  0

Direct Blocks: 645627

Fişierul este într-adevăr mic: doar un bloc. Copiem acest bloc cu dd cum s-a arătat mai devreme:

$ dd if=$IMAGE bs=4096 count=1 skip=645627 of=block.645627
1+0 records in
1+0 records out
4096 bytes (4.1 kB) copied, 0.0166104 seconds, 247 kB/s

şi apoi edităm fişierul pentru a elimina zerourile sau copiem primii 40 de bytes (mărimea dată a fişierului) :

$ dd if=block.645627 bs=1 count=40 of=start_azureus
40+0 records in
40+0 records out
40 bytes (40 B) copied, 0.000105397 seconds, 380 kB/s

$ cat start_azureus
cd /usr/src/azureus/azureus
./azureus &

Recuperat!

Observaţi că este posibil să vedeţi toţi descriptorii unei tranzacţii date. Tranzacţia pe care am folosit-o să recuperăm acest fişier a fost 4382098. Tranzacţia completă poate fi observată cu :

$ ext3grep $IMAGE --journal-transaction 4382098
[...]
Prev / Current / Next sequences numbers: 4382097 4382098 4382099
Transaction was NOT COMMITTED!
TAG: 6074=851971 6073=622598 6072=393218 6071=393395 6070=393231 6069=393409 6068=393240 6067=393371 6066=622596
REVOKE: 506451
TAG: 6056=393217 6057=1 6058=393273 6059=393232 6060=403879 6061=393216 6062=491520 6063=506302 6064=0 6065=393219

Aici observaţi, de exemplu, TAG-ul 6072=393218, însemnând că blocul 6072 conţine o copie (veche) a blocului 393218. Nu ştiu de ce este scris că tranzacţia nu a fost terminată (pare puţin probabil). Este posibil ca blocul de decizie (commit block) să fi fost suprascris şi această tranzacţie veche a jurnalului nu mai este efectiv completă.

Recuperarea fişierelor

Desigur, ar fi enervant sî recuperăm fişiere mai mari, care sunt pe mai multe blocuri, în acest mod; lasaţi-mă să recuperez manual mii de fişiere ! Aşadar tot ce s-a prezentat mai sus poate fi automatizat. Oricum, dacă recuperaţi 50.000 de fişiere atunci nu este efectiv nici o metodă să verificaţi dacă a funcţionat mai ales dacă au fost recuperate MAI MULTE fişiere decât aţi fi dorit; va fi greu să recuperaţi tot. Ar trebui să aveţi grijă să recuperaţi fişierele cât mai exact cu putinţă.

nu est enevoie de atâta grijă pentru a recupera un singur fişier, e destul să trimiteţi pathul la ext3grep:

$ ext3grep $IMAGE --restore-file carlo/bin/start_kvm
[...]
Restoring carlo/bin/start_kvm

$ cat RESTORED_FILES/carlo/bin/start_kvm
#! /bin/sh
cd /usr/src/qgt/src
./host-linux 192.168.2.4 &
cd /opt/kvm/winXPpro
sudo modprobe kvm_intel
sudo kvm -m 384 -hda vdisk6GB.img -cdrom /dev/cdrom -localtime -std-vga -net nic,vlan=0,model=rtl8139 -net tap,vlan=0
#-snapshot # -daemonize
killall -9 host-linux

Observaţi că aceasta a creat directorul RESTORED_FILES/carlo/bin în directorul curent pentru a putea recupera acest fişier. Mai observaţi şi că, dacă RESTORED_FILES/carlo/bin/start_kvm există deja în directorul curent atunci NU a fost suprascris!

Pentru ca acestea să funcţioneze va trebui prima dată să executaţi pasul 1 şi 2 al analizării discului pe care ext3grep o face (vedeţi mai jos) .

Este posibil să se descarce toate numele de fişiere pe care ext3grep le poate găsi, folosind în linia de comandă opţiunea --dump-names:

$ ext3grep $IMAGE --dump-names
carlo
carlo/.Trash
carlo/.Xauthority
carlo/.Xauthority-c
carlo/.Xauthority-l
carlo/.Xauthority-n
carlo/.alsaplayer
carlo/.alsaplayer/alsaplayer.m3u
carlo/.alsaplayer/config
[...]
carlo/www/xcw/.svn/tmp/wcprops
carlo/www/xcw/index.html
carlo/www/xmlwrapp-0.5.0.tar.gz
lost+found
lost+found/1st level admin borders (states_provinces)
lost+found/1st level admin names (states_provinces)
lost+found/2002 - cloud cover (0-10%)
[...]

Fişierele care vor ajunge în lost+found sunt fişiere pentru care nu s-a găsit un director (dar care incă au o copie la inode în jurnal). Cel mai sigur acestea sunt fişiere care au fost şterse cu mult timp în urmă şi pot fi aruncate, oricum.

După ce sunteţi satisfăcuţi de afişarea tuturor --dump-names, puteţi înlocui --dump-names cu --restore-all, care va determina --restore-file să fie apelată pentru fiecare nume de fişier afişat de --dump-names. După cum am spus înainte, este foarte indicat să se folosească foarte bine opţiunea --after în linia de comandă pentru a evita ca ext3grep să încerce recuperarea fisierelor care efectiv sunt prea vechi. Observaţi că în acest moment rezultatul pentru --dump-names este nefiltrat, iar --restore-file (--restore-all) DOAR foloseşte opţiunea --after în linia de comandă.

De exemplu,

$ time ext3grep $IMAGE --restore-all --after=1202351117
Only show/process deleted entries if they are deleted on or after Thu Feb  7 03:25:17 2008.
[...]
Loading md5.ext3grep.stage2... done
Not undeleting "carlo/.Trash" because it was deleted before 1202351117 (32767)
Not undeleting "carlo/.Xauthority" because it was deleted before 1202351117 (32767)
[...]
Cannot find an undeleted inode for file "carlo/.azureus/logs/save/1176594823051_alerts_1.log".
[...]
Restoring carlo/bin/startx
[...]
real	0m3.079s
user	0m1.332s
sys	0m1.744s

unde carlo/bin/startx este singurul fiţier recuperat. A fost ultimul fişier care a fost şters şi am setat valoarea opţiunii --after la o secundă înainte. Observaţi că este logic să fie ultimul fişier dacă am pornit X folosind acest script; astfel, era „în folosire” până am repornit.

Considerând că a verificat peste 50.000 de fişiere de p eo partiţie de 10GB, cele 3,1 secunde scoase inseamnă extrem de repede; acest lucru fiind cauzat de diverşi factori: 1) Prima dată când este rulat ext3grep face o analiză completă a partiţiei şi scrie rezultatele într-un fişier cache ( în 2 paşi, pasul 1 şi pasul 2). Aceşti paşi trebuie facuţi o singură dată. 2) Deoarece doar un singur fişier a trebuit sa fie recuperat, nu a fost nevoie de prea mult spaţiu pe disc (pe lângă asta, eu am 4 GB de RAM – deci orice era nevoie era deja în cache). 3) Am un procesor rapid. Programul a folosit totuşi 100% CPU în aceste 3,1 secunde. Încercând să recuperez multe fişiere se poate bloca accesul la disc, dar totuşi se rezolvă destul de repede (puteţi să staţi şi să aşteptaţi).

Pasul 1

Pasul 1 scrie fişierele cache în DEVICE.ext3grep.stage1, unde DEVICE este înlocuit cu numele dispozitivului (de exemplu, dacă $IMAGE este /dev/hda2, atunci DEVICE este hda2). Puţine pot merge rău în pasul 1: doar scanează tot discul si găseşte toate blocurile care par să conţină un director.

Formatul fişierelor cache din pasul 1 este :

$ cat md5.ext3grep.stage1
# Stage 1 data for md5.
# Inodes and directory start blocks that use it for dir entry '.'.
# INODE : BLOCK [BLOCK ...]
2 : 1109 6592 9312
11 : 1110
195457 : 415744
195468 : 2916 4732 17783 403469
195469 : 403470
[...]
929633 : 1885254
929659 : 1885280
# Extended directory blocks.
1178
1179
1182
[...]
1884516

În prima parte, pe prima coloană sunt inodes, urmate de un spaţiu, urmat de o coloană urmată de o listă de numere de blocuri separate prin spaţii care folosesc acel inode ca intrare cu numele „.” . Evident, poate fi doar un singur director care foloseşte acest inode, deci ext3grep trebuie să determine care din aceste numere de blocuri este ultimul care a fost cel real. A doua parte listează toate numerele de bloc care conţin blocurile extinse de director, cel existent, blocurile de director care nu sunt primele blocuri şi nu conţin intrarea de director „.”. Nu se ştie cui director îi aparţin neavând inodeul original. În Pasul 2 ext3grep va încerca să găsească cări director îi aparţin.

Pasul 2

Acest pas, executat de funcţia init_directories(), conţine în general codul heuristic. Întâi determină care blocuri sunt cu adevărat blcurile de start ale directorului, apoi atribuie fiecare bloc extins de director unui astfel de director (a se vedea şi TODO, mai jos). Ca rezultat este posibil atribuirea unui path fiecărui inode (de director). În sfârşit, acest rezultat este scris într-un fişier cache (DEVICE.ext3grep.stage2). În cazul în care ceva merge foarte rău aici, puteţi fi în stare să o rezolvaţi editând acest fişier (eliminând numere incorecte sau adăugând numerele corecte de bloc), oricum, nu adăugaţi sau eliminaţi comentarii: ext3grep va deveni confuz dacă schimbaţi fişierul prea mult.

Localizarea bazei de date

Mă tem că am folosit încă ceva pe lângă datele de pe partiţie pentru a recupera fişierele: încă mai am partiţia /var deci încă mai am baza de date locate (în /var/cache/locate/locatedb). Am folosit-o pentru a face o listă cu toate numele de fişiere care au fost şterse şi am scrs-o într-un fişier locate_output, cu formatul :

carlo
carlo/.Trash
carlo/.Xauthority
[...]

în alte cuvinte cu acelaşi format precum output-ul --dump-names. Acest fişier este deschis şi folosit de către funcţia load_locate_data, în fişierul sursă locate.cc, care umple filename_to_locatepath_map. Această hartă este ulterior folosită de funcţia parent_directory pentru a face o ghicire educativă corecta asupra directorului părinte căruia îi aparţine un fişier dat.

Chiar şi atunci, deoarece fişierul meu de bază de date de locaţii nu era complet, a trebui să adaug manual hack-uri. În special, este posibil adăugarea unei liste codate hardcoded de expresii normale în locate.cc care determină returnarea unui director părinte specific. Totuşi, am mai codat hardcoded traducerea a 3 numere de blocuri (director) la “lost+found“. Probabil este nevoie de reglajul / hackul acestei părţi de cod dacă doriţi succesul unei recuperări corecte a datelor în directorul corect.

Dacă vedeţi mesaje pe formular:

Could not find an inode for extended directory at BLOCKNR, disregarding it's contents.

citiţi vă rog acest post în întregime pentru mai multe informaţii.

Hard linkurile în plus

Deoarece inodeurile sunt refolosite, se întâmplă des ca o intrare veche de director (a unui fişier şters, a unui director şters, sau într-un bloc director care nu mai este folosit) să indice un inode care este acum folosit de altcineva. Dacă acel altcineva este de acelaşi tip (amandouă fişiere normale) atunci nu există nici o metodă de a le distinge de hardlink: două fişiere folosind acelaşi inode. Ca rezultat, recuperarea aduce multe hardlinkuri eronate.

Pentru a face pau uşoară curăţarea acestora, ext3grep oferă opţiunea în linia de comandă --show-hardlinks.

$ ext3grep $IMAGE --show-hardlinks
[...]
Inode 309562:
  carlo/bin/pc++ (309540)
  carlo/bin/pcc (309540)
  carlo/bin/pcc.unlock (309540)
Inode 702474:
  carlo/projects/libcwd/libcwd/.svn/entries (700387)
  carlo/projects/libcwd/libcwd/testsuite/tst_flush.o (700609)
[...]

Aici, hardlinkurile pentru inodeul 309562 sunt corecte. Hardlinkul pentru inodeul 702474 este greşit şi unul din fişiere ar trebui şters. După ce aţi determinat manual care fişier este eronat şi l-aţi şters, va reapărea dacă reluaţi comanda. Doar acele hardlinkuri sunt raportate dacă încă există în directorul output: puteţi folosi doar --show-hardlinks după ce aţi rulat --restore-all, sau nu va rezulta nici un output deoarece nu există un fişier output.

TODO

Programul a fost scris în timp ce învăţam cum funcţionează ext3. Prima funcţie nu depinde astfel de ceea ce am scris mai târziu. Un avantaj este acela că aceste funcţionalităţi sunt mai rapide şi vor merge chiar dacă codul scris după este greşit; mai există şi multe down-to-earth, pe care le puteţi folosi pentru a verifica ce anume se întâmplă exact fără a depinde de codul mult mai complex (şi heuristic) adăugat mai târziu. Oricum, sunt şi dezavantaje: codul de filtrare pe care l-am scris pentru --ls nu este folosit de codul scris mai târziu care utilizează --dump-names şi --restore-all. De asemenea , Pasul 2 este rezolvat fără a folosi jurnalul aşa cum e posibil folosind codul scris mai târziu. Nu este uşoară această schimbare deoarece acel cod foloseşte rezultatele din pasul 2. Cred că un algoritm mai bun pentru găsirea blocurilor corecte pentru ultima copie a directorului va fi la fel cu ce am făcut pentrua-mi recupera fişierele: găsind ultimul inode neşters al acelui director din jurnal. Totuşi el nu funcţionează aşa acum.

Opţiuni pentru linia de comandă

Toate opţiunile de linii de comandă sunt scrise mai jos folosind opţiunea --help:

$ ext3grep $IMAGE --help
Usage: ext3grep [options] [--] device-file
Options:
  --version, -[vV]       Print version and exit successfully.
  --help,                Print this help and exit successfully.
  --superblock           Print contents of superblock in addition to the rest.
                         If no action is specified then this option is implied.
  --print                Print content of block or inode, if any.
  --ls                   Print directories with only one line per entry.
                         This option is often needed to turn on filtering.
  --accept filen         Accept 'filen' as a legal filename.
                         Can be used multiple times.
  --journal              Show content of journal.
  --show-path-inodes     Show the inode of each directory component in paths.

Filters:
  --group grp            Only process group 'grp'.
  --directory            Only process directory inodes.
  --after dtime          Only entries deleted on or after 'dtime'.
  --before dtime         Only entries deleted before 'dtime'.
  --deleted              Only show/process deleted entries.
  --allocated            Only show/process allocated inodes/blocks.
  --unallocated          Only show/process unallocated inodes/blocks.
  --reallocated          Do not suppress entries with reallocated inodes.
                         Inodes are considered 'reallocated' if the entry
                         is deleted but the inode is allocated, but also when
                         the file type in the dir entry and the inode are
                         different.
  --zeroed-inodes        Do not suppress entries with zeroed inodes. Linked
                         entries are always shown, regardless of this option.
  --depth depth          Process directories recursively up till a depth
                         of 'depth'.
Actions:
  --inode-to-block ino   Print the block that contains inode 'ino'.
  --inode ino            Show info on inode 'ino'.
                         If --ls is used and the inode is a directory, then
                         the filters apply to the entries of the directory.
                         If you do not use --ls then --print is implied.
  --block blk            Show info on block 'blk'.
                         If --ls is used and the block is the first block
                         of a directory, then the filters apply to entries
                         of the directory.
                         If you do not use --ls then --print is implied.
  --histogram=[atime|ctime|mtime|dtime|group]
                         Generate a histogram based on the given specs.
                         Using atime, ctime or mtime will change the
                         meaning of --after and --before to those times.
  --journal-block jblk   Show info on journal block 'jblk'.
  --journal-transaction seq
                         Show info on transaction with sequence number 'seq'.
  --dump-names           Write the path of files to stdout.
                         This implies --ls but suppresses it's output.
  --search-start str     Find blocks that start with the fixed string 'str'.
  --search str           Find blocks that contain the fixed string 'str'.
  --search-inode blk     Find inodes that refer to block 'blk'.
  --search-zeroed-inodes Return allocated inode table entries that are zeroed.
  --inode-dirblock-table dir
                         Print a table for directory path 'dir' of directory
                         block numbers found and the inodes used for each file.
  --show-journal-inodes ino
                         Show copies of inode 'ino' still in the journal.
  --restore-file 'path'  Will restore file 'path'. 'path' is relative to root
                         of the partition and does not start with a '/' (it
                         must be one of the paths returned by --dump-names).
                         The restored directory, file or symbolic link is
                         created in the current directory as ./'path'.
  --restore-all          As --restore-file but attempts to restore everything.
                         The use of --after is highly recommended because the
                         attempt to restore very old files will only result in
                         them being hard linked to a more recently deleted file
                         and as such polute the output.
  --show-hardlinks       Show all inodes that are shared by two or more files.

New functionality was more or less added top down, so this also gives a historic overview of how the program was written.

Donations

Descărcări

Acesta este linkul pentru site+ul proiectului ext3grep: http://groups.google.com/group/ext3grep/web/ext3grep-source-code-and-overview.

Vă sfătuiesc să vă alăturaţi acestui grup şi să citiţi arhiva de comentarii: the archives of the mailinglist.

Pentru a citi arhiva pe email daca aveţi un cont gmail:

http://groups.google.com/group/ext3grep/subscribe

Copyright © 2008 Carlo Wood