Страница Ивана Рощина > Статьи >

© Иван Рощин, Москва

ZXNet: 500:95/462.53
E-mail: bestview@mtu-net.ru
WWW: http://www.ivr.da.ru

Проверка корректности файловой структуры дисков TR-DOS

Радиомир. Ваш компьютер» 6/2004)
Дополненная версия.

Введение
Теория
Программная реализация
Литература

Введение

По различным причинам (прерывание дисковых операций, связанных с записью на диск, использование программ, неправильно работающих с диском, ошибочные действия пользователя при ручном редактировании каталога и т.д.) файловая структура диска TR-DOS может оказаться некорректной.

Если работающая с диском программа не проверяет правильность файловой структуры (а в общем-то, и сама TR-DOS этого не делает — проверяется лишь специальный байт в служебном секторе, указывающий на принадлежность диска TR-DOS), то работа с «неправильным» диском может привести, вообще говоря, к непредсказуемым последствиям. Возможно и разрушение программы в памяти, и потеря информации на диске. А если, скажем, в каталоге для какого-то файла неправильно указан его размер или номер начального трека/сектора, то попытка работы с этим файлом теоретически может привести даже к повреждению дисковода при попытке позиционирования на несуществующий трек.

Ясно, что своевременное обнаружение ошибок в файловой структуре было бы очень кстати. Значит, нужна процедура, проверяющая правильность каталога диска. Такая процедура может быть использована как в специальной программе проверки дисков, так и вообще в любой программе, работающей с диском: если перед дисковыми операциями прочитать каталог и проверить его корректность, это поможет избежать неприятных последствий, а если ваша программа выполняет операции, связанные с записью на диск, работая непосредственно с каталогом, то вызов этой процедуры будет полезен и перед записью изменённого каталога на диск: если из-за ошибки в программе записываемый каталог не является корректным, это будет сразу же замечено. Ваша программа, таким образом, будет проверять саму себя!

Теория

Кратко расскажу о формате дисков TR-DOS — эта информация пригодится для понимания логики работы программы.

На каждом треке (они нумеруются с 0) находится 16 секторов (также нумеруются с 0). Длина каждого сектора — 256 (#100) байтов.

Для системных нужд выделен нулевой трек, а область с первого трека и до конца диска используется для хранения содержимого файлов.

В секторах 0—7 нулевого трека находятся элементы каталога, содержащие информацию о записанных на диске файлах. Каждый элемент каталога занимает 16 байтов, его структура описана в табл. 1.

Табл. 1
Смещение от начала Длина Комментарий
0 8 Имя файла. Если первый байт равен 0 — значит, элемент каталога не используется; если равен 1 — элемент соответствует стёртому файлу.
8 1 Расширение файла (или первый символ расширения, если используется 3-символьное расширение).
9 2 Стартовый адрес файла (или последние два символа расширения, если используется 3-символьное расширение). Для бейсик-файлов — длина программы и переменных.
11 2 Длина файла в байтах. Для бейсик-файлов — длина программы без учёта длины переменных.
13 1 Длина файла в секторах.
14 2 Дисковый адрес начала файла (первый байт — номер сектора, второй байт — номер трека).

Всего каталог содержит #800/16=128 элементов, то есть на диске может быть максимум 128 файлов. Используемые элементы каталога (соответствующие реальным файлам и удалённым файлам) расположены в его начале, неиспользуемые — в конце.

Каждый файл занимает целое число секторов. Файлы расположены друг за другом без промежутков, именно в том порядке, в котором они перечислены в каталоге. Первый файл начинается с трека 1, сектора 0.

Сектор 8 каталога (служебный сектор) содержит информацию о диске в целом (см. табл. 2). Не все байты этого сектора используются.

Табл. 2
Смещение от начала Длина Комментарий
#00 1 Этот байт должен быть равен 0 для правильного определения конца каталога, когда на диске 128 файлов.
#E1 2 Дисковый адрес первого свободного сектора (первый байт — номер сектора, второй байт — номер трека).
#E3 1 Тип дискеты:
#16 — 80 треков, 2 стороны,
#17 — 40 треков, 2 стороны,
#18 — 80 треков, 1 сторона,
#19 — 40 треков, 1 сторона.
(Здесь имеются в виду физические треки, а не логические.)
#E4 1 Общее количество файлов (включая удалённые).
#E5 2 Количество свободных секторов (сначала младший байт, потом старший).
#E7 1 Идентификационный код TR-DOS (байт #10).
#F4 1 Количество удалённых файлов.
#F5 8 Имя диска (вообще говоря, может занимать и все 11 байтов до конца сектора).

Как видим, информация, представленная в каталоге, избыточна. Например, в описателе файла можно было бы обойтись без указания дискового адреса начала файла (так как для первого файла он известен — трек 1, сектор 0, — а для любого другого файла может быть вычислен, исходя из суммы длин в секторах всех файлов до него). Многие параметры, находящиеся в служебном секторе, также можно было бы не хранить там, а вычислять, исходя из содержимого элементов каталога.

С одной стороны, избыточность — это плохо, так как данные занимают больше места. Но, с другой стороны, эта же избыточность упрощает работу с данными (скажем, когда при записи нового файла надо узнать адрес первого свободного сектора, то вместо того, чтобы вычислять его, складывая длины всех файлов, можно взять уже готовое число).

Именно за счёт избыточности удаётся обнаруживать ошибки, сравнивая записанные в каталоге значения параметров с вычисленными значениями, а также проверяя, чтобы значения параметров находились в правильных диапазонах (номер сектора — в диапазоне 0—15, номер типа диска — в диапазоне #16—#19, и т.д.). Подробную информацию о производимых проверках вы можете узнать из комментариев в тексте программы.

Программная реализация

Сначала я написал функцию, проверяющую корректность каталога, на языке C (используя компилятор Turbo C 2.0), а потом уже перенёс её на ассемблер Z80. С одной стороны, это облегчило мне тестирование и отладку, а с другой стороны — наличие функции на C поможет тем, кому потребуется добавить проверку корректности каталога в программу для работы с дисками TR-DOS (или с файлами-образами таких дисков) не на ZX Spectrum, а, например, на AMiGA или PC.

При вызове функции ей передаются два параметра: адрес каталога в памяти (т.е. адрес содержимого первых девяти секторов нулевого трека) и максимально допустимое количество треков на диске (в функции производится проверка, чтобы количество занятых секторов диска, сложенное с количеством свободных секторов, указанным в служебном секторе, не превышало количества секторов на указанном при вызове функции количестве треков).

В принципе, можно было бы считать второй параметр равным 40, 80 или 160 трекам, в зависимости от типа диска (см. табл. 2), и определять его в самой функции. Решение о передаче этого параметра при вызове функции было принято с учётом того, чтобы можно было проверять диски, отформатированные на большее количество треков, чем следует из значения типа диска, а также RAM-диски, количество треков на которых определяется объёмом выделенной под них памяти.

Функция возвращает двухбайтное значение, где младший байт — либо 0, если ошибок не обнаружено, либо код ошибки (см. табл. 3), а старший байт — номер элемента каталога (нумеруются с 0), при рассмотрении которого ошибка была обнаружена (для тех кодов ошибок, которые помечены «*» в первом столбце табл. 3). Если в каталоге несколько ошибок, то возвращаемое функцией значение будет соответствовать первой обнаруженной ошибке.

Табл. 3
Код ошибки Комментарий
1 Неправильный идентификационный код TR-DOS.
2 Первый байт служебного сектора не равен 0.
3 Неправильный тип дискеты (не #16—#19).
4 Номер первого свободного сектора не является числом в диапазоне 0—15.
5* Обнаружен файл (возможно, удалённый), номер начального сектора которого не является числом в диапазоне 0—15.
6 Фактическое общее количество файлов не совпадает с указанным в служебном секторе.
7 Фактическое количество удалённых файлов не совпадает с указанным в служебном секторе.
8* Обнаружен используемый элемент каталога, следующий после неиспользуемого.
9 Первый файл начинается не с трека 1, сектора 0.
10* Обнаружен файл (возможно, удалённый), дисковый адрес которого не равен дисковому адресу предыдущего файла, увеличенному на количество секторов в предыдущем файле.
11 Указанный в служебном секторе дисковый адрес первого свободного сектора не равен дисковому адресу последнего файла, увеличенному на количество секторов в последнем файле.
12 Файлов нет, но указанный в служебном секторе дисковый адрес первого свободного сектора — не «трек 1, сектор 0».
13 Количество занятых секторов диска, сложенное с количеством свободных секторов, указанным в служебном секторе, оказалось больше, чем содержится секторов на указанном при вызове функции максимальном количестве треков.

Ниже приведён листинг функции.

#define byte unsigned char    /* 1 байт.  */
#define word unsigned int     /* 2 байта. */

/* ==========================================================================
       Функция test_cat - проверка правильности каталога TR-DOS.

Вход:  cat - адрес каталога в памяти, tracks - максимальное количество треков
       на диске.

Выход: 2-байтное значение, где младший байт - код ошибки или 0, если нет
       ошибок, а старший байт для некоторых кодов ошибок - номер элемента
       каталога, при рассмотрении которого была обнаружена ошибка.
========================================================================== */

word test_cat (byte cat[0x900], byte tracks)
{
 byte files=0;      /* Общее количество файлов.          */
 byte del_files=0;  /* Количество удалённых файлов.      */
 byte element=0;    /* Номер текущего элемента каталога. */

 /* Проверка идентификационного кода TR-DOS. */
 if (cat[0x8E7]!=0x10) return (1);

 /* Первый байт служебного сектора должен быть равен 0. */
 if (cat[0x800]!=0) return (2);

 /* Тип дискеты должен быть числом в диапазоне 0x16-0x19. */
 if ((cat[0x8E3]<0x16)||(cat[0x8E3]>0x19)) return (3);

 /* Номер первого свободного сектора должен быть числом в диапазоне 0-15. */
 if (cat[0x8E1]>15) return (4);

 /* Подсчитываем общее количество файлов и количество удалённых файлов,
    одновременно проверяя, чтобы номер начального сектора каждого файла
    был числом в диапазоне 0-15. */

 while (cat[element*16]!=0)
 { /* Пока не обнаружим неиспользуемый элемент или не просмотрим весь каталог. */
  files++;
  if (cat[element*16]==1) del_files++;
  if (cat[element*16+0x0E]>15) return (5+(element<<8));
  element++;
 }

 /* Проверяем, правильно ли в служебном секторе указано количество файлов
    и количество удалённых файлов. */

 if (cat[0x8E4]!=files) return (6);
 if (cat[0x8F4]!=del_files) return (7);

 /* Если сейчас element=128, то просмотрен весь каталог, иначе остались
    неиспользуемые элементы, и надо проверить, чтобы все они были помечены
    как неиспользуемые. */

 while (element<128)
 {
  if (cat[element*16]!=0) return (8+(element<<8));
  element++;
 }

 if (files>0)
 {/* Файлы есть. */

  /* Первый файл должен начинаться с трека 1, сектора 0. */
  if ((cat[0x0F]!=1)||(cat[0x0E]!=0)) return (9);

  /* Дисковый адрес (т.е. начальный трек-сектор) каждого файла, начиная
     со второго, должен быть равен дисковому адресу предыдущего файла,
     увеличенному на количество секторов в предыдущем файле. */

  for (element=1;element<files;element++)
  {
   if ((cat[(element-1)*16+0x0E]+cat[(element-1)*16+0x0F]*16+cat[(element-1)*16+0x0D])!=
       (cat[element*16+0x0E]+cat[element*16+0x0F]*16)) return (10+(element<<8));
  }

  /* Указанный в служебном секторе дисковый адрес первого свободного сектора
     должен быть равен дисковому адресу последнего файла, увеличенному на
     количество секторов в последнем файле. */

  if ((cat[(files-1)*16+0x0E]+cat[(files-1)*16+0x0F]*16+cat[(files-1)*16+0x0D])!=
      (cat[0x8E1]+cat[0x8E2]*16)) return (11);
 }
 else
 {/* Файлов нет. */

  /* Указанный в служебном секторе дисковый адрес первого свободного сектора
     должен быть "трек 1, сектор 0". */
  if ((cat[0x8E2]!=1)||(cat[0x8E1]!=0)) return (12);
 }

 /* Количество занятых секторов диска, сложенное с количеством свободных
    секторов, указанным в служебном секторе, не должно быть больше, чем
    содержится секторов на указанном при вызове функции максимальном
    количестве треков. */

 /* Преобразование к типу long нужно, чтобы избежать возможного переполнения
    при сравнении (для этого разрядность long должна быть больше двух байтов). */

 if ((cat[0x8E1]+cat[0x8E2]*16+cat[0x8E5]+(((long)(cat[0x8E6]<<8))&0xFFFF)) >
    (tracks*16)) return (13);

 /* Все проверки успешно пройдены - делаем вывод, что ошибок нет. */

 return (0);
}
Скачать листинг в текстовом виде (2 КБ ZIP)

А вот аналогичная процедура на ассемблере Z80:

;---------------------------------------------------------------
;TEST_CAT - проверка правильности каталога TR-DOS.
;
;Вход:  каталог (#900 байтов) находится в памяти с адреса CAT,
;       A - максимальное количество треков на диске.
;
;Выход: A - код ошибки или 0, если нет ошибок,
;       D - для некоторых кодов ошибок - номер элемента
;           каталога, при рассмотрении которого была обнаружена
;           ошибка.

TEST_CAT   EX   AF,AF'  ;Сохраняем количество треков.

;Проверка идентификационного кода TR-DOS.

           LD   A,(CAT+#8E7)
           CP   #10
           LD   A,1       ;*
           RET  NZ

;Первый байт служебного сектора должен быть равен 0.

           LD   A,(CAT+#800)
           AND  A
           LD   A,2       ;*
           RET  NZ

;Тип дискеты должен быть числом в диапазоне #16-#19.

           LD   A,(CAT+#8E3)
           SUB  #16
           AND  %11111100
           LD   A,3       ;*
           RET  NZ

;Номер первого свободного сектора должен быть числом в диапазоне
;0-15.

           LD   A,(CAT+#8E1)
           AND  #F0
           LD   A,4       ;*
           RET  NZ

;Подсчитываем общее количество файлов и количество удалённых
;файлов, одновременно проверяя, чтобы номер начального сектора
;каждого файла был числом в диапазоне 0-15.

           LD   IX,CAT
           LD   BC,16
           LD   D,B       ;Общее количество файлов = 0.
           LD   E,B       ;Количество удалённых файлов = 0.

;Цикл будет выполняться, пока не обнаружим неиспользуемый
;элемент или не просмотрим весь каталог.

TEST_CAT_1 LD   A,(IX)
           AND  A
           JR   Z,TEST_CAT_3

           DEC  A
           JR   NZ,TEST_CAT_2
           INC  E

TEST_CAT_2 LD   A,(IX+14)
           AND  #F0
           LD   A,5       ;*
           RET  NZ

           INC  D
           ADD  IX,BC
           JR   TEST_CAT_1

;Проверяем, правильно ли в служебном секторе указано количество
;файлов и количество удалённых файлов.

TEST_CAT_3 LD   A,(CAT+#8E4)
           CP   D
           LD   A,6       ;*
           RET  NZ

           LD   A,(CAT+#8F4)
           CP   E
           LD   A,7       ;*
           RET  NZ

;Если сейчас D=128, то просмотрен весь каталог, иначе остались
;неиспользуемые элементы, и надо проверить, чтобы все они были
;помечены как неиспользуемые.

           LD   E,D       ;E - общее количество файлов.

TEST_CAT_4 BIT  7,D       ;D=128?
           JR   NZ,TEST_CAT_5

           LD   A,(IX)
           AND  A
           LD   A,8       ;*
           RET  NZ

           INC  D
           ADD  IX,BC
           JR   TEST_CAT_4

TEST_CAT_5 INC  E
           DEC  E         ;E=0?
           JR   Z,TEST_CAT_9

;Файлы есть.
;Первый файл должен начинаться с трека 1, сектора 0.

           LD   HL,(CAT+#0E)
           DEC  H
           LD   A,H
           OR   L
           LD   A,9       ;*
           RET  NZ

;Дисковый адрес (т.е. начальный трек-сектор) каждого файла,
;начиная со второго, должен быть равен дисковому адресу
;предыдущего файла, увеличенному на количество секторов
;в предыдущем файле.

           LD   IX,CAT+16
           LD   D,1       ;N текущего элемента каталога.

TEST_CAT_6 LD   B,(IX-1)  ;N трека предыдущего файла.
           LD   C,(IX-2)  ;N сектора предыдущего файла.
           CALL MAKE_CDA
           LD   A,(IX-3)  ;Кол-во секторов в предыдущем файле.
           ADD  A,C
           LD   C,A
           JR   NC,TEST_CAT_7
           INC  B

TEST_CAT_7 LD   H,B
           LD   L,C       ;HL=BC.
           LD   A,D
           CP   E         ;Все файлы обработаны?
           JR   Z,TEST_CAT_8

           LD   B,(IX+15) ;N трека текущего файла.
           LD   C,(IX+14) ;N сектора текущего файла.
           CALL MAKE_CDA
           SBC  HL,BC     ;Флаг C сброшен после MAKE_CDA.
           LD   A,10      ;*
           RET  NZ

           LD   BC,16
           ADD  IX,BC
           INC  D
           JR   NZ,TEST_CAT_6

;Указанный в служебном секторе дисковый адрес первого свободного
;сектора должен быть равен дисковому адресу последнего файла,
;увеличенному на количество секторов в последнем файле (т.е.
;должен быть равен HL).

TEST_CAT_8 LD   BC,(CAT+#8E1)
           CALL MAKE_CDA
           SBC  HL,BC     ;Флаг C сброшен после MAKE_CDA.
           LD   A,11      ;*
           RET  NZ

           JR   TEST_CAT_A

;Файлов нет.
;Указанный в служебном секторе дисковый адрес первого свободного
;сектора должен быть "трек 1, сектор 0".

TEST_CAT_9 LD   HL,(CAT+#8E1)
           DEC  H
           LD   A,H
           OR   L
           LD   A,12      ;*
           RET  NZ

;Количество занятых секторов диска, сложенное с количеством
;свободных секторов, указанным в служебном секторе, не должно
;быть больше, чем содержится секторов на указанном при вызове
;функции максимальном количестве треков.

TEST_CAT_A LD   HL,(CAT+#8E5)
           LD   BC,(CAT+#8E1)
           CALL MAKE_CDA
           ADD  HL,BC
           LD   A,13                   ;**
           RET  C      ;Переполнение!  ;**

           EX   AF,AF' ;Максимальное количество треков.
           LD   B,A
           LD   C,0
           CALL MAKE_CDA

;HL должно быть меньше BC+1.

           SCF
           SBC  HL,BC ;HL=HL-(BC+1)
           LD   A,13                   ;***
           RET  NC                     ;***

;Все проверки успешно пройдены - делаем вывод, что ошибок нет.

           XOR  A                      ;****
           RET                         ;****

;MAKE_CDA - подпрограмма преобразования дискового адреса
;в компактный вид, более удобный для вычислений.
;
;Вход:  B - N трека, C - N сектора.
;Выход: BC - N трека*16+N сектора.

MAKE_CDA   LD   A,B
           RRCA
           RRCA
           RRCA
           RRCA
           LD   B,A
           AND  #F0
           OR   C
           LD   C,A
           LD   A,B
           AND  #0F
           LD   B,A
           RET
Скачать листинг в текстовом виде (2 КБ ZIP)

Длина этой процедуры — 228 байтов. Если в программе нужно лишь выяснить, есть ошибка или нет, а какая именно ошибка — значения не имеет, то процедуру можно упростить. Для этого надо исключить из листинга двенадцать строк, помеченных «*», заменить фрагмент из двух строк, помеченных «**», на следующие строки:

           SBC  A,A
           RET  NZ     ;Переполнение!

заменить фрагмент из двух строк, помеченных «***», на следующие строки:

           CCF
           SBC  A,A
           RET

и убрать из листинга фрагмент из двух строк, помеченных «****».

В результате длина процедуры сократится на 27 байтов и составит 201 байт. Входные параметры останутся теми же, а выходные параметры будут такими: если флаг Z установлен, то всё в порядке, а если этот флаг сброшен — значит, обнаружена ошибка.

В процедуре не используется самомодификация кода, так что при необходимости она может быть помещена в ПЗУ.

Литература

  1. А.Ларченко, Н.Родионов. «ZX Spectrum и TR-DOS для пользователей и программистов». СПб, «Питер», 1994.

Другие мои статьи о TR-DOS:

1. 

«Доступ к порту #1F в TR-DOS 5.03». «ZX-Ревю» 1—2/1997.

2. 

«О сокращении времени форматирования». «ZX-Ревю» 1—2/1997.

3. 

«TR-DOS: как не допустить ошибки?». «ZX-Ревю» 5—6/1997.

4. 

«Работа с диском при включённых прерываниях». Adventurer #9, «Чёрная ворона» #3, «Радиолюбитель. Ваш компьютер» 6/2000 (дополненная версия).

5. 

«Вывод трёхсимвольных расширений файлов в операционной системе TR-DOS». «Радиолюбитель. Ваш компьютер» 7/2000.

6. 

«Усовершенствованный алгоритм определения смены диска». «Радиолюбитель. Ваш компьютер» 9/2000.

7. 

«Расширения файлов TR-DOS». «Радиолюбитель. Ваш компьютер» 12/2000, «Радиомир. Ваш компьютер» 9/2001 (под псевдонимом BV_Creator).

8. 

«Обрубаем файлам „хвост“». «Радиомир. Ваш компьютер» 4/2002.

9. 

«Использование избыточной информации для защиты файлов от повреждений». «Радиомир. Ваш компьютер» 11/2002.

Страница Ивана Рощина > Статьи >