/* 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); }