3. Файловая система DOS

Теперь, после того, как мы познакомились с логической структурой диска в MS-DOS, можно приступить к изучению одной из самых развитых подсистем операционной системы - файловой системы.

Сервис файловой системы доступен программе через прерывание MS-DOS INT 21h. Многочисленные функции этого прерывания, относящиеся к файловой системе, можно разбить на группы:

Функции первой группы позволяют программе получить разностороннюю информацию, касающуюся текущего состояния дисковой подсистемы - текущие используемые диск и каталог, размер свободного места на диске, параметры логического диска и т.д.

Функции второй группы выполняют все необходимые операции с каталогами - создание, переименование, уничтожение каталогов, изменение текущего каталога и т.д.

Третья группа функций позволяет программе выполнять практически любые операции над файлами - создание, удаление, чтение/запись, переименование, копирование, пересылка и т.п.

Заметим, что существует два класса функций для работы с файлами. Первый класс использует управляющие блоки файлов FCB. Эти функции исплоьзовались в MS-DOS версий 1.х и имеют в настоящее время чисто исторический интерес. Вам они скорее всего никогда не будут нужны, за исключением одного случая - если вам надо составить программу, способную работать под управлением MS-DOS версии 1.0 или 1.1. В этой книге мы не будем упоминать функции, предназначенные для работы с файлами через FCB. При необходимости вы сможете найти информацию об этих функциях в руководстве по операционной системе MS-DOS.

Второй класс использует файловые индексы (handle). Этот класс функций впервые появился в MS-DOS версии 2.0. Эти функции аналогичны используемым в операционной системе UNIX.

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

Первые пять файловых индексов зарезервированы операционной системой:

0 Стандартное устройство ввода (клавиатура)
1 Стандартное устройство вывода (экран)
2 Стандартное устройство для вывода сообщений об ошибках (экран)
3 Стандартное устройство последовательного ввода/вывода, обычно это асинхронный адаптер COM1.
4 Стандартное печатающее устройство (обычно первый принтерный порт LPT1)

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

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

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

Описание функций MS-DOS, предназначенных для работы с файловой системой, мы начнем с функций получения справочной информации.

3.1. Получение справочной информации

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

Если вы запускаете программу, которая находится в каком-либо каталоге на одном из дисков, то эти диск и каталог становятся текущими для MS-DOS. Это можно понимать в том смысле, что программе не требуется каждый раз при работе с файлами указывать требуемый диск или каталог.

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

Для установки текущего диска можно использовать функцию 0Eh, которая имеет следующий формат вызова:

На входе: AH = 0Eh
DL = Номер дисковода (0 - А:, 1 - В:, и т.д.)
На выходе: AL = Общее количество дисководов в системе. Эта величина соответствует параметру LASTDRIVE файла CONFIG.SYS.

Для того чтобы узнать номер текущего дисковода, программа может воспользоваться функцией 19h:

На входе: AH = 19h
На выходе: AL = Номер текущего дисковода (0 - А:, 1 - В:, и т.д.).

Функция 3Bh предназначена для установки текущего каталога:

На входе: AH = 3Bh
DL = Номер дисковода (0 - текущий, 1 - А:, 2 - В:, и т.д.)
DS:DX = Адрес буфера, содержащего путь каталога, который должен стать текущим.
На выходе: AX = Код ошибки, если CY установлен в 1.

Буфер пути может иметь максимальный размер 64 байта. Он должен содержать путь в формате ASCIIZ, т.е. строку, закрытую двоичным нулем, например: "path\dirname",0. Строка не должна содержать литеры, обозначающие диск. Если текущим должен стать корневой каталог, строка должна состоять только из одного двоичного нуля.

Для того чтобы узнать текущий каталог, вы можете воспользоваться функцией 47h:

На входе: AH = 47h
DL = Номер дисковода (0 - текущий, 1 - А:, 2 - В:, и т.д.)
DS:SI = Адрес буфера для записи пути текущего каталога.
На выходе: AX = Код ошибки, если флаг переноса CF установлен в 1.

Буфер должен иметь размер не менее 64 байтов, текущий каталог возвращается в формате ASCIIZ без литеры, обозначающей диск. Если текущим является корневой каталог, регистровая пара DS:SI будет указывать на нулевую строку (состоящую из одного двоичного нуля).

Функции MS-DOS могут помочь вам в получении информации, необходимой для организации доступа к диску на уровне секторов и кластеров. При этом вы будете избавлены от необходимости читать в память и анализированть содержимое загрузочного сектора логического диска.

Информация о таблице размещения файлов FAT для текущего диска может быть получена с помощью функции 1Bh прерывания INT 21h, имеющего следующий формат:

На входе: AH = 1Bh
На выходе: DS:BX = Адрес первого байта FAT. Это байт ID идентификации среды носителя данных, соответствует байту media в блоке параметров BIOS.
DX = Общее количество кластеров на диске.
AL = Количество секторов в одном кластере.
CX = Количество байтов в одном секторе.

Дополнительно эта функция возвращает информацию об общем количестве кластеров на диске, размере кластера в секторах и размере сектора в байтах. Для версий MS-DOS, более ранних, чем 2.0, регистровая пара DS:BX указывала на FAT, считанный в память. Более поздние версии операционной системы могут содержать по этому адресу только часть таблицы размещения файлов.

Для получения аналогичной информации не о текущем, а о любом диске, используйте функцию 1Ch. Эта функция полностью аналогична предыдущей, за исключением того, что в регистре DL должен быть указан код дисковода: 0 - текущий, 1 - А:, 2 - В: и т.д. Эта функция доступна в MS-DOS версии 2.0 и в более поздних версиях.

Если вас интересует размер свободного места на диске, вы можете его узнать с помощью функции 36h, имеющей следующий формат:

На входе: AH = 36h
DL = Номер дисковода (0 - текущий, 1 - А:, 2 - В:, и т.д.)
На выходе: AX = Количество секторов в кластере; 0FFFFh, если был задан неправильный номер дисковода;
BX = Количество свободных кластеров на диске.
CX = Количество байтов в одном секторе.
DX = Общее количество кластеров на диске.

Эта функция возвращает в регистре AX число 0FFFFh, если вы неправильно указали номер дисковода.

При обсуждении векторной таблицы связи мы рассказывали о блоках управления устройствами DDCB. Поле dev_cb векторной таблицы связи содержит FAR-адрес цепочки этих блоков.

Приведем еще раз формат блока DDCB. Напомним, что он изменяется в зависимости от версии DOS. Для версий 2.х и 3.х блок DDCB имеет следующий формат:

(0) 1 drv_num номер устройства (0 соответствует устройству А:, 1 - В: и т.д.)
(+1) 1 drv_numd дополнительный номер устройства внутри драйвера
(+2) 2 sec_size размер сектора в байтах
(+4) 1 clu_size число, на единицу меньшее количества секторов в кластере
(+5) 1 clu_base если содержимое этого поля не равно нулю, то для получения общего числа секторов в кластере надо возвести 2 в степень clu_base и получившееся число прибавить к clu_size
(+6) 2 boot_siz количество зарезервированных секторов (boot-сектора, начало корневого каталога)
(+8) 1 fat_num количество копий FAT
(+9) 2 max_dir максимальное число дескрипторов файлов в корневом каталоге (т.е. максимальное число файлов, которое может содержать корневой каталог на этом устройстве)
(+11) 2 data_sec номер первого сектора данных на диске (номер сектора, соответствующего кластеру номер 2)
(+13) 2 hi_clust максимальное количество кластеров (равно увеличенному на 1 количеству кластеров данных)
(+15) 1 fat_size количество секторов, занимаемых одной копией FAT
(+16) 2 root_sec номер первого сектора корневого каталога
(+18) 4 drv_addr FAR-адрес заголовка драйвера, обслуживающего данное устройство
(+22) 1 media байт описания среды носителя данных
(+23) 1 acc_flag флаг доступа, 0 означает, что к устройству был доступ
(+24) 4 next адрес следующего блока DDCB, для последнего блока в поле смещения находится число FFFF
--------------- только для DOS 2.x --------------
(+28) 2 dir_clu номер начального кластера текущего каталога (0 для корневого каталога)
(+30) 64 dir_path строка в формате ASCIIZ, содержащая путь к текущему каталогу
      --------------- DOS 3.х ----------------------------
(+28) 2 reserv1 зарезервировано, обычно равно 0
(+30) 2 built число FFFF в этом поле означает, что блок DDCB был построен

Формат блока DDCB для DOS версии 4.х:

(0) 1 drv_num номер устройства (0 соответствует устройству А:, 1 - В: и т.д.)
(+1) 1 drv_numd дополнительный номер устройства внутри драйвера
(+2) 2 sec_size размер сектора в байтах
(+4) 1 clu_size число, на единицу меньшее количества секторов в кластере
(+5) 1 clu_base если содержимое этого поля не равно нулю, то для получения общего числа секторов в кластере надо возвести 2 в степень clu_base и получившееся число прибавить к clu_size
(+6) 2 boot_siz количество зарезервированных секторов (boot-сектора, начало корневого каталога)
(+8) 1 fat_num количество копий FAT
(+9) 2 max_dir максимальное число дескрипторов файлов в корневом каталоге (т.е. максимальное число файлов, которое может содержать корневой каталог на этом устройстве)
(+11) 2 data_sec номер первого сектора данных на диске (номер сектора, соответствующего кластеру номер 2)
(+13) 2 hi_clust максимальное количество кластеров (равно увеличенному на 1 количеству кластеров данных)
(+15) 1 fat_size количество секторов, занимаемых одной копией FAT
(+16) 1 reserv1 зарезервироано
(+17) 2 root_sec номер первого сектора корневого каталога
(+19) 4 drv_addr FAR-адрес заголовка драйвера, обслуживающего данное устройство
(+23) 1 media байт описания среды носителя данных
(+24) 1 acc_flag флаг доступа, 0 означает, что к устройству был доступ
(+25) 4 next адрес следующего блока DDCB, для последнего блока в поле смещения находится число FFFF
(+29) 2 reserv2 зарезервироано
(+31) 2 built число FFFF в этом поле означает, что блок DDCB был построен

Файл sysp.h содержит определение типа DDCB для MS-DOS версии 4.х:

/* Блок управления устройством DOS */

#pragma pack(1)

typedef struct _DDCB_ {
        unsigned char drv_num;
        unsigned char drv_numd;
        unsigned sec_size;
        unsigned char clu_size;
        unsigned char clu_base;
        unsigned boot_siz;
        unsigned char fat_num;
        unsigned max_dir;
        unsigned data_sec;
        unsigned hi_clust;
        unsigned char fat_size;
        char reserv1;
        unsigned root_sec;
        void far *drv_addr;
        unsigned char media;
        unsigned char acc_flag;
        struct _DDCB_ far *next;
        unsigned reserv2;
        unsigned built;
} DDCB;

#pragma pack()

При описании векторной таблицы связи мы приводили примеры использования блоков DDCB. Для получения адреса блока DDCB конкретного дисковода можно воспользоваться недокументированной функцией 32h. Она имеет следующий формат вызова:

На входе: AH = 32h
DL = Номер дисковода (0 - текущий, 1 - А:, 2 - В:, и т.д.)
На выходе: AL = 0, если был задан правильный номер дисковода;
0FFh, если был задан неправильный номер дисковода;
DS:BX = Адрес блока DDCB

Для получения адреса блока DDCB текущего диска можно воспользоваться недокументированной функцией 1Fh, которая имеет формат, аналогичный функции 32h, за исключением того, что не надо задавать номер дисковода в регистре DL.

Какая еще полезная информация может быть получена при использовании функций MS-DOS?

С помощью функции 33h программа может проверить или установить флаг Ctrl-Break и узнать номер диска, с которого выполнялась загрузка операционной системы:

На входе: AH = 33h
AL = Код подфункции:
0 - Проверить текущее состояние флага Ctrl-Break;
1 - Установить флаг Ctrl-Break;
5 - Определить номер диска, который был использован для загрузки операционной системы.
DL = Значение устанавливаемого флага Ctrl-Break для подфункции 1 (0 - OFF, 1 - ON).
На выходе: DL = Текущее состояние флага Ctrl-Break для подфункции 0;
Номер диска, использованного для загрузки операционной системы для подфункции 5 (1 - А:, 2 - В:, и т.д.).

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

Состояние флага Ctrl-Break влияет на возможность прервать выполнение программы нажатием комбинации клавиш Ctrl-Break или Ctrl-C. Если флаг находится в состоянии OFF, DOS проверяет эту комбинацию клавиш только при вызове функций стандартного ввода/вывода на консоль, принтер и последовательный порт. Если флаг установлен в состояние ON, комбинация клавиш проверяется и при вызове других функций MS-DOS. Если операционная система зафиксировала нажатие указанной комбинации клавиш, она выполняет прерывание INT 23h, которое завершает работу текущей программы.

Функция 2Fh возвращает в регистровой паре ES:BX адрес текущей области DTA (Disk Transfer Area), которая используется при поиске файлов в каталогах.

Функция 54h позволяет программе узнать текущее состояние флага проверки записывающейся на диск информации. В регистре AL эта функция возвращает текущее состояние флага. Если содержимое регистра равно 1, операционная система после записи сектора считывает его для проверки. Разумеется, такая проверка снижает скорость работы программы. Если после вызова функции регистр AL содержит 0, проверка записи не выполняется.

Для установки флага проверки записи можно использовать функцию 2Eh. Перед вызовом функции в регистр AL необходимо занести новое значение флага проверки: 0 - проверка не нужна; 1 - должна выполняться проверка записанной информации.

Стандартные библиотеки трансляторов Microsoft QC 2.5 и C 6.0 содержат несколько функций, облегчающих получение справочной информации о состоянии дисковой подсистемы.

Функция _dos_getdiskfree() использует функцию 36h для получения информации о диске. Файл dos.h содержит описание этой функции:

unsigned _dos_getdiskfree(unsigned drive, 
struct diskfree_t *diskspace);

Параметр drive задает номер используемого дисковода: 0 - текущий, 1 - А:, и т.д.

Информация возвращается в структуре diskfree_t, которая определена в файле dos.h:

struct diskfree_t
{
        unsigned total_clusters;
        unsigned avail_clusters;
        unsigned sectors_per_cluster;
        unsigned bytes_per_sector;
};

В этой структуре:

unsigned total_clusters общее количество кластеров на диске;
unsigned avail_clusters количество свободных кластеров;
unsigned sectors_per_cluster количество секторов, занимаемых кластером;
unsigned bytes_per_sector размер сектора в байтах.

Для получения номера текущего диска и для установки номера текущего диска можно использовать, соответственно, функции _dos_getdrive() и _dos_setdrive().

Функция _dos_getdrive() имеет формат:

void _dos_getdrive(unsigned *drive);

Эта функция пользуется функцией 19h для получения номера текущего диска, который записывается по адресу, задаваемому параметром. Значение 1 соответствует диску А:, 2 - В:, и т.д.

Функция _dos_setdrive() предназначена для установки текущего диска и может быть использована для определения общего числа дисков в системе:

void _dos_setdrive(unsigned drive, unsigned *drivecount);

Параметр drive опеределяет текущий диск (1 - А:, и_т.д.), по адресу, задаваемому вторым параметром, функция записывает общее количество логических дисков, установленных в системе. Функция _dos_setdrive() использует функцию 0Eh прерывания INT 21h.

Для иллюстрации способов использования функций _dos_getdrive(), _dos_setdrive(), _dos_getdiskfree() мы составили следующую программу:

#include <dos.h>
#include <bios.h>
#include <conio.h>
#include <stdio.h>

void main(void);
void main(void) {

         struct diskfree_t dinfo;
         unsigned drive, drivecount;

         printf("\n"
                "\nОпределение параметров текущего логического диска"
                "\n  (C)Фролов А., 1991"
                "\n");

// Определяем номер текущего диска

         _dos_getdrive(&drive);

// Выводим на экран литеру текущего диска

         printf("\nТекущий диск:                        %c:\n",
                'A' + drive - 1);

// Вызываем функцию установки текущего диска.
// Мы не изменяем текущий диск, вызов этой функции
// нужен нам для определения количества установленных
// в системе логических дисков

         _dos_setdrive(drive, &drivecount);

// Получаем характеристики текущего диска

         _dos_getdiskfree(drive, &dinfo);

         printf("\nОбщее количество кластеров на диске: %d"
                  "\nКоличество свободных кластеров:      %d"
                  "\nКоличество секторов в кластере:      %d"
                  "\nКоличество байтов в секторе:         %d"
                  "\nРазмер диска в байтах:               %ld"
                  "\n",
                  dinfo.total_clusters,
                  dinfo.avail_clusters,
                  dinfo.sectors_per_cluster,
                  dinfo.bytes_per_sector,
                  (long)dinfo.avail_clusters *
                  dinfo.sectors_per_cluster *
                  dinfo.bytes_per_sector );

         printf("\nКоличество логических дисков:        %d"
                          "\n", drivecount);

}

3.2. Создание, удаление и переименование каталогов

После форматирования логический диск содержит корневой каталог. Если диск форматируется как системный, в этом каталоге могут находится дескрипторы файлов операционной системы IO.SYS, MSDOS.SYS, COMMAND.COM.

Операционная система предоставляет программам пользователя удобный сервис для создания, уничтожения и переименования каталогов. Используя сведения, приведенные в этой книге, вы сможете изменять структуру каталогов сами, не прибегая к услугам MS-DOS. Однако это следует делать только тогда, когда операции с каталогами по каким-то причинам нежелательно выполнять с использованием функций операционной системы.

Для создания каталога используйте функцию 39h прерывания INT 21h:

На входе: AH = 39h
DS:DX = Адрес строки в формате ASCIIZ, содержащей имя создаваемого каталога.
На выходе: AL = Код ошибки, если был установлен в 1 флаг переноса CF.

Строка, имени создаваемого каталога может содержать полный путь, состоящий из имени диска и имени каталога, в котором должен быть создан каталог, или она может состоять только из одного имени каталога. В последнем случае каталог создается в текущем каталоге на текущем диске.

Размер строки с именем каталога не должен превышать по длине 64 байта.

Удалить существующий каталог можно с помощью функции 3Ah. Формат вызова этой функции аналогичен предыдущему:

На входе: AH = 3Ah
DS:DX =Адрес строки в формате ASCIIZ, содержащей имя удаляемого каталога.
На выходе: AL = Код ошибки, если был установлен в 1 флаг переноса CF.

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

Для изменения имени каталогов и файлов предназначена функция 56h:

На входе: AH = 56h
DS:DX = Адрес строки в формате ASCIIZ, содержащей старое имя каталога или файла.
ES:DI = Адрес строки в формате ASCIIZ, содержащей новое имя каталога или файла.
На выходе: AL = Код ошибки, если был установлен в 1 флаг переноса CF.

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

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

Мы уже рассказывали о функции 3Bh, с помощью которой можно изменить текущий каталог, если это необходимо.

Стандартные библиотеки трансляторов Microsoft QC 2.5 и C 6.0 содержат несколько функций, предназначенных для работы с каталогами.

Функция getcwd() предназначена для определения текущего каталога. Прототип этой функции описан в файле direct.h:

char *getcwd(char *path, int n);

Первый параметр этой функции - адрес буфера, в который необходимо занести строку, содержащую имя текущего каталога. Размер этого буфера определяется вторым параметром.

Если в качестве первого параметра указать NULL, функция динамически закажет буфер длиной n байтов из области кучи. Эту память впоследствии необходимо будет освободить при помощи функции free(). Функция getcwd() возвращает указатель на буфер, содержащий текущий каталог.

Приведем пример простой программы, которая выводит на экран информацию о текущем каталоге:

#include <stdio.h>
#include <direct.h>

void main(void);
void main(void) {

        printf("\nТекущий каталог: %s\n",
                getcwd(NULL,70));

}

Для создания и удаления каталогов, изменения текущего каталога имеются функции mkdir(), rmdir(), chdir().

Все эти функции имеют один параметр - путь каталога, который имеет тип (char_*). В случае успешного выполнения операции функции возвращают 0, при ошибке - 1.

Приведем небольшой пример, использующий перечисленные выше функциии:

#include <direct.h>
#include <stdlib.h>
#include <stdio.h>

void main(void);
void main(void) {

         char current_dir[_MAX_DIR];   // _MAX_DIR - максимальная длина
                                                         // пути для каталога.
                                                         // Эта константа описана в
                                                         // файле stdlib.h
         char test_dir[] = "TEST_DIR";

// Запоминаем текущий каталог

         getcwd( current_dir, _MAX_DIR );

// Пытаемся создать в текущем каталоге новый каталог

         if(!mkdir(test_dir)) {

// Если удалось создать каталог, делаем его текущим

                 chdir(test_dir);
                 printf("\nКталог создан, для удаления нажмите"
                          "\nлюбую клавишу, для сохранения - Ctrl-C");

                 getch();

// Для удаления только что созданного каталога
// возвращаемся в каталог более высокого уровня

                 chdir(current_dir);
                 rmdir(test_dir);
         }

// Если каталог с таким именем уже существует или
// произошла другая ошибка при создании каталога,
// выводим сообщение о невозможности создания каталога.

         else printf("\nНе могу создать каталог!");
}

Для переименования каталогов (и файлов) предназначена функция rename():

int rename(char *oldname, char *newname);

Способ ее использования очевиден: необходимо задать старое и новое имена каталогов. Можно задавать как полный путь, так и просто имя каталога/файла. В последнем случае операция переименования выполняется над каталогами или файлами, находящимися в текущем каталоге.

Функция может возвращать один из кодов ошибки:

ENOENT Нет такого файла или каталога
EACCES Нет прав доступа
EXDEV Другой диск

Код ошибки EXDEV возвращается в том случае, когда программа указывает разные диски для старого и нового имен файлов или каталогов.

Важное замечание: если вы задаете полный путь, в строке пути повторяйте символ '\' два раза. Это нужно для того, чтобы избежать конфликта с форматом представления констант в языке С. Например:

ret_code = rename("c:\\games","c:\\games_new");

3.3. Поиск в каталогах

Часто перед программистом стоит задача определения текущего содержимого каталога. При описании логической структуры диска мы приводили текст программы, выводящей на экран содержимого корневого каталога и других каталогов. Эта программа использовала загрузочный сектор логического диска и таблицу размещения файлов. Вы можете использовать такой способ, однако, если вам не требуется информация о номерах начальных кластеров файлов и дескрипторы удаленных файлов, лучше применить специальные функции MS-DOS, предназначенные для поиска файлов в каталогах.

Пара функций 4Eh и 4Fh предназначены для сканирования каталогов.

Эти функции используются вместе следующим образом:

Приведем формат вызова функций 4Eh и 4Fh.

Функция 4Eh:

На входе: AH = 4Eh
CX = Атрибуты файла, которые будут использованы при поиске. Будут найдены файлы, имеющие заданный в этом регистре атрибут.
DS:DX = Адрес строки в формате ASCIIZ, содержащей путь каталогаили файла.
На выходе: AL = Код ошибки, если был установлен в 1 флаг переноса CF.

Функция 4Fh:

На входе: AH = 4Fh
На выходе: AL = Код ошибки, если был установлен в 1 флаг переноса CF.

Обе функции устанавливают флаг переноса в том случае, когда каталог не содержит файлов, удовлетворяющих заданному критерию поиска.

Для работы с областью DTA MS-DOS имеет две функции. Это функция 2Fh, позволяющая получить адрес области DTA (она возвращает этот адрес в регистрах ES:BX), и функция 1Ah, предназначенная для установки своей области DTA (адрес новой области DTA должен быть указан в регистрах DS:DX).

Напомним, что по умолчанию область DTA занимает 128 байтов в префиксе сегмента программы PSP со смещением 80h.

В случае успешного поиска функции 4Eh и 4Fh помещают в DTA информацию о найденных файлах в следующем формате:

(0) 20 Зарезервировано.
(+21) 1 Атрибуты найденного файла
(+22) 2 Поле времени последнего обновления фалйла
(+24) 2 Поле даты последнего обновления фалйла
(+26) 4 Длина файла
(+30) 13 Имя файла и расширение в формате ASCIIZ

Номер начального класетра, распределенного файлу или каталогу, невозможно получить с помощью функций 4Eh и 4Fh.

Стандартные библиотеки трансляторов Microsoft QC 2.5 и C 6.0 содержат две функции, предназначенные для сканирования каталогов - _dos_findfirst() и _dos_findnext().

Приведем прототипы этих функций, описанные в файле dos.h:

int _dos_findfirst(char *pattern, struct find_t *found,
                                unsigned attr);
int _dos_findnext(struct find_t *found);

В этих функциях параметр pattern определяет образец для поиска файлов, параметр attr - атрибуты файла - используется в качестве дополнительного критерия поиска. Параметр found представляет собой указатель на структуру, в которую будет записываться информация о найденных файлах. Эта структура определена в файле dos.h:

struct find_t {
        char reserved[21];      // Зарезервировано для DOS
        char attrib;            // Атрибуты файла
        unsigned wr_time;       // Время изменения файла
        unsigned wr_date;       // Дата изменения файла
        long size;              // Размер файла в байтах
        char name[13];          // Имя файла и расширение
};

Приведем текст программы просмотра содержимого каталога. Программа принимает из командной строки параметр - образец для показа файлов. Если вы укажете параметр *.*, будет выведена информация обо всех файлах. Можно задавать полный путь: c:\*.*.

#include <stdlib.h>
#include <stdio.h>
#include <dos.h>

void main(int argc, char *argv[]) ;
void print_info(struct find_t *find);
char *time_conv(unsigned time, char *char_buf);
char *date_conv(unsigned date, char *char_buf);

void main(int argc, char *argv[]) {

         struct find_t find;

// Находим первый файл, удовлетворяющий критериям поиска.
// В качестве критерия используем образец, полученный
// из командной строки. Для поиска используем файлы с любыми
// атрибутами.

         if(!_dos_findfirst(argv[1], 0xffff, &find)) {

                  printf("\n"
                         "\nИмя файла    Аттр. Дата        Время     Размер"
                         "\n------------ ----- ----------  --------  ------");

// Выводим информацию о первом найденном файле на экран

                  print_info(&find);
         }
         else {
                  printf("Задайте образец для поиска файлов !");
                  exit(-1);
         }

// Выводим информацию об остальных найденных файлах

         while(!_dos_findnext(&find)) print_info( &find );

         exit(0);
}

// Функция для вывода информации о найденных файлах

void print_info(struct find_t *pfind) {

         char timebuf[10], datebuf[12];

// Преобразуем формат даты и времени последнего изменения файла

         date_conv(pfind->wr_date, datebuf);
         time_conv(pfind->wr_time, timebuf);

// Выводим содержимое дескриптора файла

                printf("\n%-12s",pfind->name);
                printf(" %02X    %8s  %8s %8ld ",
                        pfind->attrib,
                        datebuf,
                        timebuf,
                        pfind->size);
}

// Функция преобразования формата времени

char *time_conv(unsigned t, char *buf) {

         int h, m;

         h = (t >> 11) & 0x1f, m = (t >> 5) & 0x3f;
         sprintf(buf, "%2.2d:%02.2d:%02.2d", h % 12, m, (t & 0x1f) * 2);
         return buf;
}

// Функция преобразования формата даты

char *date_conv(unsigned d, char *buf) {

         sprintf(buf, "%2.2d.%02.2d.%04.2d",
                                 d & 0x1f,(d >> 5) & 0x0f, (d >> 9) + 1980);
         return buf;
}

При запуске программы с параметром *.com на экран будет выведена информация:

Имя файла    Аттр. Дата        Время     Размер
------------ ----- ----------  --------  ------
CURDIR.COM   20    24.02.1991  08:40:24     5879 
DIRCTL.COM   20    24.02.1991  09:10:30     6273 
DISK_CTL.COM 20    13.01.1991  03:26:34     8177 
DISKA.COM    20    19.01.1991  10:08:58       27 
DISKB.COM    20    04.02.1991  08:57:34     6186 
DISKINFO.COM 20    24.02.1991  04:08:42     6075 
DISKSHOW.COM 20    31.01.1991  09:48:34     6989 
PARTSHOW.COM 20    07.02.1991  06:12:02     7357 
DIRM.COM     20    26.02.1991  11:59:50     6346 

3.4. Работа с файлами

В этом разделе мы рассмотрим функции MS-DOS, предназначенные для создания, переименования, удаления и перемещения файлов. Операции чтения из файла и записи в файл будут описаны в следующем разделе.

Для создания файла предназначена функция 3Ch прерывания INT 21h. С помощью этой функции может быть создан файл как в текущем, так и в любом другом каталоге. Если файл с указанным именем уже существует, он обрезается до нулевой длины. Будьте осторожны при использовании этой функции - она может уничтожить нужный вам файл.

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

Приведем формат вызова функции создания файла:

На входе: AH = 3Ch
CX = Атрибуты создаваемого файла:
00h - обычный файл;
01h - только читаемый файл;
02h - скрытый файл;
04h - системный файл.
DS:DX = Адрес строки, содержащей путь создаваемого файла
На выходе: AX = Код ошибки, если был установлен в 1 флаг переноса CF;
Файловый индекс, если флаг переноса сброшен в 0.

При выполнении этой функции возможны следующие ошибки:

Операционная система игнорирует попытки создания с помощью этой функции каталогов или метки диска.

Для того, чтобы случайно не уничтожить содержимое файла с таким же именем, как и создаваемый, программа может использовать функцию 5Bh. Эта функция проверяет заданный путь на предмет наличия указанного файла. Если такой файл уже существует, функция возвращает программе признак ошибки.

Формат вызова функции:

На входе: AH = 5Bh
CX = Атрибуты создаваемого файла:
00h - обычный файл;
01h - только читаемый файл;
02h - скрытый файл;
04h - системный файл.
DS:DX = Адрес строки, содержащей путь создаваемого файла
На выходе: AX = Код ошибки, если был установлен в 1 флаг переноса CF;
Файловый индекс, если флаг переноса сброшен в 0.

Если вам требуется временный файл, вы можете создать его с помощью функции 5Ah:

На входе: AH = 5Ah
CX = Атрибуты создаваемого файла:
00h - обычный файл;
01h - только читаемый файл;
02h - скрытый файл;
04h - системный файл.
DS:DX = Адрес буфера, в который функция запишет путь созданного временного файла. Размер этого буфера должен быть по крайней мере 13 байтов.
На выходе: AX = Код ошибки, если был установлен в 1 флаг переноса CF;
Файловый индекс, если флаг переноса сброшен в 0.

Операционная система не будет автоматически удалять созданный временный файл после завершения работы программы или перезагрузки. Программа должна сама удалить этот файл.

Перед тем, как начать работу с файлом, его нужно открыть. Функции, создающие новые файлы, открывают новые файлы автоматически. Для того, чтобы открыть существующий файл, вы можете воспользоваться функцией 3Dh:

На входе: AH = 3Dh
AL = Требуемый режим доступа:

Бит 7: флаг наследования

0 - файловый индекс наследуется порожденным процессом
1 - файловый индекс не наследуется порожденным процессом

Биты 4...6: режим разделения

000 - режим совместимости
001 - запрещение всех видов доступа
010 - запрещение записи
011 - запрещение чтения
100 - разрешение всех видов доступа

Бит 3:0 - зарезервировано

Биты 0...2: вид доступа

000 - чтение
001 - запись
010 - чтение/запись
DS:DX = Адрес строки, содержащей путь открываемого файла
На выходе: AX = Код ошибки, если флаг переноса CF был установлен в 1;
Файловый индекс, если флаг переноса сброшен в 0.

С помощью функции 3Dh можно открыть любой файл (но не каталог). Если требуется вид доступа "запись", открываемый файл не должен иметь атрибут "Только читаемый".

Для использования битов 4...7 (управляющих доступом к файлу другими программами в сети) должна быть запущена программа SHARE.EXE.

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

Операционная система MS-DOS версии 4.0 имеет в своем составе функцию 6Ch, обладающую расширенными возможностями по созданию и открытию файлов:

На входе: AH = 6Ch
AL = 00h
BX = байт флагов расширенного режима открытия файла
CX = атрибуты создаваемого файла, используется только при создании файлов
DX = выполняемая функция, если файл существует или не существует:

Биты 0-3 регистра DX задают действие, если файл существует:

0000h - если файл существует, вернуть признак ошибки
0001h - если файл существует, открыть его
0002h - если файл существует, заместить и открыть его

Биты 4-7 регистра DX задают действие, если файл не существует:

0000h - если файл не существует, вернуть признак ошибки
0001h - если файл не существует, создать и открыть его
DS:SI = Адрес строки, содержащей путь открываемого файла
На выходе: AX = Код ошибки, если флаг переноса CF был установлен в 1;
Файловый индекс, если флаг переноса сброшен в 0.
CX = Код выполненных действий:
0 - файл был открыт
1 - файл был создан и открыт
2 - файл был замещен и открыт

Регистр BX на входе задает флаги расширенного режима открытия файла в следующем формате:

Биты Назначение
0...2 Режим доступа при чтении/записи
3 Зарезервировано, должно быть равно 0
4...6 Режим разделения
7 Флаг наследования
8...12 Зарезервировано, должно быть равно 0
13 0 - Режим обычного использования обработчика критических ошибок INT 24h
1 - Блокировка обработчика критических ошибок INT 24h. Для того, чтобы узнать причину ошибки, программа должна использовать функцию 59h прерывания INT 21h MS-DOS.
14 Управление буферизацией:
0 - Использование стандартной для MS-DOS буферизации.
1 - Отмена буферизации. Использование этого режима замедлит работу с диском, однако вероятность потери информации при аварии в питающей сети уменьшится.

Описанная выше функция является как бы комбинацией функций 3Dh и 3Ch (открытие и создание файла). Она удобна, но при ее использовании программа должна убедиться в том, что версия используемой операционной системы не ниже, чем 4.0.

Удалить файл можно при помощи функции 41h прерывания INT 21h:

На входе: AH = 41h
DS:DX = Адрес строки в формате ASCIIZ, содержащей имя удаляемого файла.
На выходе: AL = Код ошибки, если был установлен в 1 флаг переноса CF.

С помощью этой функции нельзя удалить файл, имеющий атрибут "Только читаемый".

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

Стандартные библиотеки трансляторов Microsoft QC 2.5 и C 6.0 содержат функции для работы с файлами. Эти функции можно разделить на две группы - функции ввода/вывода низкого уровня и функции ввода/вывода потоком. Вторая группа функций использует буферизацию и будет рассмотрена в разделе, посвященном буферизованному вводу/выводу.

Функции ввода/вывода низкого уровня отображаются на описанные выше функции прерывания INT 21h (и функции этого же прерывания, предназначенные для чтения/записи, позиционирования и т.д.).

Для создания файла можно использовать функцию creat():

int creat(char *filename, int mode);

Эта функция и ее параметры описаны в файлах io.h, sys\types.h, sys\stat.h, errno.h.

Первый параметр определяет путь создаваемого файла и его имя. Если файл с указанным именем существует, и не имеет атрибута "Только читаемый", функция сбрасывает длину файла до нуля. Предыдущее содержимое файла при этом уничтожается.

Второй параметр позволяет задать атрибуты создаваемого файла. Он может иметь следующие значения:

S_IWRITE для создаваемого файла разрешена операция записи;
S_IREAD для создаваемого файла разрешена операция чтения;
S_IREAD | S_IWRITE для создаваемого файла разрешены операции чтения и записи.

В операционных системах MS-DOS и OS/2 невозможно создать файл, в который можно было бы писать, но из которого было бы нельзя читать информацию. Поэтому задание второго параметра как S_IWRITE приведет к созданию такого файла, для которого разрешены как операция записи, так и операция чтения.

После создания файла функция creat() открывает новый файл и возвращает файловый индекс (handle) или код ошибки.

Мощная функция open() предназначена как для открытия существующих файлов, так и для создания новых:

int open(char *filename, int oflag [, int pmode]);

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

Параметр oflag может являться результатом битовой операции ИЛИ над следующими константами, определенными в файле fcntl.h:

O_APPEN при записи в файл информация будет добавляться в конец файла;
O_BINARY файл открывается для работы в двоичном режиме (игнорируются управляющие символы, такие как конец строки);
O_CREAT создается новый файл и открывается для записи; эта константа игнорируется, если указанный в первом параметре файл уже существует;
O_EXCL используется вместе с O_CREAT; если указанный в первом параметре файл существует, функция возвратит признак ошибки;
O_RDONLY файл открывается только для чтения, попытка записи в файл приведет к тому, что функция записи вернет признак ошибки;
O_RDWR файл открывается как для чтения, так и для записи;
O_TEXT файл открывается в текстовом режиме;
O_TRUNC существующий файл открывается и обрезается до нулевой длины (если для этого файла разрешена операция записи);
O_WRONLY файл открывается только для записи (в операционных системах MS-DOS и OS/2 для файла, открытого с признаком O_WRONLY разрешено выполнение операции чтения).

Для того, чтобы закрыть файл, открытый функциями creat() или open(), нужно использовать функцию close():

int close(int handle);

В качестве параметра функции передается файловый индекс, полученный при открытии или создании файла. Функция возвращает 0 при успешном закрытии файла, или -1 при ошибке.

Код ошибки для этой и других функций cтандартных библиотек трансляторов Microsoft QC 2.5 и C 6.0 записывается в глобальную переменную errno.

3.5. Чтение/запись файлов

После того, как вы открыли файл, можно выполнять над ним операции чтения/записи. Для записи данных в файл предназначена функция 40h прерывания INT 21h. В качестве параметров для этой функции необходимо задать файловый индекс, полученный при открытии существующего файла или создании нового, адрес буфера, содержащего записываемые данные и количество записываемых байтов:

На входе: AH = 40h
BX = файловый индекс открытого файла
CX = количество записываемых байтов
DS:DX = Адрес буфера, содержащего записываемые данные
На выходе: AX = Код ошибки, если был установлен в 1 флаг переноса CF;
Количество действительно записанных байтов, если флаг переноса CF сброшен в 0.

При записи данные попадают в то место внутри файла, которое определяется содержимым так называемого файлового указателя позиции. При создании нового файла этот указатель сбрасывается в 0, что соответствует началу файла. При открытии файла с помощью функции 3Dh указатель также устанавливается на начало файла. Операция записи в файл с помощью функции 40h продвигает указатель вперед к концу файла на количество записываемых байтов.

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

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

Следует учитывать, что количество действительно записанных байтов может не совпадать с заданным в регистре CX при вызове функции 40h. Такая ситуация возможна, например, при записи в файл, открытый в текстовом режиме, байта Ctrl-Z (1Ah). Этот байт означает конец текстового файла. Другая возможная причина - отсутствие свободного места на диске.

Если функция вызывается с содержимым регистра CX, равным 0, файл будет обрезан или расширен до текущего положения файлового указателя.

Разумеется, что если программа, выполняющая запись в файл, работает в сети, она должна иметь соответствующие права доступа к каталогу и файлу.

Функция 40h может выполнять запись не только в файл, но и в устройство посимвольной обработки, предварительно открытое функцией 3Dh. Об этом мы говорили в разделах книги, посвященных драйверам.

Для чтения данных из файла (или устройства посимвольной обработки) предназначена функция 3Fh прерывания INT 21h:

На входе: AH = 3Fh
BX = файловый индекс открытого файла
CX = количество читаемых байтов
DS:DX = Адрес буфера для данных
На выходе: AX = Код ошибки, если был установлен в 1 флаг переноса CF;
Количество действительно прочитанных байтов, если флаг переноса CF сброшен в 0.

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

Если ваша программа составлена на языке программирования С, для записи и чтения данных она может воспользоваться функциями write() и read():

int write(int handle, void *buffer, unsigned count);

int read(int handle, void *buffer, unsigned count);

Эти функции работают аналогично функциям 40h и 3Fh прерывания INT 21h. Параметр handle определяет файл, для которого необходимо выполнить операцию записи или чтения. Параметр buffer - указатель на буфер, который содержит данные для записи или в который необходимо поместить прочитанные данные. Количество записываемых/читаемых байтов определяется третьим параметром - count.

После выполнения операции функция возвращает количество действительно записанных или прочитанных данных или -1 при ошибке. Будьте внимательны, если вы записываете или читаете больше 32К байтов - вы можете получить признак ошибки, хотя передача данных выполнилась правильно. Большие массивы данных можно записывать по частям.

В качестве примера мы приведем программу копирования файлов, которая пользуется описанными выше функциями ввода/вывода:

#include <io.h>
#include <conio.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys\types.h>
#include <sys\stat.h>
#include <malloc.h>
#include <errno.h>

void main(int, char *[]);
void main(int argc, char *argv[]) {

         int source, taget, i;
         char *buffer;
         unsigned count;

         if(argc == 3) {

// Открываем исходный копируемый файл

                 if((source = open(argv[1], O_BINARY | O_RDONLY)) == - 1) {

                          printf("\nОшибка при открытии исходного файла: %d",
                          errno);
                          exit(-1);

                 }

// Открываем выходной файл. При необходимости создаем
// новый. Если файл уже существует, выводим на экран
// запрос на перезапись содержимого существующего файла

                taget = open(argv[2], O_BINARY | O_WRONLY | O_CREAT | O_EXCL,
                                                 S_IREAD | S_IWRITE);
                if(errno == EEXIST) {

                        printf("\nФайл существует. Перезаписать? (Y,N)\n");

// Ожидаем ответ оператора и анализируем его

                        i = getch();
                        if((i == 'y') || (i == 'Y'))
                                taget = open(argv[2], O_BINARY | O_WRONLY | O_CREAT | O_TRUNC,
                                                                 S_IREAD | S_IWRITE);

                }

// Если выходной файл открыть невозможно, выводим
// сообщение об ошибке и завершаем работу программы

                if(taget == -1){
                          printf("\nОшибка при открытии выходного файла: %d",
                          errno);
                          exit(-1);
                }

// Будем читать и писать за один раз 10000 байтов

                count = 10000;

// Заказываем буфер для передачи данных

                if((buffer = (char *)malloc(count)) == NULL) {
                          printf("\nНедостаточно оперативной памяти");
                          exit(-1);
                }


// Копируем исходный файл

                while(!eof(source)) {

// Читаем count байтов в буфер buffer

                        if((count = read(source, buffer, count)) == -1) {
                          printf("\nОшибка при чтении: %d",
                          errno);
                          exit(-1);
                        }

// Выполняем запись count байтов из буфера в выходной файл

                        if((count = write(taget, buffer, count)) == - 1) {
                          printf("\nОшибка при записи: %d",
                          errno);
                          exit(-1);
                        }
                }

// Закрываем входной и выходной файлы

                close(source);
                close(taget);

// Освобождаем память, заказанную под буфер

                free(buffer);
         }

// Если при запуске программы не были указаны
// пути для входного или выходного файла,
// выводим сообщение об ошибке

         else
                  printf("\n"
                                "Задайте пути для исходного"
                                " и результирующего файлов!\n");
}

В приведенной программе для определения конца исходного файла использована функция eof():

int eof(int handle);

Для файла с файловым индексом handle эта функция возвращает одно из трех значений:

1 достигнут конец файла;
0 конец файла не достигнут;
-1 ошибка, например, неправильно указан handle.

Программа, которая читает файл с помощью функции 3Fh прерывания INT 21h, может определить момент достижения конца файла, анализируя код ошибки в регистре AX.

3.6. Позиционирование

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

Установить файловый указатель в нужную вам позицию можно с помощью функции 42h прерывания INT 21h MS-DOS:

На входе: AH = 42h
AL = метод кодирования:
00h абсолютное смещение от начала файла
01h смещение от текущей позиции
02h смещение от конца файла
BX = файловый индекс открытого файла
CX = старший байт смещения
DX = младший байт смещения
На выходе: AX = Код ошибки, если был установлен в 1 флаг переноса CF;
Младший байт текущей позиции, если флаг переноса CF сброшен в 0.
DX = Старший байт текущей позиции

Функция позволяет указывать новое значение указателя либо как абсолютное смещение от начала файла, либо как смещение от текущей позиции, либо как смещение от конца файла. В последних двух случаях используется смещение со знаком. Для указания смещения или абсолютной позиции программа должна задать в регистрах CX, DX 32-битовое значение.

Если использовать метод кодирования 02h и задать нулевое смещение, функция установит указатель на конец файла. Это обстоятельство может быть использовано для определения размера файла в байтах.

Что произойдет, если при использовании методов кодирования 01h или 02h попытаться установить указатель позиции до начала файла?

Функция 42h при этом не возвратит признак ошибки, однако если будет сделана попытка прочитать или записать данные, то соответствующая функция чтения/записи завершится с ошибкой.

Стандартные библиотеки трансляторов Microsoft QC 2.5 и C 6.0 содержат функции, предназначенные для управления содержимым файлового указателя позиции и получения текущего значения этого указателя. Это функции lseek(), tell(), filelength().

Функция lseek() работает аналогично только что описанной функции 42h. Приведем ее прототип:

long lseek(int handle, long offset, int origin);

Первый параметр определяет файл, для которого выполняется операция позиционирования. Параметр offset определяет смещение. Последний параметр задает метод кодирования смещения. Он может принимать следующие значения, описанные в фале stdio.h:

SEEK_SET Абсолютное смещение от начала файла
SEEK_CUR Смещение относительно текущей позиции
SEEK_END Смещение относительно конца файла

Функция возвращает величину текущего смещения в байтах относительно начала файла или -1L, в случае ошибки. Как и для остальных функций библиотеки С, код ошибки находится в глобальной переменной errno.

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

long filelength(int handle);

Функция возвращает размер файла в байтах. Файл задается параметром handle. В случае ошибки функция возвращает значение -1L.

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

long tell(int handle);

Эта функция возвращает текущую позицию для файла, определенного параметром handle, или -1L, в случае ошибки.

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

#include <io.h>
#include <stdio.h>
#include <fcntl.h>

void main(void);
void main(void) {

         int handle;
         long position, length;
         char buffer[2], fname[80];

// Запрашиваем имя файла, с которым будем работать

         printf("Введите имя файла: ");
         gets(fname);

// Открываем файл

         handle = open(fname, O_BINARY | O_RDONLY);

// Если такого файла нет, выводим сообщение об ошибке
// и завершаем работу программы

         if(handle == -1) {
                printf("\nНет такого файла!");
                exit(-1);
         }

// Определяем и выводим на экран размер файла в байтах

         length = filelength(handle);

         printf("\nДлина файла %s составляет %ld байтов\n", fname, length);

// Запрашиваем позицию для чтения и отображения байта

         do {

                  printf("Введите позицию: ");
                  scanf("%ld", &position);

         } while(position > length);

// Устанавливаем заданную позицию

         lseek(handle, position, SEEK_SET);

// Читаем один байт в буфер, начиная с установленной
// позиции

         if(read(handle, buffer, 1) == -1) {

// Для вывода сообщения об ошибке используем функцию perror(),
// которая добавляет к сообщению, заданному в параметре,
// расшифрованное системное сообщение об ошибке.
// Код ошибки функция perror() берет из переменной errno.

                perror("Ошибка при чтении");
                exit(-1);
         }

// Выводим считанный байт на экран

         printf( "Смещение: %ld; байт: %02.2x ('%c')\n",
                        position, (unsigned char)*buffer, *buffer);

// Определяем текущую позицию и выводим ее
// на экран

         position = tell(handle);
         printf("\nТекущая позиция в файле: %ld\n", position);

// Закрываем файл

         close(handle);
}

3.7. Изменение атрибутов, времени и даты файлов

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

Операционная система предоставляет вам все необходимые средства для изменения всех полей дескриптора файла, кроме номера начального кластера. Для изменения этого номера вам придется работать с каталогом через таблицу размещения файлов FAT. Вам придется сначала считать каталог по кластерам с помощью прерывания INT 25h, модифицировать нужные поля и записать каталог обратно на диск при помощи прерывания INT 26h.

Для работы с полем атрибутов файла предназначена функция 43h прерывания INT 21h:

На входе: AH = 43h
AL = выполняемая операция:
00h чтение атрибутов файла
01h установка новых атрибутов файла
CX = новые атрибуты файла, если AL = 01h:
Биты:
5 - бит архивации
4 - каталог
3 - метка диска
2 - системный файл
1 - скрытый файл
0 - только читаемый файл
DS:DX = путь файла в формате ASCIIZ
На выходе: AX = Код ошибки, если был установлен в 1 флаг переноса CF
CX = Если не было ошибки, этот регистр содержит атрибуты файла

При изменении атрибутов файла допустимо указывать комбинации битов в регистре CX.

Если ваша программа работает в сети, она должна иметь соответствующие права доступа к каталогу, содержащему файл, для которого программа собирается изменять байт атрибутов.

Для работы с полями времени и даты последней модификации файла предназначена функция 57h прерывания INT 21h:

На входе: AH = 57h
AL = выполняемая операция:
00h чтение даты и времени
01h установка даты и времени
BX = файловый индекс открытого файла
CX = время
DX = дата
На выходе: AX = Код ошибки, если был установлен в 1 флаг переноса CF
CX = Если не было ошибки, этот регистр содержит время последнего изменения файла
DX = Если не было ошибки, этот регистр содержит дату последнего изменения файла

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

Формат поля времени:

15            11 10               5 4                   0
+--------------------------------------------------------+
¦ Часы (0...23) ¦  Минуты (0...59) ¦  Секунды/2 (0...29) ¦
+--------------------------------------------------------+

Формат поля даты:

15             9 8               5 4              0
+--------------------------------------------------+
¦ Год (0...119) ¦  Месяц (1...12) ¦  День (1...31) ¦
+--------------------------------------------------+

Стандартные библиотеки трансляторов Microsoft QC 2.5 и C 6.0 содержат функции для чтения и изменения атрибутов файлов и времени/даты их последней модификации.

Для определения атрибутов файла можно использовать функцию _dos_getfileattr():

unsigned _dos_getfileattr(char *path, unsigned *attrib);

Эта функция получает атрибуты файла, заданного первым аргументом, и записывает байт атрибутов в младший байт по адресу, указанному вторым параметром.

В случае успешного завершения функция возвращает 0, в противном случае она возвращает код ошибки, полученный от операционной системы и устанавливает глобальную переменную errno в значение ENOENT, что означает отсутствие указанного в параметре path файла.

Для изменения атрибутов файла можно использовать функцию _dos_setfileattr():

unsigned _dos_setfileattr(char *path, unsigned attrib);

Параметр attrib может принимать следующие значения:

_A_ARCH установка бита архивации
_A_HIDDEN файл скрытый
_A_NORMAL обычный файл
_A_RDONLY только читаемый файл
_A_SUBDIR каталог
_A_SYSTEM системный файл
_A_VOLID метка диска

Для определения времени последней модификации файла можно использовать функцию _dos_getftime():

unsigned _dos_getftime(int handle, unsigned *date, unsigned *time);

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

Если вам надо изменить время или дату последней модификации файла, используйте функцию _dos_setftime():

unsigned _dos_setftime(int handle, unsigned date, unsigned time);

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

Приведем программу, изменяющую при запуске значение бита файла атрибутов "Только читаемый" для файла, имя которого передается программе в качестве параметра:

#include <dos.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>

void main(int argc, char *argv[]);
void main(int argc, char *argv[]) {

         unsigned fattr;

         _dos_getfileattr(argv[1], &fattr);

         _dos_setfileattr(argv[1], fattr ^ _A_RDONLY);
}

Программа сначала считывает байт атрибутов, затем инвертирует соответствующий бит и устанавливает новое значение байта атрибутов.

3.8. Буферизация ввода/вывода

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

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

Операционная система MS-DOS имеет несколько буферов. Их количество зависит от оператора BUFFERS, находящегося в файле CONFIG.SYS. Этот оператор позволяет определить от 2 до 99 буферов. Если файл CONFIG.SYS не содержит оператора BUFFERS, по умолчанию используются два буфера.

При увеличении количества буферов увеличивается вероятность того, что нужная часть файла уже считана и находится в оперативной памяти. Однако необходимо учитывать, что для хранения буферов расходуется основная оперативная память. Кроме того, с ростом количества буферов увеличивается время, необходимое операционной системе на анализ состояния буферов, что может привести к снижению производительности. Значительное снижение скорости работы наступает при количестве буферов порядка 50.

Обычно для машин класса AT с диском размером 20-40 мегабайтов рекомендуется использовать 32 буфера, однако для каждого конкретного случая может потребоваться подбор этого параметра для оптимизации производительности системы.

Если ваша программа интенсивно использует обращение к каталогам файловой системы, вы можете использовать утилиту MS-DOS FASTOPEN, которая запоминает в оперативной памяти расположение на диске файлов и каталогов, уменьшая интенсивность обращения к диску. Например, при использовании следующей команды в оперативной памяти будет храниться информация о расположении максимально о 100 файлах и каталогах:

FASTOPEN c:=100

В операционной системе MS-DOS версии 4.0 вы можете указать для утилиты FASTOPEN опцию /X. Эта опция вызывает размещение информации о файлах и каталогах в дополнительной (expanded) памяти. Для этой версии операционной системы вызов утилиты FASTOPEN лучше всего выполнять через оператор INSTALL файла CONFIG.SYS:

INSTALL=C:\DOS\FASTOPEN d: =(n,m) ... [/X]

В приведенной выше строке используются следующие обозначения:

d: обозначение диска;
n количество файлов или каталогов, для которых необходимо запомнить расположение, может иметь значение от 1 до 999, по умолчанию используется 34;
m количество буферов для фрагментированных файлов, может иметь значение от 1 до 999, по умолчанию используется 34;
/X задает расположение буферов в дополнительной памяти.

Еще один способ организовать буферизацию ввода/вывода для жестких дисков - использовать драйвер SMARTDRV.SYS. Этот драйвер позволяет создать для диска кэш-память в расширенной или дополнительной памяти. Например, следующая строка в файле CONFIG.SYS определяет дисковый кэш размером 530 килобайтов, размещенный в расширенной памяти:

device=c:\dos\smartdrv.sys 530

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

Если вы используете кэш-память для диска, не следует задавать оператор BUFFERS в файле CONFIG.SYS или пользоваться утилитой FASTOPEN, так как это приведет к многократной буферизации и вызовет излишние пересылки данных в оперативной памяти.

Буферизация данных имеет и свои недостатки. Если в результате аварии в питающей сети или по какой-то другой причине компьютер выключился, то информация, хранящаяся в буферах и не записанная на диск, будет потеряна.

При закрытии файла все буфера, связанные с ним, сбрасываются на диск. Если вам надо сбросить буфера, не закрывая файл, это можно сделать с помощью функции 68h прерывания INT 21h:

На входе: AH = 68h
BX = файловый индекс открытого файла
На выходе: AX = Код ошибки, если был установлен в 1 флаг переноса CF;
0, если операция выполнена успешно.

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

Обратите также внимание на функцию расширенного открытия файлов 6Ch, входящую в состав MS-DOS версии 4.0. Эта функция позволяет при открытии файла отменить буферизацию.

Стандартные библиотеки трансляторов Microsoft QC 2.5 и C 6.0 содержат многочисленные функции, использующие собственный механизм буферизации при работе с файлами. Их часто называют функциями потокового ввода/вывода. Такую буферизацию не следует путать с буферизацией, выполняемой операционной системой. Имена всех этих функций начинаются на f - fopen(), fclose(), fprintf() и т.д.

Функции потокового ввода/вывода хорошо описаны во многих учебных пособиях по языку программирования Си, поэтому мы приведем лишь краткий обзор этих функций, делая акцент на особенностях их применения.

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

extern FILE  {

                char *_ptr;     // положение текущего символа
                int   _cnt;       // количество оставшихся байтов
                char *_base;    // адрес буфера
                char  _flag;     // флаг управления доступом
                char  _file;     // файловый индекс

} _NEAR _CDECL _iob[];

Для организации потокового ввода/вывода вначале необходимо при помощи функции fopen() открыть файл. Функция fopen() имеет следующий прототип:

FILE *fopen(char *filename, char *mode);

Первый параметр указывает на строку, содержащую путь открываемого файла, второй - на строку режима открытия файла. Возможны следующие режимы:

"r" файл открывается для чтения;
"w" файл открывается для записи;
"a" файл открывается для записи, данные будут добавляться в конец файла.

К буквам r, w, a справа могут добавляться буквы t и b.

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

Строка режима открытия файла может дополнительно содержать символ '+'. Этот символ означает, что для файла разрешены операции чтения и записи одновременно.

Для закрытия файлов, открытых для ввода/вывода потоком, должна использоваться функция fclose():

int fclose(FILE *stream);

При закрытии файла освобождаются и сбрасываются на диск все буфера, распределенные этому файлу.

Если вы открыли файл с помощью низкоуровневой функции open(), вы можете создать поток для этого файла, используя функцию fdopen():

FILE *fdopen(int handle, char *mode);

В качестве первого параметра используется файловый индекс, полученный от функции open(), второй параметр аналогичен парметру mode для функции fopen().

Для того чтобы закрыть поток, созданный функцией fdopen(), необходимо использовать функцию fclose(), а не close().

Для открытого потока вы можете узнать файловый индекс с помощью функции fileno():

int fileno(FILE *stream);

Функция возвращает файловый индекс, связанный с данным потоком.

Существуют потоки, соответствующие стандартным устройствам ввода, вывода, вывода сообщений об ошибках, стандартное устройство последовательного ввода/вывода и стандартное устройство печати. Для использования этих потоков не требуются процедуры открытия и закрытия. Для работы с потоками, соответствующими стандартным устройствам ввода/вывода, в функциях необходимо указывать:

stdin стандартное устройство ввода;
stdout стандартное устройство вывода;
stderr стандартное устройство для вывода сообщений об ошибках;
stdaux стандартное последовательное устройство ввода/вывода;
stdprn стандартное печатающее устройство.

Для работы со стандартными устройствами ввода/вывода в библиотеках трансляторов для языка программирования Си имеется соответствующий набор функций, которые должны быть вам хорошо известны - printf(), scanf(), putchar() и т.д.. Мы не будем их описывать для экономии места.

Приведем обзор функций, предназначенных для потокового ввода/вывода.

Для записи данных в поток предназначена функция fwrite():

size_t fwrite(void *buffer, size_t size,
                        size_t count, FILE *stream);

Эта функция записывает в файл stream блоки информации, каждый из которых имеет длину size байтов. Количество блоков - count. Данные для записи расположены по адресу buffer.

Если файл открыт в текстовом режиме, каждый символ возврата каретки CR заменяется на два символа - возврата каретки CR и перевода строки LF.

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

Чтение данных потоком можно выполнить с помощью функции fread():

size_t fread(void *buffer, size_t size,
                        size_t count, FILE *stream);

Эта функция используется аналогично предыдущей. Для распознавания конца файла и обнаружения ошибок после вызова этой функции необходимо использовать функции feof() и ferror().

Если при использовании функции fread() вы задали значения параметров size или count, равные нулю, функция fread() не изменяет содержимое буфера buffer.

Для позиционирования внутри файла, открытого потоком с помощью функции fopen(), предназначена функция fseek():

int fseek(FILE *stream, long offset, int origin);

В этой функции параметр offset задает новое содержимое указателя текущей позиции в файле stream, а параметр origin - определяет способ задания новой позиции. Этот оператор может иметь значения, аналогичные используемым в функции lseek():

SEEK_SET Абсолютное смещение от начала файла
SEEK_CUR Смещение относительно текущей позиции
SEEK_END Смещение относительно конца файла

При открытии файла указатель текущей позиции устанавливается на начало файла. Операции ввода/вывода вызывают увеличение значения этого указателя на количество прочитанных/записанных байтов.

Функция fseek() позволяет вам установить указатель за конец файла, однако при попытке установаит указатель до начала файла функция возвратит признак ошибки - ненулевое значение.

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

Функция ftell() возвращает текущее значение указателя позиции для файла, или -1 при ошибке:

long ftell(FILE *stream);

Пара функций ftell() - fseek() позволит вам правильно организовать позиционирование для файлов, открытых в текстовом режиме.

Есть еще одна возможность организовать позиционирование внутри файлов, открытых потоком - использовать пару функций fgetpos() - fsetpos():

int fgetpos(FILE *stream, fpos_t *pos);

int fsetpos(FILE *stream, fpos_t *pos);

Эти две функции используют для запоминания и установки позиции переменную с типом fpos_t, определенным в файле stdio.h. Функция fgetpos() записывает в эту переменную текущую позицию в потоке stream. Содержимое переменной затем может быть использовано для установки позиции в потоке с помощью функции fsetpos().

Обе эти функции возвращают нулевое значение в случае успешного завершения работы, или ненулевое - при ошибке.

Среди функций потокового ввода/вывода можно выделить группу функций форматного ввода/вывода. Это такие функции, как fputc(), fgetc(), fputs(), fgets(), fprintf(), fscanf().

Функции форматного ввода/вывода сильно облегчают запись/чтение таких элементов данных, как отдельные байты, текстовые строки, числа в различных форматах.

Для записи в поток отдельных байтов используется функция fputc():

int fputc(int c, FILE *stream);

Байт c записывается в файл stream начиная с текущей позиции. После записи текущая позиция увеличивается на единицу. Функция возвращает записываемый байт или значение EOF, которое служит признаком ошибки.

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

int fgetc(FILE *stream);

Эта функция возвращает считанный из файла и преобразованный к типу int байт из потока stream. После чтения байта текущая позиция в файле увеличивается на единицу.

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

Для работы со строками предназначены функции fputs() и fgets().

Функция fputs() предназначена для вывода строки в файл, открытый потоком:

int fputs(char *string, FILE *stream);

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

Для ввода строк из текстового файла удобна функция fgets():

int fgets(char *string, int n, FILE *stream);

Функция читает байты из потока stream и записывает их в буфер string до тех пор, пока не произойдет одно из двух событий - будет прочитан символ новой строки '\n' или количество прочитанных символов не будет равно n-1.

После того, как байты будут прочитаны в буфер, в конец образованной из этих байтов строки функция записывает двоичный ноль. Если был прочитан символ новой строки '\n', он тоже записывается в буфер.

Для анализа достижения конца файла или ошибок необходимо использовать функции feof() и ferror().

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

int fprintf(FILE *stream, char *format [,arg]...);

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

#include <stdio.h>
main() {
   printf("Hello, world!");
}

Функция fprintf() имеет дополнительно один параметр - stream, который определяет выходной файл.

После завершения работы функция возвращает количество записанных байтов или отрицательную величину, если при записи произошла ошибка.

Для форматного ввода информации из файла можно использовать функцию fscanf(), аналогичную известной вам функции scanf():

int fscanf(FILE *stream, char *format [,arg]...);

Эта функция читает данные, начиная с текущей позиции в потоке stream, в переменные, определенные аргументами arg. Каждый аргумент должен являться указателем на переменную, соответствующую типу, определенному в строке формата format.

Функция fscanf() возвращает количество успешно считанных и преобразованных в указанный формат полей. Те поля, которые были считаны, но не преобразовывались, в возвращаемом функцией значении не учитываются.

При достижении конца файла функция возвращает значение EOF. Если функция возвратила нулевое значение, это означает, что преобразование полей не производилось.

Рассмотрим теперь функции, управляющие буферизацией для потокового ввода/вывода.

Функция setbuf() позволяет вам заменить системный буфер на свой собственный:

void setbuf(FILE *stream, char *buffer);

Параметр buffer должен указывать на подготовленный пользователем массив, имеющий размер BUFSIZ байтов. Константа BUFSIZ описана в файле stdio.h.

Функция setvbuf() позволяет программе не только указать свой буфер, но и задать его размер:

int setvbuf(FILE *stream, char *buffer, int mode,
                        size_t size);

Параметр stream должен указывать на открытый поток, причем для этого потока до вызова функции setvbuf() нельзя выполнять операции чтения/записи.

Параметр buffer должен указыват на подготовленный программой буфер размером size байтов. Этот буфер будет использоваться для работы с потоком stream.

Параметр mode может иметь значения _IOFBF, _IOLBF, _IONBF. Если mode равно _IOFBF или _IOLBF, параметр size указывает размер буфера. Если параметр mode равен _IONBF, буферизация не используется, парметры buffer и size игнорируются.

Параметры _IOFBF и _IOLBF эквивалентны.

Если в качестве адреса буфера buffer задать значение NULL, функция автоматически закажет буфер размером size.

Функция setvbuf() возвращает ноль при успешном завершении и ненулевую величину, если указан неправильный парметр mode или неправильный размер буфера size.

Для чего может понадобиться изменение размера буфера?

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

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

int fflush(FILE *stream);

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

Если поток открыт только для чтения, функция fflush() очищает содержимое буфера, связанного с этим потоком.

В качестве примера приведем текст программы, копирующей содержимое текстового файла. Программа копирует этот файл три раза. В первый раз одна использует буфер стандартного размера, затем увеличивает размер буфера в десять раз, и, наконец, копирует файл без использования механизма буферизации. Каждый раз программа измеряет продолжительность копирования файла с помощью функции clock(), входящей в состав стандартных библиотек трансляторов Microsoft QC 2.5 и C 6.0.

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

void filecpy(FILE *stream_from, FILE *stream_to);

// Буфера для файлов

char buf1[BUFSIZ * 10];
char buf2[BUFSIZ * 10];

void main(int argc, char *argv[]) {

         time_t start, end;
         FILE *stream_from, *stream_to;


         if(argc < 3) {
                  printf("Задайте имена файлов!\n");
                  exit(-1);
         }

// Открываем файлы и используем для копирования
// буфер стандартного размера

         if((stream_from = fopen(argv[1], "rt")) == NULL) {
                  printf("Задайте имя входного файла!\n");
                  exit(-1);
         }
         stream_to = fopen(argv[2], "wt+");

// Определяем время начала копирования

         start = clock();

// Выполняем копирование файла

         filecpy(stream_from,stream_to);

// Определяем время завершения копирования

         end = clock();

// Выводим время копирования при использовании
// буферов стандартного размера

         printf("Время копирования: %5.1f Размер буфера, байтов: %d\n",
                 ((float)end - start) / CLK_TCK, BUFSIZ);


// Задаем свой буфер большего размера

         if((stream_from = fopen(argv[1], "rt")) == NULL) exit(-1);
         stream_to = fopen(argv[2], "wt+");

// Устанавливаем буфера как для входного,
// так и для выходного файлов

         setvbuf(stream_from, buf1, _IOFBF, sizeof(buf1));
         setvbuf(stream_to,   buf2, _IOFBF, sizeof(buf2));

// Копируем файл и измеряем продолжительность
// копирования

         start = clock();
         filecpy(stream_from,stream_to);
         end = clock();

         printf("Время копирования: %5.1f Размер буфера: %d\n",
                 ((float)end - start) / CLK_TCK, BUFSIZ * 10);

// Копируем без использования буферизации

         if((stream_from = fopen(argv[1], "rt")) == NULL) exit(-1);
         stream_to = fopen(argv[2], "wt+");

         setvbuf(stream_from, NULL, _IONBF, 0);
         setvbuf(stream_to,   NULL, _IONBF, 0);

         start = clock();
         filecpy(stream_from,stream_to);
         end = clock();

         printf("Время копирования: %5.1f Буферизация не используется\n",
                 ((float)end - start) / CLK_TCK);

         exit(0);
}

// Функция для копирования файлов

void filecpy(FILE *stream_from, FILE *stream_to) {

         char linebuf[256];

// Цикл копирования. Условие выхода из цикла -
// конец входного файла

         while(!feof(stream_from)) {

// Читаем в буфер linebuf одну строку

                  if(fgets(linebuf, 255, stream_from) == NULL) break;

// Записываем содержимое буфера linebuf
// в выходной файл

                  if(fputs(linebuf, stream_to) != 0) break;
         }

// Закрываем входной и выходной файлы

         fclose(stream_from);
         fclose(stream_to);

}

3.9. Другие функции для работы с файлами

В задачу данной книги не входит описание всех функций стандартных библиотек трансляторов Microsoft QC 2.5 и C 6.0, предназначенных для работы с дисками и файловой системой. Но мы приведем еще несколько интересных и полезных на наш взгляд функций.

Как мы уже отметили, программа может использовать два режима ввода/вывода для файлов - текстовый и двоичный. Для переключения этого режима для открытого файла можно использовать функцию setmode():

int setmode(int handle, int mode);

Первый параметр - файловый индекс. Второй параметр может принимать два значения:

O_TEXT установить текстовый режим;
O_BINARY установить двоичный режим.

Функция setmode() должна вызываться перед началом ввода/вывода в открытый файл.

Мы рассказывали о позиционировании внутри файла. Если вам нужно просто установить указатель позиции на начало файла, открытого для потокового ввода/вывода, вы можете воспользоваться функцией rewind():

void rewind(FILE *stream);

Если вам нужно переназначить ввод/вывод для стандартных устройств (потоков stdin, stdout, stderr), вы можете использовать функцию freopen():

FILE *freopen(char *filename, char *mode, FILE *stream);

Функция freopen() закрывает файл, с которым был связан поток stream, и переназначает этот поток файлу, определенному параметром filename. Параметр mode задается так же, как и для функции fopen().

Можно переназначить файловый индекс для файла, открытого функцией open(). Для этого можно воспользоваться одной из двух функций - dup() или dup2():

int dup(int handle);

int dup2(int handle1, int handle2);

Первая функция связывает с открытым файлом еще один файловый индекс. Этот индекс она возвращает при успешном завершении. В случае ошибки она возвращает значение -1.

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

Функция dup2() переназначает файловый индекс handle2, связывая его с тем же файлом, которому соответствует файловый индекс handle1. Если во время вызова функции dup2() с файловым индексом handle2 связан какой-либо открытый файл, этот файл закрывается. В случае успешного завершения функция dup2() возвращает нулевое значение. Если произошла ошибка, возвращается значение -1.

3.10. Таблица открытых файлов

В первой книге при описании векторной таблицы связи мы говорили о том, что для всех открытых файлов MS-DOS хранит различную информацию в специальной таблице. Ее адрес находится в поле file_tab векторной таблицы связи.

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

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

Вы можете самостоятельно экспериментировать с этой таблицей. Можно, например, попробовать создать несколько файловых индексов для какого-либо файла и посмотреть после этого содержимое поля, в котором находится количество файловых индексов, связанных с данным файлом.

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

Однако не обольщайтесь - все что связано с таблицей файлов, отсутствует в документации по операционной системе MS-DOS. Используя эту таблицу для определения списка кластеров или для каких-либо других целей, вы рискуете потерять совместимость с последующими версиями операционной системы.