View of xos/tools/format.c


XOS | Parent Directory | View | Download

/* Formatage d'une disquette pour XOS. */
/* Copyright (C) 2008  Emmanuel Varoquaux
 
   This file is part of XOS.
 
   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.
 
   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.
 
   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>. */
 
#include "format.h"
 
#include "names.h"
#include "file_info.h"
#include "fat12.h"
 
#include <error.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <dirent.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <unistd.h>
#include <errno.h>
 
#define cluster_size (bootSector.BPB_SecPerClus * bootSector.BPB_BytsPerSec)
 
/* Fonctions de construction et destruction de l'arbre */
 
static void build_dir(struct file_info_struct *dir_info)
{
  DIR *dir;
  struct dirent *dirent;
  struct stat statbuf;
  struct file_info_struct *file_info;
  char *cwd;
  size_t i;
 
  /* lecture du repertoire */
  if (!(dir = opendir(dir_info->filename)))
    error(EXIT_FAILURE, errno, "opendir");
  dir_info->count = 0;
  dir_info->entries = NULL;
  if (!(cwd = getcwd(NULL, 0)))
    error(EXIT_FAILURE, errno, "getcwd");
  if (chdir(dir_info->filename) == -1)
    error(EXIT_FAILURE, errno, "chdir");
  while (errno = 0, dirent = readdir(dir)) {
    if (!strcmp(dirent->d_name, ".") || !strcmp(dirent->d_name, ".."))
      continue;
    if (stat(dirent->d_name, &statbuf) == -1)
      error(EXIT_FAILURE, errno, "stat");
    if (!S_ISDIR(statbuf.st_mode) && !S_ISREG(statbuf.st_mode))
      continue;
    if (!(dir_info->count & 31))
      if (!(dir_info->entries = realloc(dir_info->entries, (dir_info->count + 32) * sizeof (struct file_info_struct))))
        error(EXIT_FAILURE, errno, "realloc");
    if (!(dir_info->entries[dir_info->count].filename = strdup(dirent->d_name)))
      error(EXIT_FAILURE, errno, "strdup");
    dir_info->count++;
    generate_longname(dir_info);
    generate_shortname(dir_info);
    file_info = &dir_info->entries[dir_info->count - 1];
    file_info->is_dir = S_ISDIR(statbuf.st_mode) ? 1 : 0;
    if (!file_info->is_dir)
      file_info->size = statbuf.st_size;
  }
  if (errno)
    error(EXIT_FAILURE, errno, "readdir");
  if (chdir(cwd) == -1)
    error(EXIT_FAILURE, errno, "chdir");
  free(cwd);
  closedir(dir);
 
  /* construction des file_info de chaque entree */
  if (!(cwd = getcwd(NULL, 0)))
    error(EXIT_FAILURE, errno, "getcwd");
  if (chdir(dir_info->filename) == -1)
    error(EXIT_FAILURE, errno, "chdir");
  for (i = 0; i < dir_info->count; i++)
    if (dir_info->entries[i].is_dir)
      build_dir(&dir_info->entries[i]);
  if (chdir(cwd) == -1)
    error(EXIT_FAILURE, errno, "chdir");
  free(cwd);
}
 
static void destroy_dir(struct file_info_struct *dir_info)
{
  size_t i;
 
  for (i = 0; i < dir_info->count; i++) {
    if (dir_info->entries[i].is_dir)
      destroy_dir(&dir_info->entries[i]);
    free(dir_info->entries[i].filename);
  }
  free(dir_info->entries);
}
 
static void build_root(struct file_info_struct *root_info, const char *path)
{
  if (path) {
    root_info->filename = strdup(path);
    build_dir(root_info);
  }
  else {
    root_info->filename = NULL;
    root_info->count = 0;
  }
}
 
static void destroy_root(struct file_info_struct *root_info)
{
  if (root_info->filename) {
    destroy_dir(root_info);
    free(root_info->filename);
  }
}
 
/* Fonctions sur les secteurs defectueux */
 
static inline int is_badblock(const char *badblocks, unsigned short n)
{
  return n < bootSector.BPB_TotSec16 ? badblocks[n >> 3] & (1 << (n & 7)) : 0;
}
 
static int check_area(const char *badblocks, unsigned short start, unsigned short count)
{
  if (start >= bootSector.BPB_TotSec16)
    return 0;
  if (start + count > bootSector.BPB_TotSec16)
    count = bootSector.BPB_TotSec16 - start;
  while (start & 7 && count > 0) {
    if (badblocks[start >> 3] & (1 << (start & 7)))
      return 1;
    start++;
    count--;
  }
  while (count - 8 >= 0) {
    if (badblocks[start >> 3])
      return 1;
    start += 8;
    count -= 8;
  }
  while (count > 0) {
    if (badblocks[start >> 3] & (1 << (start & 7)))
      return 1;
    start++;
    count--;
  }
  return 0;
}
 
/* Fonctions d'ecriture dans la FAT */
 
static void set_cluster(char *fat, unsigned short n, unsigned short clusEntryVal)
{
  if (n - 2 >= countOfClusters) /* disque plein */
    goto full;
  if (FATOffset(n) + 2 > bootSector.BPB_FATSz16 * bootSector.BPB_BytsPerSec) /* FAT trop petite */
    goto full;
  setFATEntry(n, clusEntryVal, fat + (FATOffset(n) & ~(bootSector.BPB_BytsPerSec - 1)));
  return;
 
 full:
  error(EXIT_FAILURE, 0, "Disk full");
}
 
/* Ecrit recursivement dans la FAT fat, a partir du cluster idx, les chaines de
 * clusters des fichiers du repertoire dir_info.
 *
 * fat est suppose pointer vers une zone de taille
 * bootSector.BPB_FATSz16 * bootSector.BPB_BytsPerSec, initialisee a zero.
 * badblocks est utilise pour savoir quels secteurs sont defectueux.
 * Apres l'execution de la fonction, idx contient le numero du premier cluster
 * libre, et les champs clus_nr des fichiers de dir_info sont mis a jour avec
 * le numero du premier cluster du fichier. */
static void write_fat(struct file_info_struct *dir_info, const char *badblocks, char fat[], unsigned short *idx)
{
  off_t size;
  int clusters;
  unsigned short next;
  size_t i, j;
 
  for (i = 0; i < dir_info->count; i++) {
    /* calcul de la taille du fichier sur le disque */
    if (dir_info->entries[i].is_dir) {
      size = 96; /* ".", "..", 0 */
      for (j = 0; j < dir_info->entries[i].count; j++)
        size += (1 + n_long_entries(&dir_info->entries[i].entries[j])) * 32;
    }
    else
      size = dir_info->entries[i].size;
 
    if (size) {
      /* determination du premier cluster du fichier */
      while (check_area(badblocks, firstSectorOfCluster(*idx), bootSector.BPB_SecPerClus)) {
        set_cluster(fat, *idx, BAD_CLUSTER);
        (*idx)++;
      }
      dir_info->entries[i].clus_nr = *idx;
 
      /* ecriture des clusters du fichier dans la FAT */
      for (clusters = (size + cluster_size - 1) / cluster_size ? : 1; clusters > 1; clusters--) {
        next = *idx + 1;
        while (check_area(badblocks, firstSectorOfCluster(next), bootSector.BPB_SecPerClus)) {
          set_cluster(fat, next, BAD_CLUSTER);
          next++;
        }
        set_cluster(fat, *idx, next);
        *idx = next;
      }
      set_cluster(fat, *idx, EOC);
      (*idx)++;
    }
    else
      dir_info->entries[i].clus_nr = 0;
 
    /* appel recursif si repertoire */
    if (dir_info->entries[i].is_dir)
      write_fat(&dir_info->entries[i], badblocks, fat, idx);
  }
}
 
/* Utilitaires d'ecriture */
 
struct output_struct
{
  FILE *stream;
  unsigned short secnum;
};
 
static void init_output(struct output_struct *output, FILE *stream)
{
  output->stream = stream;
  output->secnum = 0;
}
 
static inline unsigned short get_secnum(struct output_struct *output)
{
  return output->secnum;
}
 
static void write_sectors(const void *buf, unsigned short nsec, struct output_struct *output)
{
  unsigned short cur;
 
  cur = get_secnum(output);
  if (cur > bootSector.BPB_TotSec16)
    error(EXIT_FAILURE, 0, "Disk full");
  if (fwrite(buf, bootSector.BPB_BytsPerSec, nsec <= bootSector.BPB_TotSec16 - cur ? nsec : bootSector.BPB_TotSec16 - cur, output->stream) != nsec) {
    if (ferror(output->stream))
      error(EXIT_FAILURE, errno, "fwrite");
    else
      error(EXIT_FAILURE, 0, "Disk full");
  }
  output->secnum += nsec;
}
 
/* Fonctions d'ecriture des fichiers */
 
static void set_dir_entry(directoryEntryStructure_t *dir_entry, unsigned char attr, const unsigned char *name, unsigned short clus_nr, off_t size)
{
  struct timeval tv;
  struct tm *tm;
  unsigned short now_time;
  unsigned short now_date;
 
  gettimeofday(&tv, NULL);
  tm = localtime(&tv.tv_sec);
  now_time = (tm->tm_sec >> 1) | (tm->tm_min << 5) | (tm->tm_hour << 11);
  now_date = tm->tm_mday | ((tm->tm_mon + 1) << 5) | ((tm->tm_year - 80) << 9);
 
  memcpy(dir_entry->DIR_Name, name, 11);
  dir_entry->DIR_Attr = attr;
  dir_entry->DIR_NTRes = 0;
  dir_entry->DIR_CrtTimeTenth = tv.tv_usec / 10000 + (tm->tm_sec & 1) * 100;
  dir_entry->DIR_CrtTime = now_time;
  dir_entry->DIR_CrtDate = now_date;
  dir_entry->DIR_LstAccDate = now_date;
  dir_entry->DIR_FstClusHI = 0;
  dir_entry->DIR_WrtTime = now_time;
  dir_entry->DIR_WrtDate = now_date;
  dir_entry->DIR_FstClusLO = clus_nr;
  dir_entry->DIR_FileSize = size;
}
 
static void set_long_dir_entry(longDirectoryEntryStructure_t *long_dir_entry, unsigned char ord, const unsigned short *name, unsigned char chk_sum)
{
  int state;
  unsigned int i;
 
  state = 0;
 
  long_dir_entry->LDIR_Ord = ord;
  for (i = 0; i < 5; i++)
    long_dir_entry->LDIR_Name1[i] = state ? 0xffff : *name++ ? : state++;
  long_dir_entry->LDIR_Attr = ATTR_LONG_NAME;
  long_dir_entry->LDIR_Type = 0;
  long_dir_entry->LDIR_Chksum = chk_sum;
  for (i = 0; i < 6; i++)
    long_dir_entry->LDIR_Name2[i] = state ? 0xffff : *name++ ? : state++;
  long_dir_entry->LDIR_FstClusLO = 0;
  for (i = 0; i < 2; i++)
    long_dir_entry->LDIR_Name3[i] = state ? 0xffff : *name++ ? : state++;
}
 
static void clear_badclusters(struct output_struct *output, const char *badblocks, char buf[])
{
  memset(buf, 0, cluster_size);
  while (check_area(badblocks, get_secnum(output), bootSector.BPB_SecPerClus))
    write_sectors(buf, bootSector.BPB_SecPerClus, output);
}
 
/* Ecrit le contenu du repertoire racine. */
static void write_root_dir(const struct file_info_struct *root, struct output_struct *output, char buf[])
{
  int offset;
  unsigned char chk_sum;
  size_t i;
  unsigned int j, n;
  int t;
 
  offset = 0;
 
  t = 1;
  for (i = 0; i < root->count; i++) {
    t += 1 + n_long_entries(&root->entries[i]);
    if (t > bootSector.BPB_RootEntCnt)
      error(EXIT_FAILURE, 0, "Root directory too big");
    if ((n = n_long_entries(&root->entries[i]))) { /* Long directory entries */
      chk_sum = chkSum((unsigned char *)root->entries[i].shortname);
      for (j = n; j > 0; j--) {
        set_long_dir_entry((longDirectoryEntryStructure_t *)(buf + offset), j == n ? j | LAST_LONG_ENTRY : j, root->entries[i].longname + 13 * (j - 1), chk_sum);
        offset += 32;
      }
    }
    set_dir_entry((directoryEntryStructure_t *)(buf + offset), root->entries[i].is_dir ? ATTR_DIRECTORY : ATTR_ARCHIVE, root->entries[i].shortname, root->entries[i].clus_nr, root->entries[i].is_dir ? 0 : root->entries[i].size);
    offset += 32;
  }
  memset(buf + offset, 0, rootDirSectors * bootSector.BPB_BytsPerSec - offset);
  write_sectors(buf, rootDirSectors, output);
}
 
/* Ecrit le contenu d'un repertoire. */
static void write_dir(const struct file_info_struct *dir_info, unsigned short parent_clus_nr, const char *badblocks, struct output_struct *output, char buf[])
{
  int offset;
  unsigned char chk_sum;
  size_t i;
  unsigned int j, n;
 
  clear_badclusters(output, badblocks, buf);
  set_dir_entry((directoryEntryStructure_t *)buf, ATTR_DIRECTORY, (const unsigned char *)".          ", dir_info->clus_nr, 0); /* dot */
  set_dir_entry((directoryEntryStructure_t *)(buf + 32), ATTR_DIRECTORY, (const unsigned char *)"..         ", parent_clus_nr, 0); /* dotdot */
  offset = 64;
  for (i = 0; i < dir_info->count; i++) {
    if ((n = n_long_entries(&dir_info->entries[i]))) { /* Long directory entries */
      chk_sum = chkSum((unsigned char *)dir_info->entries[i].shortname);
      for (j = n; j > 0; j--) {
        set_long_dir_entry((longDirectoryEntryStructure_t *)(buf + offset), j == n ? j | LAST_LONG_ENTRY : j, dir_info->entries[i].longname + 13 * (j - 1), chk_sum);
        offset += 32;
        if (offset == cluster_size) {
          write_sectors(buf, bootSector.BPB_SecPerClus, output);
          clear_badclusters(output, badblocks, buf);
          offset = 0;
        }
      }
    }
    set_dir_entry((directoryEntryStructure_t *)(buf + offset), dir_info->entries[i].is_dir ? ATTR_DIRECTORY : ATTR_ARCHIVE, dir_info->entries[i].shortname, dir_info->entries[i].clus_nr, dir_info->entries[i].is_dir ? 0 : dir_info->entries[i].size);
    offset += 32;
    if (offset == cluster_size) {
      write_sectors(buf, bootSector.BPB_SecPerClus, output);
      clear_badclusters(output, badblocks, buf);
      offset = 0;
    }
  }
  memset(buf + offset, 0, 32);
  offset += 32;
  memset(buf + offset, 0, cluster_size - offset);
  write_sectors(buf, bootSector.BPB_SecPerClus, output);
}
 
/* Ecrit le contenu d'un fichier regulier. */
static void write_reg(const struct file_info_struct *file_info, const char *badblocks, struct output_struct *output, char buf[])
{
  struct stat statbuf;
  FILE *fp;
  size_t n;
 
  stat(file_info->filename, &statbuf);
  if (!statbuf.st_size)
    return;
  if (!(fp = fopen(file_info->filename, "r")))
    error(EXIT_FAILURE, errno, "fopen");
  do {
    clear_badclusters(output, badblocks, buf);
    n = fread(buf, 1, cluster_size, fp);
    if (ferror(fp))
      error(EXIT_FAILURE, errno, "fread");
    if (n) {
      if (n < cluster_size)
        memset(buf + n, 0, cluster_size - n);
      write_sectors(buf, bootSector.BPB_SecPerClus, output);
    }
  }
  while (!feof(fp));
  fclose(fp);
}
 
/* Ecrit recursivement les donnees des fichiers du repertoire dir_info. */
static void write_data(const struct file_info_struct *dir_info, const char *badblocks, struct output_struct *output, char buf[])
{
  char *cwd;
  size_t i;
 
  if (!dir_info->filename)
    return;
  if (!(cwd = getcwd(NULL, 0)))
    error(EXIT_FAILURE, errno, "getcwd");
  if (chdir(dir_info->filename) == -1)
    error(EXIT_FAILURE, errno, "chdir");
  for (i = 0; i < dir_info->count; i++)
    if (dir_info->entries[i].is_dir) {
      write_dir(&dir_info->entries[i], dir_info->clus_nr, badblocks, output, buf);
      write_data(&dir_info->entries[i], badblocks, output, buf);
    }
    else
      write_reg(&dir_info->entries[i], badblocks, output, buf);
  if (chdir(cwd) == -1)
    error(EXIT_FAILURE, errno, "chdir");
  free(cwd);
}
 
static void write_sectors_reserved(const void *buf, unsigned short nsec, struct output_struct *output)
{
  unsigned short cur_secnum;
 
  cur_secnum = get_secnum(output);
  if (cur_secnum > bootSector.BPB_ResvdSecCnt)
    return;
  if (cur_secnum + nsec > bootSector.BPB_ResvdSecCnt)
    write_sectors(buf, bootSector.BPB_ResvdSecCnt - cur_secnum, output);
  else
    write_sectors(buf, nsec, output);
}
 
/* Genere une image de disque formatee en FAT12, et y copie le contenu d'un
 * repertoire. Les noms de fichiers longs sont supportes.
 *
 * output est le flux de destination. Il peut s'agir d'un fichier special en
 * mode caractere (comme /dev/fd0 ou /dev/stdout), ou d'un fichier regulier.
 *
 * reserved_sectors est le nombre de secteurs dans la premiere region du
 * systeme FAT (region reservee). reserved_sectors doit etre superieur ou egal
 * a 1, car le 1er secteur est toujouts occupe par le secteur de boot. Les
 * autres secteurs sont libres et pourront etre ecrits avec write_reserved().
 *
 * bootsec est un pointeur vers une zone de 512 octets, dont les octets 62 a
 * 511 seront copies dans le secteur de boot (code de demarrage). Les octets
 * 510 et 511 doivent etre respectivement 0x55 et 0xaa (boot flag).
 *
 * write_reserved est un pointeur sur une fonction d'ecriture de la region
 * reservee ou un pointeur nul. Si write_reserved est NULL, la region reservee
 * est remplie avec des zeros. Autrement, write_reserved() prend elle-meme en
 * argument un pointeur vers une fonction d'ecriture par secteurs sur le
 * fichier image. Cette fonction prend en argument un pointeur vers les
 * donnees a ecrire, et le nombre de secteurs a ecrire. Par ailleurs, si la
 * taille de la region reservee est de n secteurs, write_reserved() ne doit
 * ecrire au plus que (n - 1) secteurs, car le 1er secteur est occupe par le
 * secteur de boot. Si write_reserved() ecrit moins de (n - 1) secteurs, les
 * secteurs libres sont completes avec des zeros. write_sectors() empechera
 * write_reserved() d'ecrire plus que (n - 1) secteurs.
 *
 * write_reserved_arg est un argument qui est passe a write_reserved().
 *
 * root_path est le chemin d'acces du repertoire racine des fichiers a copier
 * sur l'image de disque. root_path peut etre NULL, auquel cas l'image ne
 * contiendra aucun fichier.
 *
 * badblocks est un tableau de bits representant les secteurs defectueux. */
/* On parcours d'abord le repertoire a copier et on construit une structure
 * contenant des informations sur les fichiers du repertoire racine. On
 * parcours ensuite cette structure une premiere fois pour generer la table
 * d'allocation des fichiers, puis une seconde fois pour ecrire les fichiers et
 * les repertoires proprement dits. */
void format(FILE *output_stream,
            unsigned short reserved_sectors,
            const char *bootsec,
            void (*write_reserved)(void (*)(const void *, unsigned short, struct output_struct *), void *, struct output_struct *),
            void *write_reserved_arg,
            const char *root_path,
            const char *badblocks)
{
  struct output_struct output;
  struct file_info_struct root;
  char *buf;
  unsigned short idx;
  unsigned short secnum;
  unsigned int i;
 
  /* initialisation */
  init_output(&output, output_stream);
  init_volume(reserved_sectors);
 
  /* lecture de l'arborescence des fichiers et construction de la structure de
     donnees */
  build_root(&root, root_path);
 
  /* controle des 3 premieres regions du systeme de fichier */
  if (check_area(badblocks, 0, bootSector.BPB_ResvdSecCnt + bootSector.BPB_NumFATs * bootSector.BPB_FATSz16 + rootDirSectors))
    error(EXIT_FAILURE, 0, "Bad sectors in the reserved region, cannot proceed");
 
  /* 0 - Reserved region */
  if (!(buf = malloc(bootSector.BPB_BytsPerSec)))
    error(EXIT_FAILURE, errno, "malloc");
  memcpy(buf, &bootSector, sizeof (bootSector_t));
  memcpy(buf + 0x3e, bootsec + 0x3e, 512 - 0x3e);
  write_sectors(buf, 1, &output);
  if (write_reserved)
    write_reserved(write_sectors_reserved, write_reserved_arg, &output);
  if (get_secnum(&output) < bootSector.BPB_ResvdSecCnt) {
    memset(buf, 0, bootSector.BPB_BytsPerSec);
    do
      write_sectors(buf, 1, &output);
    while (get_secnum(&output) < bootSector.BPB_ResvdSecCnt);
  }
  free(buf);
 
  /* 1 - FAT region */
  if (!(buf = calloc(bootSector.BPB_FATSz16 * bootSector.BPB_BytsPerSec, 1)))
    error(EXIT_FAILURE, errno, "calloc");
  set_cluster(buf, 0, bootSector.BPB_Media | 0x0f00);
  set_cluster(buf, 1, EOC);
  idx = 2;
  write_fat(&root, badblocks, buf, &idx);
  while (idx < countOfClusters + 2 && idx < bootSector.BPB_FATSz16 * bootSector.BPB_BytsPerSec * 2 / 3) {
    if (check_area(badblocks, firstSectorOfCluster(idx), bootSector.BPB_SecPerClus))
      set_cluster(buf, idx, BAD_CLUSTER);
    idx++;
  }
  for (i = 0; i < bootSector.BPB_NumFATs; i++)
    write_sectors(buf, bootSector.BPB_FATSz16, &output);
  free(buf);
 
  /* 2 - Root Directory Region */
  if (!(buf = malloc(rootDirSectors * bootSector.BPB_BytsPerSec)))
    error(EXIT_FAILURE, errno, "malloc");
  write_root_dir(&root, &output, buf);
  free(buf);
 
  /* 3 - File and Directory Data Region */
  if (!(buf = malloc(cluster_size)))
    error(EXIT_FAILURE, errno, "malloc");
  write_data(&root, badblocks, &output, buf);
  free(buf);
 
  /* completion */
  if (!(buf = calloc(bootSector.BPB_BytsPerSec, 1)))
    error(EXIT_FAILURE, errno, "calloc");
  for (secnum = get_secnum(&output); secnum < bootSector.BPB_TotSec16; secnum++)
    write_sectors(buf, 1, &output);
  free(buf);
 
  /* destruction de la structure de donnees */
  destroy_root(&root);
}