Linux программирование в примерах - Арнольд Роббинс
Шрифт:
Интервал:
Закладка:
5.1.3.1. Программа GNU link
Программа ln сложная и большая. Однако, GNU Coreutils содержит несложную программу link, которая просто вызывает link() со своими двумя аргументами. Следующий пример показывает код из файла link.с, не относящиеся к делу части удалены. Номера строк относятся к действительному файлу.
20 /* Обзор реализации:
21
22 Просто вызывает системную функцию 'link' */
23
/* ...Операторы #include для краткости опущены... */
34
35 /* Официальное имя этой программы (например, нет префикса 'g'). */
36 #define PROGRAM_NAME "link"
37
38 #define AUTHORS "Michael Stone"
39
40 /* Имя, под которым была запущена данная программа. */
41 char *program_name;
42
43 void
44 usage(int status)
45 {
/* ... для краткости опущено... */
62 }
63
64 int
65 main(int argc, char **argv)
66 {
67 program_name = argv[0];
68 setlocale(LC_ALL, "");
69 bindtextdomain(PACKAGE, LOCALEDIR);
70 textdomain(PACKAGE);
71
72 atexit(close_stdout);
73
74 parse_long_options(argc, argv, PROGRAM_NAME, GNU_PACKAGE,
75 VERSION, AUTHORS, usage);
76
77 /* Вышеприведенное обрабатывает --help и --version.
78 Поскольку других вызовов getopt нет, обработать здесь '--'. */
79 if (1 < argc && STREQ(argv[1], "--"))
80 {
81 --argc;
82 ++argv;
83 }
84
85 if (argc < 3)
86 {
87 error(0, 0, _("too few arguments"));
88 usage(EXIT_FAILURE);
89 }
90
91 if (3 < argc)
92 {
93 error(0, 0, _("too many arguments"));
94 usage(EXIT_FAILURE);
95 }
96
97 if (link(argv[1], argv[2]) != 0)
98 error(EXIT_FAILURE, errno, _("cannot create link %s to %s"),
99 quote_n(0, argv[2]), quote_n(1, argv[1]));
100
101 exit(EXIT_SUCCESS);
102 }
Строки 67–75 являются типичным шаблоном Coreutils, устанавливающими интернациональные настройки, выход по завершении и анализ аргументов. Строки 79–95 гарантируют, что link вызывается лишь с двумя аргументами. Сам системный вызов link() осуществляется в строке 97 (Функция quote_n() обеспечивает отображение аргументов в стиле, подходящем для текущей локали; подробности сейчас несущественны.)
5.1.3.2. Точка и точка-точка
Завершая обсуждение ссылок, давайте взглянем на то, как обрабатываются специальные имена '.' и '..'. На самом деле они просто являются прямыми ссылками. В первом случае '.' является прямой ссылкой на каталог, содержащий ее, а '..' — прямой ссылкой на родительский каталог. Операционная система создает для вас эти ссылки; как упоминалось ранее, код уровня пользователя не может создать прямую ссылку на каталог. Этот пример иллюстрирует ссылки:
$ pwd /* Отобразить текущий каталог */
/tmp
$ ls -ldi /tmp /* Показать номер его индекса */
225345 drwxrwxrwt 14 root root 4096 May 4 16:15 /tmp
$ mkdir x /* Создать новый каталог */
$ ls -ldi x /* И показать номер его индекса */
52794 drwxr-xr-x 2 arnold devel 4096 May 4 16:27 x
$ ls -ldi x/. x/.. /* Показать номера индексов . И .. */
52794 drwxr-xr-x 2 arnold devel 4096 May 4 16:27 x/.
225345 drwxrwxrwt 15 root root 4096 May 4 16:27 x/..
Родительский каталог корневого каталога (/..) является особым случаем; мы отложим его обсуждение до главы 8 «Файловые системы и обход каталогов».
5.1.4. Переименование файлов
При данном способе отображения элементами каталога имен на номера индексов, переименование файла концептуально очень просто:
1. Если новое имя файла обозначает существующий файл, сначала удалить этот файл.
2. Создать новую ссылку на файл через новое имя.
3. Удалить старое имя (ссылку) для файла. (Удаление имен обсуждается в следующем разделе.)
Ранние версии команды mv работали таким способом. Однако, при таком способе переименование файла не является атомарным; т.е. оно не осуществляется посредством одной непрерываемой операции. И на сильно загруженной системе злонамеренный пользователь мог бы воспользоваться условиями состояния гонки[51], разрушая операцию переименования и подменяя оригинальный файл другим.
По этой причине 4.2 BSD ввело системный вызов rename():
#include <stdio.h> /* ISO С */
int rename(const char *oldpath, const char *newpath);
На системах Linux операция переименования является атомарной; справочная страница утверждает:
Если newpath уже существует, он будет атомарно замещен .., таким образом, что при попытке другого процесса получить доступ к newpath он никогда не обнаружит его отсутствующим.
Если newpath существует, но по какой-либо причине операция завершается неудачей, rename гарантирует, что экземпляр newpath останется на месте. Однако, в ходе переписывания возможно будет окно, в котором как oldpath, так и newpath ссылаются на переименовываемый файл.
Как и в случае с другими системными вызовами, возвращенный 0 означает успех, а (-1) означает ошибку.
5.1.5. Удаление файла
Удаление файла означает удаление элемента каталога для файла и уменьшение счетчика ссылок на файл, который содержится в индексе. Содержимое файла и дисковые блоки, в котором оно размешается, не освобождаются до тех пор, пока счетчик ссылок не достигнет нуля.
Системный вызов называется unlink():
#include <unistd.h> /* POSIX */
int unlink(const char *pathname);
В нашем обсуждении ссылок на файлы имя имеет смысл; этот вызов удаляет данную ссылку (элемент каталога) для файла. Она возвращает 0 в случае успеха и -1 при ошибке. Возможность удаления файла требует права записи лишь для каталога, а не для самого файла. Этот факт может сбивать с толку, особенно начинающих пользователей Linux/Unix. Однако, поскольку операция в каталоге одна, это имеет смысл; меняется именно содержимое каталога, а не содержимое файла[52].
5.1.5.1. Удаление открытых файлов
С самых первых дней 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); /* Закрыть файл, освободить память */
Недостатком такого подхода является то, что вышедшее из-под контроля приложение может заполнить систему открытыми, но анонимными файлами, в этом случае администраторам придется попытаться найти и завершить этот процесс. В прежние дни могли потребоваться перезагрузка и проверка целостности файловой системы; к счастью, на современных системах это требуется исключительно редко.
5.1.5.2. Использование ISO С: remove()
ISO С предоставляет для удаления файлов функцию remove(); она предназначена в качестве обшей функции, годной для любой системы, поддерживающей ISO С, а не только для Unix и GNU/Linux:
#include <stdio.h> /* ISO С */
int remove(const char *pathname);
Хотя технически это не системный вызов, возвращаемое значение в том же стиле: 0 в случае успеха и -1 при ошибке, причем errno содержит значение ошибки.
В GNU/Linux remove() использует для удаления файлов системный вызов unlink(), а для удаления каталогов — системный вызов rmdir() (обсуждаемый далее в главе). (На более старых системах GNU/Linux, не использующих GLIBC, remove() является псевдонимом для unlink(); поэтому для каталогов завершается неудачей. Если у вас такая система, вам, возможно, следует ее обновить.)