Ориентация главы
Заключительная глава о файлах разбирает три способности каталогов — подъём от одиночных файлов к «контейнеру и границе файлов».Перечисление каталогов (xrtDirOpen/Next): трёхзначный перебор по элементам, причём один системный вызов выносит и тип, и размер — классические 2N вызовов readdir-со-stat здесь превращаются в N, на больших каталогах разница на порядок.Рекурсивный обход: обход дерева каталогов в глубину (разворачивает образец dir_tour).Корень-песочница (xrtRootOpen): дескриптор каталога как граница безопасности — операции внутри корня принимают только относительные имена, обход и абсолютные пути отвергаются на системном уровне; это вторая линия обороны за лексической проверкой главы 44 (IsSafeEntry) — системное звено эшелонированной обороны (когда лексику можно обойти, системная семантика подстраховывает).
47.1引入
Распаковывается загруженный пользователем архив: имена записей внутри — недоверенный вход; запись обхода вроде ../../evil.so, положенная на диск напрямую, записывается вне песочницы. Лексическая проверка главы 44 (IsSafeEntry) ловит подавляющее большинство, но у лексического слоя коренная слабость: он судит строки — симлинки, платформенные особенности и различия нормализации способны оторвать лексический вердикт от фактического поведения файловой системы.
Корень-песочница решает это системно: xrtRootOpen открывает каталог и получает дескриптор; с этого момента все операции (создать файл, открыть файл, создать каталог) ведутся от дескриптора и принимают только относительные имена — обход на уровне операционной системы теряет место, где спрятаться (не «проверил и пропустил», а «пути наружу просто нет»). У корневого дескриптора попутный дивиденд: он остаётся действительным после переименования/перемещения каталога (дескриптор не зависит от строк пути) — ротация журналов и реорганизация каталогов не прерывают идущие операции.
47.2概念
Перечисление каталогов: трёхзначный интерфейс и дивиденда нуля лишних stat
Что «ноль лишних 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:
#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; }
Что только что произошло. (1) xrtDirOpen(".", 0) открывает текущий каталог — возвращается дескриптор потока каталога (не файловый; два вида дескрипторов не смешиваются). (2) Трёхзначный цикл xrtDirNext: ITEM печатает имя элемента, END заканчивается естественно, ERROR прерывает с отчётом — изоморф трём состояниям построчного читателя главы 41 (LINE/END/отказ); единая форма потоковых интерфейсов XRT продолжается на каталогах. (3) Каждый элемент за один вызов выносит имя, тип и размер — содержимое естественно меняется вместе с текущим каталогом (список у каждого репозитория свой) — образец печатает только имена, но поле типа в Entry (файл/каталог) под рукой, дополнительный stat не нужен. (4) Имя — заимствованное представление (конвенция главы 3): печать %.*s и выброс; чтобы сохранить — скопируйте сами (хорошая форма ловушки 1 это ровно «сперва скопировать, потом собирать»).
Полная программа: операции с корнем-песочницей
Из 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; }
Что только что произошло. (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-чтение-запись). Файловые способности — фундамент системных сервисов; фундамент готов — и следующая глава добавляет последние два кирпича: асинхронность и сигналы.
Типичные ошибки
Симптом: обход изредка теряет элементы или получает 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)); /* удалять после завершения обхода */ }
Симптом: пентест обошёл IsSafeEntry — симлинки, путаница регистра или различия платформенной нормализации путей заставляют «лексически безопасный» путь фактически указывать вне корня — строковое суждение и разбор файловой системы расходятся на границе.
Причина: лексика судит строки, файловая система разбирает реальные пути — в сценариях вроде симлинков они расходятся.
if ( xrtPathIsSafeEntry(Name, false) ) { str sFull = xrtPathJoin(BaseDir, NameBuf); xrtFileOpen(sFull, ...); /* лексика пропустила - открываем; симлинк может указывать вне корня */ }
xroot Root = xrtRootOpen(BaseDir, 0); xfile File = xrtRootFileOpen(Root, NameBuf, ...); /* системная граница: обходу негде случиться */ /* лексическая проверка может стоять и впереди как быстрый фильтр - но окончательная гарантия это корневой дескриптор */
Практические задания
Перечислите каталог, посчитайте файлы и каталоги по типам и выведите общий размер — данные одного вызова использованы полностью.
Реализуйте 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 записей)
Полные сигнатуры, ограничения параметров, возвращаемые значения и ошибки