10
11 struct person {
12 char name[10]; /* имя */
13 char id[10]; /* идентификатор */
14 off_t pos; /* положение в файле для демонстрации */
15 } people[] = {
16 { "arnold", "123456789", 0 },
17 { "miriam", "987654321", 10240 },
18 { "joe", "192837465", 81920 },
19 };
20
21 int
22 main(int argc, char **argv)
23 {
24 int fd;
25 int i, j;
26
27 if (argc < 2) {
28 fprintf(stderr, "usage: %s file\n", argv[0]);
29 return 1;
30 }
31
32 fd = open(argv[1], O_RDWR | O_CREAT | O_TRUNC, 0666);
33 if (fd < 0) {
34 fprintf(stderr, "%s: %s: cannot open for read/write: %s\n",
35 argv[0], argv[1], strerror(errno));
36 return 1;
37 }
38
39 j = sizeof(people) / sizeof(people[0]); /* число элементов */
Строки 27–30 гарантируют, что программа была вызвана правильно. Строки 32–37 открывают именованный файл и проверяют успешность открытия.
Вычисление числа элементов j
массива в строке 39 использует отличный переносимый трюк число элементов является размером всего массива, поделенного на размер первого элемента. Красота этого способа в том, что он всегда верен: неважно, сколько элементов вы добавляете в массив или удаляете из него, компилятор это выяснит. Он не требует также завершающей сигнальной метки; т.е. элемента, в котором все поля содержат нули, NULL
или т.п.
Работа осуществляется в цикле (строки 41–55), который отыскивает смещение байтов, приведенное в каждой структуре (строка 42), а затем записывает всю структуру (строка 49):
41 for (i = 0; i < j; i++) {
42 if (lseek(fd, people[i].pos, SEEK_SET) < 0) {
43 fprintf(stderr, "%s: %s: seek error: %s\n",
44 argv[0], argv[1], strerror(errno));
45 (void)close(fd);
46 return 1;
47 }
48
49 if (write(fd, &people[i], sizeof(people[i])) != sizeof(people[i])) {
50 fprintf(stderr, "%s: %s: write error: %s\n",
51 argv[0], argv[1], strerror(errno));
52 (void)close(fd);
53 return 1;
54 }
55 }
56
57 /* здесь все нормально */
58 (void)close(fd);
59 return 0;
60 }
Вот результаты запуска программы:
$ ch04-holes peoplelist /* Запустить программу */
$ ls -ls peoplelist /* Показать использованные размеры и блоки */
16 -rw-r--r-- 1 arnold devel 81944 Mar 23 17:43 peoplelist
$ echo 81944 / 4096 | bc -l /* Показать блоки, если нет дыр */
20.00585937500000000000
Случайно мы знаем, что каждый дисковый блок файла использует 4096 байтов. (Откуда мы это знаем, обсуждается в разделе 5 4.2 «Получение информации о файле». Пока примите это как данное.) Финальная команда bc указывает, что файлу размером 81944 байтов нужен 21 дисковый блок. Однако, опция -s команды ls, которая сообщает нам, сколько блоков использует файл на самом деле, показывает, что файл использует лишь 16 блоков![48] Отсутствующие блоки в файле являются дырами. Это показано на рис. 4.2.
Рис. 4.2. Дыры в файле
ЗАМЕЧАНИЕ. ch04-holes.c
не осуществляет непосредственный двоичный ввод/вывод. Это хорошо демонстрирует красоту ввода/вывода с произвольным доступом: вы можете рассматривать дисковый файл, как если бы он был очень большим массивом двоичных структур данных.
На практике сохранение данных путем использования двоичного ввода/вывода является решением, которое необходимо тщательно взвесить. Например, что если предположить, что вам нужно переместить данные на систему, использующую отличный порядок байтов для целых? Или другие форматы чисел с плавающей точкой? Или на систему с другими требованиями выравнивания? Игнорирование подобных вопросов может стать слишком дорогостоящим.
4.6. Создание файлов
Как было описано ранее, open()
, очевидно, открывает лишь существующие файлы. Данный раздел описывает, как создавать новые файлы. Есть две возможности: creat()
и open()
с дополнительными файлами. Первоначально creat()
был единственным способом создания файла, но затем эта возможность была добавлена также и к open()
. Оба механизма требуют указания начальных прав доступа к файлу.
4.6.1. Определение начальных прав доступа к файлу
Как пользователь GNU/Linux, вы знакомы с правами доступа к файлу, выдаваемыми командой 'ls -l
': на чтение, запись и исполнение для каждого из владельца файла, группы и остальных. Различные сочетания часто выражаются в восьмеричной форме, в частности, для команд chmod
и chmask
. Например, права доступа к файлу -rw-r--
r-- эквивалентны восьмеричному 0644, a -rwxr-xr-x
эквивалентно восьмеричному 0755. (Ведущий 0 в нотации С означает восьмеричные значения.)
Когда вы создаете файл, вы должны знать, какую защиту необходимо назначить новому файлу. Вы можете сделать это с помощью простого восьмеричного числа, если захотите, и такие числа довольно обычно можно увидеть в старом коде. Однако, лучше использовать побитовую операцию OR для одной или более символических имен из <sys/stat.h>
, описанных в табл. 4.5.
Таблица 4.5. Символические имена POSIX для режимов доступа к файлу
Символическое имя | Значение | Комментарий |
---|---|---|
S_IRWXU |
00700 | Разрешение на чтение, запись и исполнение для владельца |
S_IRUSR |
00400 | Разрешение на чтение для владельца |
S_IREAD |
Аналогично S_IRUSR |
|
S_IWUSR |
00200 | Разрешение на запись для владельца |
S_IWRITE |
Аналогично S_IWUSR |
|
S_IXUSR |
00100 | Разрешение на исполнение для владельца. |
S_IEXEC |
Аналогично S_IXUSR |
|
S_IRWXG |
00070 | Разрешение на чтение, запись и исполнение для группы |
S_IRGRP |
00040 | Разрешение на чтение для группы |
S_IWGRP |
00020 | Разрешение на запись для группы. |
S_IXGRP |
00010 | Разрешение на исполнение для группы |
S_IRWXO |
00007 | Разрешение на чтение, запись и исполнение для остальных. |
S_IROTH |
00004 | Разрешение на чтение для остальных. |
S_IWOTH |
00002 | Разрешение на запись для остальных |
S_IXOTH |
00001 | Разрешение на исполнение для остальных |
48
По крайней мере, три из этих блоков содержат данные, которые мы записали, другие для использования операционной системой при отслеживании размещения этих данных —