Каталоги в UNIX-системах - это специальный вид файлов, который содержит набор пар {inode, name}
для построения иерархии файловой системы.
Как и обычные файлы, каталоги могут быть открыты на чтение или запись с помощью системного вызова open
. В системе Linux существует не обязательный флаг открытия O_DIRECTORY
, единственное назначие которого - проверить, что открываемый файл действительно является каталогом, а не файлом другого типа; в противном случае - диагностировать ошибку.
Формат специально файла-каталога зависит от конкретной операционной системы, и абстракцией на уровне POSIX является функции (не системные вызовы!) стандартной библиотеки Си:
#include <dirent.h>
// открытие каталога
DIR *opendir(const char *dirname);
DIR *fdopendir(int fd);
// закрытие каталога
int closedir(DIR *dirp);
Открытый каталог описывается структурой DIR
, которая при открытии каталога размещается в куче, и необходимо её освобождать с помощью функции closedir
.
Чтение из потока каталога осуществляется функцией readdir
, единицей чтения которой является не байт, а элемент структуры dirent
:
struct dirent {
ino_t d_ino; // inode файла в файловой системе
char d_name[NAME_MAX+1]; // имя файла
/* дальше могут быть ещё какие-нибудь нестандартные поля */
};
В системе Linux максимальное имя файла равно 255 (NAME_MAX
)символам, но при этом, максимальная длина пути к файлу - 4Кб (PATH_MAX
).
Перемещение текущего указателя чтения записей в каталоге осуществляется с помощью функций seekdir
и telldir
.
Каждый каталог, даже пустой, содержит, как минимум, две записи:
- специальный файл
.
- каталог, inode которого совпадает с inode того каталога, в котором он содержится; - специальный файл
..
- каталог, inode которого совпадает с inode каталога на уровень выше, либо корневого каталога, если каталог уровнем выше не существует.
Другие функции работы с каталогами:
getcwd
- получить текущий рабочий каталог;chdir
- сменить текущий каталог.
Для того, чтобы упростить работу с относительными путями файлов, в современных версиях glibc
(Linux и FreeBSD) присуствуют функции для открытия файловых дескрипторов относительно открытого каталога:
// Аналоги open
int openat(int dirfd, const char *pathname, int flags);
int openat(int dirfd, const char *pathname, int flags, mode_t mode);
// Аналог stat
int fstatat(int dirfd, const char *pathname, struct stat *statbuf, int flags);
Информация о пользователях и группах может храниться как в локальном источнике, например в файлах /etc/passwd
и /etc/groups
, так и на удаленных серверах, например LDAP.
Информацию о пользователе или группе можно получить с помощью одной из функций:
struct passwd *getpwnam(const char *name)
- получить информацию о пользователе по имени;struct passwd *getpwuid(uid_t uid)
- получить информацию о пользователе по его User ID;struct group *getgrnam(const char *name)
- получить информацию о группе по имени;struct group *getgrgid(gid_t gid)
- получить информацию о группе по её Group ID.
Время в UNIX-системах определяется как количество секунд, прошедшее с 1 января 1970 года, причем часы идут по стандартному гринвичскому времени (GMT) без учета перехода на летнее время (DST - daylight saving time).
32-разрядные системы должны прекратить своё нормальное существование 19 января 2038 года, поскольку будет переполнение знакового целого типа для хранения количества секунд.
Функция time
возвращает количество секунд с начала эпохи. Аргументом функции (в который можно передать NULL
) является указатель на переменную, куда требуется записать результат.
В случае, когда требуется более высокая точность, чем 1 секунда, можно использовать системный вызов gettimeofday
, который позволяет получить текущее время в виде структуры:
struct timeval {
time_t tv_sec; // секунды
suseconds_t tv_usec; // микросекунды
};
В этом случае, несмотря на то, что в структуре определено поле для микросекунд, реальная точность будет составлять порядка 10-20 миллисекунд для Linux.
Более высокую точность можно получить с помощью системного вызова clock_gettime
.
Человеко-представимое время состоит из даты (год, месяц, день) и времени суток (часы, минуты, секунды).
Это описывается структурой:
struct tm { /* время, разбитое на составляющие */
int tm_sec; /* секунды от начала минуты: [0 -60] */
int tm_min; /* минуты от начала часа: [0 - 59] */
int tm_hour; /* часы от полуночи: [0 - 23] */
int tm_mday; /* дни от начала месяца: [1 - 31] */
int tm_mon; /* месяцы с января: [0 - 11] */
int tm_year; /* годы с 1900 года */
int tm_wday; /* дни с воскресенья: [0 - 6] */
int tm_yday; /* дни от начала года (1 января): [0 - 365] */
int tm_isdst; /* флаг перехода на летнее время: <0, 0, >0 */
};
Для преобразования человеко-читаемого времени в машинное используется функция mktime
, а в обратную сторону - одной из функций: gmtime
или localtime
.
Во многих странах используется "летнее время", когда стрелки часов переводятся на час назад.
История введения/отмены летнего времени, и его периоды хранится в базе данных IANA.
База данных представляет собой набор правил в тектовом виде, которые компилируются в бинарное представление, используемое библиотекой glibc. Наборы файлов с правилами перехода на летнее время для разных регионов хранятся в /usr/share/zoneinfo/
.
Когда значение tm_isdst
положительное, то применяется летнее время, значение tm_isdst
- зимнее. В случае, когда значение tm_isdst
отрицательно, - используются данные из timezone data.
Многие функции POSIX API разрабатывались во времена однопроцессорных систем. Это может приводить к разным неприятным последствиям:
struct tm * tm_1 = localtime(NULL);
struct tm * tm_2 = localtime(NULL); // opps! *tm_1 changed!
Проблема заключается в том, что некоторые функции, например localtime
, возвращает указатель на структуру-результат, а не скалярное значение. При этом, сами данные структуры не требуется удалять, - они хранятся в .data
-области библиотеки glibc.
Проблема решается введением повторно входимых (reentrant) функций, которые в обязательном порядке трубуют в качестве одного из аргументов указатель на место в памяти для размещения результата:
struct tm tm_1; localtime_r(NULL, &tm_1);
struct tm tm_2; localtime_r(NULL, &tm_2); // OK
Использование повторно входимых функций является обязательным (но не достаточным) условием при написании многопоточных программ.
Некоторые reentrant-функции уже не актуальны в современных версиях glibc для Linux, и помечены как deprecated. Например, реализация readdir
использует локальное для каждого потока хранение данных.