Ripara uno store Core Data di iOS danneggiato

Sotto l'API di Swift e Objective-C, Core Data scrive un normale database SQLite 3. Quando l'app segnala che lo store "non è nel formato corretto", di solito significa che si è rotta una sola pagina o un puntatore — le righe che la tua app ha salvato sono ancora dentro al file. Recuperarle vuol dire leggere il file pagina per pagina, non consegnare i dati dei tuoi utenti a un server.

Il tuo file non lascia mai il tuo dispositivo — la riparazione avviene nel browser. 0 byte caricati.

Your data is the big block — usually intact. What breaks is the small index. Repair rebuilds it.

Drop a file here or browseTap to choose a file

ZIP · Office · PDF · video · JPG · PNG · RAR/7z · SQLite — repaired right here in your browser. Nothing is uploaded

0 bytes uploadedFree: 3 repairs/day — up to 500 MB video, 100 MB docs & archives, 50 MB photos. Free download. Single repair €5.90. No account required.

Ripara ora

  1. Trascina il file
  2. La riparazione avviene in locale
  3. Scarica il risultato

Il tipo di persistent store predefinito e consigliato di Core Data è NSSQLiteStoreType: un normale database SQLite 3 su disco, di solito chiamato <AppName>.sqlite dentro la directory Library dell'app. Quando non si apre, il coordinatore dello store lancia un errore NSCocoaErrorDomain e SQLite definisce il file malformato, ma un database malformato è raramente vuoto: le righe sono ancora nelle loro pagine, dietro un indice rotto. Trascina il .sqlite qui sopra — e aggiungi il suo file collaterale -wal se ce l'hai — e lo strumento legge il file nel tuo browser, ricostruisce lo schema e reinserisce ogni riga che riesce ancora a decodificare in uno store nuovo e apribile. Non si installa nulla e il database non viene mai caricato.

Cosa scrive davvero Core Data sul disco

Core Data è un framework di grafo di oggetti e persistenza, non un database — il database è SQLite, e Core Data ne controlla la disposizione. Apri uno store Core Data in un qualsiasi browser SQLite e non vedrai nomi di tabella comprensibili; vedrai uno schema deformato generato dal framework. Ogni entità diventa una tabella chiamata Z + il nome dell'entità in maiuscolo, quindi un'entità Person è la tabella ZPERSON. Ogni riga porta tre colonne di sistema: Z_PK (la chiave primaria intera), Z_ENT (quale entità/sottoclasse è la riga) e Z_OPT (un contatore di versione per il blocco ottimistico). Anche i tuoi attributi hanno un prefisso — firstName diventa ZFIRSTNAME — e una relazione a-uno è memorizzata come colonna di chiave esterna che contiene il Z_PK della riga di destinazione.

Accanto ai tuoi dati ci sono tre tabelle di servizio. Z_PRIMARYKEY contiene una riga per entità con le colonne Z_ENT, Z_NAME, Z_SUPER e Z_MAX — la chiave primaria più alta assegnata finora, che Core Data incrementa per allocare l'inserimento successivo. Z_METADATA contiene Z_VERSION, Z_UUID e Z_PLIST, un blob plist binario che memorizza i metadati dello store, inclusi NSStoreModelVersionHashes e l'UUID dello store. Z_MODELCACHE mette in cache il modello compilato. Le date sono memorizzate come un valore REAL di secondi a partire dalla data di riferimento di Cocoa, 2001-01-01 00:00:00 UTC — non l'epoca Unix — quindi un timestamp grezzo pari a 0 è il primo gennaio 2001.

Tutto questo vive in un contenitore SQLite standard: un array piatto di pagine di dimensione fissa (4096 byte per impostazione predefinita), dove i primi 16 byte del file sono la stringa magica SQLite format 3\000. La pagina 1 contiene l'intestazione del file e lo schema sqlite_master; ogni tabella Z è un b-tree le cui pagine interne puntano verso il basso alle pagine foglia che contengono i record veri e propri. Rompi un puntatore e SQLite rifiuta l'intero file — anche se le pagine foglia piene di righe sono intatte.

I file -wal e -shm: quale contiene i tuoi dati

Da iOS 7 (e OS X 10.9 Mavericks) Core Data apre il suo store in modalità WAL — write-ahead logging — per impostazione predefinita. Ecco perché un singolo store è in realtà fino a tre file su disco: <AppName>.sqlite, <AppName>.sqlite-wal e <AppName>.sqlite-shm. Capire la differenza decide se i tuoi dati più recenti sopravvivono.

Il file -wal è il log di scrittura anticipata (write-ahead log). Quando la tua app conferma (commit) una transazione, le pagine modificate vengono aggiunte in coda al -wal come frame invece di essere scritte direttamente nel .sqlite principale; solo un checkpoint periodico le reintegra nel database. La conseguenza: se l'app è andata in crash o è stata chiusa forzatamente prima di un checkpoint, le righe confermate più recenti possono vivere solo nel -wal. La sua disposizione è precisa — un'intestazione di 32 byte (numero magico 0x377f0682 o 0x377f0683, una versione di formato, la dimensione di pagina, una sequenza di checkpoint, due valori di salt e un checksum) seguita da frame, ognuno con un'intestazione di frame di 24 byte (numero di pagina, la dimensione del database in pagine per un frame di commit o 0 altrimenti, i due salt e due checksum) più una pagina di dati.

Il file -shm è l'indice-wal in memoria condivisa (wal-index): una struttura di lookup che SQLite usa per trovare rapidamente le pagine dentro il -wal. È interamente dato derivato — non contiene nessuna delle tue righe e SQLite lo ricostruisce automaticamente a partire dal -wal. Quindi la regola è semplice: porta il -wal, ignora lo -shm. Trascina prima il .sqlite, poi aggiungi il -wal nello slot collaterale opzionale: i suoi frame confermati vengono sovrapposti prima della scansione, così il recupero riflette l'ultima transazione confermata invece dell'ultimo checkpoint. Copiare solo il .sqlite da un dispositivo e lasciare indietro il -wal è il modo classico di perdere dati recenti in silenzio — una trappola sia per il recupero dopo un crash sia per l'estrazione forense.

Gli errori che vedi e cosa recupera il salvataggio delle righe

A livello di SQLite lo store fallisce con uno di due codici di risultato. SQLITE_CORRUPT (codice 11) stampa "database disk image is malformed": il motore ha seguito un puntatore e ha trovato qualcosa che non è una pagina b-tree valida — una pagina azzerata, un offset di cella errato, un conteggio di pagine che non concorda con la lunghezza del file. SQLITE_NOTADB (codice 26) stampa "file is not a database": i 16 byte magici dell'intestazione sono errati, il che significa o che i byte dell'intestazione sono stati danneggiati (le pagine dietro di essi spesso sono a posto) oppure che il file è davvero qualcos'altro o è cifrato. Core Data lo incapsula e presenta NSCocoaErrorDomain codice 259 (NSFileReadCorruptFileError) — "The file couldn't be opened because it isn't in the correct format." — mentre il codice SQLite grezzo è riposto nello userInfo dell'errore sotto la chiave NSSQLiteErrorDomain, quindi una riga di log che riporta NSSQLiteErrorDomain=11 è la conferma che si tratta di corruzione a livello di pagina, non di un'incompatibilità di versione dello schema.

Il recupero non cerca di riparare il file rotto sul posto. Legge tutto ciò che è ancora decodificabile e lo scrive in un database del tutto nuovo — la stessa strategia del comando .recover di SQLite. Percorre il b-tree di ogni tabella Z e decodifica i record delle foglie; per le pagine che la catena di puntatori non riesce più a raggiungere, ricorre a una scansione grezza delle pagine, catturando record autodescrittivi direttamente dal file. Le righe che non si possono attribuire a una tabella conosciuta finiscono in una tabella lost_and_found invece di essere buttate via, e i valori Z_MAX di Z_PRIMARYKEY vengono ricostruiti a partire dalle righe recuperate, così l'app può continuare a inserire senza collisioni di chiave primaria. Poiché la scansione legge anche le pagine della freelist e non compattate (un-vacuumed), le righe che la tua app aveva cancellato prima possono riaffiorare — utile, ma è bene saperlo.

I dati binari esterni vivono fuori dallo store

Un limite onesto è specifico di Core Data. Quando un attributo binario (una foto, un PDF, dell'audio) ha spuntata l'opzione "Allows External Storage", Core Data decide valore per valore se incorporare i byte nella riga oppure scriverli come file separato quando superano all'incirca i 100 KB. Questi blob esterni vengono memorizzati in una directory di supporto nascosta accanto allo store — .<storename>_SUPPORT/_EXTERNAL_DATA/ — con nomi di file UUID, e la riga del database conserva solo un riferimento a quel file, non i byte.

Quindi recuperare il solo .sqlite riporta indietro ogni attributo scalare e ogni blob incorporato, ma per i dati memorizzati esternamente restituisce il riferimento, non l'immagine. Se hai anche la cartella _EXTERNAL_DATA dello stesso container dell'app, quei file si riabbinano per nome; se quella directory è sparita, nessun recupero del database può ricostruire byte che non sono mai stati dentro al database. E poiché uno store Core Data può contenere l'intero stato privato di un'app — ogni nota, messaggio, dato sanitario o account che un utente abbia mai salvato — tutto questo gira nel tuo browser: il file viene letto dal disco, ricostruito nella scheda, e puoi guardare la scheda Rete confermare che 0 byte lasciano la tua macchina.

Cosa può e cosa non può riparare

Può riparare

  • "database disk image is malformed" (SQLITE_CORRUPT / codice 11) causato da una pagina b-tree danneggiata o da un puntatore di cella errato, con le pagine ZENTITY retrostanti intatte
  • Righe di ogni tabella ZENTITY che si decodificano ancora — estratte e reinserite in uno store .sqlite nuovo e apribile
  • Modifiche confermate senza checkpoint, sovrapposte da un file collaterale -wal fornito (le righe più recenti dopo un crash o una chiusura forzata)
  • Uno store la cui intestazione di 16 byte o il cui schema è danneggiato ma le cui pagine foglia sopravvivono — lo schema viene ricostruito a partire dalle pagine
  • Righe che i puntatori del b-tree non raggiungono più, recuperate con una scansione grezza delle pagine in una tabella lost_and_found, con Z_PRIMARYKEY.Z_MAX ricostruito

Non può riparare

  • Righe sovrascritte fisicamente o troncate dalla fine del file — quei byte non esistono più sul disco
  • Blob binari esterni memorizzati in .<storename>_SUPPORT/_EXTERNAL_DATA quando non hai anche quei file — la riga contiene solo un riferimento
  • Store cifrati (dati con NSFileProtectionComplete copiati mentre il dispositivo era bloccato, oppure SQLCipher) senza la chiave — le pagine si leggono come testo cifrato
  • Il file -shm da solo: è un indice-wal ricostruibile, non i tuoi dati — il -wal è il file che porta i commit recenti
  • Una garanzia che i dati recuperati siano completi o corretti — i dati provenienti da uno store danneggiato sono sempre da mettere in dubbio; verificali confrontandoli con una fonte attendibile

Se una riparazione fallisce, ti diciamo perché (dati mancanti rispetto a struttura danneggiata), e non ti viene mai addebitato nulla per una riparazione fallita.

Domande frequenti

La mia app registra nel log "The file couldn't be opened because it isn't in the correct format." I miei dati sono persi?

Di solito no. È NSCocoaErrorDomain codice 259 (NSFileReadCorruptFileError), e il codice SQLite grezzo che sta in userInfo sotto NSSQLiteErrorDomain è quasi sempre 11 — SQLITE_CORRUPT, "database disk image is malformed." Vuol dire che si è rotta una pagina o un puntatore e SQLite rifiuta l'intero file; le pagine foglia che contengono le tue righe sono di norma intatte e possono essere estratte in un nuovo store.

Mi servono i file -wal e -shm, oppure solo il .sqlite?

Porta il .sqlite e, se ce l'hai, il -wal. Dopo un crash le righe confermate più recenti possono vivere solo nel -wal finché non viene eseguito un checkpoint, quindi aggiungerlo come file collaterale recupera l'ultima transazione. Lo -shm è un indice in memoria condivisa ricostruibile che non contiene nessuno dei tuoi dati — puoi ignorarlo. Copiare solo il .sqlite e lasciare indietro il -wal è il modo più comune di perdere dati recenti.

Perché le tabelle si chiamano ZPERSON, Z_PK, Z_METADATA e così via?

È lo schema deformato di Core Data. Ogni entità diventa una tabella Z+ENTITÀ, ogni riga ha le colonne di sistema Z_PK, Z_ENT e Z_OPT, gli attributi hanno il prefisso Z, e le informazioni di servizio vivono in Z_PRIMARYKEY, Z_METADATA e Z_MODELCACHE. Il recupero conserva questi nomi e ricostruisce i contatori Z_MAX di Z_PRIMARYKEY, così lo store recuperato si riapre nella tua app e può ancora inserire nuove righe.

Le mie foto e i miei allegati torneranno?

Solo se erano memorizzati dentro il database. Un attributo binario con "Allows External Storage" attivato scrive i valori oltre all'incirca 100 KB in file separati sotto una directory nascosta .<storename>_SUPPORT/_EXTERNAL_DATA, conservando solo un riferimento nella riga. Recuperare il .sqlite restituisce quel riferimento; i byte tornano solo se hai anche la cartella _EXTERNAL_DATA dello stesso container dell'app.

È sicuro passare il database della mia app attraverso uno strumento di riparazione online?

Uno store Core Data è uno dei file peggiori da consegnare a un server che non controlli — può contenere ogni record, messaggio o credenziale che la tua app abbia mai salvato per un utente. Qui non si carica nulla: il file viene letto dal tuo disco e ricostruito nel tuo browser, e puoi aprire la scheda Rete e confermare che 0 byte del database vengono trasmessi.

Correlati: Riparare un database SQLite · Riparare file di Excel · Verifica la promessa di zero caricamenti