ФАЙЛОВАЯ СЛУЖБА (DUD.SYS)
Для начала о структуре блочного
устройства в iS-DOS'е. Любое блочное устройство (дискета, электронный
диск или винчестер) с точки зрения iS-DOS'а
представляет собой набор блоков размером в 256 байт и нумерующихся от 0 до
65535. Заголовок устройства содержится в 0-ом блоке. Подробно структура 0-го
блока приводилась ранее при описании рестарта $binit(#0F), сейчас же повторим
лишь следующее:
10(3)* "DSK": признак iS-DOS
18(2)
Размер устройства в блоках
20(2) Номер первого блока главного каталога
Подробнее см. ПРИЛОЖЕНИЕ 1.
Размер устройства должен быть кратен 8. Связано это с
представлением карты занятых и свободных блоков, в которой каждому блоку
устройства соответствует 1 бит. Занятому блоку соответствует 1, свободному - 0.
Карта начинается с 1-го блока диска и занимает столько блоков подряд, сколько
необходимо для устройства соответствующего размера. Структура карты очень
проста: по 1 биту на блок (8 блоков в байте, до 2048 блоков устройства на 1
блок карты), 0-му блоку устройства соответствует 7-ой бит 0-го байта карты,
1-му блоку - 6-ой бит и т.д. На обычном флоппи-диске карта занимает 2 блока.
Максимально (на винчестере) карта может занимать 32 блока.
На устройстве должен находиться хотя бы один каталог,
т.е. главный. Номер 0-го блока главного каталога указан в 0-ом блоке устройства
со смещением 20.
Любой каталог в iS-DOS'е
представляет собой файл, содержащий в себе 32-байтовые описатели файлов, первый
из которых (0-ой) - это описатель самого каталога. Т.о. в iS-DOS'е
у каталогов два описателя: внутренний и внешний. Исключение составляет лишь сам
главный каталог, не имеющий внешнего описателя. У двух описателей должны быть
одинаковые имена, основную же информацию о каталоге хранит внутренний
описатель. Внешний же служит лишь для открывания каталога по имени и печати
имени каталога в панели.
Любой файл (и каталог) в iS-DOS'е
может быть непрерывным или сегментированным. Непрерывный файл (каталог)
занимает на устройстве непрерывную последовательность блоков. Номер начального
блока файла указывается в описателе файла со смещением 17. У сегментированных
файлов здесь указывается номер блока описателя сегментов. Это
специальный блок, имеющий следующую структуру:
0-й байт -
число сегментов (от 0 до 85),
следующие 2 байта - номер начального блока 1-го сегмента,
3-й байт -
размер сегмента в блоках (от 1 до 255).
Следующие
3 байта описывают следующий сегмент и т.д. (см. ПРИЛОЖЕНИЕ 22)
В iS-DOS'е
существуют понятия текущего устройства (их может быть 8, они переключаются
рестартом $swblk), текущего каталога (открывается рестартом $open1) и текущего
файла.
Этот рестарт открывает устройство и
его главный каталог. Для этого считывается 0-й блок устройства и проверяется на
наличие признака "DSK" со смещением 10 (13 для новой системы) в нём.
Проверяется размер устройства на кратность 8 и неравенство 0. Затем рестарт
пытается открыть главный каталог по номеру его начального блока, указанного в
том же блоке со смещением 20.
Входных параметров нет. Выходных также. Возможные ошибки:
87 - нет
признака "DSK" или неверный размер устройства
86 -
каталог не открывается: ошибка в описателе каталога плюс все возможные ошибки
драйвера
Пример: выход из программы fileshow. Ожидается нажатие
клавиши. Если это <Cs/Ent>, то открывается новое устройство:
LD C,$kwait ;ждем отпуска
клавиш
RST 16
DEC C ;ждем нажатия клавиши
RST 16
CP #17 ;Cs/Enter?
JR NZ,3$
LD C,$clear ;очистить кэш
RST 16
;
очистить буфер драйвера и настроить его на новую дискету:
LD C,$binit
RST 16
RET C
LD C,$open ;открыть
устройство
RST
16
RET C
;и,
наконец, выход с перерисовкой экрана
3$ XOR A
LD A,#F4
RET
Открыть каталог. На входе в регистре
DE подается номер начального блока каталога. Выходных параметров нет. Возможна
ошибка с номером 86 (см. предыдущий рестарт). Считывается 0-й блок каталога и
проверяется: байты 8..10 должны быть пробелами, в 11-ом байте 0-ой и 5-ый биты
должны быть в 1, 19-й и 20-й байты должны содержать номер 0-го блока. При любом
несоответствии получим ошибку 86.
Возможные ошибки:
81 - файл не найден
85 -
ломаный блок описателя сегментов (текущего или искомого каталога)
86 - ломаный каталог
В качестве примера рассмотрим как программа coca.com
открывает устройство и каталог альтернативной панели:
;достанем системный вектор оболочки (см. ПРИЛОЖЕНИЕ
17)
LD C,$gmpan ;#87
RST 16
EXX
DEC BC
LD A,(BC) ;номер текущей панели
INC BC
OR A
JR NZ,$+3
INC BC
LD A,(BC) ;канал альтернативной
панели
LD C,$stchn
RST 16
RET C
EXX
;HL=адрес тела канала альтернативной панели (см. ПРИЛОЖЕНИЕ
18)
LD B,(HL) ;номер устройства
INC HL
LD E,(HL)
INC HL
LD D,(HL)
; DE= номер 0-го блока каталога
LD C,$swblk
RST 16
RET C
LD C,$open1
RST 16
RET C
…
Создать файл в текущем каталоге на
текущем устройстве. В HL - адрес 32-байтового описателя файла. В нем необходимо
указать имя, расширение файла, байт состояния (если непрерывный - 6-ой бит =1).
Старший байт длины обнуляется, т.о. создать можно файл длиной не более 255
блоков (65280 байт). Если Вам требуется создать файл большей длины, пользуйтесь
дополнительно рестартом $fadd(#2F) или $eadd(#31). После создания файл будет
открыт. Следует помнить, что сей рестарт, равно как и большинство рестартов
этого уровня работает через кэш. Поэтому:
1. Всегда следите за сохранностью кэша;
2. Так как результат действий рестарта будет также в
кэше в виде модифицированных блоков, не забудьте по окончании работы программы
сохранить эти модифицированные блоки на устройстве с помощью рестарта
$flush(#02).
Возможные ошибки:
82 - файл с таким именем и типом уже существует
84 -
переполнение каталога (128 файлов)
92 - на
диске нет места
105 -
каталог непрерывен и заполнен до краев.
А также любые ошибки кэша и драйвера.
Для примера предлагается кусок из программы move.com:
…
;Открыть выходной каталог:
CALL G_DEST
RET C
SBC HL,HL
LD (FLEN),HL ;Обнулить длину
LD HL,FSTAT
SET 6,(HL) ;Непрерывный файл
LD HL,FILE
LD C,$crfil
RST
16
RET C
;Копируем описатель файла:
EXX
EX DE,HL
LD HL,FILE+32
LD BC,32
LDIR
LD C,$putf ;и сохраняем его
RST 16
RET C
…
FILE EQU $
FSTAT EQU FILE+11
FLEN EQU FILE+14
Найти файл по имени и уничтожить. Если
файл не найден, не докладывать. В регистре HL подается адрес 11-байтового
описателя: 8 байт имени и 3 байта расширения.
Возможные ошибки:
89 - файл защищен от удаления
85 -
неверный блок описателя сегментов
93 - попытка освободить блоки за концом устройства
Пример из
программы sv_image.com:
…
;текущая
дата для создаваемого файла:
LD C,$g_com ;#45
RST 16
EXX
DEC HL
DEC HL
LD D,(HL)
DEC HL
LD E,(HL)
LD
(DATE),DE
;в
зависимости от ключа: удалять или нет:
LD HL,FILE
OPTION LD A,0
OR A
LD C,$erfil
CALL NZ,16
RET C
DEC C ;$crfil
RST 16
RET C
…
Найти и открыть файл по имени и
расширению. В HL на входе подается адрес 11-байтового описателя файла. Если
найденный файл - каталог, то он открывается.
Выход
(если без ошибок, т.е. флаг C сброшен), то в регистре A:
#00 - открыт файл;
#20 - открыт каталог;
HL' -
адрес системного 32-байтового описателя файла (этот адрес можно получить с
помощью рестарта $bkfcb(#35)).
Возможные ошибки:
81 - файл
не найден
85 -
ломаный блок описателя сегментов (текущего или искомого каталога)
86 - ломаный каталог
Рассмотрим для примера кусочек программы unicolor.com
в той части, где программа ищет подкаталог HELP и в нем файл с расширением
.hlp:
…
;Текущий
путь для печати его в окне:
LD HL,PATH+8 ;буфер для пути
PUSH HL
LD B,pathSZ ;длина буфера
;Заполним
буфер пробелами
LD
(HL)," "
INC HL
DJNZ $-3
POP HL
XOR A
LD DE,56
LD C,$g_way ;#47
RST
16
RET C
;Ищем
конец пути, т.е. первый пробел:
1$ LD A,(HL)
CP " "
INC HL
JR NZ,1$
DEC HL
PUSH HL
;В
системном описателе - текущий файл:
LD C,$bkfcb
RST 16
EXX
POP DE
;Перекодируем
имя текущего файла в буфер пути:
LD C,$convr
RST 16
RET C
;Попытаемся
найти HELP*.hlp
XOR A
LD (hlpCSR+1),A
;Копируем
8 байт имени текущего файла в буфер имени hlp-файла:
LD DE,hlpNAM
PUSH DE
LD BC,8
LDIR
; …и,
наконец, пользуемся нашим рестартом fopen. Сперва откроем каталог:
LD HL,HELP
LD C,$fopen
RST 16
POP HL
JR C,GW_EX ;уходим по ошибке
;а
затем им же и файл:
RST 16
JR C,GW_EX
;не
отходя далеко от кассы, достанем длину файла:
EXX
LD BC,14
ADD HL,BC
LD E,(HL)
INC HL
LD D,(HL)
…
hlpNAM DEFS 8
DEFM "hlp"
HELP DEFM "HELP"
Открыть файл по номеру в регистре A.
На выходе, если все в порядке: A-сохраняется,
HL' - адрес системного 32-байтового описателя файла.
Возможные ошибки:
80 - номер файла слишком велик (больше 21-го байта
описателя каталога -1)
Пример из
программы calc.com. С помощью рестарта $opnum программа открывает файл, на
котором стоит курсор:
ORG 24000
;пытаемся
открыть параметр:
LD C,$opcat
RST 16
RET C
JR Z,1$
;параметра
в строке нет, следовательно, открываем файл под курсором:
LD C,$mwait ;"Wait
Please!"
RST 16
LD C,$g_cur ;#8A
RST 16
;A=номер
файла, на котором стоит курсор. Открываем файл по его номеру в каталоге:
LD C,$opnum
JR 2$
1$ EXX
;открываем
параметр по имени:
LD C,$find
2$ RST 16
RET C
EXX
;каталоги
не обслуживаем:
LD DE,11
ADD HL,DE
BIT 5,(HL)
RET NZ
LD DE,26-11
ADD HL,DE
;HL указывает на длину файла в его описателе
…
Считать в кэш или найти в кэше
соответствующий блок каталога и установить регистровую пару HL' на описатель
искомого файла в этом блоке.
Вход: E -
номер файла.
Файл не открывается, т.е. сохраняется текущий
открытый файл.
Возможные ошибки: см. предыдущий рестарт.
Пример из
программы univ.res. Цикл подбора файлов, подходящих под маску:
PODBOR
LD DE,#100
1$ INC E ;номер файла
LD C,$gname
RST 16
JR C,2$
PUSH DE
EXX
;HL
указывает на описатель файла в кэше
LD DE,TRAF ;трафарет (11 байт)
;сравнение
11 байт по адресам в HL и DE:
LD C,$cpfil ;#8F
RST 16
POP DE
JR NZ,1$
PUSH DE
CALL WIBCN ;работа с файлом
POP DE
RET C
JR 1$
;Ошибка.
В регистре A ее код:
2$ CP
80 ;каталог кончился
RET Z
SCF ;иначе: что-то серьезное
RET
Сохранить в текущем каталоге
измененный вектор текущего файла (тот, что доступен через $bkfcb(#35)). Полезно
для переименования файла и тому подобных операций. Номер файла лежит по
адресу bkfcb-1.
Возможные ошибки: ошибки
файловые, ошибки кэша и драйвера:
80 - номер
файла слишком велик
85 -
неверный блок описателя сегментов (ломаный каталог)
7 - ошибка
чтения/записи (драйвер)
62 - несоответствие числа модифицированных блоков (кэш)
Вот так выглядит применение этого рестарта на выходе
из программы calc:
…
;в HL - контрольная сумма открытого файла
;в (ADRKK+1) - адрес контрольной суммы
в системном описателе файла (bkfcb+26)
ADRKK LD (0),HL
LD C,$putf
RST 16
RET C
LD C,$flush
RST 16
RET C
XOR A
RET
Прочитать DE байт из текущего
открытого файла со смещением AHL байт от начала файла по адресу в IX.
Возможные ошибки:
100 -
попытка чтения за концом файла (т.е. AHL+DE больше длины файла).
106 - файл
не открыт
170 -
чтение 0 байт (DE=0)
171 - файл
защищен от чтения
7 - ошибка чтения/записи (драйвер)
Пример из
программы tree.com. Она открывает главный каталог, находит там файл
treecat.txt, проверяет, влезет ли он в буфер под кэш и считывает его:
…
LD C,$open ;главный каталог
RST 16
RET C
LD HL,FILE
LD C,$find ;найти по имени файл
RST 16
RET C
EXX
LD BC,#000F
ADD HL,BC
LD D,(HL)
DEC HL
LD E,(HL) ;DE=длина файла
LD HL,BUFF
;BUFF
- буфер за концом программы
ADD HL,DE
;
адрес конца файла в буфере
LD C,$g_cnf ;#10
RST 16
PUSH DE ;длина файла
EXX
LD BC,5
ADD HL,BC
LD E,(HL)
INC HL
LD D,(HL)
PUSH DE ;адрес кэша
EXX
POP DE
EX DE,HL
XOR A
SBC HL,DE
LD A,130
POP DE ;длина файла
RET C ;не хватает памяти
XOR A
LD L,A
LD H,A ;AHL=0
LD IX,BUFF
LD C,$rpart
RST 16
RET C
…
Записать DE байт в текущей открытый
файл со смещением AHL байт от начала файла с адреса в IX.
Возможные ошибки:
100 -
попытка записи за концом файла (т.е. AHL+DE больше длины файла).
106 - файл
не открыт
170 -
запись 0 байт (DE=0)
172 - файл
защищен от записи
7 - ошибка
чтения/записи (драйвер)
62 - несоответствие числа модифицированных блоков (кэш)
Для примера рассмотрим сохранение параметров
программой date.com на выходе из программы:
;сохранение
текущей среды в буфере curID
LD IX,curID
XOR A
LD C,$p_stat
RST 16
;Достать
среду последнего запущенного com-файла, т.е. date.com
LD C,$g_com
RST 16
EXX
LD BC,#FFF7
ADD HL,BC
PUSH HL
POP IX
;открыть файл date.com:
XOR A
LD C,$g_stat
RST
16
RET C
;записать
в него 1 байт со смещением 2 из WDCSR:
LD IX,WDCSR
LD DE,1
XOR A
LD HL,2
LD C,$wpart
RST 16
RET C
;сосчитать
контрольную сумму date.com
LD HL,CALC
LD C,$run
RST 16
JR NC,0$
;ошибка:
CP 37 ;нет резидентной задачи?
SCF
RET NZ
0$ LD C,$flush
RST 16
RET C
…
CALC DEFM "@calc"
DEFB 13
Прочитать B блоков
открытого файла начиная с DE-го по адресу в HL. Чтение осуществляется мимо
кэша, однако следует помнить, что если файл сегментированный, то система сама
подчитывает в кэш блок описатель сегментов файла. Т.е. следите, чтобы ваша
программа не повредила кэш к тому моменту, когда Вы соберетесь воспользоваться
этим рестартом, равно как и большинством рестартов этого уровня системы.
Возможные ошибки:
101 -
попытка чтения за концом файла (т.е. DE+B больше длины файла в блоках).
106 - файл
не открыт
7 - ошибка чтения/записи (драйвер)
Пример из жизни копировщиков: начало цикла копирования
программы image.com:
COPY
;B=размер
буфера копирования
PUSH BC
;переключиться
на входное устройство:
FROM LD BC,$swblk
RST 16
POP BC
RET C
;Считать
B блоков в буфер BUFF из открытого файла:
LD HL,BUFF
LD C,$rifle
RST
16
RET C
;переключиться на выходное устройство:
PUSH BC
DEST LD BC,$swblk
RST 16
POP BC
RET C
;Записать
B блоков на текущее устройство
LD C,$write
RST 16
RET C
…
Записать B блоков в открытый файл
начиная с DE-го с адреса в HL. Запись осуществляется мимо кэша. Посему после
использования данного рестарта может возникнуть ситуация несоответсвия блоков в
кэше блокам на устройстве, что чревато боком. Исправить дело можно очистив
принудительно кэш рестартом $clear(#01) или пересоздав его рестартом
$creat(#00).
Возможные ошибки:
101 -
попытка чтения за концом файла (т.е. DE+B больше длины файла в блоках).
106 - файл
не открыт
7 - ошибка чтения/записи (драйвер)
В качестве примера - кусок программы ibm_is.com:
;Открыть
выходной файл:
O_FILE LD A,0
CALL OPNUM
JR C,0$
EXX
LD BC,15
ADD HL,BC
LD E,(HL)
INC HL
LD D,(HL)
;DE=длина
файла в блоках
LD A,(OBUFSZ+2)
LD B,A
;A=B=размер выходного буфера в блоках.
;Добавить в конец файла A блоков:
LD C,$fadd
RST 16
JR C,0$
;Записать
B блоков в файл начиная с DE-го блока из буфера O_BUF:
LD C,$wifle
LD HL,O_BUF
RST 16
0$ POP HL
POP DE
POP BC
RET
;Подпрограмма
открывает файл по номеру в регистре A.
OPNUM CP
0
RET Z ;файл уже и так открыт
LD (OPNUM+1),A
LD C,$opnum
RST 16
RET
Прочитать один блок номер DE открытого
файла в кэш.
Выход
(если все O.K.):
HL' - адрес блока в кэше. (Если блок уже находился в
кэше, то считывания с устройства не происходит, рестарт просто находит его и
сообщает адрес. Тогда A=1, иначе A=0.)
DE'- адрес описателя блока в кэше. Необходим для
рестарта $modo(#2E).
Возможные ошибки:
101 -
попытка чтения за концом файла (т.е. AHL+DE больше длины файла).
106 - файл
не открыт
170 -
чтение 0 байт (DE=0)
171 - файл
защищен от чтения
7 - ошибка чтения/записи (драйвер)
Пример: очистка каталога при его создании программой
mkdir.com:
LD HL,(FILE+14)
;HL=длина
файла-каталога в байтах
LD A,L
OR A
LD A,H
JR NZ,$+3
DEC A
OR A
RET Z
LD B,A
;B=A=число
блоков, которые нужно очистить
LD DE,1 ;номер блока
1$ LD
C,$qrvbl ;прочитать блок
RST 16
RET C
EXX
;HL=адрес
блока в кэше
XOR A
LD B,A
;256 нулей:
LD (HL),A
INC HL
DJNZ $-2
PUSH DE ;адрес описателя
блока
EXX
POP HL
EX DE,HL
LD C,$modo
RST 16
RET C
EX DE,HL
INC DE ;номер блока
DJNZ 1$
RET
Модифицирование блока в кэше, т.е.
пометка блока как измененного и подлежащего выгрузке на устройство рестартом
$flush(#02) или автофлашем, запускающимся при превышении числа модифицированных
блоков некоего предельного значения.
Вход: DE -
адрес описателя блока в кэше. Этот адрес выдается на выходе рестартами
$quard(#06) и $qrvbl(#2D). Обычно рестарт $modo используется сразу после этих
рестартов. Именно сразу, поскольку блок может сместиться или вообще быть
вытеснен из кэша при вызове многих рестартов, считывающих блоки в кэш,
например: $open, $open1, $fopen, $find, $rpart, $gname, $opnum, $crfil и др.
Возможные ошибки:
62 -
несоответствие числа модифицированных блоков (кэш)
7 - ошибка чтения/записи (драйвер)
Вот что делает программа arzt+.com, когда сдвигает
файл is_dos.sys:
…
;Прочитать
0-ой блок устройства в кэш:
LD DE,0
LD C,$quard
RST 16
RET C
EXX
LD BC,32+17
ADD HL,BC
LD BC,(FR1ST+1)
;BC=номер
блока, где после сдвига находится файл is_dos.sys:
LD (HL),C
INC HL
LD
(HL),B
;Сосчитаем
новую контрольную сумму описателя файла is_dos.sys:
LD BC,31-18
ADD HL,BC
LD B,#20
XOR A
XOR (HL)
DEC HL
DJNZ $-2
;Контрольная
сумма также помещается в 0-ом блоке устройства:
DEC HL
DEC HL
DEC HL
DEC HL
LD
(HL),A
;И, наконец,
модифицируем блок в кэше:
LD C,$modo
RST 16
RET C
…
Добавить A блоков к сегментированному
файлу. Добавленные блоки вставляются перед блоком номер DE (первый добавленный
блок становится DE-ым). Если DE слишком велик, то добавление все равно
производится в конец файла.
Возможные ошибки:
62 -
несоответствие числа модифицированных блоков (кэш)
92 - нет
места на диске
94 -
переполнение блока описателей сегментов (85 сегментов)
102 -
непрерывный файл
103 -
добавить/удалить 0 блоков (A=0)
106 - файл
не открыт
7 - ошибка чтения/записи (драйвер)
Пример из
программы wet.com. Добавление специального блока в начало текстового файла и
заполнение его вектором:
EXIT XOR A
LD E,A
LD D,A ;DE=0: номер блока
INC A ;A=1: число блоков
LD C,$fadd
RST 16
RET C
LD HL,BUFF ;буфер блока
;Очистка
буфера:
PUSH HL
XOR A
LD B,A
LD (HL),A
INC HL
DJNZ
$-2
LD HL,VECTOR ;вектор
редактора
LD C,VECSZ ;размер вектора
LD DE,BUFF+#80+edcsr ;+смещение
LDIR
POP HL ;буфер
LD D,B
LD E,B ;DE=0
LD C,$wvblk ;записать 1 блок
RST 16
RET C
…