355 500 произведений, 25 200 авторов.

Электронная библиотека книг » Арнольд Роббинс » Linux программирование в примерах » Текст книги (страница 14)
Linux программирование в примерах
  • Текст добавлен: 6 мая 2017, 11:00

Текст книги "Linux программирование в примерах"


Автор книги: Арнольд Роббинс



сообщить о нарушении

Текущая страница: 14 (всего у книги 55 страниц)

5.4.4.2. Возвращаясь к V7 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 это повторяет */

5.4.5. Работа с символическими ссылками

В общем, символические ссылки ведут себя подобно прямым ссылкам; файловые операции, такие, как open() и stat(), применяются к указываемому файлу вместо самой символической ссылки. Однако, бывают моменты, когда в самом деле необходимо работать с символической ссылкой вместо файла, на которую она указывает.

По этой причине существует системный вызов lstat(). Он действует точно также, как stat(), но если проверяемый файл окажется символической ссылкой, возвращаемые сведения относятся к символической ссылке, а не к указываемому файлу. А именно:

• S_ISLNK(sbuf.st_mode) будет true.

• sbuf.st_size содержит число байтов в имени указываемого файла.

Мы уже видели, что системный вызов symlink() создает символическую ссылку. Но если дана существующая символическая ссылка, как можно получить имя файла, на которую она указывает? (Очевидно, ls может получить это имя; поэтому мы должны быть способны это сделать.)

Открывание ссылки с помощью open() для чтения ее с использованием read() не будет работать, open() следует по ссылке на указываемый файл. Таким образом, символические ссылки сделали необходимым дополнительный системный вызов, который называется readlink():

#include /* POSIX */

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.)

5.5. Смена владельца, прав доступа и времени изменения

Несколько других системных вызовов дают вам возможность изменять другие относящиеся к файлу сведения: в частности, владельца и группу файла, права доступа к файлу и времена доступа и изменения файла.

5.5.1. Смена владельца файла: chown(), fchown() и lchown()

Владелец и группа файла изменяются с помощью трех сходных системных вызовов.

#include /* POSIX */

#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 «Имена пользователя и группы» мы увидим, как соотносить имена пользователя и группы с соответствующими числовыми значениями

5.5.2. Изменение прав доступа: chmod() и fchmod()

Изменение прав доступа осуществляется с помощью одного из двух системных вызовов, chmod() и fchmod():

#include /* POSIX */

#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.

5.5.3. Изменение временных отметок: 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 /* POSIX */

#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()»

5.5.3.1. Подделка 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().


    Ваша оценка произведения:

Популярные книги за неделю