/* 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 . */
#include "format.h"
#include "names.h"
#include "file_info.h"
#include "fat12.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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);
}