Марк Митчелл - Программирование для Linux. Профессиональный подход
Заметьте, что команда strace показала метки полей структуры, в которой хранятся аргументы. Эта структура заполняется в системном вызове: Linux помещает в поле sys имя операционной системы, а в поле node — имя компьютера. Функция uname() будет описана ниже, в разделе 8.15. "Функция uname()".
Системный вызов write() выводит полученные результаты на экран. Вспомните, что дескриптор 1 соответствует стандартному выходному потоку. Третий аргумент — это количество отображаемых символов. Функция возвращает число действительно записанных символов.
write(1, "myhostnamen", 11) = 11
Эта строка может отобразиться искаженной, поскольку вывод программы hostname смешивается с результатами работы команды strace. Если запускаемая программа создает слишком много выходных данных, лучше перенаправить вывод команды strace в файл с помощью опции -о имя_файла.
8.2. Функция access(): проверка прав доступа к файлу
Функция access() определяет, имеет ли вызывающий ее процесс право доступа к заданному файлу. Функция способна проверить любую комбинацию привилегий чтения, записи и выполнения, а также факт существования файла.
Функция access() принимает два аргумента: путь к проверяемому файлу и битовое объединение флагов R_OK, W_OK и X_OK, соответствующих правам чтения, записи и выполнения. При наличии у процесса всех необходимых привилегий функция возвращает 0. Если файл существует, а нужные привилегии на доступ к нему у процесса отсутствуют, возвращается -1 и в переменную errno записывается код ошибки EACCES (или EROFS, если проверяется право записи в файл, который расположен в файловой системе, смонтированной только для чтения).
Если второй аргумент равен F_OK, функция access() проверяет лишь факт существования файла. В случае обнаружения файла возвращается 0, иначе — -1 (в переменную errno помещается также код ошибки ENOENT). Когда один из каталогов на пути к файлу недоступен, в переменную errno будет помещён код EACCES.
Программа, показанная в листинге 8.1, с помощью функции access() проверяет существование файла и определяет, разрешен ли к нему доступ на чтение/запись. Имя файла задается в командной строке.
Листинг 8.1. (check-access.c) Проверка прав доступа к файлу#include <errno.h>
#include <stdio.h>
#include <unistd.h>
int main(int argc, char* argv[]) {
char* path = argv[1];
int rval;
/* Проверка существования файла. */
rval = access(path, F_OK);
if (rval == 0)
printf("%s existsn", path);
else {
if (errno == ENOENT)
printf("%s does not existn", path);
else if (errno == EACCES)
printf("%s is not accessiblen", path);
return 0;
}
/* Проверка права доступа. */
rval = access(path, R_OK);
if (rval == 0)
printf("%s is readablen", path);
else
printf("%s is not readable (access denied)n", path);
/* проверка права записи. */
rval = access(path, W_OK);
if (rval == 0)
printf("%s is writablen", path);
else if (errno == EACCES)
printf("%s is not writable (access denied)n", path);
else if (errno == EROFS)
printf("%s is not writable (read-only filesystem)n",
path);
return 0;
}
Вот как, к примеру, проверить права доступа к файлу README, расположенному на компакт-диске:
% ./check-access /mnt/cdrom/README
/mnt/cdrom/README exists
/mnt/cdrom/README is readable
/mnt/cdrom/README is not writable (read-only filesystem)
8.3. Функция fcntl(): блокировки и другие операции над файлами
Функция fcntl() — это точка доступа к нескольким особым операциям над файлами. Первым аргументом функции является дескриптор файла, вторым указывается код операции. Для некоторых операций требуется также дополнительный, третий аргумент. В этом разделе описана наиболее распространенная операция, выполняемая с помощью функции fcntl(): блокирование файлов.
Функция fcntl() позволяет программе поставить на файл блокировку чтения иди записи. Это напоминает применение исключающих семафоров, которые описывались в главе 5, "Взаимодействие процессов". Блокировка чтения ставится на файл, доступный для чтения. Соответственно блокировка записи ставится на файл, доступный для записи. Несколько процессов могут удерживать блокировку чтения одного и того же файла, но только одному процессу разрешено ставить блокировку записи. Файл не может быть одновременно заблокирован и для чтения, и для записи. Учтите, что наличие блокировки не мешает другим процессам открывать файл и осуществлять чтение/запись его данных, если только они сами не попытаются вызвать функцию fcntl().
Прежде чем ставить блокировку на файл, необходимо создать и обнулить структуру типа flock. В поле l_type должна быть записана константа F_RDLCK в случае блокировки чтения и константа F_WRLCK — в случае блокировки записи. Далее следует вызвать функцию fcntl(), передав ей дескриптор файла, код операции F_SETLCKW и указатель на структуру типа flock. Если аналогичная блокировка уже была поставлена другим процессом, функция fcntl() перейдет в режим ожидания, пока "мешающая" ей блокировка не будет снята.
В листинге 8.2 показана программа, которая открывает для записи указанный файл, а затем ставит на него блокировку записи. Программа ждет нажатия клавиши <Enter>, после чего снимает блокировку и закрывает файл.
Листинг 8.2. (lock-file.c) Установка блокировки записи с помощью функции fcntl()#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
int main(int argc, char* argv[]) {
char* file = argv[1];
int fd;
struct flock lock;
printf("opening %sn", file);
/* Открытие файла. */
fd = open(file, O_WRONLY);
printf("lockingn");
/* инициализация структуры flock. */
memset(&lock, 0, sizeof(lock));
lock.l_type = F_WRLCK;
/* Установка блокировки записи. */
fcntl(fd, F_SETLKW, &lock);
printf("locked; hit Enter to unlock... ");
/* Ожидание нажатия клавиши <Enter>. */
getchar();
printf("unlockingn");
/* Снятие блокировки. */
lock.l_type = F_UNLCK;
fcntl(fd, F_SETLKW, &lock);
close(fd);
return 0;
}
Скомпилируйте программу и запустите ее с каким-нибудь тестовым файлом, скажем, /tmp/test-file:
% cc -o lock-file lock-file.с
% touch /tmp/test-file
% ./lock-file /tmp/test-file
opening /tmp/test-file
locking
locked; hit Enter to unlock...
Теперь откройте другое окно и вызовите программу еще раз с тем же файлом:
% ./lock-file /tmp/test-file
opening /tmp/test-file
locking
Пытаясь поставить блокировку на файл, программа сама окажется заблокированной. Вернитесь в первое окно и нажмите <Enter>:
unlocking
В результате программа, запущенная во втором окне, немедленно продолжит свою работу. Если необходимо, чтобы функция fcntl() не переходила в режим ожидания в случае, когда блокировку поставить невозможно, задайте в качестве кода операции константу F_SETLCK, а не F_SETLKW. Если функция обнаружит, что запрашиваемый файл уже заблокирован, она немедленно вернет -1.
В Linux имеется системный вызов flock(), также реализующий операцию блокирования файла. Но у функции fcntl() есть большое преимущество: она работает с файловыми системами NFS[28] (при условии, что сервер NFS имеет относительно недавнюю версию и сконфигурирован правильно). Так что. имея доступ к двум компьютерам, которые монтируют одну и ту же файловую систему через NFS, можно повторить показанный выше пример на двух разных машинах.
8.4. Функции fsync() и fdatasync(): очистка дисковых буферов
В большинстве операционных систем при записи в файл данные не передаются на диск немедленно. Вместо этого операционная система помещает их в резидентный кэш-буфер с целью сокращения числа обращений к диску и повышения оперативности программы. Когда буфер заполнится или произойдет какое-нибудь другое событие (например, истечет определенный промежуток времени), система запишет содержимое буфера на диск в ходе одной непрерывной операции.
В Linux тоже поддерживается такой тип кэширования. Обычно он способствует существенному повышению производительности. Но он же делает ненадежными программы, зависящие от целостности дисковых данных. Если система внезапно выйдет из строя, например вследствие сбоя ядра или отключения питания, любые данные, находящиеся в памяти и еще не записанные на диск, будут потеряны.