Skip to content

Latest commit

 

History

History
 
 

posix_dirent_time

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 

POSIX API для работы с файловой системой и временем

Каталоги

Каталоги в 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 - сменить текущий каталог.

'at'-функции

Для того, чтобы упростить работу с относительными путями файлов, в современных версиях 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.

Daylight Saving Time

Во многих странах используется "летнее время", когда стрелки часов переводятся на час назад.

История введения/отмены летнего времени, и его периоды хранится в базе данных IANA.

База данных представляет собой набор правил в тектовом виде, которые компилируются в бинарное представление, используемое библиотекой glibc. Наборы файлов с правилами перехода на летнее время для разных регионов хранятся в /usr/share/zoneinfo/.

Когда значение tm_isdst положительное, то применяется летнее время, значение tm_isdst - зимнее. В случае, когда значение tm_isdst отрицательно, - используются данные из timezone data.

Reentrant-функции

Многие функции 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 использует локальное для каждого потока хранение данных.