API доступа к файловой системе позволяет веб-приложениям читать или сохранять изменения непосредственно в файлах и папках на устройстве пользователя.
Опубликовано: 19 августа 2024 г.
API доступа к файловой системе позволяет разработчикам создавать мощные веб-приложения, взаимодействующие с файлами на локальном устройстве пользователя, такие как IDE, фото- и видеоредакторы, текстовые редакторы и многое другое. После предоставления веб-приложению доступа этот API позволяет ему читать или сохранять изменения непосредственно в файлах и папках на устройстве пользователя. Помимо чтения и записи файлов, API доступа к файловой системе предоставляет возможность открыть каталог и перечислить его содержимое.
Если вы уже работали с чтением и записью файлов, многое из того, что я сейчас расскажу, вам покажется знакомым. В любом случае, я рекомендую вам прочитать это, потому что не все системы одинаковы.
API доступа к файловой системе поддерживается большинством браузеров Chromium на Windows, macOS, ChromeOS, Linux и Android. Заметным исключением является Brave, где в настоящее время он доступен только за флагом .
Использование API доступа к файловой системе
Чтобы продемонстрировать возможности и полезность API доступа к файловой системе, я написал текстовый редактор для одного файла. Он позволяет открывать текстовый файл, редактировать его, сохранять изменения на диск или создавать новый файл и сохранять изменения на диск. В нём нет ничего сложного, но он предоставляет достаточно возможностей, чтобы помочь вам понять основные концепции.
Поддержка браузеров
Обнаружение признаков
Чтобы узнать, поддерживается ли API доступа к файловой системе, проверьте, существует ли интересующий вас метод выбора.
if ('showOpenFilePicker' in self) {
// The `showOpenFilePicker()` method of the File System Access API is supported.
}
Попробуйте!
Посмотрите, как работает API доступа к файловой системе, в демонстрационном примере текстового редактора .
Прочитать файл из локальной файловой системы
Первый вариант использования, который я хочу рассмотреть, — это предложить пользователю выбрать файл, а затем открыть и прочитать этот файл с диска.
Предложите пользователю выбрать файл для чтения.
Точкой входа в API доступа к файловой системе является window.showOpenFilePicker() . При вызове он отображает диалоговое окно выбора файла и предлагает пользователю выбрать файл. После выбора файла API возвращает массив дескрипторов файлов. Необязательный параметр options позволяет влиять на поведение средства выбора файла, например, позволяя пользователю выбирать несколько файлов, каталогов или файлы разных типов. Без указания каких-либо параметров средство выбора файла позволяет пользователю выбрать один файл. Это идеально подходит для текстового редактора.
Как и многие другие мощные API, вызов функции showOpenFilePicker() должен выполняться в безопасном контексте и должен осуществляться внутри пользовательского жеста.
let fileHandle;
butOpenFile.addEventListener('click', async () => {
// Destructure the one-element array.
[fileHandle] = await window.showOpenFilePicker();
// Do something with the file handle.
});
После того как пользователь выберет файл, showOpenFilePicker() возвращает массив дескрипторов, в данном случае одноэлементный массив, содержащий один FileSystemFileHandle , который включает свойства и методы, необходимые для взаимодействия с файлом.
Полезно сохранить ссылку на дескриптор файла, чтобы его можно было использовать позже. Он понадобится для сохранения изменений в файле или для выполнения любых других операций с файлом.
Чтение файла из файловой системы
Теперь, когда у вас есть дескриптор файла, вы можете получить его свойства или получить доступ к самому файлу. Пока что я прочитаю его содержимое. Вызов handle.getFile() возвращает объект File , который содержит объект типа blob. Чтобы получить данные из этого объекта, вызовите один из его методов ( slice() , stream() , text() или arrayBuffer() ).
const file = await fileHandle.getFile();
const contents = await file.text();
Объект File , возвращаемый методом FileSystemFileHandle.getFile() , доступен для чтения только до тех пор, пока базовый файл на диске не изменился. Если файл на диске изменен, объект File становится недоступным для чтения, и вам потребуется снова вызвать getFile() , чтобы получить новый объект File для чтения измененных данных.
Собираем всё воедино
Когда пользователи нажимают кнопку «Открыть» , в браузере отображается окно выбора файла. После выбора файла приложение считывает его содержимое и помещает его в <textarea> .
let fileHandle;
butOpenFile.addEventListener('click', async () => {
[fileHandle] = await window.showOpenFilePicker();
const file = await fileHandle.getFile();
const contents = await file.text();
textArea.value = contents;
});
Записать файл в локальную файловую систему.
В текстовом редакторе есть два способа сохранить файл: «Сохранить» и «Сохранить как» . «Сохранить» записывает изменения обратно в исходный файл, используя ранее полученный дескриптор файла. « Сохранить как» же создает новый файл и, следовательно, требует нового дескриптора файла.
Создайте новый файл
Для сохранения файла вызовите метод showSaveFilePicker() , который отобразит окно выбора файла в режиме "сохранения", позволяя пользователю выбрать новый файл для сохранения. Для текстового редактора я также хотел, чтобы он автоматически добавлял расширение .txt , поэтому я добавил несколько дополнительных параметров.
async function getNewFileHandle() {
const options = {
types: [
{
description: 'Text Files',
accept: {
'text/plain': ['.txt'],
},
},
],
};
const handle = await window.showSaveFilePicker(options);
return handle;
}
Сохраните изменения на диск.
Весь код для сохранения изменений в файле вы найдете в моей демонстрационной версии текстового редактора на GitHub . Основные взаимодействия с файловой системой описаны в файле fs-helpers.js . В самом простом виде процесс выглядит следующим образом. Я пройдусь по каждому шагу и объясню его.
// fileHandle is an instance of FileSystemFileHandle..
async function writeFile(fileHandle, contents) {
// Create a FileSystemWritableFileStream to write to.
const writable = await fileHandle.createWritable();
// Write the contents of the file to the stream.
await writable.write(contents);
// Close the file and write the contents to disk.
await writable.close();
}
Запись данных на диск осуществляется с помощью объекта FileSystemWritableFileStream , подкласса WritableStream . Создание потока осуществляется путем вызова метода createWritable() для объекта дескриптора файла. При вызове createWritable() браузер сначала проверяет, предоставил ли пользователь разрешение на запись в файл. Если разрешение на запись не предоставлено, браузер запрашивает у пользователя разрешение. Если разрешение не предоставлено, createWritable() генерирует исключение DOMException , и приложение не сможет записывать данные в файл. В текстовом редакторе обработка исключений DOMException осуществляется в методе saveFile() .
Метод write() принимает строку, что необходимо для текстового редактора. Но он также может принимать объект BufferSource или объект Blob . Например, вы можете напрямую передать в него поток данных:
async function writeURLToFile(fileHandle, url) {
// Create a FileSystemWritableFileStream to write to.
const writable = await fileHandle.createWritable();
// Make an HTTP request for the contents.
const response = await fetch(url);
// Stream the response into the file.
await response.body.pipeTo(writable);
// pipeTo() closes the destination pipe by default, no need to close it.
}
Также вы можете использовать seek() или truncate() внутри потока, чтобы обновить файл в определенной позиции или изменить размер файла.
Указание предлагаемого имени файла и начального каталога.
Во многих случаях может потребоваться, чтобы ваше приложение предлагало имя файла или местоположение по умолчанию. Например, текстовый редактор может предложить имя файла по умолчанию Untitled Text.txt вместо Untitled . Этого можно добиться, передав свойство suggestedName в качестве параметра функции showSaveFilePicker .
const fileHandle = await self.showSaveFilePicker({
suggestedName: 'Untitled Text.txt',
types: [{
description: 'Text documents',
accept: {
'text/plain': ['.txt'],
},
}],
});
То же самое относится и к каталогу запуска по умолчанию. Если вы разрабатываете текстовый редактор, вам может потребоваться запустить диалоговое окно сохранения или открытия файла в папке documents по умолчанию, тогда как для графического редактора — в папке pictures по умолчанию. Вы можете указать каталог запуска по умолчанию, передав свойство startIn методам showSaveFilePicker , showDirectoryPicker() или showOpenFilePicker следующим образом.
const fileHandle = await self.showOpenFilePicker({
startIn: 'pictures'
});
Список известных системных каталогов выглядит следующим образом:
-
desktop: папка рабочего стола пользователя, если таковая существует. -
documents: Каталог, в котором обычно хранятся документы, созданные пользователем. -
downloads: Каталог, где обычно хранятся загруженные файлы. -
music: Каталог, где обычно хранятся аудиофайлы. -
pictures: Каталог, где обычно хранятся фотографии и другие статичные изображения. -
videos: Каталог, где обычно хранятся видео или фильмы.
Помимо общеизвестных системных каталогов, в качестве значения для startIn можно также передать существующий дескриптор файла или каталога. В этом случае диалоговое окно откроется в том же каталоге.
// Assume `directoryHandle` is a handle to a previously opened directory.
const fileHandle = await self.showOpenFilePicker({
startIn: directoryHandle
});
Указание назначения различных средств выбора файлов
Иногда в приложениях используются разные средства выбора файлов для разных целей. Например, текстовый редактор с расширенными возможностями может позволять пользователю открывать текстовые файлы, а также импортировать изображения. По умолчанию каждое средство выбора файлов будет открывать файл в последнем запомненном месте. Этого можно избежать, сохраняя значения id для каждого типа средства выбора. Если id указан, реализация средства выбора файлов запоминает отдельную последнюю использованную директорию для этого id .
const fileHandle1 = await self.showSaveFilePicker({
id: 'openText',
});
const fileHandle2 = await self.showSaveFilePicker({
id: 'importImage',
});
Хранение файловых или директорских дескрипторов в IndexedDB
Дескрипторы файлов и каталогов сериализуемы, это означает, что вы можете сохранить дескриптор файла или каталога в IndexedDB или вызвать postMessage() для их отправки между одним и тем же источником верхнего уровня.
Сохранение дескрипторов файлов или каталогов в IndexedDB позволяет хранить состояние, или запоминать, с какими файлами или каталогами работал пользователь. Это дает возможность вести список недавно открытых или отредактированных файлов, предлагать повторно открыть последний файл при запуске приложения, восстанавливать предыдущий рабочий каталог и многое другое. В текстовом редакторе я сохраняю список из пяти последних открытых пользователем файлов, что позволяет получить к ним доступ снова.
В следующем примере кода показано сохранение и извлечение дескриптора файла и дескриптора каталога. Вы можете увидеть это в действии на Glitch. (Для краткости я использую библиотеку idb-keyval .)
import { get, set } from 'https://unpkg.com/idb-keyval@5.0.2/dist/esm/index.js';
const pre1 = document.querySelector('pre.file');
const pre2 = document.querySelector('pre.directory');
const button1 = document.querySelector('button.file');
const button2 = document.querySelector('button.directory');
// File handle
button1.addEventListener('click', async () => {
try {
const fileHandleOrUndefined = await get('file');
if (fileHandleOrUndefined) {
pre1.textContent = `Retrieved file handle "${fileHandleOrUndefined.name}" from IndexedDB.`;
return;
}
const [fileHandle] = await window.showOpenFilePicker();
await set('file', fileHandle);
pre1.textContent = `Stored file handle for "${fileHandle.name}" in IndexedDB.`;
} catch (error) {
alert(error.name, error.message);
}
});
// Directory handle
button2.addEventListener('click', async () => {
try {
const directoryHandleOrUndefined = await get('directory');
if (directoryHandleOrUndefined) {
pre2.textContent = `Retrieved directroy handle "${directoryHandleOrUndefined.name}" from IndexedDB.`;
return;
}
const directoryHandle = await window.showDirectoryPicker();
await set('directory', directoryHandle);
pre2.textContent = `Stored directory handle for "${directoryHandle.name}" in IndexedDB.`;
} catch (error) {
alert(error.name, error.message);
}
});
Сохраненные дескрипторы файлов или каталогов и права доступа.
Поскольку права доступа не всегда сохраняются между сессиями , следует проверить, предоставил ли пользователь разрешение на доступ к файлу или каталогу, используя queryPermission() . Если нет, вызовите requestPermission() для (повторного) запроса разрешения. Это работает одинаково как для файловых, так и для каталоговых дескрипторов. Вам нужно выполнить fileOrDirectoryHandle.requestPermission(descriptor) или fileOrDirectoryHandle.queryPermission(descriptor) соответственно.
В текстовом редакторе я создал метод verifyPermission() , который проверяет, предоставил ли пользователь уже разрешение, и при необходимости отправляет запрос.
async function verifyPermission(fileHandle, readWrite) {
const options = {};
if (readWrite) {
options.mode = 'readwrite';
}
// Check if permission was already granted. If so, return true.
if ((await fileHandle.queryPermission(options)) === 'granted') {
return true;
}
// Request permission. If the user grants permission, return true.
if ((await fileHandle.requestPermission(options)) === 'granted') {
return true;
}
// The user didn't grant permission, so return false.
return false;
}
Запрашивая разрешение на запись одновременно с запросом на чтение, я уменьшил количество запросов на предоставление прав; пользователь видит один запрос при открытии файла и предоставляет разрешение как на чтение, так и на запись в него.
Открытие каталога и перечисление его содержимого.
Чтобы перечислить все файлы в каталоге, вызовите showDirectoryPicker() . Пользователь выбирает каталог в выпадающем списке, после чего возвращается объект FileSystemDirectoryHandle , который позволяет перечислить и получить доступ к файлам в каталоге. По умолчанию у вас будет доступ на чтение к файлам в каталоге, но если вам нужен доступ на запись, вы можете передать методу параметр { mode: 'readwrite' } .
butDir.addEventListener('click', async () => {
const dirHandle = await window.showDirectoryPicker();
for await (const entry of dirHandle.values()) {
console.log(entry.kind, entry.name);
}
});
Если вам дополнительно необходимо получить доступ к каждому файлу с помощью getFile() , например, чтобы узнать размеры отдельных файлов, не используйте await для каждого результата последовательно, а обрабатывайте все файлы параллельно, например, с помощью Promise.all() .
butDir.addEventListener('click', async () => {
const dirHandle = await window.showDirectoryPicker();
const promises = [];
for await (const entry of dirHandle.values()) {
if (entry.kind !== 'file') {
continue;
}
promises.push(entry.getFile().then((file) => `${file.name} (${file.size})`));
}
console.log(await Promise.all(promises));
});
Создание или доступ к файлам и папкам в каталоге.
Из каталога можно создавать файлы и папки, а также получать к ним доступ, используя методы getFileHandle() или getDirectoryHandle() соответственно. Передав необязательный объект options с ключом create и логическим значением true или false , можно определить, следует ли создавать новый файл или папку, если они еще не существуют.
// In an existing directory, create a new directory named "My Documents".
const newDirectoryHandle = await existingDirectoryHandle.getDirectoryHandle('My Documents', {
create: true,
});
// In this new directory, create a file named "My Notes.txt".
const newFileHandle = await newDirectoryHandle.getFileHandle('My Notes.txt', { create: true });
Определение пути к элементу в каталоге
При работе с файлами или папками в каталоге может быть полезно определить путь к нужному элементу. Это можно сделать с помощью метода resolve() название которого говорит само за себя. Для определения пути элемент может быть прямым или косвенным дочерним элементом каталога.
// Resolve the path of the previously created file called "My Notes.txt".
const path = await newDirectoryHandle.resolve(newFileHandle);
// `path` is now ["My Documents", "My Notes.txt"]
Удаление файлов и папок в каталоге
Если у вас есть доступ к каталогу, вы можете удалить содержащиеся в нем файлы и папки с помощью метода removeEntry() . Для папок удаление может быть рекурсивным и включать все подпапки и содержащиеся в них файлы.
// Delete a file.
await directoryHandle.removeEntry('Abandoned Projects.txt');
// Recursively delete a folder.
await directoryHandle.removeEntry('Old Stuff', { recursive: true });
Удаление файла или папки напрямую
Если у вас есть доступ к дескриптору файла или каталога, вызовите метод remove() для объекта FileSystemFileHandle или FileSystemDirectoryHandle , чтобы удалить его.
// Delete a file.
await fileHandle.remove();
// Delete a directory.
await directoryHandle.remove();
Переименование и перемещение файлов и папок.
Файлы и папки можно переименовать или переместить в новое место, вызвав метод move() интерфейса FileSystemHandle . Интерфейс FileSystemHandle имеет дочерние интерфейсы FileSystemFileHandle и FileSystemDirectoryHandle . Метод move() принимает один или два параметра. Первый может быть либо строкой с новым именем, либо дескриптором FileSystemDirectoryHandle целевой папки. В последнем случае необязательный второй параметр — это строка с новым именем, поэтому перемещение и переименование могут происходить за один шаг.
// Rename the file.
await file.move('new_name');
// Move the file to a new directory.
await file.move(directory);
// Move the file to a new directory and rename it.
await file.move(directory, 'newer_name');
Интеграция функции перетаскивания
Интерфейсы перетаскивания HTML позволяют веб-приложениям принимать перетаскиваемые файлы на веб-страницу. Во время операции перетаскивания перетаскиваемые элементы файлов и каталогов связываются с записями файлов и каталогов соответственно. Метод DataTransferItem.getAsFileSystemHandle() возвращает промис с объектом FileSystemFileHandle , если перетаскиваемый элемент является файлом, и промис с объектом FileSystemDirectoryHandle , если перетаскиваемый элемент является каталогом. Следующий листинг демонстрирует это в действии. Обратите внимание, что DataTransferItem.kind интерфейса Drag and Drop имеет значение "file" как для файлов , так и для каталогов, тогда как FileSystemHandle.kind API доступа к файловой системе имеет значение "file" для файлов и "directory" для каталогов.
elem.addEventListener('dragover', (e) => {
// Prevent navigation.
e.preventDefault();
});
elem.addEventListener('drop', async (e) => {
e.preventDefault();
const fileHandlesPromises = [...e.dataTransfer.items]
.filter((item) => item.kind === 'file')
.map((item) => item.getAsFileSystemHandle());
for await (const handle of fileHandlesPromises) {
if (handle.kind === 'directory') {
console.log(`Directory: ${handle.name}`);
} else {
console.log(`File: ${handle.name}`);
}
}
});
Доступ к исходной частной файловой системе
Исходная частная файловая система — это точка хранения, которая, как следует из названия, является частной для источника страницы. Хотя браузеры обычно реализуют это, сохраняя содержимое этой исходной частной файловой системы на диск, предполагается , что это содержимое не будет доступно пользователю. Аналогично, не предполагается, что существуют файлы или каталоги с именами, совпадающими с именами дочерних элементов исходной частной файловой системы. Хотя браузер может создать впечатление, что файлы существуют, внутри — поскольку это исходная частная файловая система — браузер может хранить эти «файлы» в базе данных или любой другой структуре данных. По сути, если вы используете этот API, не ожидайте найти созданные файлы, точно соответствующие друг другу, где-либо на жестком диске. Вы можете работать с исходной частной файловой системой как обычно, как только получите доступ к корневому объекту FileSystemDirectoryHandle .
const root = await navigator.storage.getDirectory();
// Create a new file handle.
const fileHandle = await root.getFileHandle('Untitled.txt', { create: true });
// Create a new directory handle.
const dirHandle = await root.getDirectoryHandle('New Folder', { create: true });
// Recursively remove a directory.
await root.removeEntry('Old Stuff', { recursive: true });
Доступ к файлам, оптимизированным для повышения производительности, из исходной частной файловой системы.
Исходная частная файловая система предоставляет опциональный доступ к особому типу файлов, оптимизированному для высокой производительности, например, за счет предоставления доступа на запись на месте и эксклюзивного доступа к содержимому файла. В Chromium 102 и более поздних версиях в исходной частной файловой системе есть дополнительный метод для упрощения доступа к файлам: createSyncAccessHandle() (для синхронных операций чтения и записи). Он доступен в FileSystemFileHandle , но исключительно в Web Workers .
// (Read and write operations are synchronous,
// but obtaining the handle is asynchronous.)
// Synchronous access exclusively in Worker contexts.
const accessHandle = await fileHandle.createSyncAccessHandle();
const writtenBytes = accessHandle.write(buffer);
const readBytes = accessHandle.read(buffer, { at: 1 });
Полифиллинг
Полностью реализовать полифил для методов API доступа к файловой системе невозможно.
- Метод
showOpenFilePicker()можно аппроксимировать элементом<input type="file">. - Метод
showSaveFilePicker()можно имитировать с помощью элемента<a download="file_name">, хотя это запускает программную загрузку и не позволяет перезаписывать существующие файлы. - Метод
showDirectoryPicker()можно частично эмулировать с помощью нестандартного элемента<input type="file" webkitdirectory>.
Мы разработали библиотеку browser-fs-access , которая использует API доступа к файловой системе везде, где это возможно, и во всех остальных случаях переключается на следующие оптимальные варианты.
Безопасность и права доступа
Команда Chrome разработала и внедрила API доступа к файловой системе, используя основные принципы, изложенные в документе «Контроль доступа к мощным функциям веб-платформы» , включая пользовательский контроль и прозрачность, а также эргономику.
Открытие файла или сохранение нового файла

При открытии файла пользователь предоставляет разрешение на чтение файла или каталога с помощью средства выбора файла. Средство выбора файла может отображаться только при использовании жеста пользователя в защищенном контексте . Если пользователь передумает, он может отменить выбор в средстве выбора файла, и сайт не получит к нему доступа. Это аналогично поведению элемента <input type="file"> .

Аналогично, когда веб-приложение хочет сохранить новый файл, браузер отображает окно выбора файла, позволяя пользователю указать имя и местоположение нового файла. Поскольку пользователь сохраняет новый файл на устройство (а не перезаписывает существующий), окно выбора файла предоставляет приложению разрешение на запись в файл.
Запретные папки
Для защиты пользователей и их данных браузер может ограничить возможность сохранения файлов в определенные папки, например, в основные папки операционной системы, такие как Windows, или в папку «Библиотека» macOS. В этом случае браузер отобразит запрос и предложит пользователю выбрать другую папку.
Изменение существующего файла или каталога
Веб-приложение не может изменять файл на диске без явного разрешения пользователя.
Запрос разрешения
Если пользователь хочет сохранить изменения в файле, к которому ранее был предоставлен доступ для чтения, браузер отображает запрос на разрешение сайту записывать изменения на диск. Запрос на разрешение может быть инициирован только действием пользователя, например, нажатием кнопки «Сохранить».

В качестве альтернативы, веб-приложение, редактирующее несколько файлов, например, интегрированная среда разработки (IDE), может также запрашивать разрешение на сохранение изменений в момент открытия файла.
Если пользователь выбирает «Отмена» и не предоставляет права на запись, веб-приложение не сможет сохранить изменения в локальном файле. Необходимо предоставить пользователю альтернативный способ сохранения данных, например, возможность «скачать» файл или сохранить данные в облаке.
Прозрачность

После того как пользователь предоставил веб-приложению разрешение на сохранение локального файла, браузер отображает значок в адресной строке. При нажатии на значок открывается всплывающее окно со списком файлов, к которым пользователь предоставил доступ. Пользователь всегда может отозвать этот доступ, если захочет.
Сохранение разрешений
Веб-приложение может продолжать сохранять изменения в файле без запроса подтверждения, пока не будут закрыты все вкладки, относящиеся к исходному файлу. После закрытия вкладки доступ к сайту полностью теряется. При следующем использовании веб-приложения пользователю будет предложено повторно предоставить доступ к файлам.
Обратная связь
Мы хотим услышать о вашем опыте использования API доступа к файловой системе.
Расскажите о проектировании API.
Есть ли что-то в API, что работает не так, как вы ожидали? Или отсутствуют какие-то методы или свойства, необходимые для реализации вашей идеи? Есть вопрос или комментарий по модели безопасности?
- Создайте заявку на обсуждение в репозитории WICG File System Access на GitHub или добавьте свои мысли к уже существующей заявке.
Проблема с реализацией?
Вы обнаружили ошибку в реализации Chrome? Или реализация отличается от спецификации?
- Сообщите об ошибке на сайте https://new.crbug.com . Обязательно укажите как можно больше подробностей, инструкции по воспроизведению и установите для параметра Components значение
Blink>Storage>FileSystem.
Планируете использовать API?
Планируете использовать API доступа к файловой системе на своем сайте? Ваша публичная поддержка помогает нам расставлять приоритеты в разработке новых функций и показывает другим производителям браузеров, насколько важно их поддерживать.
- Поделитесь своими планами по его использованию в ветке обсуждения WICG на форуме.
- Отправьте твит @ChromiumDev , используя хэштег
#FileSystemAccess, и расскажите, где и как вы его используете.
Полезные ссылки
- Публичное пояснение
- Спецификация доступа к файловой системе и спецификация файлов.
- Отслеживание ошибки
- Запись на ChromeStatus.com
- Определения TypeScript
- API доступа к файловой системе — модель безопасности Chromium
- Компонент Blink:
Blink>Storage>FileSystem
Благодарности
Спецификация API доступа к файловой системе была написана Мариной Круссельбринк .