
Текст книги "Linux программирование в примерах"
Автор книги: Арнольд Роббинс
Жанры:
Программирование
,сообщить о нарушении
Текущая страница: 14 (всего у книги 55 страниц)
cat
В разделе 4.4.4 «Пример: Unix cat» мы обещали вернуться к программе V7 cat
, чтобы посмотреть, как она использует системный вызов stat()
. Первая группа строк, использовавшая ее, была такой:
31 fstat(fileno(stdout), &statb);
32 statb.st_mode &= S_IFMT;
33 if (statb.st_mode != S_IFCHR && statb.st_mode != S_IFBLK) {
34 dev = statb.st_dev;
35 ino = statb.st_ino;
36 }
Этот код теперь должен иметь смысл. В строке 31 вызывается fstat()
для стандартного вывода, чтобы заполнить структуру statb
. Строка 32 отбрасывает всю информацию в statb.st_mode
за исключением типа файла, используя логическое AND с маской S_IFMT
. Строка 33 проверяет, что используемый для стандартного вывода файл не является файлом устройства. В таком случае программа сохраняет номера устройства и индекса в dev
и ino
. Эти значения затем проверяются для каждого входного файла в строках 50–56.
50 fstat(fileno(fi), &statb);
51 if (statb.st_dev == dev && statb.st_ino == ino) {
52 fprintf(stderr, "cat: input %s is outputn",
53 ffig ? "-" : *argv);
54 fclose(fi);
55 continue;
56 }
Если значения st_dev
и st_ino
входного файла совпадают с соответствующими значениями выходного файла, cat
выдает сообщение и продолжает со следующего файла, указанного в командной строке.
Проверка сделана безусловно, хотя dev
и ino
устанавливаются, лишь если вывод не является файлом устройства. Это срабатывает нормально из-за того, как эти переменные объявлены:
int dev, ino = -1;
Поскольку ino
инициализирован значением (-1), ни один действительный номер индекса не будет ему соответствовать[60]60
Это утверждение было верно для V7, на современных системах больше нет таких гарантий – Примеч. автора.
[Закрыть]. То, что dev
не инициализирован так, является небрежным, но не представляет проблемы, поскольку тест в строке 51 требует, чтобы были равными значения как устройства, так и индекса. (Хороший компилятор выдаст предупреждение, что dev
используется без инициализации: 'gcc -Wall
' сделает это.)
Обратите также внимание, что ни один вызов fstat()
не проверяется на ошибки. Это также небрежность, хотя не такая большая, маловероятно, что fstat()
завершится неудачей с действительным дескриптором файла
Проверка того, что входной файл не равен выходному файлу, осуществляется лишь для файлов, не являющихся устройствами. Это дает возможность использовать cat
для копирования ввода из файлов устройств в самих себя, как в случае с терминалами:
$ tty /* Вывести имя устройства текущего терминала */
/dev/pts/3
$ cat /dev/pts/3 > /dev/pts/3 /* Копировать ввод от клавиатуры на экран */
this is a line of text /* Набираемое в строке */
this is a line of text /* cat это повторяет */
В общем, символические ссылки ведут себя подобно прямым ссылкам; файловые операции, такие, как open()
и stat()
, применяются к указываемому файлу вместо самой символической ссылки. Однако, бывают моменты, когда в самом деле необходимо работать с символической ссылкой вместо файла, на которую она указывает.
По этой причине существует системный вызов lstat()
. Он действует точно также, как stat()
, но если проверяемый файл окажется символической ссылкой, возвращаемые сведения относятся к символической ссылке, а не к указываемому файлу. А именно:
• S_ISLNK(sbuf.st_mode)
будет true
.
• sbuf.st_size
содержит число байтов в имени указываемого файла.
Мы уже видели, что системный вызов symlink()
создает символическую ссылку. Но если дана существующая символическая ссылка, как можно получить имя файла, на которую она указывает? (Очевидно, ls
может получить это имя; поэтому мы должны быть способны это сделать.)
Открывание ссылки с помощью open()
для чтения ее с использованием read()
не будет работать, open()
следует по ссылке на указываемый файл. Таким образом, символические ссылки сделали необходимым дополнительный системный вызов, который называется readlink()
:
#include
int readlink(const char *path, char *buf, size_t bufsiz);
readlink()
помещает содержимое символической ссылки, на имя которой указывает path
, в буфер, на который указывает buf
. Копируется не более bufsiz
символов. Возвращаемое значение равно числу символов, помещенных в buf
, либо -1, если возникла ошибка, readlink()
не вставляет завершающий нулевой байт.
Обратите внимание, что если буфер, переданный readlink()
, слишком маленький, информация будет потеряна; полное имя указываемого файла будет недоступно. Чтобы использовать readlink()
должным образом, вы должны делать следующее:
1. Используйте lstat()
, чтобы убедиться, что это символическая ссылка.
2. Убедитесь, что ваш буфер для содержимого символической ссылки составляет по крайней мере 'sbuf.st_size + 1
' байтов; '+ 1
' нужно для завершающего нулевого байта, чтобы сделать буфер годной к употреблению строкой С.
3. Вызовите readlink()
. Не мешает проверить, что возвращенное значение равно sbuf.st_size
.
4. Добавьте '' к байту после содержимого ссылки, чтобы превратить его в строку С. Код для всего этого мог бы выглядеть примерно так:
/* Проверка ошибок для краткости опущена */
int count;
char linkfile[PATH_MAX], realfile[PATH_MAX]; /* PATH_MAX в
strut stat sbuf;
/* ...поместить в linkfile путь к нужной символической ссылке... */
lstat(linkfile, &sbuf); /* Получить сведения от stat */
if (!S_ISLNK(sbuf.st_mode)) /* Проверить, что это ссылка */
/* не символическая ссылка, обработать это */
if (sbuf.st_size + 1 > PATH_МАХ) /* Проверить размер буфера */
/* обработать проблемы с размером буфера */
count = readlink(linkfile, realfile, PATH_MAX);
/* Прочесть ссылку */
if (count != sbuf.st_size)
/* происходит что-то странное, обработать это */
realfile(count) = ''; /* Составить строку С */
Данный пример для простоты представления использует буферы фиксированного размера. Реальный код мог бы использовать для выделения буфера нужного размера malloc()
, поскольку массивы фиксированного размера могли бы оказаться слишком маленькими. Файл lib/xreadlink.c
в GNU Coreutils делает именно это. Он читает содержимое символической ссылки в память, выделенную malloc()
. Мы покажем здесь саму функцию, большая часть файла представляет собой стереотипные определения. Номера строк относятся к началу файла:
55 /* Вызвать readlink для получения значения ссылки FILENAME.
56 Вернуть указатель на завершенную NUL строку в выделенной malloc памяти.
57 При ошибке readlink вернуть NULL (использовать errno для диагноза).
58 При ошибке realloc или если значение ссылки больше SIZE_MAX,
59 выдать диагностику и выйти. */
60
61 char*
62 xreadlink(char const* filename)
63 {
64 /* Начальный размер буфера для ссылки. Степень 2 обнаруживает
65 арифметическое переполнение раньше, но не рекомендуется. */
66 size_t buf_size = 128;
67
68 while(1)
69 {
70 char *buffer = xmalloc(buf_size);
71 ssize_t link_length = readlink(filename, buffer, buf_size);
72
73 if (link_length < 0)
74 {
75 int saved_errno = errno;
76 free(buffer);
77 errno = saved_errno;
78 return NULL;
79 }
80
81 if ((size_t)link_length < buf_size)
82 {
83 buffer[link_length] = 0;
84 return buffer;
85 }
86
87 free(buffer);
88 buf_size *= 2;
89 if (SSIZE_MAX < buf_size || (SIZE_MAX / 2 < SSIZE_MAX && buf_size == 0))
90 xalloc_die();
91 }
92 }
Тело функции состоит из бесконечного цикла (строки 68–91), разрываемого в строке 84, которая возвращает выделенный буфер. Цикл начинается выделением первоначального буфера (строка 70) и чтения ссылки (строка 71). Строки 73–79 обрабатывают случай ошибки, сохраняя и восстанавливая errno таким образом, что она может корректно использоваться вызывающим кодом.
Строки 81–85 обрабатывают случай «успеха», при котором размер содержимого ссылки меньше размера буфера. В этом случае добавляется завершающий ноль (строка 83), а затем буфер возвращается, прерывая бесконечный цикл. Это гарантирует, что в буфер помещено все содержимое ссылки, поскольку у readlink()
нет возможности сообщить о «недостаточном размере буфера».
Строки 87–88 освобождают буфер и удваивают размер буфера для следующей попытки в начале цикла. Строки 89–90 обрабатывают случай, при котором размер ссылки слишком велик: buf_size
больше, чем SSIZE_MAX
, или SSIZE_MAX
больше, чем значение, которое может быть представлено в знаковом целом того же размера, который использовался для хранения SIZE_MAX
, и buf_size
обернулся в ноль. (Это маловероятные условия, но странные вещи все же случаются.) Если одно из этих условий верно, программа завершается с сообщением об ошибке. В противном случае функция возвращается в начало цикла, чтобы сделать еще одну попытку выделить буфер и прочесть ссылку.
Некоторое дополнительное разъяснение: условие 'SIZE_MAX / 2 < SSIZE_MAX
' верно лишь на системах, в которых 'SIZE_MAX < 2 * SSIZE_MAX
'; мы не знаем таких, но лишь на таких системах buf_size
может обернуться в ноль. Поскольку на практике это условие не может быть истинным, компилятор может оптимизировать все выражение, включив следующую проверку 'buf_size == 0
'. После прочтения этого кода вы можете спросить: «Почему не использовать lstat()
для получения размера символической ссылки, не выделить буфер нужного размера с помощью malloc()
, и все?» На это есть несколько причин.[61]61
Спасибо Джиму Мейерингу (Jim Meyering) за объяснение проблем – Примеч. автора.
[Закрыть]
• lstat()
является системным вызовом – лучше избежать накладных расходов по его вызову, поскольку содержимое большинства символических ссылок поместится в первоначальный размер буфера в 128.
• Вызов lstat()
создает условие состязания: ссылка может измениться между исполнением lstat()
и readlink()
, в любом случае вынуждая повторение.
• Некоторые системы не заполняют должным образом член st_size
для символической ссылки. (Печально, но верно.) Сходным образом, как мы увидим в разделе 8.4.2 «Получение текущего каталога: getcwd()
», Linux в /proc
предоставляет специальные символические ссылки, у которых st_size
равен нулю, но для которых readlink()
возвращает действительное содержимое.
Наконец, буфер не слишком большой, xreadlink()
использует free()
и malloc()
с большим размером вместо realloc()
, чтобы избежать бесполезного копирования, которое делает realloc()
. (Поэтому комментарий в строке 58 устарел, поскольку realloc()
не используется; это исправлено в версии Coreutils после 5.0.)
Несколько других системных вызовов дают вам возможность изменять другие относящиеся к файлу сведения: в частности, владельца и группу файла, права доступа к файлу и времена доступа и изменения файла.
chown()
, fchown()
и lchown()
Владелец и группа файла изменяются с помощью трех сходных системных вызовов.
#include
#include
int chown(const char *path, uid_t owner, gid_t group);
int fchown(int fd, uid_t owner, gid_t group);
int lchown(const char *path, uid_t owner, gid_t group);
chown()
работает с аргументом имени файла, fchown()
работает с открытым файлом, а lchown()
работает с символической ссылкой вместо файла, на который эта ссылка указывает. Во всех других отношениях эти три вызова работают идентично, возвращая 0 в случае успеха и -1 при ошибке.
Стоит заметить, что один системный вызов изменяет как владельца, так и группу файла. Чтобы изменить лишь владельца или лишь группу, передайте (-1) в качестве того идентификационного номера, который должен остаться без изменений.
Хотя вы могли бы подумать, что можно передать соответствующее значение из полученного заранее struct stat
для файла или файлового дескриптора, этот метод больше подвержен ошибкам. Возникает условие состязания: между вызовами stat()
и chown()
владелец или группа могут измениться.
Вы могли бы поинтересоваться: «Зачем нужно изменять владельца символической ссылки? Права доступа и владение ей не имеют значения». Но что случится, если пользователь уходит, а все его файлы все еще нужны? Необходима возможность изменения владельца всех файлов этого лица на кого-то еще, включая символические ссылки.
Системы GNU/Linux обычно не позволяют рядовым пользователям (не root) изменять владельца («отдавать») своих файлов. Смена группы на одну из групп пользователя, конечно, разрешена. Ограничение в смене владельцев идет от BSD систем, у которых тоже есть этот запрет. Главная причина в том, что разрешение пользователям отдавать файлы может нарушить дисковый учет. Рассмотрите такой сценарий:
$ mkdir mywork /* Создать каталог */
$ chmod go-rwx mywork /* Установить права доступа drwx– */
$ cd mywork /* Перейти в него */
$ myprogram > large_data_file /* Создать большой файл */
$ chmod ugo+rw large_data_file /* Установить доступ -rw-rw-rw– */
$ chown otherguy large_data_file /* Передать файл otherguy */
В этом примере large_data_file
теперь принадлежит пользователю otherguy
. Первоначальный пользователь может продолжать читать и записывать файл из-за его прав доступа. Но дисковое пространство, которое он занимает, будет записано на счет otherguy
. Однако, поскольку он находится в каталоге, который принадлежит первому пользователю и к которому otherguy
не может получить доступ, otherguy
не имеет возможности удалить файл.
Некоторые системы System V разрешают пользователям передавать свои файлы. (При смене владельца соответствующие биты файлов setuid
и setgid
сбрасываются.) Это может быть особенной проблемой, когда файлы извлекаются из архива .tar
или .cpio
; извлеченные файлы имеют UID и GID, закодированный в архиве. На таких системах программы tar
и cpio
имеют опции, предотвращающие это, но важно знать, что поведение chown()
действительно отличается на разных системах.
В разделе 6.3 «Имена пользователя и группы» мы увидим, как соотносить имена пользователя и группы с соответствующими числовыми значениями
chmod()
и fchmod()
Изменение прав доступа осуществляется с помощью одного из двух системных вызовов, chmod()
и fchmod()
:
#include
#include
int chmod(const char *path, mode_t mode);
int fchmod(int fildes, mode_t mode);
chmod()
работает с аргументом имени файла, a fchmod()
работает с открытым файлом. (В POSIX нет вызова lchmod()
, поскольку система игнорирует установки прав доступа для символических ссылок. Хотя на некоторых системах такой вызов действительно есть). Как и для большинства других системных вызовов, они возвращают 0 в случае успеха и -1 при ошибке. Права доступа к файлу может изменить лишь владелец файла или root
.
Значение mode создается таким же образом, как для open()
и creat()
, как обсуждалось в разделе 4.6 «Создание файлов». См. также табл. 5.2, в которой перечислены константы прав доступа.
Система не допустит установки бита setgid (S_ISGID
), если группа файла не совпадает с ID действующей группы процесса или с одной из его дополнительных групп. (Мы пока не обсуждали подробно эти проблемы; см. раздел 11.1.1 «Реальные и действующие ID».) Разумеется, эта проверка не относится к root
или коду, выполняющемуся как root
.
utime()
Структура struct stat
содержит три поля типа time_t
:
st_atime
Время последнего доступа к файлу (чтение)
st_mtime
Время последнего изменения файла (запись).
st_ctime
Время последнего изменения индекса файла (например, переименования)
Значение time_t
представляет время в «секундах с начала эпохи». Эпоха является Началом Времени для компьютерных систем GNU/Linux и Unix используют в качестве начала Эпохи полночь 1 января 1970 г по универсальному скоординированному времени (UTC).[62]62
UTC представляет собой независимое от языка сокращение для Coordinated Universal Time (универсальное скоординированное время). Старый код (а иногда и люди постарше) называют это Гринвичским временем (Greenwich Mean Time, GMT), которое является временем в Гринвиче, Великобритания. Когда стали широко использоваться часовые пояса, в качестве точки отсчета, относительно которого все остальные часовые пояса отсчитывались либо вперед, либо назад, был выбран Гринвич – Примеч. автора.
[Закрыть] Системы Microsoft Windows используют в качестве начала Эпохи полночь 1 января 1980 г. (очевидно, местное время).
Значения time_t
иногда называют временными отметками (timestamps). В разделе 6.1 «Время и даты» мы рассмотрим, как получаются эти данные и как они используются. Пока достаточно знать, чем является значение time_t
и то, что оно представляет секунды с начала Эпохи.
Системный вызов utime()
позволяет изменять отметки времени доступа к файлу и его изменения:
#include
#include
int utime(const char *filename, struct utimbuf *buf);
Структура utimbuf
выглядит следующим образом:
struct utimbuf {
time_t actime; /* время доступа */
time_t modtime; /* время изменения */
};
При успешном вызове возвращается 0, в противном случае возвращается -1. Если buf
равен NULL
, система устанавливает время доступа и время изменения равным текущему времени.
Чтобы изменить только одну временную отметку, используйте оригинальное значение из struct stat
. Например.
/* Для краткости проверка ошибок опущена */
struct stat sbuf;
struct utimbuf ut;
time_t now;
time(&now); /* Получить текущее время дня, см. след. главу */
stat("/some/file", &sbuf); /* Заполнить sbuf */
ut.actime = sbuf.st_atime; /* Время доступа без изменений */
ut.modtime = now – (24 * 60 * 60);
/* Установить modtime на 24 часа позже */
utime("/some/file", &ut); /* Установить значения */
Вы можете спросить себя: «Почему может понадобиться кому-нибудь изменять времена доступа и изменения файла?» Хороший вопрос.
Чтобы на него ответить, рассмотрите случай программы, создающей дублирующие архивы, такой, как tar
или cpio
. Эти программы должны прочесть содержание файла, чтобы заархивировать его. Чтение файла, конечно, изменяет время доступа к файлу.
Однако, этот файл, возможно, не читался человеком в течение 10 лет. Некто, набрав 'ls -lu
', что отображает время доступа (вместо времени изменения по умолчанию), увидел бы, что последний раз данный файл просматривали 10 лет назад. Поэтому программа архивации должна сохранить оригинальные значения времени доступа и изменения, прочесть файл для архивации, а затем восстановить первоначальное время с помощью utime()
.
Аналогичным образом, рассмотрите случай архивирующей программы, восстанавливающей файл из архива. В архиве хранятся первоначальные значения времени доступа и изменения. Однако, когда файл извлечен из архива во вновь созданную копию на диске, новый файл имеет текущие дату и время для значений времени доступа и изменения.
Однако полезнее, когда вновь созданный файл выглядит, как если бы он имел тот же возраст, что и оригинальный файл в архиве. Поэтому архиватор должен иметь возможность устанавливать значения времени доступа и изменения в соответствии со значениями в архиве.
ЗАМЕЧАНИЕ. В новом коде вы можете захотеть использовать вызов
utimes()
(обратите внимание на s в имени), который описан далее в книге, в разделе 14.3.2 «Файловое время в микросекундах:utimes()
»
utime(file, NULL)
Некоторые более старые системы не устанавливают значения времени доступа и изменения равным текущему времени, когда второй аргумент utime()
равен NULL
. Однако код более высокого уровня (такой, как GNU touch
) проще, если он может полагаться на один стандартизованный интерфейс.
Поэтому библиотека GNU Coreutils содержит замещающую функцию для utime()
, которая обрабатывает этот случай, которую потом может вызвать код более высокого уровня. Это отражает принцип проектирования «выбор лучшего интерфейса для работы», который мы описали в разделе 1.5 «Возвращаясь к переносимости».
Замещающая функция находится в файле lib/utime.c
в дистрибутиве Coreutils Следующий код является версией из Coreutils 5.0. Номера строк относятся к началу файла:
24 #include
25
26 #ifdef HAVE_UTIME_H
27 # include
28 #endif
39
30 #include "full-write.h"
31 #include "safe-read.h"
32
33 /* Некоторые системы (даже имеющие
34 эту структуру. */
35 #ifndef HAVE_STRUCT_UTIMBUF
36 struct utimbuf
37 {
38 long actime;
39 long modtime;
40 };
41 #endif
42
43 /* Эмулировать utime(file, NULL) для систем (подобных 4.3BSD),
44 которые не устанавливают в этом случае текущее время для времени
45 доступа и изменения file. Вернуть 0, если успешно, -1 если нет. */
46
47 static int
48 utime_null(const char *file)
49 {
50 #if HAVE_UTIMES_NULL
51 return utimes(file, 0);
52 #else
53 int fd;
54 char c;
55 int status = 0;
56 struct stat sb;
57
58 fd = open(file, O_RDWR);
59 if (fd < 0
60 || fstat(fd, &sb) < 0
61 || safe_read(fd, &c, sizeof c) == SAFE_READ_ERROR
62 || lseek(fd, (off_t)0, SEEK_SET) < 0
63 || full_write(fd, &c, sizeof c) != sizeof с
64 /* Можно сделать – это необходимо на SunOS4.1.3 с некоторой комбинацией
65 заплат, но та система не использует этот код: у нее есть utimes.
66 || fsync(fd) < 0
67 */
68 || (st.st_size == 0 && ftruncate(fd, st.st_size) < 0)
69 || close(fd) < 0)
70 status = -1;
71 return status;
72 #endif
73 }
74
75 int
76 rpl_utime(const char *file, const struct utimbuf *times)
77 {
78 if (times)
79 return utime(file, times);
80
81 return utime_null(file);
82 }
Строки 33–41 определяют структуру struct utimbuf
; как сказано в комментарии, некоторые системы не объявляют эту структуру. Работу осуществляет функция utime_null()
. Используется системный вызов utimes()
, если он доступен (utimes()
является сходным, но более развитым системным вызовом, который рассматривается в разделе 14.3.2 «Файловое время в микросекундах: utimes()
.» Он допускает также в качестве второго аргумента NULL
, что означает использование текущего времени.)
В случае, когда время должно обновляться вручную, код осуществляет обновление, прочитав сначала из файла байт, а затем записав его обратно. (Первоначальный touch Unix работал таким способом.) Операции следующие:
1. Открыть файл, строка 58.
2. Вызвать для файла stat()
, строка 60.
3. Прочесть один байт, строка 61 Для наших целей safe_read()
действует подобно read()
; это объясняется в разделе 10.4.4 «Повторно запускаемые системные вызовы»).
4. Переместиться обратно на начало файла с помощью lseek()
, строка 62. Это сделано для записи только что прочитанного байта обратно поверх себя.
5. Записать байт обратно, строка 63. full_write()
действует подобно write()
; это также рассматривается в разделе 10.4.4 «Повторно запускаемые системные вызовы»).
6. Если файл имеет нулевой размер, использовать ftruncate()
для установки его размера в ноль (строка 68). Это не изменяет файл, но имеет побочный эффект обновления времени доступа и изменения (ftruncate()
была описана в разделе 4 8 «Установка длины файла».)
7. Закрыть файл, строка 69.
Все эти шаги осуществляются в одной длинной последовательной цепи проверок внутри if
. Проверки сделаны так, что если любое сравнение неверно, utime_null()
возвращает -1, как обычный системный вызов, errno
автоматически устанавливается системой для использования кодом более высокого уровня.
Функция rpl_utime()
(строки 75–82) является «заместителем utime()
». Если второй аргумент не равен NULL
, она вызывает настоящую utime()
. В противном случае она вызывает utime_null()
.