Articolo pubblicato su CP nello speciale "System Programming"

 

 

Long file name in DOS e Windows 3.x

di Roberto Bianchi

 

 

Una delle caratteristiche di Windows 95 che ne hanno aumentato la flessibilità e la semplicità d'uso è la possibilità di utilizzare un nome di file non limitato al formato 8.3. Il supporto dei nomi lunghi ha accorciato le distanze rispetto ad altri sistemi operativi come System di APPLE  e OS/2. Il nome lungo (d'ora in poi LFN, Long File Name) permette di chiamare file e directory in modo significativo ed esplicativo.

Per poter supportare i LFN Microsoft ha dovuto estendere il vecchio file system (sistema di gestione dei file su dischi) ereditato dal DOS e aggiungervi il nome lungo e altre informazioni. Questo articolo analizzerà in dettaglio la nuova mappa degli interrupt e mostrerà come scrivere una libreria C per gestire i nomi lunghi anche da programmi DOS e Windows 3.x.

 

Il file system FAT

Nel 1981 IBM introdusse sul mercato il suo primo personal computer che utilizzava un nuovo sistema operativo appositamente sviluppato da Microsoft, il Disk Operating System. Il computer si basava su un microprocessore INTEL a 16 bit (8088) e due floppy disk a bassa densità. Quel sistema operativo (il DOS) utilizzava come file system la FAT (File Allocation Table), così chiamata dal nome di una sua componente principale, la tabella di allocazione dei file.

In  Figura 1 è riportata la schematizzazione del file system FAT.

 

Il primo blocco di Figura 1 contiene informazioni sulle partizioni presenti nel disco. Nel settore di boot c'è il codice che viene eseguito a ogni accensione della macchina per caricare il sistema operativo. Esso eventualmente accederà al settore di boot di un'altra partizione nel caso di più sistemi operativi sullo stesso disco.

I due blocchi successivi contengono la FAT propriamente detta, cioè la tabella che ha dato il nome al file system del DOS. Essa è un array di valori (puntatori ai cluster del disco) a 12 bit  per la FAT12 o a 16 bit per la FAT16. Questa tabella è presente in duplice copia (FAT1 e FAT2) per motivi di sicurezza, infatti è proprio grazie alla doppia copia della FAT che i comandi DOS CHKDSK e SCANDISK sono in grado di recuperare gli eventuali collegamenti persi sul disco. La Figura 2 mostra come è organizzato il blocco FAT1 e quindi FAT2, evidenziando in particolare come sia possibile determinare l’esatta posizione del file sul disco estraendo il cluster iniziale dalla root directory.

 

Ad esempio il file FILEA che ha come riferimento 0002 nel puntatore al cluster iniziale (informazione contenuta nel quarto o quinto blocco del file system), è contenuto nei cluster 2, 3 e 4.Come dicevamo l’ampiezza del valore nel blocco FAT1 può essere a 12, 16 o 32 bit, permettendo quindi di gestire rispettivamente 4096, 65536 e 232 cluster.

In realtà il numero dei cluster disponibili è leggermente inferiore visto che alcuni valori sono riservati. Ad esempio una singola entrata nella FAT12 (usata da MS-DOS 3.x, 4.x e 5.0) può contenere  i seguenti valori:

 

000h                Cluster libero

001h                Cluster inutilizzato

FF0h-FF6h      Valore riservato

FF7h                Cluster danneggiato

FF8h-FFFh      Ultimo cluster del file

002h-FEFh      Cluster occupato dal file (max. 4078 cluster)

 

Per la FAT16 cambia solo l'ampiezza del singolo valore dell'array, ad esempio un cluster danneggiato viene segnalato dal valore FFF7h invece di FF7h. Un discorso a parte meriterebbe la FAT32 resa disponibile dalla versione OSR 2 di Windows 95 che ha introdotto modifiche rilevanti, compresa una nuova e interessante interfaccia dell’interrupt 21h.

Il quarto blocco di Figura 1 (la root directory) contiene i riferimenti dei file e delle directory presenti nella root  (directory principale del disco) compreso il primo cluster utilizzato dal file che viene usato come indice per l’accesso alla tabella FAT1.

Il quinto, invece, elenca tutti i riferimenti ai file e alle sottodirectory che fanno parte di quelle contenute nella root. In pratica il quarto e il quinto blocco sono un array di strutture lunghe 32 byte simili a quelle di Figura 3, in cui i campi Ora-Data di Creazione e Data Ultimo Accesso non sono utilizzati.

 

Notare che i primi quattro blocchi hanno dimensione e posizione fissa sul volume, mentre l'ultimo blocco ha dimensioni e posizioni variabili dal momento che cresce all'aumentare delle sottodirectory e dei file sul disco. L'ultimo blocco, infatti, è solo una catena di cluster (chain cluster) registrati nelle tabelle FAT1 e FAT2 che può crescere fino ad occupare tutto lo spazio disponibile sul disco. Ricapitolando, quando su un PC con il file system FAT si immette il comando type c:\autoexec.bat il sistema operativo ricerca nella root directory la struttura di 32 byte che contiene il nome autoexec.bat e se la trova estrae da essa il puntatore al primo cluster nel blocco FAT1 e da lì legge il numero di tutti i cluster utilizzati dal file. Con questa lista in mano il sistema operativo è grado di leggere l’intero file dal disco e di visualizzarlo sullo schermo.

 

 

La VFAT di Windows 95

La Figura 3 riporta la struttura di una singola entrata  (blocco primario) nella root directory per la VFAT di Windows 95. La VFAT ha fatto la sua prima comparsa nella versione 3.5 di Windows NT. Oggi NT supporta la VFAT ma il suo file system nativo è il NTFS che offre un più elevato grado di affidabilità.

Ogni entrata della root directory ha dimensione fissa di 32 byte. Windows 95 utilizza proprio queste strutture di dati per memorizzare il nome lungo del file.

Per ogni file o directory con il nome lungo Windows crea un blocco primario con il nome in formato 8.3. L'algoritmo utilizzato, è descritto tra le pieghe della documentazione ufficiale. Al blocco principale sono poi aggiunti uno o più blocchi secondari. La cosa dipende dalla lunghezza del nome, considerato che su ogni blocco ci stanno 13 caratteri UNICODE ). Questi blocchi secondari sono marcati come oggetti di volume, read-only, sistema e nascosti 0Fh in offset Bh di Figura 3 invece di 20h (per i file). Per il DOS e quindi anche per i programmi WIN16 i blocchi così marcati sono invisibili, i vecchi programmi vedono solamente il blocco primario contenente l’alias MS-DOS ovvero il nome corto del file.

 

Nomi lunghi a 16 bit

Questa rapida carrellata sulla VFAT è servita per rendersi conto che le informazioni relative ai file con il nome lungo ci sono sempre sotto Windows 95 e sono disponibili anche ai programmi DOS e Win16. Come fare per accedervi? Si potrebbero scrivere delle funzioni a basso livello che siano in grado di gestire le nuove entrate nella VFAT. In sostanza si tratterebbe di scrivere una nuova versione delle funzioni per la manipolazione dei file attraverso l'interrupt 21h. Obiettivo decisamente ambizioso oltre che pericoloso per l'integrità di tutti i dati su disco.

In alternativa si potrebbe scrivere solo una routine che ottenga trasparentemente dalla nuova VFAT l'alias MS-DOS (cioè il nome in formato 8.3) ed esegua poi tutte le funzioni su di esso. Anche in questo caso, però, dovrebbero essere riscritte ex-novo le funzioni dell'interrupt 21h relative alla creazione e alla ridenominazione dei file.

 

Accesso ai file con l'interrupt 21h

Nel modulo Kernel32 l'accesso ai file avviene attraverso un VxD indipendentemente dal fatto che il codice sia a 16 o 32 bit. I nuovi servizi sono stati sviluppati in modo del tutto simile ai loro omologhi forniti da MS-DOS. Dal punto di vista del programmatore basterà inserire nel registro AH il valore 71h e poi gestire il tutto come se fosse una normale chiamata a MS-DOS. La Tabella1 riassume tutte le funzioni di gestione dei file e delle directory accessibili attraverso la nuova interfaccia dell'interrupt 21h di Windows 95. Nella tabella viene evidenziata, quando esiste, la corrispondente funzione Win32 che incapsula la funzione. Oltre a quelli indicati esistono altri servizi, dalla funzione 71A2h alla 71A5h, ma come riporta la documentazione Microsoft, essi sono riservate ad uso interno di Windows 95.

 

La directory corrente

Analizziamo ora in dettaglio una delle funzioni  riportate nella Tabella1. La funzione prescelta è la 7147h, cioè quella che restituisce la directory corrente del task. Il servizio fornito in precedenza da MS-DOS richiedeva che i registri della CPU fossero impostati come segue:

 

Prima dell'interrupt

     AL = 47h    

     DL = Drive 0 corrente, 1 A:, 2 B: ...    

     DS = Segmento del buffer per la directory

     SI = Offset del buffer per la directory

Errore

     Flag di carry impostato

     AX = Codice errore

 

Naturalmente questo servizio è tuttora supportato, ma nel caso in cui la directory ha il nome lungo, questa funzione restituirebbe in DS:SI solo il nome nel formato 8.3. Per ottenere il nome lungo, invece, basta effettuare la chiamata all'interrupt 21h con la seguente impostazione:

 

Prima dell'interrupt

     AX = 7147h     

     DL = Drive 0 corrente, 1 A:, 2 B: ...    

     DS = Segmento del buffer per la directory

     SI = Offset del buffer per la directory

Errore

     Flag di carry impostato

     AX = Codice errore

 

Risulta evidente che la sola azione diversa da compiere è quella di caricare il valore 71h nel registro AH. Il programmatore che ha già una certa esperienza  nell'utilizzo degli interrupt in programmi DOS non avrà nessuna difficoltà nel riconoscere ed usare questa nuova interfaccia dell'interrupt 21h. Per gli altri si tratta solo di seguire alla lettera le istruzioni, magari dando un'occhiata a qualche vecchio manuale DOS.

 

Sviluppo di una API

A questo punto disponiamo di tutte le informazioni necessarie per mettere a punto un'apposita libreria che consenta a programmi DOS e Windows 3.x di utilizzare i nomi lunghi. Dovendo lavorare con i registri della CPU è preferibile utilizzare il linguaggio Assembly per lo sviluppo delle funzioni. In verità si potrebbe scrivere tutto in C, ma si perderebbe parecchio in termini di efficienza e di velocità e visto l'obiettivo è scrivere librerie, non è proprio il caso di farlo!

Lasceremo al C il compito di gestire i parametri della funzione e di restituire il codice d'errore. Tutte le routine hanno una struttura simile e prevedono l'impostazione dei registri secondo le regole mostrate nella Tabella1 (vedere il file interrupt.doc per i dettagli) e l'invocazione dell'interrupt. Il seguente frammento di codice (tratto dal file lfn16s.c) presenta la funzione GetCurrentDirectory(), che può considerata una sorta di template generico. Il sorgente Assembly è inglobato nel codice C.

 

int GetCurrentDirectory(LPSTR buf, BYTE drv)

{

  int err;

  _asm {

            push ds

stc

            mov ax, 7147h  ;directory corrente

            mov dl, drv

            mov si, WORD PTR buf[2]

            mov ds, si

            mov si, WORD PTR buf[0]

            int 21h

 

            jc  error

            xor ax, ax  ;nessun errore

 

    error:

            pop ds

            mov err, ax

  }

  return err;

}

 

Sul codice di GetCurrentDirectory() vi sono due osservazioni da fare. La prima è che, sebbene nella nell'esempio non sia indispensabile, è sempre buona norma salvare all'entrata e ripristinare all'uscita il registro del segmento dati (DS). La seconda cosa da notare è che impostando il carry prima della chiamata int 21h ci si assicura che se dopo la sua esecuzione esso è a off, sicuramente l'interrupt lo ha disattivato per segnalare che l’interrupt è stato servito e non si sono verificati errori durante la sua esecuzione.

GetCurrentDirectory() restituisce 0 se non ci sono stati errori. Al termine il buffer puntato da buf contiene il nome della directory corrente.

La libreria fornita a corredo dell'articolo contiene anche le seguenti funzioni:

 

FileExist()

IsLFNSupported()

closes()

reads()

writes()

seeks()

tells()

 

Esse sono state aggiunte per completare la funzionalità della libreria e per semplificare il suo utilizzo. La funzione IsLFNSupported() è disponibile solo nella DLL per Windows. Le ultime cinque funzioni sostituiscono le corrispondenti funzioni standard del C (senza la 's' finale) che dipendono dal modello di memoria scelto.

Per alcuni dei servizi elencati nella Tabella1 sono state sviluppate più routine a seconda del numero di funzionalità effettivamente offerte. Per esempio la funzione 7143h, che legge e imposta gli attributi di un file, prevede l'esecuzione di nove azioni differenti, determinate in base al contenuto del registro BL.

Tutte le funzioni sono contenute nel file lfn16s.c da cui derivano sia la libreria per DOS che quella per Windows 3.x.

In allegato con il file lfn16s.c troverete un makefile per la versione WIN16 e uno per la versione DOS. In caso di compilazione della libreria DOS bisogna rimuovere la definizione di WIN16_DLL_LIBRARY (da lfn16s.c) poiché alcune funzioni tipo LibMain() e WEP() sono senza significato per il DOS.

 

Test della libreria

Per mettere alla prova la libreria ho realizzato un semplice programma (vedere la Figura 4) che utilizza la funzione GetVolumeInfo() per visualizzare le caratteristiche del file system presente sul disco fisso.

 

 

Esso mostra la directory corrente con GetCurrentDirectory(), crea un file dal nome lungo con CreateFile() e vi scrive dentro alcuni byte, lo chiude e ne verifica lo stato tramite FileExist(). Del programma esistono poi due versioni, dos.exe per il DOS e lfn16.exe per Windows, che fanno esattamente la stessa cosa. 

Naturalmente se si cercasse di eseguire uno dei due software su un PC senza Windows 95, tutto ciò che si otterrebbe sarebbe un messaggio di avvertimento dal momento che il programma non può essere eseguito. Lo stesso accadrebbe in WINDOWS NT, poiché per quanto mi risulta fino alla versione 4.0 questa nuova interfaccia dell’interrupt 21h non è stata ancora inclusa nel sistema operativo. E ciò e perfettamente logico in quanto la compatibilità con le applicazioni MS-DOS e WIN16 non è una condizione sine qua non nel progetto di NT.

In allegato sono presenti le librerie doslfn.lib per il DOS e lfn16s.dll e lfn16s.lib (libreria d'importazione) per Windows 3.x più il file header e un makefile per Visual C++ 1.52. Sul dischetto è presente anche il file interrupt.doc che racchiude in forma digitale il contenuto della Tabella 1 con relativi dettagli.

 

Conclusioni

Come si è visto,  la nuova interfaccia dell'interrupt 21h è relativamente semplice da utilizzare. Nonostante ciò è consigliabile limitarne il suo uso laddove proprio non se ne può fare a meno. È possibile, infatti, che in futuro questi servizi spariscano.

Attualmente sia le funzioni a 16 bit che quelle a 32 bit passano tramite l'imbuto dell'interrupt 21h, anzi per certi versi si potrebbe dire che le funzioni a 16 bit siano più dirette rispetto a quelle a 32, ma il presente passa per i 32 bit e fortunatamente non si fermerà lì.

 

 

 

Roberto Bianchi lavora nel campo dell’informatica da diversi anni, occupandosi di programmazione a basso ed alto livello su PC, Reti e Sistemi dipartimentali.