MS-DOS для программиста© Александр Фролов, Григорий ФроловТом 19, М.: Диалог-МИФИ, 1995, 253 стр. 2.4. Файлы и каталогиВы, конечно, знаете, что файловая система MS-DOS имеет древовидную структуру. В корневом каталоге располагаются 32-байтовые элементы, которые содержат информацию о файлах и других каталогах. Для чтения корневого каталога необходимо определить его расположение и размер. Расположение и размер корневого каталогаКорневой каталог находится сразу за последней копией FAT . Количество секторов, занимаемых одной копией FAT, находится в блоке параметров BIOS в загрузочном секторе (поле fatsize), а количество копий FAT - в поле fatcnt блока BPB . Следовательно, перед корневым каталогом находится один загрузочный сектор и fatcnt * fatsize секторов таблицы размещения файлов FAT. Размер корневого каталога можно определить исходя из значения поля rootsize. При форматировании диска в это поле записывается максимальное количество файлов и каталогов, которые могут находиться в корневом каталоге. Для каждого элемента в каталоге отводится 32 байта, поэтому корневой каталог имеет длину 32 * rootsize байт. Корневой каталог занимает непрерывную область фиксированного размера. Размер корневого каталога задается при форматировании и определяет максимальное количество файлов и каталогов, которые могут быть в нем описаны. Для определения количества секторов, занимаемых корневым каталогом, можно воспользоваться следующей формулой: RootSecs = sectsize / (32 * rootsize) В этой формуле sectsize - размер сектора в байтах, он может быть получен из соответствующего поля загрузочного сектора. Область файлов и подкаталоговНа рис. 2.3 изображены все области логического диска. Такую структуру имеют логические диски , расположенные в разделах жестких дисков, а также дискеты.
Рис. 2.3. Структура логического диска MS-DOS Вслед за корневым каталогом на логическом диске находится область файлов и подкаталогов корневого каталога . Область данных разбита на кластеры, причем нумерация кластеров начинается с числа 2. Кластеру с номером 2 соответствуют первые секторы области данных. Теперь мы можем привести формулу, которая позволит нам связать номер кластера с номерами секторов, занимаемых им на логическом диске: SectNu = DataStart + ((ClustNu - 2) * clustsize) В этой формуле использованы следующие
обозначения:
Дескрипторы файловКак мы уже говорили, любой каталог содержит
32-байтовые элементы - дескрипторы, описывающие
файлы и другие каталоги. Приведем формат
дескриптора:
В любом каталоге, кроме корневого, два первых дескриптора имеют специальное назначение. Первый дескриптор содержит в поле имени строку: ". " Этот дескриптор указывает на содержащий его каталог. То есть каталог имеет ссылку сам на себя. Второй специальный дескриптор содержит в поле имени строку: ".. " Этот дескриптор указывает на каталог более высокого уровня. Если в поле номера первого занимаемого кластера для дескриптора с именем ".. " находится нулевое значение, это означает, что данный каталог содержится в корневом каталоге. Таким образом, в древовидной структуре каталогов имеются ссылки как в прямом, так и в обратном направлении. Эти ссылки можно использовать для проверки сохранности структуры каталогов файловой системы. Атрибуты файловБайт атрибутов является принадлежностью
каждого файла. Биты этого байта имеют следующие
значения:
Обычно файлы имеют следующие атрибуты:
Дескрипторы удаленных файловПри удалении файла первый байт его имени заменяется на байт E5h (символ "х"). Все кластеры, распределенные файлу, отмечаются в FAT как свободные. Если вы только что удалили файл, его еще можно восстановить, так как в дескрипторе сохранились все поля, кроме первого байта имени файла. Но если на диск записать новые файлы, то содержимое кластеров удаленного файла будет изменено и восстановление станет невозможным. Время создания или изменения файлаОстановимся подробнее на полях времени и даты создания или последней модификации файла. MS-DOS обновляет содержимое этих полей после любой операции, изменяющей содержимое файла - создания файла, перезаписи содержимого файла, добавления данных в файл или обновления содержимого файла. После обновления файла MS-DOS устанавливает бит архивации 5 байта атрибутов в 1. Формат поля времени показан на рис. 2.4.
Рис. 2.4. Формат поля времени Старшие пять бит содержат значение часа модификации файла, шесть бит с номерами 5 - 10 содержат значение минут модификации файла, и, наконец, в младших 5 битах хранится значение секунд, деленное на 2. Для того, чтобы время обновления файла уместилось в шестнадцати битах, пришлось пойти на снижение точности времени до двух секунд. Дата создания или изменения файлаФормат даты обновления файла напоминает формат времени (рис. 2.5).
Рис. 2.5. Формат поля даты Для того чтобы получить значение года обновления файла, необходимо прибавить к величине, хранимой в старших семи битах, значение 1980. Поля месяца и дня каких-либо особенностей не имеют, они полностью соответствуют календарной дате. Длина файлаПоле длины в дескрипторе содержит точную длину файла в байтах. Для каталогов в поле длины записано нулевое значение. Вы не можете работать с каталогом средствами MS-DOS, как с обычным файлом. Единственный способ прочитать каталог как файл - использование таблицы FAT для определения цепочки занимаемых каталогом кластеров и чтение секторов, соответствующих этим кластерам при помощи прерывания INT 25h . Программа ROOTVIEWПриведем исходный текст программы ROOTVIEW (листинг 2.3), которая читает загрузочный сектор выбранного диска, вычисляет размер и расположение корневого каталога. Затем программа читает корневой каталог и выводит на экран его содержимое. Листинг 2.3. Файл rootview\rootview.cpp #include <stdio.h> #include <conio.h> #include <malloc.h> #include <dos.h> #include <ctype.h> typedef struct _EBPB_ { unsigned sectsize; char clustsize; unsigned ressecs; char fatcnt; unsigned rootsize; unsigned totsecs; char media; unsigned fatsize; unsigned seccnt; unsigned headcnt; unsigned hiddensec_low; unsigned hiddensec_hi; unsigned long drvsecs; } EBPB; typedef struct _BOOT_ { char jmp[3]; char oem[8]; EBPB bpb; char drive; char reserved; char signature; unsigned volser_lo; unsigned volser_hi; char label[11]; char fat_format[8]; char boot_code[450]; } BOOT; typedef struct _FTIME_ { unsigned sec : 5, min : 6, hour : 5; } FTIME; typedef struct _FDATE_ { unsigned day : 5, month : 4, year : 7; } FDATE; typedef struct _FITEM_ { char name[8]; char ext[3]; char attr; char reserved[10]; FTIME time; FDATE date; unsigned cluster_nu; unsigned long size; } FITEM; int getboot(BOOT far *boot, int drive); union REGS reg; struct SREGS segreg; struct { unsigned long first_sect; unsigned nsect; void far* buf; } cb; int main(void) { int i; int root_begin, root_sectors; char drive; char boot[512]; BOOT far* boot_rec = (BOOT far*) boot; FITEM *root_buffer, far *rptr; printf("\nЧтение корневого каталога " "логического диска" "\n (C)Фролов А., 1995\n"); // Запрашиваем диск, для которого необходимо // выполнить чтение загрузочной записи. printf("\nВведите обозначение диска (A, B, ...):"); drive = getche(); // Вычисляем номер дисковода drive = toupper(drive) - 'A'; // Читаем загрузочную запись в буфер int status = getboot((BOOT far*)boot_rec, drive); // Если произошла ошибка (например, неправильно указано // обозначение диска), завершааем работу программы if(status) { printf("\nОшибка при чтении загрузочного сектора"); return(-1); } // Вычисляем номер первого сектора // корневого каталога root_begin = boot_rec->bpb.ressecs + boot_rec->bpb.fatsize * boot_rec->bpb.fatcnt; // Вычисляем длину корневого каталога root_sectors = (boot_rec->bpb.rootsize * 32) / boot_rec->bpb.sectsize; // Заказываем буфер для корневого каталога root_buffer = (FITEM *) malloc(root_sectors * boot_rec->bpb.sectsize); if(root_buffer == NULL) { printf("\nМало памяти"); return(-1); } // Читаем корневой каталог в буфер root_buffer cb.first_sect = root_begin; cb.nsect = root_sectors; cb.buf = (void far*)root_buffer; _BX = FP_OFF(&cb); _DS = FP_SEG(&cb); _CX = 0xffff; _DX = 0; _AX = drive; asm int 25h asm pop ax asm jc error // Показываем содержимое корневого каталога printf("\n" "\nИмя файла Аттр. Дата " "Время Кластер Размер" "\n------------ ----- ---------- " "-------- ------- ------"); for(rptr = root_buffer;; rptr++) { printf("\n"); // Признак конца каталога - нулевой байт в начале // имени файла или байт 0xF6 (пустой каталог) if(rptr->name[0] == 0 || rptr->name[0] == (char)0xF6) break; // Выводим содержимое дескриптора файла for(i=0; i<8; i++) printf("%c",rptr->name[i]); printf("."); for(i=0; i<3; i++) printf("%c",rptr->ext[i]); printf(" %02X %02d-%02d-%02d %02d:%02d:%02d ", rptr->attr, rptr->date.day, rptr->date.month, rptr->date.year + 1980, rptr->time.hour, rptr->time.min, rptr->time.sec * 2); printf(" %-6ld %lu", (long)rptr->cluster_nu, (long)rptr->size); } // Освобождаем память free(root_buffer); return 0; error: printf("\nОшибка при чтении каталога"); free(root_buffer); return(-1); } /** * getboot * * Прочитать загрузочную запись * * int getboot(BOOT far *boot, int drive); * * boot - указатель на буфер, в который * будет прочитана загрузочная запись * * drive - номер физического НМД * (0 - первый НМД, 1 - второй, ...) **/ int getboot(BOOT far *boot, int drive) { cb.first_sect = 0; cb.nsect = 1; cb.buf = (void far*)boot; _BX = FP_OFF(&cb); _DS = FP_SEG(&cb); _CX = 0xffff; _DX = 0; _AX = drive; asm int 25h // Извлекаем из стека оставшееся там после // вызова прерывания слово asm pop ax asm jc err return(0); err: return(1); } Заметьте, что приведенная выше программа предоставляет вам параметр, который невозможно получить с помощью команды dir - номер первого кластера, распределенного файлу. Операционная система MS-DOS не дает программам иной возможности определить номер первого кластера файла, чем чтение каталога по секторам. Программа FATSCANПрограмма FATSCAN (листинг 2.4) предназначена для исследования подкаталогов корневого каталога НМД, а также для демонстрации основных приемов работы с таблицей размещения файлов FAT . Вы можете использовать ее для исследования структуры каталогов диска. Листинг 2.4. Файл rootview\ rootview.cpp // // Для этой программы требуется модель // памяти Large // #include <stdio.h> #include <stdlib.h> #include <conio.h> #include <malloc.h> #include <dos.h> #include <bios.h> #include <ctype.h> #include <string.h> typedef struct _EBPB_ { unsigned sectsize; char clustsize; unsigned ressecs; char fatcnt; unsigned rootsize; unsigned totsecs; char media; unsigned fatsize; unsigned seccnt; unsigned headcnt; unsigned hiddensec_low; unsigned hiddensec_hi; unsigned long drvsecs; } EBPB; typedef struct _BOOT_ { char jmp[3]; char oem[8]; EBPB bpb; char drive; char reserved; char signature; unsigned volser_lo; unsigned volser_hi; char label[11]; char fat_format[8]; char boot_code[450]; } BOOT; typedef struct _FTIME_ { unsigned sec : 5, min : 6, hour : 5; } FTIME; typedef struct _FDATE_ { unsigned day : 5, month : 4, year : 7; } FDATE; typedef struct _FITEM_ { char name[8]; char ext[3]; char attr; char reserved[10]; FTIME time; FDATE date; unsigned cluster_nu; unsigned long size; } FITEM; int getboot(BOOT far *boot, int drive); int fat(char far *b_fat, int t_fat, unsigned idx); union REGS reg; struct SREGS segreg; struct { unsigned long first_sect; unsigned nsect; void far* buf; } cb; int main(void) { BOOT *boot_rec; int i,j, k, status, fat_sectors; unsigned total_sectors; int ffat, root_begin, root_sectors; char drive; unsigned far *fat_buffer; FITEM far *root_buffer, far *rptr; char cbuf[128]; char far *clust_buffer; unsigned cur_clust; printf("\n\nЧтение каталогов логического диска" "\n (C)Фролов А., 1995\n"); // Заказываем буфер для чтения загрузочной записи boot_rec = (BOOT*)malloc(sizeof(BOOT)); if(boot_rec == NULL) { printf("\nМало памяти"); return -1; } // Запрашиваем диск, для которого необходимо // выполнить чтение загрузочной записи. printf("\nВведите обозначение диска (C, D, ...):"); drive = getche(); drive = toupper(drive) - 'A'; if(drive < 2) { printf("\nМожно указывать только НМД"); free(boot_rec); return(-1); } // Читаем загрузочную запись в буфер status = getboot((BOOT _far*)boot_rec, drive); if(status) { printf("\nОшибка при чтении загрузочного сектора"); free(boot_rec); return(-1); } // Определяем формат таблицы FAT total_sectors = boot_rec->bpb.totsecs; // Если мы работаем с расширенным разделом диска, // общее количество секторов на диска берем из // расширенного PBP if(total_sectors == 0) total_sectors = boot_rec->bpb.drvsecs; // Определяем формат FAT if(!strncmp(boot_rec->fat_format, "FAT16", 5)) { printf("\nFAT имеет 16-битовый формат"); ffat = 16; } else { printf("\nFAT имеет 12-битовый формат"); ffat = 12; } // Определяем количество секторов, занимаемых FAT fat_sectors = boot_rec->bpb.fatsize; // Заказываем буфер для FAT fat_buffer = (unsigned int*) farmalloc((long)fat_sectors * boot_rec->bpb.sectsize); if(fat_buffer == NULL) { printf("\nМало памяти"); free(boot_rec); return -1; } // Вычисляем номер первого сектора FAT j = boot_rec->bpb.ressecs; // Читаем FAT в буфер fat_buffer // Заполняем регистровые структуры для вызова // прерывания DOS INT 25h cb.first_sect = j; cb.nsect = fat_sectors; cb.buf = (void far*)fat_buffer; _BX = FP_OFF(&cb); _DS = FP_SEG(&cb); _CX = 0xffff; _DX = 0; _AX = drive; asm int 25h asm pop ax // Вычисляем номер первого сектора корневого каталога root_begin = j + fat_sectors * boot_rec->bpb.fatcnt; // Вычисляем длину корневого каталога root_sectors = (boot_rec->bpb.rootsize * 32) / boot_rec->bpb.sectsize; // Заказываем буфер для корневого каталога root_buffer = (FITEM far *) farmalloc(root_sectors * boot_rec->bpb.sectsize); if(root_buffer == NULL) { printf("\nМало памяти"); free(boot_rec); farfree(fat_buffer); return -1; } cb.first_sect = root_begin; cb.nsect = root_sectors; cb.buf = (void far*)root_buffer; _BX = FP_OFF(&cb); _DS = FP_SEG(&cb); _CX = 0xffff; _DX = 0; _AX = drive; asm int 25h asm pop ax // Показываем содержимое корневого каталога printf("\n" "\nИмя файла Аттр. " "Дата Время Кластер Размер" "\n------------ ----- " "---------- -------- ------- ------"); for(rptr = root_buffer;;rptr++) { printf("\n"); // Признак конца каталога - нулевой байт в начале // имени файла if(rptr->name[0] == 0) break; // Выводим содержимое дескриптора файла for(i=0; i<8; i++) printf("%c",rptr->name[i]); printf("."); for(i=0; i<3; i++) printf("%c",rptr->ext[i]); printf(" %02X %02d-%02d-%02d %02d:%02d:%02d ", rptr->attr, rptr->date.day, rptr->date.month, rptr->date.year + 1980, rptr->time.hour, rptr->time.min, rptr->time.sec * 2); printf(" %-6ld %lu", (long)rptr->cluster_nu, (long)rptr->size); } // Получаем буфер для чтения кластеров каталога clust_buffer = (char far*) farmalloc(boot_rec->bpb.clustsize * boot_rec->bpb.sectsize); if(clust_buffer == NULL) { printf("\nМало памяти"); farfree(root_buffer); free(boot_rec); farfree(fat_buffer); return -1; } printf("\nНомер первого кластера каталога:"); gets(cbuf); cur_clust = atol(cbuf); // Переменная k используется в качестве флага. // При первом просмотре каталога ее значение равно 0, // затем эта переменная устанавливается в 1. k = 0; for(;;) { // Сохраняем номер кластера каталога unsigned long j = cur_clust; // Вычисляем номер следующего кластера, распределенного // каталогу cur_clust = fat((char far*)fat_buffer, ffat, cur_clust); printf("%d ", cur_clust); // Читаем кластер в буфер clust_buffer cb.first_sect = root_begin + root_sectors + ((j-2)*boot_rec->bpb.clustsize); cb.nsect = boot_rec->bpb.clustsize; cb.buf = (void far*)clust_buffer; _BX = FP_OFF(&cb); _DS = FP_SEG(&cb); _CX = 0xffff; _DX = 0; _AX = drive; asm int 25h asm pop ax // Показываем содержимое каталога rptr = (FITEM far *)clust_buffer; // Первый дескриптор в каталоге указывает на // этот же каталог. В поле имени первого дескриптора // находится строка ". ". // Этот факт можно использовать // для проверки каталога. Если вы по ошибке указали // номер кластера, не принадлежащего каталогу, // программа завершит работу с сообщением об ошибке. if(k == 0) { k = 1; if(_fstrncmp(rptr->name,". ",8) != 0) { printf("\nЭто не каталог !"); farfree(root_buffer); free(boot_rec); farfree(fat_buffer); farfree(clust_buffer); return(-1); } } printf("\n" "\nИмя файла Аттр. Дата " "Время Кластер Размер" "\n------------ ----- ---------- " "-------- ------- ------"); for(;;rptr++) { printf("\n"); // Признак конца каталога - нулевой байт в начале // имени файла if(rptr->name[0] == 0) break; // Выводим содержимое дескриптора файла for(i=0; i<8; i++) printf("%c",rptr->name[i]); printf("."); for(i=0; i<3; i++) printf("%c",rptr->ext[i]); printf(" %02X %02d-%02d-%02d %02d:%02d:%02d ", rptr->attr, rptr->date.day, rptr->date.month, rptr->date.year + 1980, rptr->time.hour, rptr->time.min, rptr->time.sec * 2); printf(" %-6ld %lu", (long)rptr->cluster_nu, (long)rptr->size); } // Если этот кластер - последний из распределенных // каталогу, завершаем работу программы if((cur_clust == 0xfff) || (cur_clust == 0xffff)) break; } farfree(root_buffer); free(boot_rec); farfree(fat_buffer); farfree(clust_buffer); return 0; } int getboot(BOOT far *boot, int drive) { cb.first_sect = 0; cb.nsect = 1; cb.buf = (void far*)boot; _BX = FP_OFF(&cb); _DS = FP_SEG(&cb); _CX = 0xffff; _DX = 0; _AX = drive; asm int 25h asm pop ax asm jc err return 0; err: return 1; } /** * fat * * Выбрать элемент из FAT * * Функция выбирает элемент с заданным номером из таблицы * размещения файлов FAT . Формат FAT передается * функции как параметр * * int fat(b_fat, t_fat, idx); * * char far *b_fat - буфер, содержащий FAT * * int t_fat - формат FAT (12 или 16) * * unsigned idx - номер элемента FAT , который * должен быть выбран * **/ int fat(char far *b_fat, int t_fat, unsigned idx) { div_t clust_nu ; int cluster; if(t_fat == 12) { clust_nu = div(idx * 3, 2); if(clust_nu.rem != 0) cluster = (*((int far*)(b_fat + clust_nu.quot)) >> 4) & 0xfff; else cluster = *((int far*)(b_fat + clust_nu.quot)) & 0xfff; } else if(t_fat == 16) { cluster = *((int far*)(b_fat + idx * 2)); } else { printf("Ошибка в формате FAT \n"); exit(-100); } return(cluster); } |