Текст книги "Linux программирование в примерах"
Автор книги: Арнольд Роббинс
Жанры:
Программирование
,сообщить о нарушении
Текущая страница: 12 (всего у книги 55 страниц)
Удаление файла означает удаление элемента каталога для файла и уменьшение счетчика ссылок на файл, который содержится в индексе. Содержимое файла и дисковые блоки, в котором оно размешается, не освобождаются до тех пор, пока счетчик ссылок не достигнет нуля.
Системный вызов называется unlink()
:
#include
int unlink(const char *pathname);
В нашем обсуждении ссылок на файлы имя имеет смысл; этот вызов удаляет данную ссылку (элемент каталога) для файла. Она возвращает 0 в случае успеха и -1 при ошибке. Возможность удаления файла требует права записи лишь для каталога, а не для самого файла. Этот факт может сбивать с толку, особенно начинающих пользователей Linux/Unix. Однако, поскольку операция в каталоге одна, это имеет смысл; меняется именно содержимое каталога, а не содержимое файла[52]52
Конечно, меняются служебные данные файла (число ссылок), но это не влияет ни на какой другой атрибут файла, также, как не влияет на содержимое файла. Обновление счетчика ссылок на файл является единственной операцией над файлом, при которой не проверяются права доступа к файлу – Примеч. автора.
[Закрыть].
С самых первых дней Unix было возможно удалять открытые файлы. Просто вызовите unlink()
с именем файла после успешного вызова open()
или creat()
.
На первый взгляд, это кажется странным. Поскольку система освобождает блоки данных, когда счетчик ссылок на файл достигает нуля, возможно ли использовать открытый файл?
Ответ – да, вы можете продолжить использовать открытый файл обычным способом. Система знает, что файл открыт, поэтому она откладывает освобождение хранилища файла до тех пор, пока не будет закрыт последний дескриптор файла. Когда файл вообще больше не используется, память освобождается.
Эта операция также оказывается полезной: это простой способ для программы получить временный файл, который гарантированно будет и конфиденциальным, и автоматически освобождаемым по завершении использования.
/* Получение конфиденциального временного хранилища,
проверка ошибок для краткости опущена */
int fd;
mode_t mode = O_CREAT | O_EXCL | O_TRUNC | O_RDWR;
fd = open("/tmp/myfile", mode, 0000); /* Открыть файл */
unlink("/tmp/myfile"); /* Удалить его */
/* ... продолжить использование файла... *
close(fd); /* Закрыть файл, освободить память */
Недостатком такого подхода является то, что вышедшее из-под контроля приложение может заполнить систему открытыми, но анонимными файлами, в этом случае администраторам придется попытаться найти и завершить этот процесс. В прежние дни могли потребоваться перезагрузка и проверка целостности файловой системы; к счастью, на современных системах это требуется исключительно редко.
remove()
ISO С предоставляет для удаления файлов функцию remove()
; она предназначена в качестве обшей функции, годной для любой системы, поддерживающей ISO С, а не только для Unix и GNU/Linux:
#include
int remove(const char *pathname);
Хотя технически это не системный вызов, возвращаемое значение в том же стиле: 0 в случае успеха и -1 при ошибке, причем errno
содержит значение ошибки.
В GNU/Linux remove()
использует для удаления файлов системный вызов unlink()
, а для удаления каталогов – системный вызов rmdir()
(обсуждаемый далее в главе). (На более старых системах GNU/Linux, не использующих GLIBC, remove()
является псевдонимом для unlink()
; поэтому для каталогов завершается неудачей. Если у вас такая система, вам, возможно, следует ее обновить.)
Мы начали главу с обсуждения разделов, файловых систем и индексов. Мы также увидели, что элементы каталога связывают имена с номерами индексов. Поскольку элементы каталога не содержат другой информации, прямые ссылки ограничены файлами внутри одной и той же файловой системы. Это должно быть; нет способа отличить индекс 2341 одной файловой системы от индекса 2341 другой файловой системы. Вот что случится, если мы попытаемся это сделать:
$ mount /* Показать использующиеся файловые системы */
/dev/hda2 on / type ext3 (rw)
/dev/hda5 on /d type ext3 (rw)
...
$ ls -li /tmp/message /* Предыдущий пример был в файловой системе / */
228786 -rw-r–r– 2 arnold devel 19 May 4 15:51 /tmp/message
$ cat /tmp/message
Hi, how ya doin' ?
$ /bin/pwd /* Текущий каталог в другой файловой системе */
/d/home/arnold
$ ln /tmp/message . /* Попытка создать ссылку */
ln: creating hard link './message' to '/tmp/message': Invalid cross-device link
На больших системах часто много разделов как на физически подключенных локальных дисках, так и на удаленно смонтированных файловых системах. Ограничение прямых ссылок одной и той же файловой системой неудобно, например, если некоторые файлы или каталоги должны быть перемешены в новое место, но старое программное обеспечение использует жестко вшитые в код имена файлов для старых местоположений.
Чтобы обойти это ограничение, 4.2 BSD ввело символические ссылки (symbolic links, называемые также soft links). Символическая ссылка является особой разновидностью файла (также, как особой разновидностью файла является каталог). Содержимое этого файла представляет собой путь к файлу, на который данный файл «указывает». Все современные Unix-системы, включая Linux, предусматривают символические ссылки; конечно, они теперь являются частью POSIX.
Символические ссылки могут ссылаться на любой файл в системе. Они могут также ссылаться на каталоги. Это упрощает перемещение каталогов с места на место, когда на старом месте остается символическая ссылка, указывающая на новое положение.
При обработке имени файла система отмечает символические ссылки и осуществляет требуемые действия в файле или каталоге, который указан. Символические ссылки создаются с помощью опции -s
команды ln
:
$ /bin/pwd /* Где мы находимся */
/d/home/arnold /* В другой файловой системе */
$ ln -s /tmp/message ./hello /* Создать символическую ссылку */
$ cat hello /* Использовать ее */
Hi, how ya doin' ?
$ ls -l hello /* Отобразить информацию о ней */
lrwxrwxrwx 1 arnold devel 12 May 4 16:41 hello -> /tmp/message
Файл, на который указывает ссылка, необязательно должен существовать. Система обнаруживает это во время исполнения и действует соответствующим образом:
$ rm /tmp/message /* Удалить указываемый файл */
$ cat ./hello /* Попытка использования через символическую ссылку */
cat: ./hello: No such file or directory
$ echo hi again > hello /* Создать новое содержание файла */
$ ls -l /tmp/message /* Показать информацию об указываемом файле */
-rw-r–r– 1 arnold devel 9 May 4 16:45 /tmp/message
$ cat /tmp/message /* ...и содержание */
hi again
Символические ссылки создаются с помощью системного вызова symlink()
:
#include
int symlink(const char *oldpath, const char *newpath);
Аргумент oldpath
содержит указываемый файл или каталог, a newpath
является именем создаваемой символической ссылки. При успехе возвращается 0, а при ошибке (-1), возможные значения errno
см. в справочной странице для symlink(2). У символических ссылок есть свои недостатки:
• Они занимают лишнее дисковое пространство, требуя отдельного индекса и блока данных. Прямые ссылки занимают лишь элемент каталога.
• Они добавляют лишние накладные расходы. Ядро должно больше работать для разрешения имени пути, содержащего символические ссылки.
• Они могут создать «циклы». Рассмотрите следующее:
$ rm -f a b /* Убедиться, что 'a' и 'b' не существуют */
$ ln -s a b /* Создать ссылку старого файла 'a' на новый 'b' */
$ ln -s b a /* Создать ссылку старого файла 'b' на новый 'a' */
$ cat а /* Что случилось? */
cat: a: Too many levels of symbolic links
Ядро должно быть способно определить такой случай и выдать сообщение об ошибке.
• Они легко обрываются. Если переместить указываемый файл в другое место или переименовать его, символическая ссылка больше не действительна. С прямой ссылкой такого случиться не может.
5.2. Создание и удаление каталоговСоздание и удаление каталогов просто. Двумя системными вызовами, что неудивительно, являются mkdir()
и rmdir()
соответственно:
#include
#include
int mkdir(const char *pathname, mode_t mode);
#include
int rmdir(const char *pathname);
Оба возвращают 0 при успехе и (-1) при ошибке, с соответствующим errno
. Аргумент mode
для mkdir()
представляет права доступа, которые должны быть использованы для каталога. Он полностью идентичен аргументам mode
для creat()
и open()
, обсуждавшимся в разделе 4.6 «Создание файлов».
Обе функции обрабатывают '.
' и '..
' в создаваемом или удаляемом каталоге. Перед удалением каталог должен быть пуст; если это не так, errno
устанавливается в ENOTEMPTY
. (В данном случае, «пуст» означает, что каталог содержит только '.
' и '..
'.)
Новым каталогам, как и всем файлам, присваивается идентификационный номер группы. К сожалению, его работа запутана. Мы отложим обсуждение до раздела 11.5.1 «Группа по умолчанию для новых файлов и каталогов».
Обе функции работают на одном уровне каталога за раз. Если /somedir
существует, a /somedir/sub1
нет, 'mkdir("/somedir/sub1/sub2")
' завершится неудачей. Каждый компонент в длинном пути должен создаваться отдельно (в соответствии с опцией -р mkdir
, см. mkdir(1)).
Также, если pathname
завершается символом '/
', на некоторых системах mkdir()
и rmdir()
потерпят неудачу, а на других нет. Следующая программа, ch05-trymkdir.с
, демонстрирует оба аспекта.
1 /* ch05-trymkdir.c – Демонстрирует поведение mkdir().
2 Любезность Nelson H.F. Beebe. */
3
4 #include
5 #include
6 #include
7
8 #if !defined(EXIT_SUCCESS)
9 #define EXIT_SUCCESS 0
10 #endif
11
12 void do_test(const char *path)
13 {
14 int retcode;
15
16 errno = 0;
17 retcode = mkdir(path, 0755);
18 printf("mkdir("%s") returns %d: errno = %d [%s)n",
19 path, retcode, errno, strerror(errno));
20 }
21
22 int main(void)
23 {
24 do_test("/tmp/t1/t2/t3/t4"); /*Попытка создания в подкаталоге*/
25 do_test("/tmp/t1/t2/t3");
26 do_test("/tmp/t1/t2");
27 do_test("/tmp/t1");
28
29 do_test("/tmp/u1"); /* Создать подкаталоги */
30 do_test("/tmp/u1/u2");
31 do_test("/tmp/u1/u2/u3");
32 do_test("/tmp/u1/u2/u3/u4");
33
34 do_test("/tmp/v1/"); /* Как обрабатывается завершающий '/'? */
35 do_test("/tmp/v1/v2/");
36 do_test("/tmp/v1/v2/v3/");
37 do_test("/tmp/v1/v2/v3/v4/");
38
39 return(EXIT_SUCCESS);
40 }
Вот результаты для GNU/Linux:
$ ch05-trymkdir
mkdir("/tmp/t1/t2/t3/t4") returns -1: errno = 2 [No such file or directory)
mkdir("/tmp/t1/t2/t3") returns -1: errno = 2 [No such file or directory)
mkdir("/tmp/t1/t2") returns -1: errno = 2 [No such file or directory]
mkdir("/tmp/t1") returns 0: errno = 0 [Success]
mkdir("/tmp/u1") returns 0: errno = 0 [Success]
mkdir("/tmp/u1/u2") returns 0: errno = 0 [Success]
mkdir("/tmp/u1/u2/u3") returns 0: errno = 0 [Success]
mkdir("/tmp/u1/u2/u3/u4") returns 0: errno = 0 [Success]
mkdir("/tmp/v1/") returns 0: errno = 0 [Success]
mkdir("/tmp/v1/v2/") returns 0: errno = 0 (Success]
mkdir("/tmp/v1/v2/v3/") returns 0: errno = 0 [Success]
mkdir("/tmp/v1/v2/v3/v4/") returns 0: errno = 0 [Success]
Обратите внимание, как GNU/Linux принимает завершающий слеш. Не все системы так делают.
5.3. Чтение каталоговВ оригинальных системах Unix чтение содержимого каталогов было просто. Программа открывала каталог с помощью open()
и непосредственно читала двоичные структуры struct direct
, по 16 байтов за раз. Следующий фрагмент кода из программы V7 rmdir
[53]53
См /usr/src/cmd/rmdir
с в дистрибутиве V7 – Примеч. автора.
[Закрыть], строки 60–74. Он показывает проверку на пустоту каталога.
60 if ((fd = open(name, 0)) < 0) {
61 fprintf(stderr, "rmdir: %s unreadablen", name);
62 ++Errors;
63 return;
64 }
65 while (read(fd, (char*)&dir, sizeof dir) == sizeof dir) {
66 if (dir.d_ino == 0) continue;
67 if (!strcmp(dir.d_name, ".") || !strcmp(dir.d_name, ".."))
68 continue;
69 fprintf(stderr, "rmdir: %s not emptyn", name);
70 ++Errors;
71 close(fd);
72 return;
73 }
74 close(fd);
В строке 60 каталог открывается для чтения (второй аргумент равен 0, что означает O_RDONLY
). В строке 65 читается struct direct
. В строке 66 проверяется, не является ли элемент каталога пустым, т. е. с номером индекса 0. Строки 67 и 68 проверяют на наличие '.
' и '..
'. По достижении строки 69 мы знаем, что было встречено какое-то другое имя файла, следовательно, этот каталог не пустой.
(Тест '!strcmp(s1, s2)
' является более короткой формой 'strcmp(s1, s2) == 0
', т.е. проверкой совпадения строк. Стоит заметить, что мы рассматриваем '!strcmp(s1, s2)
' как плохой стиль. Как сказал однажды Генри Спенсер (Henry Spencer), «strcmp()
это не boolean!».)
Когда 4.2 BSD представило новый формат файловой системы, который допускал длинные имена файлов и обеспечивал лучшую производительность, были также представлены несколько новых функций для абстрагирования чтения каталогов. Этот набор функций можно использовать независимо от того, какова лежащая в основе файловая система и как организованы каталоги. Основная ее часть стандартизована POSIX, а программы, использующие ее, переносимы между системами GNU/Linux и Unix.
Элементы каталогов представлены struct dirent
(не то же самое, что V7 struct direct
!):
struct dirent {
...
ino_t d_ino; /* расширение XSI – см. текст */
char d_name[...]; /* О размере этого массива см. в тексте */
...
};
Для переносимости POSIX указывает лишь поле d_name
, которое является завершающимся нулем массивом байтов, представляющим часть элемента каталога с именем файла. Размер d_name
стандартом не указывается, кроме того, что там перед завершающим нулем может быть не более NAME_MAX
байтов. (NAME_MAX
определен в
.) Расширение XSI POSIX предусматривает поле номера индекса d_ino
.
На практике, поскольку имена файлов могут быть различной длины, a NAME_MAX
обычно довольно велико (подобно 255), struct dirent
содержит дополнительные члены, которые помогают вести на диске учет элементов каталогов с переменными длинами. Эти дополнительные члены не существенны для обычного кода.
Следующие функции предоставляют интерфейс чтения каталогов:
#include
#include
DIR *opendir(const char *name); /* Открыть каталог для чтения */
struct dirent *readdir(DIR *dir); /* Вернуть struct dirent за раз */
int closedir(DIR *dir); /* Закрыть открытый каталог */
void rewinddir(DIR *dirp); /* Вернуться в начало каталога */
Тип DIR
является аналогом типа FILE
в
. Это непрозрачный тип, что означает, что код приложения не должен знать, что находится внутри него; его содержимое предназначено для использования другими процедурами каталогов. Если opendir()
возвращает NULL
, именованный каталог не может быть открыт для чтения, а errno содержит код ошибки.
Открыв переменную DIR*
, можно использовать ее для получения указателя на struct dirent
, представляющего следующий элемент каталога. readdir()
возвращает NULL
, если достигнут конец каталога[54]54
То есть прочитаны все элементы каталога – Примеч. науч. ред.
[Закрыть] или произошла ошибка.
Наконец, closedir()
является аналогичной функции fclose()
в
; она закрывает открытую переменную DIR*
. Чтобы начать с начала каталога, можно использовать функцию rewinddir()
.
Имея в распоряжении (или по крайней мере в библиотеке С) эти функции, мы можем написать небольшую программу catdir
, которая «отображает» содержимое каталога. Такая программа представлена в ch05-catdir.с
:
1 /* ch05-catdir.с – Демонстрация opendir(), readdir(), closedir(). */
2
3 #include
4 #include
5 #include
6 #include
7
8 char *myname;
9 int process(char *dir);
10
11 /* main – перечисление аргументов каталога */
12
13 int main(int argc, char **argv)
14 {
15 int i;
16 int errs = 0;
17
18 myname = argv[0];
19
20 if (argc == 1)
21 errs = process("."); /* по умолчанию текущий каталог */
22 else
23 for (i = 1; i < argc; i++)
24 errs += process(argv[i]);
25
26 return (errs != 0);
27 }
Эта программа вполне подобна ch04-cat.c
(см. раздел 4.2 «Представление базовой структуры программы»); функция main()
почти идентична. Главное различие в том, что по умолчанию используется текущий каталог, если нет аргументов (строки 20–21).
29 /*
30 * process – сделать что-то с каталогом, в данном случае,
31 * вывести пары индекс/имя в стандартный вывод.
32 * Возвращает 0, если все OK, иначе 1.
33 */
34
35 int
36 process(char *dir)
37 {
38 DIR *dp;
39 struct dirent *ent;
40
41 if ((dp = opendir(dir)) == NULL) {
42 fprintf(stderr, "%s: %s: cannot open for reading: %sn",
43 myname, dir, strerror(errno));
44 return 1;
45 }
46
47 errno = 0;
48 while ((ent = readdir(dp)) != NULL)
49 printf("%8ld %sn", ent->d_ino, ent->d_name);
50
51 if (errno != 0) {
52 fprintf(stderr, "%s: %s: reading directory entries: %sn",
53 myname, dir, strerror(errno));
54 return 1;
55 }
56
57 if (closedir(dp) != 0) {
58 fprintf(stderr, "%s: %s: closedir: %sn",
59 myname, dir, strerror(errno));
60 return 1;
61 }
62
63 return 0;
64 }
Функция process()
делает всю работу и большую часть кода проверки ошибок. Основой функции являются строки 48 и 49:
while ((ent = readdir(dp)) != NULL)
printf("%8ld %sn", ent->d_ino, ent->d_name);
Этот цикл читает элементы каталога, по одной за раз, до тех пор, пока readdir()
не возвратит NULL
. Тело цикла отображает для каждого элемента номер индекса и имя файла. Вот что происходит при запуске программы:
$ ch05-catdir /* По умолчанию текущий каталог */
639063 .
639062 ..
639064 proposal.txt
639012 lightsabers.url
688470 code
638976 progex.texi
639305 texinfo.tex
639007 15-processes.texi
639011 00-preface.texi
639020 18-tty.texi
638980 Makefile
639239 19-i18n.texi
...
Вывод никаким образом не сортируется; он представляет линейное содержимое каталога. (Как сортировать содержимое каталога мы опишем в разделе 6.2 «Функции сортировки и поиска»).
Есть несколько соображений по переносимости. Во-первых, не следует предполагать, что двумя первыми элементами, возвращаемыми readdir()
, всегда будут '.
' и '..
'. Многие файловые системы используют организацию каталогов, которые отличаются от первоначального дизайна Unix, и '.
' и '..
' могут быть в середине каталога или даже вовсе не присутствовать[55]55
В системах GNU/Linux могут монтироваться файловые системы многих операционных систем, не относящихся к Unix. Во многих коммерческих системах Unix также можно смонтировать файловые системы MS-DOS. В таких случаях предположения относительно файловых систем Unix неприменимы – Примеч. автора.
[Закрыть].
Во-вторых, стандарт POSIX ничего не говорит о возможных значениях d_info
. Он говорит, что возвращенные структуры представляют элементы каталогов для файлов; это предполагает, что readdir()
не возвращает пустые элементы, поэтому реализация GNU/Linux readdir()
не беспокоится с возвратом элементов, когда 'd_ino == 0
'; она переходит к следующему действительному элементу.
Поэтому по крайней мере на системах GNU/Linux и Unix маловероятно, что d_ino
когда-нибудь будет равен нулю. Однако, лучше по возможности вообще избегать использования этого поля.
Наконец, некоторые системы используют d_fileno
вместо d_ino
в struct dirent
. Знайте об этом, когда нужно перенести на такие системы код, читающий каталоги.
Косвенные системные вызовы
«Не пробуйте это дома, дети!»
– М-р Wizard -
Многие системные вызовы, такие, как
open()
,read()
иwrite()
, предназначены для вызова непосредственно из кода пользователя: другими словами, из кода, который пишете вы как разработчик GNU/Linux.Однако, другие системные вызовы существуют лишь для того, чтобы дать возможность реализовать стандартные библиотечные функции более высокого уровня, и никогда не должны вызываться непосредственно. Одним из таких системных вызовов является GNU/Linux
getdents()
; он читает несколько элементов каталога в буфер, предоставленный вызывающим – в данном случае, кодом реализацииreaddir()
. Затем кодreaddir()
возвращает действительные элементы каталога, по одному за раз, пополняя при необходимости буфер.Эти системные вызовы только-для-библиотечного-использования можно отличить от вызовов для-использования-пользователем по их представлению в странице справки. Например, из getdents(2).
ИМЯ
getdents – получить элементы каталога
ОПИСАНИЕ
#include
#include
#include
#include
_syscall3(int, getdents, uint, fd, struct dirent*,
dirp, uint, count);
int getdents(unsigned int fd, struct dirent *dirp,
unsigned int count);
Любой системный вызов, использующий макрос
_syscallX()
, не должен вызываться кодом приложения. (Дополнительную информацию об этих вызовах можно найти в справочной странице для intro(2); вам следует прочесть эту справочную страницу, если вы этого еще не сделали.)В случае
getdents()
на многих других системах Unix есть сходный системный вызов; иногда с тем же именем, иногда с другим. Поэтому попытка использования этих вызовов привела бы в любом случае лишь к большому беспорядку с переносимостью; гораздо лучше во всех случаях использоватьreaddir()
, интерфейс которого хорошо определен, стандартизован и переносим.