AngoLinux

Visalizzazione della directory principale e delle sottodirectory di un filesystem FAT

- A cura del Prof. Stefano Salvi -


Ci scontriamo con i cluster e con la FAT12.

Il testo dell'esercizio è il seguente:

Scrivere un programma in C che visualizzi tutte le caratteristiche del dispositivo e del file system lette dal boot sector di un dispositivo o partizione formattato con file system FAT.
Il programma visualizzerà anche la root directory (in un formato simile al DOS), sfruttando le informazioni lette nel boot record.
Quando incontrerà un'entry corrispondente ad una sottodirectory, stamperà anche il contenuto della sottodirectory, indentata di due spazi rispetto alla directory che la contiene, ricorsivamente per ogni livello di sottodirectrory. Utilizzare:
  1. Una struttura che descriva il disco, per comunicare tra le varie funzioni
  2. Una funzione che apra il dispositivo, stampi il boot record e compili la struttura del punto 1
  3. Una funzione che stampi un'entry di directory, a partire dalla posizione in byte sul disco
  4. Una funzione che stampi la root directory.
  5. Una funzione che stampi una sottodirectory, percorrendo la catena dei cluster
  6. Una funzione che calcoli la posizione in byte dell'inizio di un cluster
  7. Una funzione che legga il numero del prossimo cluster dalla FAT, dato un numero di clustrer.

NOTE:

  • Per accedere al floppy occorre aprire il dispositivo /dev/fd0 con la funzione open per i file non bufferizzati.
    Per avere i diritti su questo dispositivo occorre che il nostro utente sia associato al gruppo floppy.
    Per associare un'utente al gruppo floppy si deve utilizzare il comando gpasswd -a <utente> floppy. Il comando gpasswd richiede i privilegi di root.
  • Per accedere al primo disco fisso occorre aprire il dispositivo /dev/hda con la funzione open per i file non bufferizzati.
    Per avere i diritti su questo dispositivo occorre che il nostro utente sia associato al gruppo disk.
    Per associare un'utente al gruppo disk si deve utilizzare il comando gpasswd -a <utente> disk. Il comando gpasswd richiede i privilegi di root.
  • Per accedere alla prima partizione del primo disco fisso occorre aprire il dispositivo /dev/hda1 con la funzione open per i file non bufferizzati.
    Per avere i diritti su questo dispositivo occorre che il nostro utente sia associato al gruppo disk.
    Per associare un'utente al gruppo disk si deve utilizzare il comando gpasswd -a <utente> disk. Il comando gpasswd richiede i privilegi di root.
  • Per leggere dati da un file non bufferizzato si può usare la funzione read.
  • Per posizionarsi su di un byte di un file non bufferizzato si può usare la funzione lseek. Questa funzione ha un parametro che indica il punto di partenza dello spostamento. Noi faremo tutti spostamenti assoluti, quindi useremo la costante SEEK_SET, per indicare che la posizione indicata è relativa all'inizio del file.
  • Il file header linux/msdos_fs.h contiene la definizione delle strutture utilizzate dal file system FAT. Utilizza alcuni tipi definiti in linux/types.h.
  • In particolare considerando l'inaffidabilità dei floppy disk è indispensabile controllare gli eventuali errori nelle funzioni di posizionamento e lettura.
  • È oportuno stampare le caratteristiche di ogni file su di un'unica riga, per avere una lista facilmente leggibile. Gli attributi potranno essere stampati indicando una lettera (A per archive, S per system ecc.) se un attributo è settato, un - se non è settato.
  • La data e l'ora sono codificati ciascuna in 16 bit, secondo il seguente schema:
    Data
    bit 0..4Giorno
    bit 5..8Mese
    bit 9..15Anno (a partire dal 1980)
    Ora
    bit 0..4Secondi (di due in due)
    bit 5..10Minuti
    bit 11..15Ore
    Per decodificarle si possono utilizzare due metodi:
    • Shift e maschere
    • Struct con i campi di bit
    Consiglio di sperimentare la seconda strada
  • La directory termina quando si legge un'entry il cui nome incomincia con il carattere di vaore 0. Dato che la funzione che stampa la root directory non conoscerà il contenuto della directory entry (che sarà una variabile locale della funzione che la stampa), suggerisco che la funzione di stampa ritorni:
    la posizione della prossima dir entry
    se la dir entry non è l'ultima della directory
    zero
    se il primo carattere del nome di una directory entry vale 0, quindi è l'ultima.
  • Se il primo carattere del nome di una directory entry vale 0xe5, la directory entry è cancellata, quindi non andrà stampata
  • Se attributi di una directory entry sono ATTR_HIDDEN, ATTR_RO, ATTR_SYS e ATTR_VOLUME, la directory entry contiene i caratteri di un nome lungo, quindi non andrà stampata
  • Ogni sottodirectory contiene due entry:
    .
    La sottodirectory corrente
    ..
    La directory padre, contenete la sottodirectory corrente
    Per queste due entry non va stampata la sottodirectory, perchè risulterebbe in una stampa ricorsiva infinita.
  • Le sottodirectory sono contentue nell'area dati del disco, quindi per stamparle occorre:
    1. Individuare la posizione sul disco del cluster
    2. Leggere dati fino alla fine del cluster
    3. Individuare nella FAT il prossimo cluster
    4. Se il nuovo cluster non è un termonatore (cluster 0xfff) ricominciare dal punto 1
  • Le prime due entry della FAT contengono il descrittore del mezzo, quindi il primo cluster che troveremo nell'area dati è il 2.
  • Il floppy utilizza una FAT a 12 bit. Con questo formato il numero di cluster è contenuto in un intero a 12 bit. Due interi a 12 bit sono immagazzinati in tre byte, secondo il seguente schema:
    Byte della ternaContenuto
     7....0
    0Cluster pari, bit 0..7
    1Cluster pari, bit 8..11 Cluster dispari, bit 0..3
    2Cluster dispari, bit 4..11

Una possibile soluzione è la seguente:
/* leggifloppy.c
 * Stefano Salvi - 25/9/02
 * Programma che:
 * - legge il boot sector di un disco (floppy o hard disk) con file system FAT
 *   e ne stampa le caratteristiche.
 * - legge la boot directory.
 * - ogni volta che trova un'entry di tipo 'directory', dopo aver stampato l'entry
 *   stmapa la sotto directory descritta
 * la stampa delle sottodirectory deve essere ricorsiva (se una sottodirectory contiene
 * altre sottodirectory, stampa anche quelle).
 * Ogni volta che entra in una sottodirectory, indenta la lista di 2 spazi.
 */
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/types.h>       
#include <linux/msdos_fs.h>

// Descrittore del disco
struct disco {
  int fd;		// File Descriptor per il dispositivo
  int rootDir;		// posizione in byte della root Directory
  int rootDirSize;	// Dimensione in 'entries' della root directory
  int clusterBytes;	// Dimensione di un cluster in byte
  int firstDataByte;	// Primo byte dell'area dati
  int FATPos;		// Posizione della FAT (in byte)
};

// Struttura con i campi di bit che descrive la 'data' in formato MS-DOS
struct date {
   unsigned int giorno:5;
   unsigned int mese:4;
   unsigned int anno:7;
};

// Struttura con i campi di bit che descrive l' 'ora' in formato MS-DOS
struct time {
   unsigned int sec:5;
   unsigned int min:6;
   unsigned int ora:5;	//perchè si tengono solo i pari
};


int openDevice (char *device, struct disco *d);		// apre il device, legge boot rec
void readRootDir (struct disco *d);			// Legge l'albero delle directory
int printDirEntry (struct disco *d, int pos, int indent); // Stampa un'entry di dir.
void readSubDir (struct disco *d, int cluster, int indent); // Legge una subdirectroy
int clusterByte (struct disco *d, int cluster);		// Trasforma in byte un num. cluster
int nextCluster (struct disco *d, int cluster);		// Legge nella FAT il prossimo cluster

int main(int argc, char **argv)
{
struct disco d;			// Descrittore del dispositivo
char *device = "/dev/fd0";	// Nome del file (dispositivo) da aprire

  if (argc > 1)			// Se c'e' almeno un parametro
  {
    device = argv [1];		// Prende il primo parametro e lo usa come 'dispositivo'
  }

  if(openDevice(device, &d))	// Apre il dispositivo
  {
    return 1;
  }

  readRootDir (&d);
}

/* printDirEntry
 * Stampa un'entry di directory.
 * Riceve il descrittore del dispositivo (d) e la posizione in byte dell'entry.
 * Ritorna la posizione della prossima direntry, oppure 0 se incontra l'ultima
 * direntry (nome che comincia con byte 0x00).
 * Operazioni:
 * - Si posiziona sul record
 * - Legge una 'dir entry'
 * - Se la 'dir entry' ha nome che comincia per 0x00, ritorna 0
 * - Se l'entry non e' 'hidden' o cancellata, la stampa
 * - Ritorna la posizione della prossima dir entry.
 * Non stampa le entry cancellate (che cominciano per 0xe5) o con gli attributi 
 * 'hidden,system,readonly,label' (sono nomi estesi, quindi non stampabili)
 * Se trova una sottodirectory, che non sia '.' o '..', la stampa dopo il nome della
 * sottodirectory, indentata di due spazi.
 */
int printDirEntry (struct disco *d, int pos, int indent)
{
struct msdos_dir_entry dirEntry;// Buffer per il record di directory
struct date *p;			// Record per visualizzare le date
struct time *t;			// Record per visualizzare le ore
int i;

  // Posizionamento sulla dir entry
  if (lseek(d->fd, pos, SEEK_SET) < 0)
  {
    printf ("Errore nel posizionamento al byte %d\n",pos);
    return 0;
  }

  // Lettura dir entry
  if (read(d->fd,&dirEntry,sizeof(dirEntry)) != sizeof(dirEntry))
  {
    printf ("Errore nella lettura della DirEntry al byte %d\n",pos);
    return 0;
  }

  if (dirEntry.name[0] == 0)	// Entry terminatrice della Directory
  {
    return 0;			// Termina il 'chiamante'
  }

  /* Se il file non e' 'hidden,system,readonly,label' (nomi estesi) o comincia 
   *con 0xe5 (file cancellati)
   */
  if (dirEntry.attr != (ATTR_HIDDEN | ATTR_RO | ATTR_SYS | ATTR_VOLUME) && 
    (dirEntry.name[0] & 0xff) != 0xe5)
  {
    // Indenta l'entry
    for (i = 0; i < indent; i++)
    {
      printf (" ");
    }

    /* Analizza bit per bit gli 'attr' e per ognuno stampa una lettera */
    if(dirEntry.attr & ATTR_RO)
    {
      printf("R");
    }
    else
    {
      printf("-");
    }

    if(dirEntry.attr&ATTR_HIDDEN)
    {
      printf("H");
    }
    else
    {
      printf("-");
    }

    if(dirEntry.attr&ATTR_SYS)
    {
      printf("S");
    }
    else
    {
      printf("-");
    }

    if(dirEntry.attr&ATTR_VOLUME)
    {
      printf("L");
    }
    else
    {
      printf("-");
    }

    if(dirEntry.attr&ATTR_DIR)
    {
      printf("D");
    }
    else
    {
      printf("-");
    }

    if(dirEntry.attr&ATTR_ARCH)
    {
      printf("A  ");
    }
    else
    {
      printf("-  ");
    }

    /*informazioni sul file*/
    for(i=0;i<8;i++)			// Otto cratteri del nome
    {
      printf("%c",dirEntry.name[i]);
    }
    printf (".");
    for(i=0;i<3;i++)			// Tre caratteri dell'estensione
    {
      printf("%c",dirEntry.ext[i]);
    }
    p=(struct date *)&(dirEntry.date);	// Sovrappone 'date' a data
    printf(" %2d/%02d/%04d ",p->giorno,p->mese,p->anno+1980);
    t=(struct time *)&(dirEntry.time);	// Sovrappone 'time' a ora
    printf(" %2d:%02d:%02d",t->ora,t->min,t->sec);
    printf(" %8d\n",(__u32)dirEntry.size);

    // Se e' una sottodirectory, e non e' ne' '.' ne '..'
    if((dirEntry.attr&ATTR_DIR) && 				// E' una 'dir'
      !(dirEntry.name[0] == '.' && dirEntry.name[1] == ' ') &&	// Non e' '. '
      // Non e' nemmeno '.. '
      !(dirEntry.name[0] == '.' && dirEntry.name[1] == '.' && dirEntry.name[2] == ' '))
    {
      // Stampa la sottodirectory, aumentando l'indent
      readSubDir (d, dirEntry.start, indent + 2);
    }
  }

  return pos + sizeof (dirEntry);
}

/* readSubDir
 * Legge un'intera sottodirectory, posizionata a partire dal cluster 'cluster', sul
 * dispositivo 'd' e la stampa indentata di 'indent' spazi.
 * Se la directory e' registrata su piu' cluster, letto il primo cluster trova nella
 * FAT il prossimo per continuare.
 */
void readSubDir (struct disco *d, int cluster, int indent)
{
int pos;	// Posizione in byte dell'entry corrente
int end;	// Fine del cluster, in byte

  do {
    pos = clusterByte (d, cluster);			// Calcola inizio in byte
    end = clusterByte (d, cluster + 1);			// Calcola fine in byte
    do {
      if ((pos = printDirEntry (d, pos, indent)) == 0)	// Stampa una dir entry
      {
        return;						// Se e' l'untima, termina
      }
    } while (pos < end);				// Finche' non ha finito il cluster
    
    cluster = nextCluster (d, cluster);			// Cerca il nuovo cluster
  } while (cluster);					// Finche' non e' arrivato all'ultimo
}

/* nextCluster
 * Ritorna il prossimo cluster letto dalla FAT nell'entry relativa al cluster 'cluster'
 * pasato per paramentro, nel dispositivo descritto da 'd'
 * Se il cluster e' l'ultimo del file (il prossimo cluster e' 0xfff) ritorna 0.
 *
 * FAT a 12 bit:
 * 2 cluster ogni 3 byte.
 * Indico con 'P' il cluster pari e con D il cluster dispari:
 * I tre byte sono codificati cosi':
 * PP DP DD
 */
int nextCluster (struct disco *d, int cluster)
{
unsigned char dCluster [3];			// Buffer per la coppia di elementi
int pos = d -> FATPos + (cluster / 2) * 3;	// Posizione in byte della coppia di elementi
int nuovoCluster;				// Cluster letto nell'elemento

  // Si posiziona sulla coppia di elementi
  if (lseek(d->fd, pos, SEEK_SET) < 0)
  {
    printf ("Errore nel posizionamento al byte %d\n",pos);
    return 0;
  }

  // Legge la coppia
  if (read(d->fd,dCluster,sizeof(dCluster)) != sizeof(dCluster))
  {
    printf ("Errore nella lettura dell' elemento di FAT al byte %d\n",pos);
    return 0;
  }

  // Ricompone il numero di cluster in base alla formula esposta all'inizio
  if (cluster % 2)		// Cluster pari
  {
    nuovoCluster = dCluster [2];
    nuovoCluster = ((dCluster [1] >> 4) & 0xf) | ((nuovoCluster << 4) & 0xff0);
  } else {			// Cluster dispari
    nuovoCluster = dCluster [1];
    nuovoCluster = ((nuovoCluster << 8) & 0xf00) | (dCluster [0] & 0xff);
  }

  // Se il nuovo cluster e' 0xfff, ritorna 0, se no ritorna il nuovo cluster
  return (nuovoCluster == 0xfff) ? 0 : nuovoCluster;
}

/* clusterByte
 * Converte in byte dall'inizio del disco un numero di cluster.
 * Utilizza le informazioni del descrittore per il calcolo
 * Le prime due entry nella FAT sono riservate, quindi il primo cluster e' il 2
 */
int clusterByte (struct disco *d, int cluster)
{
  if (cluster < 2)			// Se il cluster e' illegale
  {
    return 0;				// ritorno 0 (errore)
  }
  cluster -= 2;				// tolgo i cluster riservati
  cluster *= d -> clusterBytes;		// moltiplico per la dimensione del cluster
  return cluster + d -> firstDataByte;	// aggiungo l'inizio dell'area dati
}

/* readRootDir
 * Stampa la root directory del disco.
 * Riceve come parametro il descrittore del disco.
 * Stampa 'd -> rootDirSize' elementi.
 * Se 'printDirEntry' indica che la directory e' teminata, finisce prima.
 */
void readRootDir (struct disco *d)
{
int pos = d -> rootDir;		// Posizione della directory
int i;				// Contatore entries

  for (i = 0; i < d -> rootDirSize; i++)	// quante ce ne sono al massimo
  {
    if ((pos = printDirEntry (d, pos, 0)) == 0)	// Stampa una dir entry (indent 0)
    {
      break;					// Se e' l'untima, termina
    }
  }
}

/* openDevice
 * Apre il file 'device', ne legge il boot record, compilando il descrittore
 * puntato da 'd'.
 * Ritorna 0 se tutto questo funziona.
 * Ritorna 1 se c'e' un errore.
 */
int openDevice (char *device, struct disco *d)
{
struct fat_boot_sector boot;	// Record nel quale leggere il primo settore

  if((d->fd=open(device,O_RDONLY))==-1)	// Apre il dispositivo
  {
    printf("Errore di apertura! Forse mancano i diritti sul dispositivo\n");
    return 1;
  }

  read(d->fd,&boot,sizeof(boot));	// Legge il boot sector

  // Compila il descrittore del dispositivo
  d->rootDir= (boot.reserved + boot.fats * boot.fat_length) * 
    *(__u16 *)boot.sector_size;			// posizione in byte della root Directory
  d->rootDirSize=*(__u16 *)boot.dir_entries;	// Dimensione in 'entries' della root directory
  // Dimensione di un cluster in byte
  d->clusterBytes = boot.cluster_size * *(__u16 *)boot.sector_size;
  // Primo byte dell'area dati
  d->firstDataByte = d->rootDir + (d->rootDirSize * sizeof (struct msdos_dir_entry));
  // posizione in byte della FAT
  d -> FATPos = boot.reserved * *(__u16 *)boot.sector_size;

  // Stampa le caratteristiche interessanti
  printf("\nNum\t\t Description\t\t\t Variabile\n");
  printf("%s\t Volume Name\t\t\t (system_id[8])\n",boot.system_id);
  printf("%d\t\t bytes per logical sector\t (sector_size[2])\n",
    *(__u16 *)boot.sector_size); //dimensione settore  
  printf("%d\t\t sectors/cluster\t\t (cluster_size)\n",boot.cluster_size); //settori per cluster
  printf("%d\t\t reserved sectors\t\t (reserved)\n",boot.reserved);
  printf("%d\t\t number of FATs\t\t\t (fats)\n",boot.fats);
  printf("%d\t\t root directory entries\t\t (dir_entries)\n",*(__u16 *)boot.dir_entries);
  printf("%d\t\t number of sectors\t\t (sectors[2])\n",*(__u16*)boot.sectors);
  printf("%d\t\t media code\t\t\t (media)\n",boot.media);
  printf("%d\t\t sectors/FAT\t\t\t (fat_length)\n",boot.fat_length);
  printf("%d\t\t Start in bytes of ROOT dir\n",d->rootDir);
  printf("%d\t\t sectors per track\t\t (secs_track)\n",(__u16)boot.secs_track);
  printf("%d\t\t number of heads\t\t (heads)\n",boot.heads);
  printf("%d\t\t hidden sectors\t\t\t (hidden)\n",boot.hidden);
  printf("%d\t\t number of sectors\t\t (total_sect)\n\n",boot.total_sect);

  return 0;
}

Per provare il programma, scaricare il sorgente, compilarlo con il comando cc floppy2.c -o floppy2 ed eseguirlo con il comando ./floppy2.


[Home Page dell'ITIS "Fermi"] [Indice Quarta] [Precedente] [Successivo]

© Ing. Stefano Salvi - Released under GPL licence