Ориентация главы

Заключительная глава о файлах разбирает три способности каталогов — подъём от одиночных файлов к «контейнеру и границе файлов».Перечисление каталогов (xrtDirOpen/Next): трёхзначный перебор по элементам, причём один системный вызов выносит и тип, и размер — классические 2N вызовов readdir-со-stat здесь превращаются в N, на больших каталогах разница на порядок.Рекурсивный обход: обход дерева каталогов в глубину (разворачивает образец dir_tour).Корень-песочница (xrtRootOpen): дескриптор каталога как граница безопасности — операции внутри корня принимают только относительные имена, обход и абсолютные пути отвергаются на системном уровне; это вторая линия обороны за лексической проверкой главы 44 (IsSafeEntry) — системное звено эшелонированной обороны (когда лексику можно обойти, системная семантика подстраховывает).

47.1引入

Распаковывается загруженный пользователем архив: имена записей внутри — недоверенный вход; запись обхода вроде ../../evil.so, положенная на диск напрямую, записывается вне песочницы. Лексическая проверка главы 44 (IsSafeEntry) ловит подавляющее большинство, но у лексического слоя коренная слабость: он судит строки — симлинки, платформенные особенности и различия нормализации способны оторвать лексический вердикт от фактического поведения файловой системы.

Корень-песочница решает это системно: xrtRootOpen открывает каталог и получает дескриптор; с этого момента все операции (создать файл, открыть файл, создать каталог) ведутся от дескриптора и принимают только относительные имена — обход на уровне операционной системы теряет место, где спрятаться (не «проверил и пропустил», а «пути наружу просто нет»). У корневого дескриптора попутный дивиденд: он остаётся действительным после переименования/перемещения каталога (дескриптор не зависит от строк пути) — ротация журналов и реорганизация каталогов не прерывают идущие операции.

47.2概念

Перечисление каталогов: трёхзначный интерфейс и дивиденда нуля лишних stat

1ОткрытиеxrtDirOpen(путь, флаги) -> дескриптор потока каталога
2ПереборxrtDirNext(дескриптор, элемент-выход) -> три состояния: ITEM получен / END конец / ERROR отказ
3Элементxdirentry несёт представление имени + тип/размер (один вызов выносит всё)
4ЗакрытиеxrtDirClose

Что «ноль лишних stat» значит для производительности: классический readdir, получив имя, за «файл это или каталог» зовёт ещё один stat — N элементов, 2N системных вызовов; каталоговый API XRT выносит тип и размер напрямую (родная информация каталогов Windows/Linux уже содержит их) — N вызовов, на больших каталогах быстрее на порядок. Имя в элементе — представление-заимствование, срок действия до следующего Next (дисциплина итерации главы 18).

Рекурсивный обход (обход дерева каталогов в глубину)

Обход дерева каталогов в глубину: перечисляем текущий уровень → встретив подкаталог, входим рекурсивно → по завершении возвращаемся. Глубина рекурсии защищена границами ресурсов главы 3 (от злобно глубоких деревьев); параллельные добавления и удаления во время обхода — реальный риск (содержимое меняют извне посреди перечисления) — семантика обхода держит это как «снимок по возможности»; когда нужна строгая согласованность, сперва соберите, потом обрабатывайте (каталожная версия ловушки 2 главы 18).

Корень-песочница: дескриптор как граница (системный уровень, не лексический)

xrtRootOpen / xrtRootOpenIn (вложенные подкорни) открывают каталог как корень; xrtRootFileOpen (открыть файл внутри), xrtRootDirCreate/Remove (создать/удалить каталог внутри) принимают только относительные имена — ../ и абсолютные пути отвергаются. Сотрудничество с лексической проверкой главы 44: лексический слой — первый фильтр (быстрый отказ очевидно злобным формам, ноль стоимости), системный корень — окончательная гарантия (семантически верно, покрывает лексические слепые пятна) — две линии обороны не избыточность, а эшелонированная оборона — стандартная поза инженерной безопасности: дешёвое держит напор, крепкое подстраховывает.

Временные каталоги в сцепке (стандартный контейнер пакетного складирования)

xrtDirTemp (упомянут в главе 45) создаёт во временном каталоге уникально названный каталог — для сценария «пачке временных файлов нужен отдельный дом» (склад распаковки, промежуточные продукты сборки): создали временный каталог → складывайте что угодно → удаление всего каталога завершает дело. На порядок чище рассыпанных временных файлов — семантика «дома» превращает зачистку в один вызов.

47.3示例

Полная программа: перечисление без лишних stat

Из образца репозитория examples/file/directory/main.c:

examples/file/directory/main.c源码 ↗
#include <stdio.h>

#include <xrt.h>























 */


/* 展示零额外 stat 的目录枚举。 */
int main(void)
{
	xdir Dir = xrtDirOpen(".", 0u);
	xdirentry Entry;
	xdirnext Next;

	if ( Dir == NULL ) {
		return 1;
	}
	while ( (Next = xrtDirNext(Dir, &Entry)) == XDIR_NEXT_ITEM ) {
		printf("%.*s\n", (int)Entry.Name.Size, Entry.Name.Data);
	}
	if ( Next == XDIR_NEXT_ERROR ) {
		(void)xrtDirClose(Dir);
		return 1;
	}
	return xrtDirClose(Dir) ? 0 : 1;
}
$ gcc -O1 -DXRT_MODULE_ALL -I single -include xrt.h impl.c examples/file/directory/main.c -lws2_32 -liphlpapi.git.gitattributes...

Что только что произошло. (1) xrtDirOpen(".", 0) открывает текущий каталог — возвращается дескриптор потока каталога (не файловый; два вида дескрипторов не смешиваются). (2) Трёхзначный цикл xrtDirNext: ITEM печатает имя элемента, END заканчивается естественно, ERROR прерывает с отчётом — изоморф трём состояниям построчного читателя главы 41 (LINE/END/отказ); единая форма потоковых интерфейсов XRT продолжается на каталогах. (3) Каждый элемент за один вызов выносит имя, тип и размер — содержимое естественно меняется вместе с текущим каталогом (список у каждого репозитория свой) — образец печатает только имена, но поле типа в Entry (файл/каталог) под рукой, дополнительный stat не нужен. (4) Имя — заимствованное представление (конвенция главы 3): печать %.*s и выброс; чтобы сохранить — скопируйте сами (хорошая форма ловушки 1 это ровно «сперва скопировать, потом собирать»).

Полная программа: операции с корнем-песочницей

Из examples/file/root/main.c — безопасное хранение недоверенных относительных имён:

examples/file/root/main.c源码 ↗
#include <stdio.h>
#include <string.h>

#include <xrt.h>
























 */


/* 使用目录能力安全处理不可信的相对文件名。 */
int main(int argc, char** argv)
{
	cstr sName = argc > 1 ? argv[1] : "message.txt";
	const cstr sDirectory = ".xrt-root-example-data";
	xfileoptions Options;
	xfile File;
	xroot Parent;
	xroot Root;

	Parent = xrtRootOpen(".");
	if ( Parent == NULL ) {
		fprintf(stderr, "%s\n", xrtErrorMessage(xrtGetError()));
		return 1;
	}
	if ( !xrtRootDirCreate(Parent, sDirectory, 0700u) ) {
		fprintf(stderr, "%s\n", xrtErrorMessage(xrtGetError()));
		(void)xrtRootClose(Parent);
		return 1;
	}
	Root = xrtRootOpenIn(Parent, sDirectory);
	if ( Root == NULL ) {
		fprintf(stderr, "%s\n", xrtErrorMessage(xrtGetError()));
		(void)xrtRootRemove(Parent, sDirectory);
		(void)xrtRootClose(Parent);
		return 1;
	}
	xrtFileOptionsInit(&Options);
	Options.Flags = XFILE_WRITE | XFILE_CREATE | XFILE_TRUNCATE;
	File = xrtRootFileOpen(Root, sName, &Options);
	if ( File == NULL ) {
		fprintf(stderr, "%s\n", xrtErrorMessage(xrtGetError()));
		(void)xrtRootClose(Root);
		(void)xrtRootRemove(Parent, sDirectory);
		(void)xrtRootClose(Parent);
		return 1;
	}
	if ( !xrtWriteFull(File, "stored inside root\n", 19u, NULL) ||
		 !xrtClose(File) || !xrtRootRemove(Root, sName) ||
		 !xrtRootClose(Root) ||
		 !xrtRootRemove(Parent, sDirectory) ||
		 !xrtRootClose(Parent) ) {
		fprintf(stderr, "%s\n", xrtErrorMessage(xrtGetError()));
		return 1;
	}
	printf("stored and removed %s/%s\n", sDirectory, sName);
	return 0;
}
$ gcc -O1 -DXRT_MODULE_ALL -I single -include xrt.h impl.c examples/file/root/main.c -lws2_32 -liphlpapistored and removed .xrt-root-example-data/message.txt

Что только что произошло. (1) xrtRootOpenIn открывает корень под родительским каталогом — вложенная форма разрешает «по корню на каждую загрузку». (2) xrtRootDirCreate создаёт каталог внутри корня, xrtRootFileOpen открывает файл внутри — всё относительными именами: передача ../escape или абсолютного пути отвергается на системном уровне (не лексическая проверка, а естественное следствие семантики дескриптора — операция происходит внутри корня, варианта «вне корня» просто нет). (3) После хранения и зачистки xrtRootClose закрывает корень — ресурсы внутри управляются жизненным циклом корня как единое целое. (4) Путь в выводе несёт префикс корня (читаемо для аудита), но ни одна операция не зависела от этой строки — дескриптор не зависит от пути, переименование и перемещение не прерывают. Образцы dir_tour и root_tour оставлены продвинутым чтением: рекурсивный обход и операции с корнем — каждая группа собственной серией утверждений покрывает полную интерфейсную поверхность.

47.4契约

  • Трёхзначный перебор: ITEM/END/ERROR изоморфны потоковым интерфейсам всей библиотеки; представления имён заимствуются не дольше итерации.
  • Ноль лишних stat: тип и размер выносятся с элементом — большой каталог за N вызовов, не за 2N.
  • Границы обхода: глубина под защитой границ ресурсов; добавления/удаления посреди обхода — по семантике «снимок по возможности»; для строгой согласованности сперва собрать, потом обрабатывать.
  • Семантика песочницы: внутри корня — только относительные имена; обход и абсолютные пути отвергаются на системном уровне; корневой дескриптор не зависит от строк пути (действителен после переименований и перемещений).
  • Эшелонированная оборона: лексическая проверка (глава 44) как быстрый фильтр + системная семантика корня как подстраховка — линии сотрудничают, а не дублируют.
  • Временные каталоги: xrtDirTemp с уникальным именем, управление целым каталогом — стандартный контейнер пакетного складирования.

От примеров к инженерии: три хозяина каталожных способностей

Загрузки и распаковка (арена корня-песочницы): по корню на каждую загрузку (вложенный RootOpenIn) — записи обхода отвергаются системно, легальные восстанавливаются внутри корня, по завершении корень удаляется целиком; лексическая проверка главы 44 делает предварительную быструю фильтрацию, корневой дескриптор даёт окончательную гарантию.Доступ к каталогам ресурсов (перечисление + корень в сцепке): ресурсы плагинов, каталоги статики — дескриптор корня закреплён, и эксплуатация может переименовывать и переасамблировать каталог (дескриптор не зависит от пути), сервис не прерывается.Пакетное складирование сборки (арена временных каталогов): промежуточные продукты задач сборки/конвертации управляются целыми каталогами — DirTemp создаёт дом, задача по завершении удаляет; на порядок чище рассыпанных временных файлов (пакетная версия атомарной доставки главы 46).

Инженерная дисциплина обхода

Рекурсивный обход выглядит просто; в инженерном виде ему нужны четыре дисциплины.Потолок глубины: злобно глубокое дерево (или кольцо симлинков) способно взорвать наивную рекурсию — границы ресурсов ставят шлюз на входе перечисления.Политика симлинков: следовать (по умолчанию — риск кольца) или не следовать (определить по полю типа и пропустить) — политика объявляется явно и фиксируется комментарием.Восстановление после ошибок: отказ перечисления одного каталога (права) — прервать или пропустить? решает цель обхода (аудит прерывает, чистка пропускает).Предположение о порядке: порядок перечисления определяет файловая система (не отсортирован) — списки, которым нужен порядок, сортируются после сбора (глава 14). Четыре дисциплины вместе: обходчик — каркасный код, а не заготовка цикла — сперва ответьте на четыре вопроса, потом пишите.

Слияние с первыми двумя главами о файлах

Здесь три файловые главы сливаются в полную карту: дескрипторы и чтение-запись главы 45 (жизненный цикл одиночного файла), отображения-блокировки-атомарная доставка главы 46 (параллельность и производительность), каталоги и песочница этой главы (организация и безопасность). Их типичные сочетания пройдут через всю оставшуюся книгу: служба статики тома 7 (корневой дескриптор + перечисление + отображение), инструменты сборки тома 12 (обход каталогов + временные каталоги + атомарная доставка), распаковщик (корень-песочница + трёхзначный перебор + Full-чтение-запись). Файловые способности — фундамент системных сервисов; фундамент готов — и следующая глава добавляет последние два кирпича: асинхронность и сигналы.

Типичные ошибки

Ловушка 1: удаление содержимого каталога при обходе (капкан «снимка по возможности»):Ловушка 1: удаление содержимого каталога при обходе (капкан «снимка по возможности»)

Симптом: обход изредка теряет элементы или получает ERROR — параллельный скрипт чистки удаляет файлы в цели обхода; воспроизведение зависит от тактов.

Причина: перечисление каталога это «снимок по возможности» — семантика нижележащего потока каталогов не обещает отразить все изменения за время обхода.

错误写法
while ( (Next = xrtDirNext(Dir, &Entry)) == XDIR_NEXT_ITEM ) {
	if ( IsOld(Entry.Name) ) {
		DeleteByName(DirPath, Entry.Name);   /* удаляем посреди обхода - нижележащий поток дестабилизирован */
	}
}
正确写法
xarray tOld;
xrtArrayInit(&tOld, sizeof(str));
while ( (Next = xrtDirNext(Dir, &Entry)) == XDIR_NEXT_ITEM ) {
	if ( IsOld(Entry.Name) ) {
		xrtArrayPush(&tOld, &(str){ xrtStrDupView(Entry.Name) });   /* сперва собрать имена */
	}
}
xrtDirClose(Dir);
for ( size_t i = 0; i < tOld.Count; i++ ) {
	DeleteByName(DirPath, *(str*)xrtArrayGet(&tOld, i));   /* удалять после завершения обхода */
}
Ловушка 2: лексическая проверка как единственная линия обороны (утерянная глубина):Ловушка 2: лексическая проверка как единственная линия обороны (утерянная глубина)

Симптом: пентест обошёл IsSafeEntry — симлинки, путаница регистра или различия платформенной нормализации путей заставляют «лексически безопасный» путь фактически указывать вне корня — строковое суждение и разбор файловой системы расходятся на границе.

Причина: лексика судит строки, файловая система разбирает реальные пути — в сценариях вроде симлинков они расходятся.

错误写法
if ( xrtPathIsSafeEntry(Name, false) ) {
	str sFull = xrtPathJoin(BaseDir, NameBuf);
	xrtFileOpen(sFull, ...);   /* лексика пропустила - открываем; симлинк может указывать вне корня */
}
正确写法
xroot Root = xrtRootOpen(BaseDir, 0);
xfile File = xrtRootFileOpen(Root, NameBuf, ...);   /* системная граница: обходу негде случиться */
/* лексическая проверка может стоять и впереди как быстрый фильтр - но окончательная гарантия это корневой дескриптор */

Практические задания

Базовое: статистика перечисления (использование данных без лишних stat)

Перечислите каталог, посчитайте файлы и каталоги по типам и выведите общий размер — данные одного вызова использованы полностью.

Продвинутое: рекурсивный списочник (дебют четырёх дисциплин обхода)

Реализуйте list_tree(路径, 深度上限) (путь, потолок глубины): вывод дерева с отступами (в глубину), подсчёт файлов и подкаталогов на каждом уровне, отметка «пропущен» для каталогов за потолком — рекурсивный обход в сцепке с границами ресурсов.

Вызов: безопасный распаковщик (эшелонированная оборона в полной форме)

Реализуйте выкладку архива с корнем-песочницей: структура каталогов внутри корня (RootDirCreate уровень за уровнем), файлы на диск (RootFileOpen+Write), все злобные записи (обход/абсолютные/имена устройств) отвергаются со списком. Критерии приёмки: пакет с пятью злобными записями (по одной на обход/абсолютный путь/имя устройства/кольцо симлинков/сверхглубокую вложенность) полностью отвергнут, вне корня ноль файлов; легальные записи восстановлены полностью; после удаления корня ноль остатка — по строке вывода на каждый из пяти классов атак.

Итоги

ТемаКратко
Трёхзначный переборDirOpen/Next/Close: ITEM = получен / END = конец / ERROR = отказ, изоморф потоковым интерфейсам библиотеки
Ноль лишних statтип и размер выносятся с элементом - N вызовов вместо 2N; на больших каталогах быстрее на порядок
Рекурсивный обходобход в глубину + шлюз глубины границ ресурсов; добавления/удаления посреди обхода - семантика снимка по возможности
Корень-песочницаRootOpen/RootOpenIn/RootFileOpen/RootDirCreate: только относительные имена, обход отвергается на системном уровне
Эшелонированная обороналексическая проверка (глава 44) быстро фильтрует + семантика корневого дескриптора подстраховывает - дешёвое держит напор, крепкое ловит
Временные каталогиxrtDirTemp уникальное имя - пакетное складирование целым каталогом
Три хозяиназагрузки и распаковка (арена корня) / каталоги ресурсов (перечисление + корень) / пакетное складирование (временный каталог)
Четыре дисциплины обходапотолок глубины / политика симлинков / восстановление после ошибок / предположение о порядке - каркас, не заготовка

📖 Справочники API в этой главе:file (132 записей)path (41 записей)

Полные сигнатуры, ограничения параметров, возвращаемые значения и ошибки