Skip to content

Come lavorare con diversi file system

Node.js espone molte funzionalità dei file system. Ma non tutti i file system sono uguali. Di seguito sono riportate le migliori pratiche suggerite per mantenere il tuo codice semplice e sicuro quando si lavora con diversi file system.

Comportamento del file system

Prima di poter lavorare con un file system, è necessario sapere come si comporta. Diversi file system si comportano in modo diverso e hanno più o meno funzionalità di altri: distinzione tra maiuscole e minuscole, indifferenza tra maiuscole e minuscole, conservazione delle maiuscole e minuscole, conservazione della forma Unicode, risoluzione del timestamp, attributi estesi, inode, autorizzazioni Unix, flussi di dati alternativi ecc.

Fai attenzione a inferire il comportamento del file system da process.platform. Ad esempio, non presumere che, poiché il tuo programma è in esecuzione su Darwin, stai quindi lavorando su un file system che non distingue tra maiuscole e minuscole (HFS+), poiché l'utente potrebbe utilizzare un file system che distingue tra maiuscole e minuscole (HFSX). Allo stesso modo, non presumere che, poiché il tuo programma è in esecuzione su Linux, stai quindi lavorando su un file system che supporta autorizzazioni Unix e inode, poiché potresti trovarti su un particolare disco esterno, USB o di rete che non lo fa.

Il sistema operativo potrebbe non rendere facile l'inferenza del comportamento del file system, ma non tutto è perduto. Invece di tenere un elenco di ogni file system e comportamento noto (che sarà sempre incompleto), puoi sondare il file system per vedere come si comporta effettivamente. La presenza o l'assenza di determinate funzionalità che sono facili da sondare, sono spesso sufficienti per inferire il comportamento di altre funzionalità che sono più difficili da sondare.

Ricorda che alcuni utenti potrebbero avere diversi file system montati in vari percorsi nell'albero di lavoro.

Evita un approccio al minimo comune denominatore

Potresti essere tentato di far agire il tuo programma come un file system di minimo comune denominatore, normalizzando tutti i nomi di file in maiuscolo, normalizzando tutti i nomi di file nella forma Unicode NFC e normalizzando tutti i timestamp dei file a una risoluzione di 1 secondo. Questo sarebbe l'approccio del minimo comune denominatore.

Non farlo. Saresti in grado di interagire in modo sicuro solo con un file system che ha esattamente le stesse caratteristiche del minimo comune denominatore in ogni aspetto. Non saresti in grado di lavorare con file system più avanzati nel modo in cui gli utenti si aspettano e ti imbatteresti in collisioni di nomi di file o timestamp. Molto probabilmente perderesti e corromperesti i dati dell'utente attraverso una serie di complicati eventi dipendenti e creeresti bug che sarebbero difficili se non impossibili da risolvere.

Cosa succede quando in seguito devi supportare un file system che ha solo una risoluzione del timestamp di 2 secondi o 24 ore? Cosa succede quando lo standard Unicode avanza per includere un algoritmo di normalizzazione leggermente diverso (come è successo in passato)?

Un approccio al minimo comune denominatore tenderebbe a cercare di creare un programma portatile utilizzando solo chiamate di sistema "portatili". Questo porta a programmi che sono difettosi e in realtà non portatili.

Adotta un approccio superset

Sfrutta al meglio ogni piattaforma che supporti adottando un approccio superset. Ad esempio, un programma di backup portatile dovrebbe sincronizzare correttamente i btime (l'ora di creazione di un file o una cartella) tra i sistemi Windows e non dovrebbe distruggere o alterare i btime, anche se i btime non sono supportati sui sistemi Linux. Lo stesso programma di backup portatile dovrebbe sincronizzare correttamente i permessi Unix tra i sistemi Linux e non dovrebbe distruggere o alterare i permessi Unix, anche se i permessi Unix non sono supportati sui sistemi Windows.

Gestisci i diversi filesystem facendo agire il tuo programma come un filesystem più avanzato. Supporta un superset di tutte le funzionalità possibili: distinzione tra maiuscole e minuscole, conservazione delle maiuscole e minuscole, sensibilità alla forma Unicode, conservazione della forma Unicode, permessi Unix, timestamp ad alta risoluzione in nanosecondi, attributi estesi ecc.

Una volta che hai la conservazione delle maiuscole e minuscole nel tuo programma, puoi sempre implementare l'insensibilità alle maiuscole e minuscole se hai bisogno di interagire con un filesystem insensibile alle maiuscole e minuscole. Ma se rinunci alla conservazione delle maiuscole e minuscole nel tuo programma, non puoi interagire in modo sicuro con un filesystem che conserva le maiuscole e minuscole. Lo stesso vale per la conservazione della forma Unicode e la conservazione della risoluzione dei timestamp.

Se un filesystem ti fornisce un nome file in un mix di minuscole e maiuscole, conserva il nome file esattamente nella forma fornita. Se un filesystem ti fornisce un nome file in forma Unicode mista o NFC o NFD (o NFKC o NFKD), conserva il nome file esattamente nella sequenza di byte fornita. Se un filesystem ti fornisce un timestamp in millisecondi, conserva il timestamp con una risoluzione in millisecondi.

Quando lavori con un filesystem inferiore, puoi sempre sottocampionare in modo appropriato, con funzioni di confronto come richiesto dal comportamento del filesystem su cui è in esecuzione il tuo programma. Se sai che il filesystem non supporta i permessi Unix, non dovresti aspettarti di leggere gli stessi permessi Unix che scrivi. Se sai che il filesystem non conserva le maiuscole e minuscole, dovresti essere pronto a vedere ABC in un elenco di directory quando il tuo programma crea abc. Ma se sai che il filesystem conserva le maiuscole e minuscole, dovresti considerare ABC come un nome file diverso da abc, quando rilevi la ridenominazione dei file o se il filesystem è sensibile alle maiuscole e minuscole.

Conservazione della maiuscola/minuscola

Potresti creare una directory chiamata test /abc e rimanere sorpreso di vedere a volte che fs.readdir('test') restituisce ['ABC']. Questo non è un bug in Node. Node restituisce il nome del file come lo memorizza il filesystem, e non tutti i filesystem supportano la conservazione della maiuscola/minuscola. Alcuni filesystem convertono tutti i nomi di file in maiuscolo (o minuscolo).

Conservazione della forma Unicode

La conservazione della maiuscola/minuscola e la conservazione della forma Unicode sono concetti simili. Per capire perché la forma Unicode dovrebbe essere conservata, assicurati di aver prima capito perché la maiuscola/minuscola dovrebbe essere conservata. La conservazione della forma Unicode è altrettanto semplice quando compresa correttamente. Unicode può codificare gli stessi caratteri usando diverse sequenze di byte. Diverse stringhe possono sembrare uguali, ma avere sequenze di byte diverse. Quando si lavora con stringhe UTF-8, fai attenzione che le tue aspettative siano in linea con il modo in cui funziona Unicode. Proprio come non ti aspetteresti che tutti i caratteri UTF-8 codifichino in un singolo byte, non dovresti aspettarti che diverse stringhe UTF-8 che sembrano uguali all'occhio umano abbiano la stessa rappresentazione in byte. Questa potrebbe essere un'aspettativa che puoi avere di ASCll, ma non di UTF-8.

Potresti creare una directory chiamata test/ café (forma Unicode NFC con sequenza di byte <63 61 66 c3 a9> e string.length ===5) e rimanere sorpreso di vedere a volte che fs.readdir('test') restituisce ['café'] (forma Unicode NFD con sequenza di byte <63 61 66 65 cc 81> e string.length ===6). Questo non è un bug in Node. Node.js restituisce il nome del file come lo memorizza il filesystem, e non tutti i filesystem supportano la conservazione della forma Unicode. HFS+, per esempio, normalizzerà tutti i nomi di file in una forma quasi sempre uguale alla forma NFD. Non aspettarti che HFS+ si comporti allo stesso modo di NTFS o EXT 4 e viceversa. Non provare a cambiare i dati in modo permanente attraverso la normalizzazione come un'astrazione che perde per mascherare le differenze Unicode tra i filesystem. Questo creerebbe problemi senza risolverne nessuno. Piuttosto, conserva la forma Unicode e usa la normalizzazione solo come funzione di confronto.

Invarianza alla Forma Unicode

L'invarianza alla forma Unicode e la conservazione della forma Unicode sono due comportamenti diversi del filesystem spesso confusi l'uno con l'altro. Proprio come l'invarianza al caso è stata talvolta implementata in modo errato normalizzando permanentemente i nomi dei file in maiuscolo durante l'archiviazione e la trasmissione dei nomi dei file, così l'invarianza alla forma Unicode è stata talvolta implementata in modo errato normalizzando permanentemente i nomi dei file a una certa forma Unicode (NFD nel caso di HFS+) durante l'archiviazione e la trasmissione dei nomi dei file. È possibile e molto meglio implementare l'invarianza alla forma Unicode senza sacrificare la conservazione della forma Unicode, utilizzando la normalizzazione Unicode solo per il confronto.

Confronto tra diverse forme Unicode

Node.js fornisce string.normalize ('NFC' / 'NFD') che puoi usare per normalizzare una stringa UTF-8 in NFC o NFD. Non dovresti mai memorizzare l'output di questa funzione ma usarlo solo come parte di una funzione di confronto per verificare se due stringhe UTF-8 apparirebbero uguali all'utente. Puoi usare string1.normalize('NFC')=== string2.normalize('NFC') o string1.normalize('NFD')=== string2.normalize('NFD') come funzione di confronto. Quale forma usi non importa.

La normalizzazione è veloce, ma potresti voler usare una cache come input per la tua funzione di confronto per evitare di normalizzare la stessa stringa molte volte. Se la stringa non è presente nella cache, normalizzala e memorizzala nella cache. Fai attenzione a non archiviare o persistere la cache, usala solo come cache.

Tieni presente che l'utilizzo di normalize () richiede che la tua versione di Node.js includa ICU (altrimenti normalize () restituirà semplicemente la stringa originale). Se scarichi l'ultima versione di Node.js dal sito web, includerà ICU.

Risoluzione Timestamp

Potresti impostare l'mtime (l'ora di modifica) di un file su 1444291759414 (risoluzione in millisecondi) ed essere sorpreso di vedere a volte che fs.stat restituisce il nuovo mtime come 1444291759000 (risoluzione a 1 secondo) o 1444291758000 (risoluzione a 2 secondi). Questo non è un bug in Node. Node.js restituisce il timestamp così come lo memorizza il filesystem, e non tutti i filesystem supportano la risoluzione del timestamp in nanosecondi, millisecondi o 1 secondo. Alcuni filesystem hanno anche una risoluzione molto grossolana per il timestamp atime in particolare, ad esempio 24 ore per alcuni filesystem FAT.

Non Corrompere Nomi di File e Timestamp Tramite Normalizzazione

Nomi di file e timestamp sono dati dell'utente. Proprio come non riscriveresti mai automaticamente i dati dei file dell'utente per trasformare i dati in maiuscolo o normalizzare i terminatori di riga CRLF in LF, così non dovresti mai modificare, interferire o corrompere nomi di file o timestamp tramite la normalizzazione di case / forma Unicode / timestamp. La normalizzazione dovrebbe essere usata solo per il confronto, mai per alterare i dati.

La normalizzazione è effettivamente un codice hash con perdita di dati. Puoi usarla per testare certi tipi di equivalenza (ad es. se diverse stringhe appaiono uguali anche se hanno sequenze di byte diverse) ma non puoi mai usarla come sostituto dei dati effettivi. Il tuo programma dovrebbe passare i dati dei nomi di file e dei timestamp così come sono.

Il tuo programma può creare nuovi dati in NFC (o in qualsiasi combinazione di forma Unicode preferisca) o con un nome file in minuscolo o maiuscolo, o con un timestamp con una risoluzione di 2 secondi, ma il tuo programma non dovrebbe corrompere i dati utente esistenti imponendo la normalizzazione di case / forma Unicode / timestamp. Piuttosto, adotta un approccio di superset e preserva case, forma Unicode e risoluzione del timestamp nel tuo programma. In questo modo, sarai in grado di interagire in sicurezza con i filesystem che fanno lo stesso.

Usa le Funzioni di Confronto di Normalizzazione Appropriatamente

Assicurati di usare le funzioni di confronto di case / forma Unicode / timestamp in modo appropriato. Non usare una funzione di confronto di nomi di file che non fa distinzione tra maiuscole e minuscole se stai lavorando su un filesystem che fa distinzione tra maiuscole e minuscole. Non usare una funzione di confronto che non fa distinzione tra forma Unicode se stai lavorando su un filesystem che fa distinzione tra forma Unicode (ad es. NTFS e la maggior parte dei filesystem Linux che preservano sia NFC che NFD o forme Unicode miste). Non confrontare i timestamp con una risoluzione di 2 secondi se stai lavorando su un filesystem con una risoluzione del timestamp in nanosecondi.

Preparati a Leggere Differenze nelle Funzioni di Confronto

Fai attenzione che le tue funzioni di confronto corrispondano a quelle del filesystem (o sonda il filesystem se possibile per vedere come lo confronterebbe effettivamente). L'insensibilità alle maiuscole, ad esempio, è più complessa di un semplice confronto toLowerCase(). In realtà, toUpperCase() è solitamente migliore di toLowerCase() (poiché gestisce alcuni caratteri di lingue straniere in modo diverso). Ma ancora meglio sarebbe sondare il filesystem poiché ogni filesystem ha la sua tabella di confronto di case integrata.

Ad esempio, HFS+ di Apple normalizza i nomi di file in forma NFD, ma questa forma NFD è in realtà una versione precedente dell'attuale forma NFD e a volte può essere leggermente diversa dalla forma NFD dell'ultimo standard Unicode. Non aspettarti che HFS+ NFD sia esattamente uguale a Unicode NFD tutto il tempo.