.---+-=[ A tale of interests ]=------+---------------------------=[12]=----. | i| \__________________ ___ __ _ | | I| \ | | I| '-=[__2 ]=-| | // | | L\:-=[Ak : Kaboum le HD, na pu ]=--------------[~~~~~~~~~~~~]-----| | \ / | '________________________________________________________________________.I[MK-110]I.______' Cet article ne discute pas réellement d'une technique particulière mais plutôt d'un exemple concret qui démontre comment cela peut être plaisant de perdre tout le contenu d'un disque dur. La dernière fois que l'un de mes disques a passer l'âme, c'était un vieux 40 megabytes en 1992. Donc, comme vous pouvez le constater, je suis pas très familier avec ce genre d'expérience. N'empêche, il y a 1 semaine, le disque de 120gig sur laquelle reposait mon router, mon CVS de projets commerciaux, mes documents, ma pr0n, mes warez et ma vie (on va dire) s'est mis a grincer des dents. Les partitions (Linux EXT3) du disque sont soudainement devenus illisibles. Quel fâcheux contretemps, un jour de semaine avec du travail a finir rapidement. J'avais deja décider de faire mon deuil de mon porn, mes warez et des trucs que j'avais pas vraiment besoin. Mes projets commerciaux avaient, par miracle été copier 1 journée avant sur un CD (Aleluya). Il restait tout de même un petit hic, il me manquait quelques fichiers très importants (que j'avais pris le soin de zipper la veille). Vu l'importance de retrouver ce fichier a caractère vitale (mes états de compte et autres papiers important pour que le gouvernement puisse investir dans de la commandite), je me suis mis a réfléchir a des façons de réussir a retrouver mon petit fichier zip perdu. C'est a ce moment la que la quête commence :) Choses a savoir: La partition qui ne monte plus était la /dev/hdc2 elle contenait une partition EXT3 dont approximativement les 10 premiers megabytes étaient fichus ainsi qu'a plusieurs autre endroits si on se fit au son fatiguant. Qu'est-ce que j'ai fait: J'ai tout d'abord utiliser hexedit pour ouvrir la partition (hexedit /dev/hdc2) et j'ai chercher pour le pattern 'facture_1112.xls'. Je savais que ce fichier se trouvait dans mon fichier ZIP, et les noms de fichier ne sont jamais compresser dans un fichier ZIP, seulement leur contenu (merci ZIP). Alors si tout va bien, je devrais être capable de retrouver ce pattern, sinon je peux réellement dire adieux a mon précieux fichier. BINGO, je retrouve le pattern vers la moitiez de la partition. Prochaine étape, extraire le ZIP et vérifier si il est encore valide. Cette partie est plus difficile mais voici grossomodo mon approche: J'ai premièrement,ete me perdre dans le code source de zlib, pour finalement accélérer mes démarches en consultant le très utile www.wotsit.org, un site que je consulte depuis quelques années et qui contient la spécification de plusieurs formats de fichier. Voici ce qui en a ressorti d'important: -> local file header signature 4 bytes (0x04034b50) -> end of central dir signature 4 bytes (0x06054b50) Le premier pattern (0x04034b50) va me permettre de trouver le début de mon fichier, et le 2ieme (0x06054b50) sa fin. Alors, tout joyeux j'ai commencer a concevoir un petit code pour extraire tout les zip que je trouverais sur le disque en question: ------------------last_hope.cpp------------------------ // Ce define a ete tricky a trouver, c'est ce qui me // permet d'utiliser de l'adressage 64bits a l'intérieur // des fichiers. Sans cela je serais limiter a la lecture // de fichier de 4 Gigs. // Linux supporte le 64bits, mais par défaut il fallback // en 32, on doit alors spécifier ce macro au début de // notre code. Je trouve que ca ressemble a un ugly hack // pour un os, mais bon, tant que ca fonctionne bien. #define _FILE_OFFSET_BITS 64 #include #include #include #define FAULTY_DEVICE "/dev/hdc2" // la partition sur le evil HD #define ZIP_START "\x50\x4b\x03\x04" // le pattern de depart #define ZIP_END "\x50\x4b\x05\x06" // le pattern de fin /*------------------------------------------------------*/ // Le bon vieux main , rien a en redier /*------------------------------------------------------*/ int main(int argc,char* argv[]) { // ici je verifie si le systeme est bel et bien capable de supporter le mode 64bits // dans le cas d'un systeme 32bits, le sizeof retourne 12 au lieu de 16. Cr le off_t // pese 4bits au lieu de 8. if (sizeof(fpos_t) != 16) { std::cout << "This system dont support 64bits file io" << std::endl; return 0; } // Variables 'wedontcare' globales char sign[4]; int count = 0; int size = 0; bool inside_zip = false; fpos_t off_start; fpos_t off_end; fpos_t off_now; char tempfile[255]; FILE* fd = fopen(FAULTY_DEVICE,"rb"); // On ouvre la partition en lecture/binaire // on devine facilement ce que cela fait :) if (fd == NULL) { std::cout << "Unable to open faulty device." << std::endl; return -1; } // Ici on lis la partition du debut vers la fin while (!feof(fd)) { count++; // on decale le pattern que l'on cherche a chaque lecture car le HD n'utilise // pas des blocs fixes de 4 bytes par exemple. sign[0] = sign[1]; sign[1] = sign[2]; sign[2] = sign[3]; sign[3] = fgetc(fd); // Si on trouve un pattern, on le note if (strncmp(sign,ZIP_START,4)==0) { std::cout << "ZIP START here. OFFSET: " << ftell(fd) << std::endl; if (inside_zip == true) { std::cout << "WARNING: ZIP ALREADY STARTED. [SKIP]" << std::endl; } else { fgetpos(fd,&off_start); off_start.__pos -= 4; } inside_zip = true; } // si on trouve le pattern de fin , on le note et on lis le fichier if (strncmp(sign,ZIP_END,4)==0) { std::cout << "ZIP END here. OFFSET: " << ftell(fd) << std::endl; if (inside_zip == false) std::cout << "WARNING: ZIP NOT STARTED" << std::endl; inside_zip = false; fgetpos(fd,&off_end); sprintf(tempfile,"%d.zip",time(NULL)); std::cout << "WRITING TO: " << tempfile << std::endl; std::cout << "START:" << off_start.__pos << std::endl; std::cout << "END:" << off_end.__pos << std::endl; std::cout << "FILESIZE : " << (off_end.__pos - off_start.__pos) << std::endl; fgetpos(fd,&off_now); off_end.__pos += 18; fsetpos(fd,&off_start); // ICI on discarde les fichiers plus gros que 400 megabytes, pour 2 raisons, // la premiere etant que mon fichier doit peser maximum 50 kilobytes et secondo // ca empeche de faire un malloc() de 4 gig quand il tombe sur des blocks non-ordonnee // (voir la fin de l'article pour ample explications) if (((off_end.__pos - off_start.__pos) > 400000) || ((off_end.__pos - off_start.__pos) < 0)) std::cout << "TOO BIG TO BE REAL" << std::endl; else { // C'est cheap, cest pas fiable, mais ca fonctionne alors SHUT // La premiere version de ce code avait pas de check (plus haut), alors // si je pognais un faux-positif de 2 GIG, mon PC swappait until death. char* data = (char*) malloc(off_end.__pos - off_start.__pos); if (data == NULL) { std::cout << "NOT ENOUGH MEMORY, WHERE IS THE SWAP WHEN YOU NEED IT!" << std::endl; return -1; } fread(data,off_end.__pos-off_start.__pos,1,fd); FILE* out = fopen(tempfile,"wb"); fwrite(data,off_end.__pos-off_start.__pos,1,out); fclose(out); free(data); } fseek(fd,off_now,SEEK_SET); fsetpos(fd,&off_now); } // Cela empeche de creer l'effet classique "Coudonc le process es-tu geler?" if (count % 32768000 ==0) { size += 128; std::cout << size << " megabytes read. " << std::endl; } } // on ferme le FS fclose(fd); } -EOF---------------last_hope.cpp-----------------EOF--- Une fois compiler, l'exécution s'est bien dérouler et j'ai pus extraire quelque centaines de fichiers ZIP, cela a pris au moins 1 heure et j'ai pus retrouver mon précieux fichier! Il y a tout de même quelques problèmes qui m'ont amener a changer le code si-dessus legerement mais je ne tient pas a rentre mon code encore moins lisible en ajoutant les "hacks" qui ont ete apporter ici et la. (IE: Le handling des parties défectueuses) Le plus gros problème que j'ai eu a rencontrer porte le nom de defragmentation :) Certains blocs n'étaient pas dans l'ordre d'écriture mais pele-mêle, alors c'est bien entendu que je ne pouvais pas lire ce genre de fichier (C'est ce qui cause des faux-positifs de 2 gigs). Pour ma chance, le fichier que je recherchait remplissait 2 conditions, être tout petit(~50k) et ne pas se trouve enfermer dans un bloc de data faux-positif. La prochaine fois qu'un disque saute, la nouveau version de mon code pourra scanner plusieurs fois la surface en éliminant les faux positifs pour ne pas perdre du précieux contenu dure d'accès. Cela termine ma "quête", j'ai trouver l'idée de vous en faire part intéressante, comme quoi que tout est possible quand on est désespérerez et qu'on roule sous Linux :) (Ok, savoir coder depuis une décennie ça aide) La prochaine fois j'vais vous raconter comment j'ai installer Fedora Core 3 avec 4 CD scraps en ptracant anaconda via GDB pour créer un race condition entre moi pis le OS pour avoir accès au mount point du CD. :)