В качестве примера приведем упрощенную версию утилиты cp(1), копирующую один файл в другой с использованием отображения файла в память.
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <unistd.h>
#include <fcntl.h>
main(int argc, char *argv[]) {
int fd_src, fd_dst;
caddr_t addr_src, addr_dst;
struct stat filestat;
/* Первый аргумент - исходный файл, второй - целевой */
fd_dst=open(argv[2], O_RDWR | O_CREAT);
/* Определим размер исходного файла */
fstat(fd_src, &filestat);
/* Сделаем размер целевого файла равным исходному */
lseek(fd_dst, filestat.st_size - 1, SEEK_SET);
/* Зададим отображение */
addr_src=mmap((caddr_t)0, filestat.st_size,
PROT_READ, MAP_SHARED, fd_src, 0);
addr_dst=mmap((caddr_t)0, filestat.st_size,
PROT_READ | PROT_WRITE, MAP_SHARED, fd_dst, 0);
/* Копируем области памяти */
memcpy(addr_dst, addr_src, filestat.st_size);
exit(0);
}
Поскольку, как обсуждалось выше, с помощью вызова mmap(2) нельзя изменить размер файла, это было сделано с помощью вызова lseek(2) с последующей записью одного байта так, что размер целевого файла стал равным размеру исходного. При этом в целевом файле образуется "дыра", которая, к счастью, сразу же заполняется содержимым копируемого файла.
Владение файлами
Владелец-пользователь и владелец-группа файла могут быть изменены с помощью системных вызовов chown(2), fchown(2) и lchown(2):
#include <unistd.h>
#include <sys/types.h>
int chown(const char *path, uid_t owner, gid_t group);
int fchown(int fildes, uid_t owner, gid_t group);
int lchown(const char *path, uid_t owner, gid_t group);
Все три вызова работают одинаково за исключением ситуации, когда адресуемый файл является символической связью. В последнем случае вызов lchown(2) действует на сам файл - символическую связь, а не на целевой файл (т.е. не следует символической связи). В функциях chown(2) и lchown(2) файл адресуется по имени, а в fchown(2) - по файловому дескриптору. Если значение owner или group установлено равным -1, соответствующий владелец файла не изменяется.
В версиях BSD UNIX только суперпользователь может изменить владение файлом. Это ограничение призвано, в первую очередь, не допустить "скрытие" файлов под именем другого пользователя, например, при установке квотирования ресурсов файловой системы. Владельца-группу можно изменить только для файлов, которыми вы владеете, причем им может стать одна из групп, членом которой вы являетесь. Эти же ограничения определены и стандартом POSIX.1.
В системах ветви System V эти ограничения являются конфигурируемыми, и в общем случае в UNIX System V пользователь может изменить владельца собственных файлов.
В случае успешного изменения владельцев файла биты SUID и SGID сбрасываются, если процесс, вызвавший chown(2) не обладает правами суперпользователя.
Права доступа
Как уже обсуждалось в предыдущей главе, каждый процесс имеет четыре пользовательских идентификатора - UID, GID, EUID и EGID. В то время как UID и GID определяют реального владельца процесса, EUID и EGID определяют права доступа процесса к файлам в процессе выполнения. В общем случае реальные и эффективные идентификаторы эквивалентны. Это значит, что процесс имеет те же привилегии, что и пользователь, запустивший его. Однако, как уже обсуждалось выше, возникают ситуации, когда процесс должен получить дополнительные привилегии, чаще всего - привилегии суперпользователя. Это достигается установкой битов SUID и SGID. Примером такого процесса может служить утилита passwd(1), изменяющая пароль пользователя.
Права доступа к файлу могут быть изменены с помощью системных вызовов chmod(2) и fchmod(2):
#include <sys/types.h>
#include <sys/stat.h>
int chmod(const char *path, mode_t mode);
int fchmod(int fildes, mode_t mode);
Значение аргумента mode определяет устанавливаемые права доступа и дополнительные атрибуты (такие как SUID, SGID и Sticky bit), и создается путем логического объединения различных флагов, представленных в табл. 2.14. Вторая колонка таблицы содержит восьмеричные значения для девяти битов прав доступа (чтение, запись и выполнение для трех классов доступа) и трех битов дополнительных атрибутов.
Таблица 2.14. Флаги аргумента mode
Флаг | Биты | Значение |
---|---|---|
S_ISUID | 04000 | Установить бит SUID |
S_ISGID | 020#0 | Установить бит SGID, если # равно 7, 5, 3 или 1. Установить обязательное блокирование файла, если # равно 6, 4, 2 или 0 |
S_ISVTX | 01000 | Установить Sticky bit |
S_IRWXU | 00700 | Установить право на чтение, запись и выполнение для владельца-пользователя |
S_IRUSR | 00400 | Установить право на чтение для владельца-пользователя |
S_IWUSR | 00200 | Установить право на запись для владельца-пользователя |
S_IXUSR | 00100 | Установить право на выполнение для владельца-пользователя |
S_IRWXG | 00070 | Установить право на чтение, запись и выполнение для владельца-группы |
S_IRGRP | 00040 | Установить право на чтение для владельца-группы |
S_IWGRP | 00020 | Установить право на запись для владельца-группы |
S_IXGRP | 00010 | Установить право на выполнение для владельца-группы |
S_IRWXO | 00007 | Установить право на чтение, запись и выполнение для остальных пользователей |
S_IROTH | 00004 | Установить право на чтение для остальных пользователей |
S_IWOTH | 00002 | Установить право на запись для остальных пользователей |
S_IXOTH | 00001 | Установить право на выполнение для остальных пользователей |
Некоторые флаги, представленные в таблице, уже являются объединением нескольких флагов. Так, например, флаг S_RWXU эквивалентен S_IRUSR | S_IWUSR | S_IXUSR. Значение флага S_ISGID зависит от того, установлено или нет право на выполнение для группы (S_IXGRP). В первом случае, он будет означать установку SGID, а во втором - обязательное блокирование файла.
Для иллюстрации приведем небольшую программу, создающую файл с полными правами доступа для владельца, а затем изменяющую их. После каждой установки прав доступа в программе вызывается библиотечная функция system(3S), позволяющая запустить утилиту ls(1) и отобразить изменение прав доступа и дополнительных атрибутов.
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
main() {
int fd;
/* Создадим файл с правами rwx------ */
fd = creat("my_file", S_IRUSR | S_IWUSR | S_IXUSR);
system("ls -l my_file");
/*Добавим флаг SUID */
fchmod(fd, S_IRWXU | S_ISUID);
/* Установим блокирование записей файла */
fchmod(fd, S_IRWXU | S_ISUID | S_ISGID);
system("ls -l my_file");
/* Теперь установим флаг SGID */
fchmod(fd, S_IRWXU | S_ISUID | S_ISGID | S_IXGRP);
system("ls -l my_file");
}
В результате запуска программы на выполнение, получим следующий вывод:
$ a.out
-rwx------ 1 andy user 0 Jan 6 19:28 my_file
-rws------ 1 andy user 0 Jan 6 19:28 my_file
-rws--1--- 1 andy user 0 Jan 6 19:28 my_file
-rws--s--- 1 andy user 0 Jan 6 19:28 my_file
Перемещение по файловой системе
Каждый процесс имеет два атрибута, связанных с файловой системой - корневой каталог (root directory) и текущий рабочий каталог (current working directory). Когда некоторый файл адресуется по имени (например, в системных вызовах open(2), creat(2) или readlink(2)), ядро системы производит поиск файла, начиная с корневого каталога, если имя файла задано как абсолютное, либо текущего каталога, если имя файла является относительным. Абсолютное имя файла начинается с символа '/', обозначающего корневой каталог. Все остальные имена файлов являются относительными. Например, имя /usr/bin/sh является абсолютным, в то время как mydir/test1.c или ../andy/mydir/test1.c - относительным, при котором фактическое расположение файла в файловой системе зависит от текущего каталога.
Процесс может изменить свой корневой каталог с помощью системного вызова chroot(2) или fchroot(2).
#include <unistd.h>
int chroot (const char *path);
int fchroot(int fildes);
После этого поиск всех адресуемых файлов с абсолютными именами будет производиться, начиная с нового каталога, указанного аргументом path. Например, после изменения корневого каталога на домашний каталог пользователя абсолютное имя инициализационного скрипта .profile станет /.profile.
Изменение корневого каталога может потребоваться, например, при распаковке архива, созданного с абсолютными именами файла, в другом месте файловой системы, либо при работе над большим программным проектом, затрагивающим существенную часть корневой файловой системы. В этом случае для отладочной версии удобно создать собственную корневую иерархию.
Процесс также может изменить и текущий каталог. Для этого используются системные вызовы chdir(2) или fchdir(2):
#include <unistd.h>
int chdir(const char* path);
int fchdir(int fildes);
Например, внутренняя команда командного интерпретатора cd может быть реализована следующим кодом:
...
char newdir[PATH_MAX];
...
/* Предположим, что имя нового каталога,
введенного пользователем, уже находится
в переменной newdir*/
if (chdir(newdir) == -1) perror("sh: cd");
...
Метаданные файла
Как уже говорилось, каждый файл помимо собственно данных содержит метаданные, описывающие его характеристики, например, владельцев, права доступа, тип и размер файла, а также содержащие указатели на фактическое расположение данных файла. Метаданные файла хранятся в структуре inode. Часть полей этой структуры могут быть получены с помощью системных вызовов stat(2):
#include <sys/types.h>
#include <sys/stat.h>
int stat(const char *path, struct stat *buf);
int lstat (const char *path, struct stat *buf);
int fstat(int fildes, struct stat *buf);
В качестве аргумента функции принимают имя файла или файловый дескриптор (fstat(2)) и возвращают заполненные поля структуры stat, которые приведены в табл. 2.15.
Таблица 2.15. Поля структуры stat
Поле | Значение |
---|---|
mode_t st_mode | Тип файла и права доступа |
ino_t st_ino | Номер inode. Поля st_ino и st_dev однозначно определяют обычные файлы |
dev_t st_dev | Номер устройства, на котором расположен файл (номер устройства файловой системы) |
dev_t st_rdev | Для специального файла устройства содержит номер устройства, адресуемого этим файлом |
nlink_t st_link | Число жестких связей |
uid_t st_uid | Идентификатор пользователя-владельца файла |
gid_t st_gid | Идентификатор группы-владельца файла |
off_t st_size | Размер файла в байтах. Для специальных файлов устройств это поле не определено |
time_t st_atime | Время последнего доступа к файлу |
time_t st_mtime | Время последней модификации данных файла |
time_t st_ctime | Время последней модификации метаданных файла |
long st_blksize | Оптимальный размер блока для операций ввода/вывода. Для специальных файлов устройств и каналов это поле не определено |
long st_blocks | Число размещенных 512-байтовых блоков хранения данных. Для специальных файлов устройств это поле не определено |
Для определения типа файла служат следующие макроопределения, описанные в файле <sys/stat.h>:
Таблица 2.16. Определение типа файла
Макроопределение | Тип файла |
---|---|
S_ISFIFO(mode) | FIFO |
S_ISCHR(mode) | Специальный файл символьного устройства |
S_ISDIR(mode) | Каталог |
S_ISBLK(mode) | Специальный файл блочного устройства |
S_ISREG(mode) | Обычный файл |
S_ISLNK(mode) | Символическая связь |
S_ISSOCK(mode) | Сокет |
Все значения времени, связанные с файлом (время доступа, модификации данных и метаданных) хранятся в секундах, прошедших с 0 часов 1 января 1970 года. Заметим, что информация о времени создания файла отсутствует.
Приведенная ниже программа выводит информацию о файле, имя которого передается ей в качестве аргумента:
#include <sys/types.h>
#include <sys/stat.h>
#include <time.h>
main(int argc, char *argv[]) {
struct stat s;
char* ptype;
lstat(argv[1] , &s); /* Определим тип файла */
if (S_ISREG(s.st_mode)) ptype = "Обычный файл";
else if (S_ISDIR(s.st_mode)) ptype = "Каталог";
else if (S_ISLNK(s.st_mode)) ptype = "Симв. Связь";
else if (S_ISCHR(s.st_mode)) ptype = "Симв. Устройство";
else if (S_ISBLK(s.st_mode)) ptype = "Бл.устройство";
else if (S_ISSOCK(s.st_mode)) ptype = "Сокет";
else if (S_ISFIFO(s.st_mode)) ptype = "FIFO";
else ptype = "Неизвестный тип";
/* Выведем информацию о файле */
/* Его тип */
printf("type = %s\n", ptype);
/* Права доступа */
printf("perm =%o\n", s.st_mode & S_IAMB);
/* Номер inode */
printf("inode = %d\n", s.st_ino);
/* Число связей */
printf("nlink = %d\n", s.st_nlink);
/* Устройство, на котором хранятся данные файла */
printf("dev = (%d, %d)\n", major(s.st_dev), minor(s.st_dev));
/* Владельцы файла */
printf("UID = %d\n", s.st_uid);
printf("GID = %d\n", s.st_gid);
/* Для специальных файлов устройств - номера устройства */
printf("rdev = (%d, %d)\n", major(s.st_rdev),
minor(s.st_rdev));
/* Размер файла */