Introducció

Data Recovery System es el nou sistema de data recovery creat per al programa Carta (i d'altres). Està format per la llibreria libdrs i certes modificacions a la llibreria libdrf.

Documentació

El document doc/pfc1380.pdf dins el repositori de libdrs conté la memòria de desenvolupament i funcionament intern del projecte. Per a fer servir la llibreria, només cal treballar amb la interficie C, que es troba als fitxers src/lib/capi.cpp i src/lib/data-recovery.h i que es detalla a continuació. Al fitxer drs.c del codi de Carta 6.1.0 podem veure com es fan servir aquestes funcions.

En general, si no es diu lo contrari, totes les funcionalitats que s'expliquen en aquest document ja es troben implementades. Cal veure el fitxer drs.c de Carta, que fa d'interfície amb la llibreria DRS; i les llibreries libdrs i libdrf/branches/drs.

Funcionament

Primer cal inicialitzar la llibreria, identificant el nom del programa i usuari que en fa us. Després s'obtindrà una sessió de treball, ja sigui triant-ne una de la llista de sessions inacabades, o creant-ne una de nova. A mesura que l'usuari va realitzant la seva feina, es van emmagatzemant les diverses operacions que realitza. Cada cert temps, a més, el programa que fa us de la llibreria realitzarà un checkpoint. En qualsevol moment, l'usuari d'una sessió activa pot llistar les operacions anteriors i desfer la seva feina fins a un punt donat.

La llibreria DRS, al igual que la DRF, treballa amb tags. D'aquesta manera, podem veure un fitxer de data recovery com un DRF on anem guardant nous tags cada vegada que l'usuari els modifica. Realment es guarden les diferencies entre el tag modificat i el tag anterior.

Inicialització

Per inicialitzar la llibreria només cal cridar la funció

dr_handle_t *handle;
handle = data_recovery_init((const char*)user_name, (const char*)app_name);

Ambdós arguments han de ser diferents de NULL. Retorna un dr_handle_t* en cas d'èxit, NULL en cas d'error.

Obtenció d'una sessió

El procediment correcte es primer veure si hi han sessions inacabades i donar a l'usuari la opció de continuar-les:

dr_session_t *list;
unsigned int  num;
list = data_recovery_list_sessions((dr_session_t*)handle, (unsigned int*)&num);

Retorna una llista de dr_session_t* i el número de sessions contingudes a aquesta llista a num. En cas de que no existeixi cap sessió inacabada, retorna NULL i num igual a 0.

El llistat de sessions es pot explorar directament, especialment els camps user, app i date de l'estructura.

Si es vol continuar amb una de les sessions de la llista, s'ha executar

data_recovery_resume_session(handle,(dr_session_t*) list[i]);

Aquesta funció retornarà zero en cas d'error. Per crear una nova sessió es fa servir la mateixa funció però amb el segon argument (dr_session_t*) igual a NULL.

En tot cas, si list_sessions ens ha tornat una llista, i independentment de si hem fet resume d'una o no, hem de lliberar la llista de sessions:

data_recovery_free_sessions(list,num);

Emmagatzemament d'operacions

Cada cop que l'usuari realitza una modificació a les dades de treball, cap emmagatzemar l'operació:

dr_begin_op(handle,(unsigned short) opcode);
_envia_tags();
dr_end_op  (handle);

El opcode es un valor numèric que ens ha de servir més endavant per identificar la operació que ha realitzat l'usuari per generar aquests canvis. Es tracta, per tant, d'un valor que només te significat dins l'aplicació que està fent servir la llibreria Data Recovery. La funció _envia_tags en aquest exemple, ha de enviar a la llibreria els nous tags resultants de la operació que ha realitzat l'usuari. En el cas de Carta, es realitza la mateixa operació que quan es salva el projecte, generant les estructures necessàries per la llibreria DRF i cridant drf_save. Com veurem més endavant es tracta d'una llibreria DRF modificada per treballar amb Data Recovery. En comptes d'escriure cada tag a un fitxer DRF, lo que fa es cridar a la funció:

dr_set_tag (handle, (unsigned short) num_tag, (char) tipus_tag, (void*) ptr_tag, (unsigned int) nbytes);

Per tant per emmagatzemar una operació cal cridar dr_begin_op amb el identificador d'operació desitjat, fer tantes crides dr_set_tag com vulguem, i finalment cridar dr_end_op. La llibreria DRS s'encarregarà d'escriure al fitxer de sessió les modificacions que ha sofert cada tag amb aquesta operació.

Generació de checkpoints

Cada cert temps, es necessari realitzar checkpoints. Només cal cridar la funció

dr_checkpoint(handle);

La creació de checkpoints periòdics es necessària per agilitzar l'accés al fitxer de sessió, de manera que no cal llegir el fitxer complert per anar a un punt donat, sinó només cal llegir des del checkpoint immediatament anterior. També afegeix més robustesa al fitxer. Si una part es perd o es danya, encara es podrà accedir a les dades posteriors a aquesta part.

Desfer operacions

El propòsit principal de la llibreria, a més de continuar sessions que han fallat, es el de poder desfer la sessió actual fins a un punt anterior en el temps (Desfer errors, fer fork de projectes, etc.).

Per realitzar aquesta feina, primer necessitem poder llistar o navegar a través dels canvis emmagatzemats al fitxer de sessió. La llibreria DRS ens ho permet mitjançant un [iterador]:

dr_iterator it = dr_get_iterator(handle).

L'iterador es un objecte que ens serveix com a punter sobre la sessió, i ens permet moure'l endavant i enrere en el temps. Inicialment apunta al final del fitxer (per tant, no apunta a una operació existent). Per llegir la operació immediatament anterior en el temps fem:

dr_operation_t op;
dr_iterate(iterator,(dr_operation_t*) &op, (char)0); /* 0: enrere en el temps, 1: endavant */

Retorna 0 si ja estem al principi (direcció=0) o al final (direcció=1) del fitxer. Mitjançant l'estructura dr_operation_t podem accedir a l'informació d'aquesta operació: op.date es la data en que es va emmagatzemar. op.code es el identificador d'operació que es va fer servir a dr_begin_op().

Amb aquesta funció podem recórrer el fitxer obtenint un llistat de operacions (per mostrar a l'usuari, per exemple). Si volem tornar a l'estat en que es trobaven les dades en una operació donada, farem:

dr_undo_to(handle, (dr_operation_t*) &op);

En aquest moment els tags interns de la llibreria s'han actualitzat (des-actualitzat) fins el punt en que es trobaven al realitzar la operació especificada. Ara els hem de llegir al nostre programa amb les funcions:

unsigned short tagNum;
char           tagType;
unsigned int   tagNElements;
char          *tagPtr;

while ((tagPtr=dr_next_tag(handle,&tagNum,&tagType,&tagNElements)) {
 ...
}

Aquesta funció ens va retornant les tags una a una, cal repetir-la fins que retorni NULL. tagNum es el identificador del tag, tagType el tipus (BYTE, LONG, etc.) tagNElements el número de elements del tipus (No el número de bytes) i tagPtr el punter a les dades contingudes al tag (tagNElements de tipus tagType). Aquest punter no es pot alliberar ni modificar el seu contingut, doncs per motius d'eficiència es un punter a les dades internes que emmagatzema la llibreria.

També es pot fer servir dr_get_tag, però no es recomanable per que ens demana el número de tag i no sabem quins tags hi han (no tenen perquè ser consecutius).

Finalment ens hem de recordar d'alliberar l'iterador amb dr_free_iterator(iterador).

Llibreria DRF

Per que tot això funcioni sense masses modificacions dins del Carta, es va modificar la llibreria DRF. Aquesta llibreria modificada es va anomenar temporalment libdrf.x per diferenciar-la de la versió estable. Actualment, aquest codi es troba a branches/drs dins el repositori de libdrf.

Les modificacions respecte a la llibreria DRF original estàn orientades a tenir dues formes de treballar. La tradicional, llegint/escrivint els tags a un fitxer DRF a disk; i una nova forma enviant aquests tags a la llibreria DRS amb dr_set_tag i rebent-los amb dr_get_tag o dr_next_tag. Internament canvia poca cosa dins la llibreria, només que en comptes de treballar amb un FILE* a tot arreu es fa servir una estructura accessor que al final de tot ens dirà si escrivim a disk o a la llibreria.

Des del punt de vista del Carta aquesta llibreria es igual a la normal, només que si fem un drf_open amb un path del tipus "drs://nnnnnnnn", aquest DRF* que ens retorna treballarà amb DRS i no a un fitxer. El valor nnnnn ha de ser la direcció de memòria del handle de la llibreria DRS existent.

Idealment s'ha de continuar desenvolupant aquest branch de libdrf i no la versió tradicional.

Treball pendent

Checkpoints

Veig que el Carta ara mateix només realitza checkpoints al crear una nova sessió (carregar un DRF nou, crear un nou projecte). S'hauria de generar un checkpoint de tant en tant (cada X operacions enmagatzemades). Millor encara, es podria modificar la llibreria libdrs per que ho fes internament de forma automàtica. Veure l'apartat millores.

Desfer

Dins el Carta no es va arribar a acabar el formulari de desfer operacions. El principal problema es donar un codi a cada modificació que fa l'usuari i desprès poder mostrar un icono d'eina o cursor que identifiqui aquella operació.

Millores

Checkpoints

Revisant el projecte passat un any no veig el motiu per no fer els checkpoints automàtics. La propia llibreria, a l'hora de escriure una nova operació al fitxer de sessió, podria escriure-la en forma de checkpoint i no de diferencies si ho troba convenient. Per exemple, si veu que s'han escrit més de X operacions incrementals des del darrer checkpoint, o més de X MBs, o ha passat mes de un temps donat. Tal com està ara mateix es la aplicació qui ha de estar controlant el numero d'operacions que s'han guardat.

Altres

A la memoria del projecte (pfc1380.pdf) es llisten algunes millores que es podrien fer (Secció 5.3 Trabajo futuro). Es interessant sobretot la linia de temps que permetria fer rewind al projecte fins a un punt anterior, similar al desfer però sense haver de mostrar un llistat d'operacions. Per això existeix la funció dr_seek_to(handle,(time_t)pos) a la llibreria.