OMMAND.COM, который несомненно является основной частью опера- ционной системы. Фиксированные диски имеют главную запись загрузки, которая содержит таблицу разделов, позволяющую разделить диск между нес- колькими операционными системами. Таблица разделов содержит ин- формацию о том, где на диске начинается раздел DOS, а также пер- вый сектор какого раздела содержит запись начальной загрузки. В остальном раздел организован так же, как и дискета. 5.1.1 Чтение таблицы размещения файлов. Диск использует таблицу размещения файлов (FAT) для отведения дискового пространства файлам и хранения информации о свободных секторах. Из соображений безопасности на всех дисках хранятся две копии FAT. Они хранятся последовательно, в секторах с самыми младшими доступными логическими номерами, начиная со стороны 0, дорожки 0, сектора 2 (сектор 1 также занят записью начальной загрузки). Число секторов, занимаемых FAT определяется размером и типом диска. Отметим, что в MS DOS 3.0 размер записи FAT может быть 16 битов для фиксированного диска. Здесь мы будем рассматри- вать только 12-битные записи; для получения информации о 16-бито- вых записях, смотрите Техническое руководство по MS DOS. Таблица размещения файлов хранит информацию о каждом кластере секторов на диске. Кластер это группа стандартных секторов разме- ром 512 байт (независимо от типа диска MS DOS всегда работает с 512-байтными секторами). Группа секторов используется, чтобы уменьшить размер FAT. Однако большие кластеры, используемые на фиксированном диске напрасно расходуют дисковое пространство при записи маленьких файлов (утилита размером 500 байт берет 4K дис- кового пространства). Имеется набор размеров кластеров и размеров FAT, используемых в IBM PC: Тип диска Секторов на кластер Размер FAT дискета 160K 1 1 дискета 180K 1 1 дискета 320K 2 2 дискета 360K 2 2 дискета 1.2M 1 7 винчестер 10M 8 8 винчестер 20M 4 40 При большем размере кластера напрасно расходуется дисковое пространство, но когда большие диски имеют малый размер кластера, то таблица размещения файлов становится слишком большой. При работе с дисками DOS загружает копию FAT в память, по возможности сохраняя ее там, поэтому при большом размере FAT может расходо- ваться много оперативной памяти. Поскольку большинство AT имеют достаточно много памяти, то для них приемлемы намного большие FAT. Поэтому для 20M винчестера взяты меньшие размеры кластеров, чем для 10M, обеспечивая экономию дискового пространства. Для дискет емкостью 1.2M выбран кластер размером в 1 сектор, так как их основное назначение состоит в хранении копий жесткого диска, а следовательно компактность очень важна. Каждая позиция в таблице размещения файлов соответствует опре- деленной позиции кластера на диске. Обычно файл занимает несколь- ко кластеров и запись в каталоге файлов содержит номер стартового кластера, в котором записано начало файла. Просмотрев позицию FAT, соответствующую первому кластеру, DOS находит номер класте- ра, в котором хранится следующая порция этого файла. Этому клас- теру соответствует своя запись в FAT, которая в свою очередь содержит номер следующего кластера в цепочке. Для последнего кластера, занятого файлом FAT содержит значения от FF8H до FFFH. Неиспользуемым (или освобожденным) кластерам записывается значе- ние 000, а плохим секторам - FF7H. Наконец, значения от FF0H до FF7H приписываются резервным кластерам. Номер кластера содержит 3 шестнадцатиричные цифры, для хране- ния которых требуется 1 1/2 байта. Для уменьшения размеров FAT числа для двух соседних кластеров хранятся в трех последователь- ных байтах таблицы. MS DOS автоматически производит все необходи- мые вычисления. Первые три байта FAT не используются для номеров кластеров. Первый байт содержит код, определяющий тип диска (см. [1.1.5]), а следующие 2 байта оба равны FFH. Поскольку эти позиции таблицы заняты, то кластеры нумеруются, начиная с 2, причем кластеры 2 и 3 занимают вторую тройку байт таблицы. MS DOS 3.0 может создавать FAT с записями размером 16 бит. Такие записи необходимы для фиксированных дисков размером более 10M, которые имеют больше, чем 4086 кластеров. На рис. 5-1 пока- зана связь между FAT и кластерами на диске. Очень редко имеются причины вносить изменения прямо в таблицу размещения файлов. MS DOS заботится обо всех файловых операциях и обеспечивает процедуры, анализирующие таблицу на предмет наличия свободного пространства на диске. Однако для некоторых специаль- ных целей, таких как восстановление удаленных файлов или написа- ние драйвера блочного устройства, необходим прямой доступ к FAT. При прямом доступе к FAT надо соблюдать следующие правила. Для нахождения следующего кластера файла: 1. Умножьте номер кластера на 1.5. 2. Прочитайте 2 байта с полученным смещением (окгругляя вниз). 3. Если номер кластера четный, то возьмите младшие 12 бит, иначе возьмите старшие 12 бит. Для преобразования номера кластера в логический номер сектора: 1. Вычтите 2 из номера кластера. 2. Умножьте результат на число секторов в кластере. Высокий уровень. В данном примере читается FAT и поределяется значение, храня- щееся для кластера номер 6. В [5.4.2] объясняется начальный код, читающий сектора FAT. Результатом является 12-битное число, представленное в виде трех шестнадцатиричных цифр (4 бита каж- дая), возвращаемое в виде строки. В примере пары чисел, состоящих из двух цифр, объединены и в качестве результата берутся правые или левые три цифры. Когда Бейсик преобразует символ в 16-ную форму, то он возвращает только одну цифру, если первая была ну- лем, поэтому удаленный ноль должен быть восстановлен, чтобы этот метод работал правильно. 100 '''чтение секторов FAT 110 DEFINT A-Z 120 DATA &H55, &H8B, &HEC, &H1E, &H8B, &H76, &H0C, &H8B 130 DATA &H04, &H8B, &H76, &H0A, &H8B, &H14, &H8B, &H76 140 DATA &H08, &H8B, &H0C, &H8B, &H76, &H06, &H8A, &H1C 150 DATA &H8E, &HD8, &H8B, &HC3, &HBB, &H00, &H00, &HCD 160 DATA &H25, &H59, &H1F, &H5D, &HCA, &H08, &H00 170 DEF SEG = &H1000 'помещаем машинный код с этого адреса 180 FOR N = 0 TO 38 'читаем 39 байтов данных 190 READ Q: POKE N,Q 'переносим их в память 200 NEXT ' 210 READSECTOR = 0 'начинаем процедуру с 1-го байта 220 BUFFER = &H2000 'адрес буфера приема данных 230 LOGICALNUMBER = 1 'начальные сектора FAT 240 NUMBERSECTORS = 2 '2 сектора в FAT 250 DRIVE = 0 'читаем накопитель A 260 CALL READSECTOR(BUFFER,LOGICALNUMBER,NUMBERSECTORS,DRIVE) 270 '''определяем номер следующего кластера файла 280 DEF SEG = &H2000 'буфер, где хранится FAT 290 CLUSTERNUMBER! = 6 'кластер номер 6 300 C! = CLUSTERNUMBER! 'делаем копию 310 C! = INT (C!*1.5) 'умножаем на 1.5 и округляем 320 X = PEEK(C!) 'читаем 2 байта с этой позиции 330 Y = PEEK(C!+1) ' 340 X$ = HEX$(X): Y$ = HEX$(Y) 'переводим в 16-ные строки 350 IF LEN(X$) = 1 THEN X$ = "0"+X$ 'делаем из 2-символьными 360 IF LEN(Y$) = 1 THEN Y$ = "0"+Y$ ' 370 H$ = Y$ + X$ 'объединяем числа в одну строку 380 '''проверяем кластер на четность 390 IF CLUSTERNUMBER! MOD 2 <> 0 THEN 420 'уход, если нечетный 400 NEXTCLUSTER$ = RIGHT$(H$,3) 'если четный, то правые 3 цифры 410 GOTO 430 420 NEXTCLUSYER$ = LEFT$(H$,3) 'если нечетный, то левые 430 PRINT NEXTCLUSTER$ 'печатаем результат Средний уровень. Функция DOS 1CH дает информацию о таблице размещения файлов, но не дает саму FAT. Поместите номер накопителя в DL, где 0 = накопитель по умолчанию, 1 = A, и т.д. При возврате DX содержит число кластеров в FAT, а CX - число байтов в секторе. DS:BX ука- зывает на байт, содержащий первый байт FAT, т.е. на код, указы- вающий тип диска; эти коды перечислены в [1.1.5]. Низкий уровень. Намного легче получить доступ к FAT в языке ассемблера. Отме- тим, что умножение номера кластера на 1.5 производится копирова- нием числа, сдвигом копии вправо на 1 бит для деления пополам и сложением копии с оригиналом. Этот метод автоматически окгруляет результат вниз. Код, считывающий сектора FAT в память, обсуждает- ся в [5.4.2]. ;---в сегменте данных BUFFER DB 1024 DUP(0) ;отводим место для 2 секторов ;---читаем FAT в память LEA BX,BUFFER ;указываем на буфер данных MOV DX,1 ;логический номер сектора MOV CX,2 ;2 сектора MOV AL,0 ;накопитель A INT 25H ;читаем сектора POP CX ;восстанавливаем стек ;---получаем номер кластера MOV AX,3 ;номер кластера в AX MOV CX,AX ;делаем копию MOV DX,AX ;делаем вторую копию SHR DX,1 ;делим вторую копию на 2 ADD CX,DX ;складываем между собой ADD BX,CX ;добавляем как смещение MOV DX,[BX] ;получаем 2 байта из этого места TEST AX,1 ;номер кластера нечетный? JNZ ODD_CLUSTER ;уход, если да AND DX,0000111111111111B ;получаем номер JMP SHORT CONTINUE ;уход через обработку нечетного ODD_CLUSTER: MOV CL,4 ;подготовка к сдвигу вправо SHR DX,CL ;сдвигаем вниз старшие 12 битов CONTINUE: 5.1.2 Определение доступного дискового пространства. Хотя в следующем подразделе объянено как восстановить ситуаци- цию при ошибке из-за нехватки места на диске, но нет лучшего лекарства, чем предусмотрительность. Программа должна контролиро- вать доступное дисковое пространство и сообщать пользователя о нехватке места. Если места не хватает, то пользователь может выйти из программы и устранить проблему без потери информации. Высокий уровень. Следующая ассемблерная подпрограмма возвращает в переменную CLUSTERS число свободных кластеров на указанном диске. Надо по- местить номер накопителя в DRIVENUM, где 1 = A, 2 = B и т.д. В приложении Г объясняется как ассемблерные подпрограммы включаются в программы на Бейсике. 10 DEFINT A-Z 'используем целые переменные 20 DRIVENUM = 1 'сюда помещаем номер накопителя 30 CLUSTERS = 0 'инициализируем переменную 40 DATA &H55, &H8B, &HEC, &H8B, &H76, &H06, &H8B 50 DATA &H14, &HB4, &H36, &HCD, &H21, &H8B, &H7E 60 DATA &H08, &H89, &H1D, &H5D, &HCA, &H04, &H00 70 DEF SEG = &H1000 'помещаем подпрограмму 80 FOR N = 0 TO 20 'берем каждый байт 90 READ Q: POKE N,Q 'читаем его и помещаем в память 100 NEXT ' 110 FREESPACE = 0 'указатель на начало процедуры 120 CALL FREESPACE(CLUSTERS,DRIVENUM) 'вызов процедуры 130 PRINT "CLUSTERS: ";CLUSTERS 'печать числа кластеров Средний уровень. Функция 36H прерывания 21H сообщает сколько имеется свободного пространства на диске. Единственный входной регистр DL, который должен содержать номер накопителя. Накопитель по умолчанию обоз- начается 0, накопитель A - 1 и т.д. При возврате BX содержит число доступных кластеров, AX - число секторов в кластере, а CX - количество байт в секторе. Небольшое упражнение в умножении дает желаемый результат. В следующем примере проверяется, что на двухсторонней дискете осталось по меньшей мере 2K дискового пространства: MOV AH,36H ;номер функции MOV DL,1 ;накопитель A INT 21H ;получаем информацию CMP BX,2 ;имеется ли 2 свободных кластера? JL RUNNING_OUT ;если нет, то сообщаем об этом 5.1.3 Получение/установка размера файла. Программа может пожелать проверить размер файла по разным причинам. Одна из возможных причин состоит в определении числа записей, содержащихся в файле. Другая - в определении позиции конца файла, с тем чтобы файловый указатель был установлен верно для добавления в файл новых данных, без изменения существующих. Конечно, размер файла устанавливается автоматически функцией DOS. Иногда программа может нуждаться в резервировании дискового пространства для дальнейшего использования. В этом случае надо открыть файл в режиме прямого доступа и записать такой номер записи, чтобы файл имел достаточную длину. Записи между "фиктив- ной" и реально относящимися к файлу будут заполнены теми данными, которые случайно окажутся в дисковых секторах, отведенных для файла при этой операции. Высокий уровень. В Бейсике функция LOF (длина файла) возвращает точное число байтов, отведенных файлу (предупреждаем, однако, что старые вер- сии Бейсика - 1.х - возвращают число 128-байтных блоков, исполь- зуемых файлом). Файл должен быть открыт и ссылаться на него надо по номеру, под которым был открыт файл. Формат X = LOF(1). В следующем примере определяется сколько 64-байтных записей содер- жится в файле, открытом как #3: 100 OPEN "FILENAME" AS #3 'открываем файл 110 RECORDLEN = 64 'определяем длину записи 120 NUMBREC = LOF(3)/RECORDLEN 'вычисляем число записей Средний уровень. FCB функция 23H прерывания 21H сообщает число записей в файле. Если приписать файлу длину записи в 1 байт, то его размер будет возвращен в байтах. DS:DX должны указывать на управляющий блок открытого файла. Затем вызовите функцию. Если файл не найден, то в AL возвращается FF. В противном случае в AL возвращается 0, а число записей помещается в поле номера записи прямого доступа FCB (байты 33-36). Для правильной работы поле длины записи FCB должно быть установлено после открытия файла, но перед вызовом функции; это двухбайтное поле расположено по смещению 14 в FCB. Если раз- мер файла неточно делится на длину записи, то сообщаемое число записей округляется вверх. Вот пример, в котором используется длина записи равная 1: ;---определение размера файла LEA DX,FCB ;DS:DX указывает на FCB MOV BX,DX ;копируем указатель в BX MOV CX,1 ;размер записи в CX MOV [BX]+14,CX ;пишем в поле размера записи FCB MOV AH,23H ;функция сообщающая размер файла INT 21H ;вызов функции MOV AX,[BX]+33 ;получаем младшую часть размера файла MOV CX,[BX]+35 ;получаем старшую часть размера файла Можно также устанавливать длину файла, используя управляющие блоки файла. Для этого надо использовать функцию записи блока с прямым доступом, которая обсуждается в [5.4.5]. У этой функции имеется частный случай, когда число записанных записей устанавли- вается равным нулю, то длина файла устанавливается равной числу записей, указанному в поле записи прямого доступа. Метод, использующий дескриптор файла (file handle) не имеет функции, которая непосредственно сообщала бы длину файла, однако имеется возможность вычислить размер, передвинув указатель файла с начала на конец файла. При открытии файла указатель файла авто- матически устанавливается на первый байт файла. Указатель файла перемещается функцией 42H прерывания 21H. Надо поместить в AL кодовое число 2, напраляющее указатель на конец файла. В BX дол- жен быть указан номер файла, а CX:DX содержит смещение от конца файла до позиции, в которую должен быть установлен указатель, поэтому поместите 0 в оба этих регистра. Затем вызовите функцию. При возврате DX:AX будет содержать новую позицию указателя, отно- сительно его предыдущей позиции - т.е. будет содержать длину файла (DX содержит старший байт). При возникновении ошибки будет установлен флаг переноса, а в AX будет возвращено 1, если непра- вилен номер функции и 6, если неправилен номер файла. Не забудьте затем снова вернуть указатель на начало файла, если это необходи- мо. Поместите 0 в AL, CX и DX и вызовите функцию снова. Вот при- мер: ;---открываем файл LEA DX,FILE_PATH ;DS:DX указывают на путь файла MOV AL,0 ;открываем для чтения MOV AH,3DH ;функция открытия файла INT 21H ;открываем его JC OPEN_ERROR ;проверка на ошибку MOV HANDLE,AX ;запоминаем номер файла ;---определяем длину файла MOV AH,42H ;функция перемещения указателя MOV AL,2 ;код установки на конец файла MOV BX,HANDLE ;номер файла в BX MOV CX,0 ;0 в CX и DX MOV DX,0 ; INT 21H ;сдвигаем указатель JC POINTER_ERROR ;ошибка? MOV FSIZE_HIGH,DX ;запоминаем размер файла MOV FSIZE_LOW,DX ; 5.1.4 Восстановление после ошибок, связанных с нехваткой пространства на диске. При попытке записи на полный диск может произойти крах прог- раммы. Часто легко избежать этого, даже в Бейсике, проверив пред- варительно наличие дискового пространства [5.1.2]. Однако, если ошибка произошла, то постарайтесь дать пользователю возможность исправить ее. Позвольте ему сохранить только часть данных или стереть какой-нибудь другой файл и повторить попытку. Или, еще более радикальное средство, позвольте пользователю вставить дру- гую дискету. Последний подход должен реализовываться с большой осторожностью. Сначала закройте все открытые файлы. Затем выдайте запрос на смену дискеты. После того, как пользователь сообщит, что новая дискета на месте, создайте новый файл и запишите туда данные. Высокий уровень. В Бейсике надо установить процедуру обработки ошибок, как показано в [7.2.5]. Если оператор Бейсика делает попытку писать на полный диск, то возвращается код ошибки #61. При этом управле- ние может быть передано процедуре обработки ошибок, которая ин- формирует пользователя о проблеме и позволяет ему справиться с ней, не теряя данных. 100 ON ERROR GOTO 5000 'разрешаем обработку ошибок . . 200 OPEN FNAME$ FOR OUTPUT AS #1 'открываем файл 210 FOR N = 1 TO ARRLEN 'начинаем писать массив на диск 220 PRINT #1, ARRAY$(N) 'записываем один элемент 230 NEXT ' . . 5000 IF ERR = 61 THEN 5100 'диск полон? 5100 IF ERR = ... 'другие ошибки ... . 5100 '''восстановление при переполнении диска 5110 BEEP: PRINT "Disk full - choose an option:" 5120 PRINT "(A) - Re-edit the file" 5130 PRINT "(B) - Delete some other file from disk" 5140 PRINT "(C) - Use different diskette" . (здесь идет процедура восстановления) . 5500 RESUME Средний уровень. Все функции DOS, которые пишут на диск, выдают определенный код ошибки при попытке записи на полный диск. Вот сводка этих кодов: Метод доступа Функция Название Код ошибки FCB 15H Последовательная запись AL = 1 FCB 22H Прямая запись AL = 1 FCB 27H Прямая запись блока AL = 1 Дескриптор 40H Запись в файл/устройство CX <> BX Проверяйте эти ошибочные условия после каждой записи на диск. Поскольку критической ошибки не происходит, то восстановление не вызывает проблем. Надо только проверять на ошибку каждый раз когда Вы вызываете одну из этих функций и создать хорошую проце- дуру обработки ошибок по Вашему вкусу. Раздел 2. Работа с каталогами диска. Каждый диск имеет один корневой каталог, с которого начинается поиск всех остальных каталогов. Корневой каталог может содержать элементы, указывающие на подкаталоги, которые в свою очередь могут содержать ссылки на другие подкаталоги, образуя древовидную структуру каталогов. Корневой каталог всегда расположен в опреде- ленных секторах диска; подкаталоги хранятся как обычные дисковые файлы, поэтому они могут быть расположены в любом месте диска. Отметим, что фиксированный диск может содержать до четырех корне- вых каталогов, если он разбит на разделы, хотя MS DOS "видит" только один корневой каталог. Каталоги могут иметь различные размеры, в зависимости от размера диска и его разбиения на разде- лы. В следующей таблице приведены размеры и позиции корневых каталогов для разных типов дисков: Тип диска Размер каталога Число элементов Начальный сектор дискета 160K 4 сектора 64 9 дискета 180K 4 сектора 64 9 дискета 320K 7 секторов 112 15 дискета 360K 7 секторов 112 15 дискета 1.2M 14 секторов 224 29 жесткий 10M ----------переменные------------ жесткий 20M ----------переменные------------ В зависимости от разбиения на разделы фиксированный диск может иметь различные размеры каталога и номер начального сектора. Если весь диск отведен для MS DOS, то на XT и AT под корневой каталог отводится 32 сектора, что позволяет иметь в нем 512 элементов. Как корневой каталог, так и подкаталоги, используют 32 байта для хранения информации об одном файле, независимо от типа диска. Таким образом в каждом секторе может храниться информация о 16-ти элементах каталога. Каждое 32-байтное поле разбито следующим образом: байты 0-7 Имя файла 8-10 Расширение файла 11 Атрибут файла 12-21 Зарезервировано 22-23 Время последнего доступа к файлу 24-25 Дата последнего доступа к файлу 26-27 Начальный кластер 28-31 Размер файла Точка между именем файла и его 3-байтным расширением не хранится. Все поля выравнены на левую границу, а пустые байты заполняются пробелами (код ASCII 32). Атрибут файла определяет является ли файл спрятанным, защищенным от записи и т.д. [5.2.6]. Он опред- ляет также специальные элементы каталога, такие как подкаталоги или метка тома. Информация о времени и дате упакована, поэтому для чтения этих значений требуются битовые операции [5.2.5]. Начальный кластер указывает на позицию в таблице размещения файлов (FAT), которая обсуждалась в [5.1.1]. FAT хранит информа- цию о свободном пространстве на диске, а также отводит сектора при записи файла. FAT отводит дисковое пространство порциями, большими чем 1 сектор, которые называются кластерами. Файл распо- ложен в цепочке кластеров и FAT содержит соответствующую цепочку элементов, указывающих, где эти кластеры расположены на диске. Каталог должен указывать на начальное звено цепочки элементов файла в FAT, и эта информация содержится в поле начальный номер кластера. Поскольку файл обычно занимает последний отведенный ему кластер не целиком, то поле размер файла хранит точную длину файла в байтах. 5.2.1 Чтение/изменение корневого каталога. Каталоги диска подразделяются на корневой каталог (обсуждаемый здесь) и подкаталоги (обсуждаемые в [5.2.3]). Когда пользователь программы вводит имя какого-либо файла для работы, то бывает полезным проверить, имеется ли этот файл на самом деле. Обычно изменения в корневом каталоге производятся в ходе обычных файло- вых операций или с помощью специальных функций DOS. Однако можно работать с каталогом напрямую. Большая нужда в таком подходе возникает при работе на языках высокого уровня, где утилиты DOS по большей части недоступны. Корневой каталог читается и изменяется загрузкой его в память с использованием подхода, показанного в [5.4.2], когда читаются абсолютные сектора диска. Эти операции не оставляют места между секторами, когда они загружаются в память. Буфер, содержащий данные сектора, может рассматриваться как набор 32-байтных полей и пара указателей, которые могут использоваться для движения по каталогу. Один указатель всегда кратен 32 и указывает на начало элемента каталога. Второй указатель добавляется к первому и ука- зывает на одно из полей в 32-байтном элементе. Данные в памяти могут быть изменены требуемым образом, а затем весь буфер записы- вается обратно на диск. Имеется два метода чтения абсолютных секторов диска и в обоих случаях только одно число отличает случаи чтения и записи. Пос- кольку ошибка при записи на диск может легко повредить все содер- жимое диска, то надо действовать аккуратно. Сначала убедитесь, что операция чтения сектора выполнена верно во всех отношениях. После этого можно попробовать записать на диск, взяв точную копию кода, использованного для чтения и заменив только номер функции. Высокий уровень. Бейсик выводит каталог по команде FILES. При этом выводятся только имена файлов. FILES дает каталог накопителя по умолчанию; для указания накопителя напишите FILES "A:" и т.д. Можно потребо- вать, чтобы была выведена информация об отдельном файле, написав FILES "A:MYFILE.DAT". Как и в операционной системе имя файла может содержать * и ?. Оператор FILES снабжает информацией поль- зователя, но иногда наличие некоторого файла хочет проверить программа. В этом случае надо открыть файл для последовательного чтения и если он не существует, то возникнет ошибочная ситуация. Смотрите обсуждение и пример в [5.2.3]. Для поиска любой информации, относящейся к корневому каталогу, используйте процедуру на машинном языке, приведенную в [5.4.2]. После того как данные каталога в памяти, установите указатели, как описано выше, и ведите поиск по буферу памяти с 32-байтным интервалом. Нижеприведенный пример ищет элемент каталога, относя- щийся к стертому файлу. Когда файл стирается, то первый байт имени файла заменяется на E5H, но все остальное содержимое данно- го элемента остается неизменным. Конечно, при этом освобождается дисковое пространство, отведенное файлу в FAT. Процедура восста- новления удаленного файла должна знать номер начального кластера в FAT. В примере этот 2-байтный номер кластера помещается со смещением 26 в элементе каталога. 100 '''чтение секторов каталога в память с сегмента &H2000 110 INPUT "Enter erased filename ", FNAME$ 120 IF LEN(FNAME$) > 12 THEN BEEP: GOTO 110 130 IF INSTR(FNAME$,".") > 9 THEN BEEP: GOTO 110 140 '''дополнение имени и расширения файла нулями 150 Y = INSTR(FNAME$,".") 160 IF Y = 0 THEN FIRSTPART$ = FNAME$: GOTO 230 170 EXTEN$ = LEFT$(FNAME$, LEN(FNAME$) - Y) 180 EXTEN$ = EXTEN$ + STRING$(3 - LEN(EXTEN$),"") 190 FIRSTPART$ = RIGHT$(FNAME$,Y - 1) 200 FIRSTPART$ = FIRSTPART$ + STRING$(8 - LEN(FIRSTPART$),"") 210 FNAME$ = FIRSTPART$ + EXTEN$ 220 '''теперь хотим найти удаленный файл 230 MID$(FNAME$,1,1) = CHR$(&HE5) 'заменяем первый символ 240 DIRPTR = 0 'указатель на элемент 250 FIELDPTR = 26 'указатель на номер кластера 260 FOR N = 1 TO 112 'на дискете 112 элементов 270 X$ = "" 'чистим X$ 280 FOR M = 0 TO 10 'читаем имя файла из каталога 290 X$ = X$ + PEEK(DIRPTR + M) 'берем по символу 300 NEXT ' 310 IF X$ = FNAME$ THEN 340 'совпадает с введенной строкой 320 NEXT 'если нет, то следующий 330 PRINT "Too late - file entry obliterated": END 'уже нет 340 X = PEEK(DIRPTR + FIELDPTR) 'нашли его, берем 1-й байт и 350 Y = PEEK(DIRPTR + FIELDPTR + 1) '2-й байт номера кластера 360 Z = X + 256*Y 'теперь номер кластера в Z Средний уровень. MS DOS обеспечивает две пары функций для поиска файлов, одна для файлов, открытых методом управляющих блоков файла, а другая - для файлов, открытых методом дескриптора файлов. Одна из функций каждой пары ищет первое появление имени файла в каталоге, а дру- гая ищет последующие появления, когда в имени файла содержатся джокеры. Только метод, использующий дескриптор файла позволяет искать подкаталоги. Метод FCB: Функция 11H прерывания 21H ищет первое появление файла. Уста- новите DS:DX на неоткрытый FCB и выполните функцию. При возврате AL будет содержать 0, если файл найден, и FF - если нет. DTA заполняется информацией из каталога. Для обычных FCB первый байт DTA содержит номер накопителя (1 = A и т.д.), а следующие 32 байта содержат элемент каталога. Для расширенного FCB первые 7 байтов файла копируются в первые 7 байтов расширенного FCB, вось- мой байт указывает на накопитель, а следующие 32 байта - элемент каталога. ;---в сегменте данных FCB DB 1,'NEWDATABAK',25DUP(0) ;---ищем файл MOV AH,11H ;функция поиска в каталоге LEA DX,FCB ;указываем на FCB INT 21H ;ищем CMP AL,0 ;успешно? JNE NO_FILE ;если нет, то процедура обработки ошибки LEA BX,DTA ;теперь DS:BX указывает на элемент каталога После использования функции 11H можно использовать функцию 12H для поиска следующих подходящих элементов, когда имя файла содер- жит джокеры. В данном случае в имени файла допустим только символ "?", но не "*". Эта функция работает в точности так же, как и первая, и если найден второй файл, то информация о первом файле в DTA будет уничтожена повторной записью. Метод дескриптора файлов: Функция 4EH прерывания 21H ищет файл с данным именем. DS:DX должны указывать на строку, дающую путь файла. Например, B:\EURO- PE\FRANCE\PARIS указывает на файл PARIS. Строка может содержать до 63 символов и завершаться символом ASCII 0. Имя файла может содержать джокеры, включая как "?", так и "*". Поместите атрибут файла в CX; если он обычный то 0, в противном случае проконсуль- тируйтесь в [5.2.6] относительно значений атрибута. При возврает устанавливается флаг переноса, если файл не най- ден. Если файл найден, то функция заполняет DTA информацией о файле. Отметим частный случай использования DTA методом дескрип- тора файлов - обычно, DTA используется функциями MS DOS для рабо- ты через FCB. Первые 21 байт DTA зарезервированы DOS для поиска следующих совпадающих файлов. Двадцать второй байт дает атрибут файла, за ним следуют два байта, содержащие время и еще два байта содержащие дату. Следующие 4 байта содержат размер файла (младшее слово сначала). И, наконец, дается имя файла в виде строки пере- менной длины, заканчивающейся байтом ASCII 0. Точка (ASCII 46) разделяет имя и расширение и не один из этих элементов не запол- нен пробелами. ;---в сегменте данных PATH DB 'B:FRANCE\PARIS\4EME',0 ;---ищем файл MOV AH,4EH ;номер функции LEA DX,PATH ;DS:DX указывают на путь MOV CX,0 ;обычный атрибут файла INT 21H ;ищем файл JC NO_FILE ;уход, если не найден LEA BX,DTA ;DS:BX указывают на DTA MOV AL,[BX]+21 ;теперь атрибут файла в AL Следующее появление имени файла (когда используются джокеры) ищется с помощью функции 4FH прерывания 21H. Она готовится в точности так же, как и функция 4EH, при этом указатель DTA не должен меняться. Когда других совпадений не найдено, то устанав- ливается флаг переноса, а в AX появляется 18. 5.2.2 Создание/удаление подкаталога. Программа может создавать или удалять подкаталоги, при выпол- нении некоторых условий. Для создания подкаталога необходимо, чтобы было по крайней мере одно пустое место в корневом каталоге. Для удаления подкаталога необходимо, чтобы он не содержал файлов или ссылок на другие подкаталоги. Кроме того, Вы не можете уда- лить подкаталог, который является Вашим текущим каталогом (тот, с которым по умолчанию выполняются все операции над каталогами). Отметим также, что невозможно удалить корневой каталог. Высокий уровень. Бейсик предоставляет команды MKDIR (создай каталог) и RMDIR (удали каталог). За обеими должны следовать стандартные пути указания каталога, содержащие до 63 символов, включая имя накопи- теля. Путь должен быть заключен в кавычки. Чтобы добавить подка- талог с именем STORKS в подкаталог BIRDS напишите MKDIR "B:MAM- MALS\BIRDS\STORKS". После выполнения этой команды будет создан файл STORKS, используемый как подкаталог и факт его существования будет отражен в создании элемента с именем STORKS в подкаталоге с именем BIRDS. Для удаления этого подкаталога надо сначала удалить из него все файлы [5.3.2]. Затем надо использовать команду RMDIR "B:MAMMALS\BIRDS\STORKS". В этих примерах предполагалось, что Вашим текущим каталогом являлся корневой каталог. Однако, если Ваш текущий каталог нахо- дится где-то на пути к подкаталогу, над которым осуществляются операции, то нет необходимости указывать весь путь. Поэтому, если Вашим текущим каталогом является BIRDS, то для создания или уда- ления подкаталога STORKS можно использовать команды MKDIR "\STORKS" или RMDIR "\STORKS". Средний уровень. Поскольку управляющие блоки файлов обслуживают только корневой каталог, то для создания или удаления подкаталога надо использо- вать дескрипторы файлов. Создание подкаталога: DS:DX должны указывать на строку, дающую накопитель и путь к каталогу, в котором должен быть создан подкаталог. Строка должна завершаться байтом ASCII 0. Для открытия подкаталога с именем PRIMATES в корневом каталоге накопителя A: надо записать строку в виде "A:\PRIMATES". Для открытия подкаталога в другом подкаталоге с именем MAMMALS напишите "A:\MAMMALS\PRIMATES". Имя накопителя A: может быть опущено если Вы работаете с накопителем, используе- мым по умолчанию, и путь может начинаться с текущего каталога. Поместите в AH 39H и выполните прерывание 21H; если указан пра- вильный путь, то будет создан новый каталог. В противном случае будет установлен флаг переноса, а AX будет содержать код ошибки 3 (путь неверен) или 5 (нет доступа). В примере создается подката- лог PRIMATES: ;---в сегменте данных PATH DB 'A:MAMMALS\PRIMATES',0 ;---создаем подкаталог с именем PRIMATES LEA DX,PATH ;DS:DX должны указывать на путь MOV AH,39H ;номер функции INT 21H ;создаем подкаталог JC ERROR_ROUT ;обработка ошибок Удаление подкаталога: Для удаления подкаталога надо сформировать строку, в точностью совпадающую с той, которую Вы указывали при создании каталога. Затем поместите в AH 3AH и выполните прерывание 21H. Опять при невыполнении функции в AX будут возвращены коды 3 или 5 (код 5 может указывать, что каталог непустой). 5.2.3 Чтение/изменение подкаталога. Подкаталоги во многом подобны корневому каталогу, за исключе- нием того, что они хранятся как обычные файлы, а не в заранее предопределенных секторах. Подкаталоги невозможно спутать с обыч- ными файлами, поскольку объект каталога, относящийся к подкатало- гу, имеет специальный байт атрибутов (с установленным битом 5 - см. [5.2.6]). Подкаталоги начинаются с двух специальных 32-байт- ных объектов, первый из которых имеет имя точка, а второй - две точки. Они ориентируют подкаталог среди окружающих каталогов. Ссылки на подкаталоги нижнего уровня записываются как обычные ссылки на файлы. Предполагается, что подкаталог может быть прочитан как любой другой файл, поэтому вроде бы не составляет труда загрузить его в память. Но, к сожалению, создатели MS DOS поместили 0 в поле длины файла для элементов, относящихся к подкаталогам. В резуль- тате DOS считает, что этот файл имеет нулевую длину и отказывает- ся читать его. Нет простого способа преодолеть эту проблему. Высокий уровень. В Бейсике команда FILES может использовать стандартные имена путей для вывода подкаталога; например, FILES "B:MAMMALS\BIRDS" выводит все файлы, содержащиеся в подкаталоге BIRDS. Эта команда может быть использована и для получения информации о наличии в каталоге определенного файла. Например, FILES "LEVEL1\NEWDATA" ищет файл NEWDATA и выводит его имя, если он найден. Хотя это может быть полезным для пользователя, но часто самой программе необходимо знать существует или нет указанный файл. Чтобы устано- вить это попытайтесь открыть файл для последовательного чтения. Если он не будет найден, то возникнет ошибочное условие 63. Соз- дайте процедуру обработки ошибок, как описано в [5.4.8]. Затем используйте переменную, чтобы отметить был ли найден требуемый файл (в нашем примере переменная "EXISTS"). Если программе не нужно, что этот файл был открыт, то закройте его перед тем как двинуться дальше. 100 ON ERROR GOTO 1000 'процедура обработки ошибок 110 EXISTS = 1 'начальное значение "флага" 120 INPUT "Enter filename: ",S$ 'запрос имени файла 130 OPEN S$ FOR INPUT AS #3 'открываем его для послед. чтения 140 IF EXISTS = 0 THEN BEEP: PRINT "File does not exist" . . 1000 IF ERR = 53 THEN 1500 'файл не существует? 1010 IF ERR = 64 THEN ... 'другие ошибки . 1500 EXISTS = 0 'меняем значение флага 1510 RESUME 140 'продолжаем выполнение программы Средний уровень. Функции работы через дескрипторы файлов, которые использова- лись для доступа к корневому каталогу [5.2.1] могут так же просто обращаться к любому подкаталогу. Чтобы вывести все содержимое каталога надо просто использовать функцию 4EH для поиска файлов *.*, а затем повторять поиск, используя функцию 4FH. Когда больше не будет файлов, то будет установлен флаг переноса, а AL будет содержать 18. Каждый раз, когда будет обнаружен очередной эле- мент, в DTA будет записана информация о файле, включая полный его путь (отмечаем использование DTA в функциях, использующих деск- риптор файла). Следующий пример выводит полные пути всех обычных файлов подкаталога. ;---в сегменте данных PATH DB 'A:MAMMALS\*.*',0 DTAH DB 256 DUP(?) ;---установка DTA LEA DX,DTA ;DS:DX указывают на DTA MOV AH,1AH ;функция установки DTA INT 21H ;устанавливаем DTA ;---ищем первый файл MOV AH,4EH ;номер функции LEA DX,PATH ;указываем на строку пути MOV CX,0 ;только нормальные атрибуты INT 21H ;ищем *.* JC ERROR ;обработка ошибок ;---выводим имя файла NEXT_LINE: LEA BX,DTA ;BX указывает на DTA ADD BX,30 ;смещение для имени файла NEXT_CHAR: MOV DL,[BX] ;получаем символ из имени CMP DL,0 ;проверка на конец строки JE END_STR ;уход, если конец MOV AH,2 ;иначе, выодим символ INT 21H ; INC BX ;увеличиваем указатель JMP SHORT NEXT_CHAR ;следующий символ ;---возврат каретки/перевод строки в конце строки END_STR: MOV AH,2 ;функция вывода символа MOV DL,13 ;код возврата каретки INT 21H ;выводим MOV DL,10 ;код перевода строки INT 21H ;выводим ;---ищем следующий файл LEA DX,PATH ;указываем на строку пути MOV AH,4FH ;номер функции INT 21H ;ищем следующий файл JC FINISHED ;если нет, то выход JMP SHORT NEXT_LINE ;иначе выводим имя файла FINISHED: 5.2.4 Получение/установка текущего каталога. Текущий каталог это каталог, в котором DOS ищет файл, для которого не указан путь. Если не установлено противного, то теку- щий каталог является корневым каталогом. Высокий уровень. Бейсик устанавливает текущий каталог с помощью команды CHDIR. За командой должна следовать строка, указывающая путь к каталогу, на который надо перейти. Строка может содержать до 63-х символов, включая имя накопителя, и должна быть заключена в кавычки. CHDIR "C:MAMMALS\PRIMATES\GIBBONS" делает подкталог GIBBONS текущим каталогом. Чтобы перейти в корневой каталог напишите CHDIR "\" или CHDIR "B:\". Бейсик версии 3.0 может сообщать путь к текущему каталогу, как это делает команда DOS PATH. Просто введите PRINT ENVI- RON$("PATH"). Средний уровень. Функция 3BH прерывания 21H устанавливает текущий каталог. DS:DX должны указывать на путь к каталогу в стандартном виде и эта строка должна завершаться байтом ASCII 0. Например, B:BIRDS\- PARROTS\POLLY делает POLLY текущим каталогом. B: может быть опу- щено, если это текущий накопитель по умолчанию [5.3.1]. Чтобы сделать текущим корневой каталог накопителя A: напишите A:\. В примере текущим каталогом устанавливается POLLY: ;---в сегменте данных PATH DB 'B:BIRDS\PARROTS\POLLY',0 ;---делаем POLLY текущим каталогом MOV AH,3BH ;номер функции LEA DX,PATH ;DS:DX должны указывать на путь INT 21H ;устанавливаем текущий каталог Чтобы определить какой каталог является текущим надо использо- вать функцию 47H прерывания 21H. DS:SI должны указывать на об- ласть данных размером 64 байта, в которую будет записан путь. В DL указывается накопитель, причем 0 = "по умолчанию", 1 = A, 2 = B и т.д. При возврате функция возвращает строку без имени накопи- теля. Если был указан несуществующий накопитель, то в AL возвра- щается код ошибки 15. Строка начин