@poe-platform/safe-fs 0.1.629 → 0.1.631
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/safe-fs/bridge/stats.js +1 -1
- package/dist/safe-fs/contracts/descriptor.d.ts +31 -0
- package/dist/safe-fs/contracts/descriptor.js +1 -0
- package/dist/safe-fs/contracts/errors.d.ts +1 -1
- package/dist/safe-fs/contracts/errors.js +1 -1
- package/dist/safe-fs/contracts/filesystem.d.ts +14 -1
- package/dist/safe-fs/core.d.ts +3 -0
- package/dist/safe-fs/core.js +2 -0
- package/dist/safe-fs/fs/capabilities.d.ts +1 -1
- package/dist/safe-fs/fs/capabilities.js +5 -3
- package/dist/safe-fs/fs/descriptor.d.ts +21 -0
- package/dist/safe-fs/fs/descriptor.js +244 -0
- package/dist/safe-fs/fs/devices/index.d.ts +5 -1
- package/dist/safe-fs/fs/devices/index.js +53 -4
- package/dist/safe-fs/fs/memory/index.d.ts +8 -1
- package/dist/safe-fs/fs/memory/index.js +125 -8
- package/dist/safe-fs/fs/memory/limits.d.ts +1 -0
- package/dist/safe-fs/fs/memory/limits.js +7 -1
- package/dist/safe-fs/fs/mount/index.d.ts +5 -1
- package/dist/safe-fs/fs/mount/index.js +69 -6
- package/dist/safe-fs/fs/overlay/index.d.ts +2 -0
- package/dist/safe-fs/fs/overlay/index.js +9 -1
- package/dist/safe-fs/fs/quota/index.js +5 -1
- package/dist/safe-fs/fs/readonly/index.d.ts +2 -0
- package/dist/safe-fs/fs/readonly/index.js +35 -2
- package/dist/safe-fs/fs/real/index.d.ts +3 -0
- package/dist/safe-fs/fs/real/index.js +73 -3
- package/dist/safe-fs/fs/s3/filesystem.d.ts +3 -0
- package/dist/safe-fs/fs/s3/filesystem.js +5 -0
- package/dist/safe-fs/fs/scoped.js +68 -17
- package/dist/safe-fs/fs/webdav/webdav.d.ts +3 -0
- package/dist/safe-fs/fs/webdav/webdav.js +5 -0
- package/dist/safe-fs/index.d.ts +3 -0
- package/dist/safe-fs/index.js +2 -0
- package/dist/safe-fs/python/emscripten.d.ts +14 -0
- package/dist/safe-fs/python/emscripten.js +354 -0
- package/dist/safe-fs/python/filesystem.d.ts +51 -0
- package/dist/safe-fs/python/filesystem.js +216 -0
- package/dist/safe-fs/python/flags.d.ts +3 -0
- package/dist/safe-fs/python/flags.js +14 -0
- package/dist/safe-fs/python/index.d.ts +7 -0
- package/dist/safe-fs/python/index.js +4 -0
- package/dist/safe-fs/python/request.d.ts +2 -0
- package/dist/safe-fs/python/request.js +61 -0
- package/dist/safe-fs/python/stat.d.ts +8 -0
- package/dist/safe-fs/python/stat.js +31 -0
- package/dist/safe-fs/xml.d.ts +3 -0
- package/dist/safe-fs/xml.js +41 -19
- package/package.json +5 -1
|
@@ -19,7 +19,7 @@ export function bridgeStats(stat) {
|
|
|
19
19
|
gid: stat.gid ?? 0,
|
|
20
20
|
rdev: 0,
|
|
21
21
|
size: stat.size,
|
|
22
|
-
blksize: stat.preferredIoBlockSize ?? 4096,
|
|
22
|
+
blksize: stat.ioBlockSize ?? stat.preferredIoBlockSize ?? 4096,
|
|
23
23
|
blocks: Math.ceil(stat.size / 512),
|
|
24
24
|
atimeMs: stat.atimeMs,
|
|
25
25
|
mtimeMs: stat.mtimeMs,
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { FileStat, FsOptions } from "./filesystem.js";
|
|
2
|
+
export interface OpenFileOptions extends FsOptions {
|
|
3
|
+
readonly access: "read" | "write" | "readwrite";
|
|
4
|
+
readonly creation?: "never" | "ifMissing" | "exclusive";
|
|
5
|
+
readonly truncate?: boolean;
|
|
6
|
+
readonly append?: boolean;
|
|
7
|
+
readonly mode?: number;
|
|
8
|
+
readonly synchronization?: "data" | "all";
|
|
9
|
+
}
|
|
10
|
+
export interface FileDescriptorCapabilities {
|
|
11
|
+
readonly position?: boolean;
|
|
12
|
+
readonly readObservation?: boolean;
|
|
13
|
+
readonly openTruncate?: boolean;
|
|
14
|
+
readonly positionedRead: boolean;
|
|
15
|
+
readonly positionedWrite: boolean;
|
|
16
|
+
readonly positionedAppendWrite?: boolean;
|
|
17
|
+
readonly delegateZeroLengthWrite?: boolean;
|
|
18
|
+
readonly truncate: boolean;
|
|
19
|
+
readonly synchronization: "none" | "volatile" | "storage";
|
|
20
|
+
}
|
|
21
|
+
export interface FileDescriptor {
|
|
22
|
+
readonly capabilities: FileDescriptorCapabilities;
|
|
23
|
+
getPosition?(options?: FsOptions): Promise<number>;
|
|
24
|
+
probeRead?(options?: FsOptions): Promise<"ready" | "blocked" | "unknown">;
|
|
25
|
+
stat(options?: FsOptions): Promise<FileStat>;
|
|
26
|
+
read(buffer: Uint8Array, position: number | null, options?: FsOptions): Promise<number>;
|
|
27
|
+
write(buffer: Uint8Array, position: number | null, options?: FsOptions): Promise<number>;
|
|
28
|
+
truncate(length: number, options?: FsOptions): Promise<void>;
|
|
29
|
+
sync(dataOnly: boolean, options?: FsOptions): Promise<void>;
|
|
30
|
+
close(): Promise<void>;
|
|
31
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -27,7 +27,7 @@ declare const descriptions: {
|
|
|
27
27
|
readonly EPERM: "operation not permitted";
|
|
28
28
|
readonly EPIPE: "broken pipe";
|
|
29
29
|
readonly EROFS: "read-only file system";
|
|
30
|
-
readonly ESPIPE: "
|
|
30
|
+
readonly ESPIPE: "illegal seek";
|
|
31
31
|
readonly ETIMEDOUT: "operation timed out";
|
|
32
32
|
readonly EXDEV: "cross-device link not permitted";
|
|
33
33
|
};
|
|
@@ -26,7 +26,7 @@ const descriptions = {
|
|
|
26
26
|
EPERM: "operation not permitted",
|
|
27
27
|
EPIPE: "broken pipe",
|
|
28
28
|
EROFS: "read-only file system",
|
|
29
|
-
ESPIPE: "
|
|
29
|
+
ESPIPE: "illegal seek",
|
|
30
30
|
ETIMEDOUT: "operation timed out",
|
|
31
31
|
EXDEV: "cross-device link not permitted",
|
|
32
32
|
};
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import type { ByteSource } from "./io.js";
|
|
2
|
+
import type { FileDescriptor, OpenFileOptions } from "./descriptor.js";
|
|
3
|
+
export type { FileDescriptor, FileDescriptorCapabilities, OpenFileOptions } from "./descriptor.js";
|
|
2
4
|
export type FileType = "file" | "directory" | "symlink" | "character";
|
|
3
5
|
export type EntryComparison = "same" | "distinct" | "unknown";
|
|
4
6
|
export interface FileStat {
|
|
5
7
|
readonly type: FileType;
|
|
6
8
|
readonly size: number;
|
|
7
9
|
readonly allocatedBytes?: number;
|
|
10
|
+
readonly ioBlockSize?: number;
|
|
8
11
|
readonly preferredIoBlockSize?: number;
|
|
9
12
|
readonly mode: number;
|
|
10
13
|
readonly mtimeMs: number;
|
|
@@ -15,6 +18,8 @@ export interface FileStat {
|
|
|
15
18
|
readonly identityScope?: object | symbol;
|
|
16
19
|
readonly ino?: number;
|
|
17
20
|
readonly dev?: number;
|
|
21
|
+
readonly rdevMajor?: number;
|
|
22
|
+
readonly rdevMinor?: number;
|
|
18
23
|
readonly nlink?: number;
|
|
19
24
|
readonly uid?: number;
|
|
20
25
|
readonly gid?: number;
|
|
@@ -24,6 +29,7 @@ export interface DirectoryEntry {
|
|
|
24
29
|
readonly type: FileType;
|
|
25
30
|
}
|
|
26
31
|
export interface FileSystemCapabilities {
|
|
32
|
+
readonly open?: boolean;
|
|
27
33
|
readonly readOnly?: boolean;
|
|
28
34
|
readonly read?: boolean;
|
|
29
35
|
readonly stat?: boolean;
|
|
@@ -47,6 +53,7 @@ export interface FileSystemCapabilities {
|
|
|
47
53
|
readonly truncate?: boolean;
|
|
48
54
|
readonly streamingAppend?: boolean;
|
|
49
55
|
readonly randomAccessWrite?: boolean;
|
|
56
|
+
readonly independentWriteStreams?: boolean;
|
|
50
57
|
readonly symlinks?: boolean;
|
|
51
58
|
readonly hardlinks?: boolean;
|
|
52
59
|
readonly permissions?: boolean;
|
|
@@ -54,6 +61,7 @@ export interface FileSystemCapabilities {
|
|
|
54
61
|
readonly atomicRename?: boolean;
|
|
55
62
|
readonly atomicFileStaging?: boolean;
|
|
56
63
|
readonly atomicFileMutation?: boolean;
|
|
64
|
+
readonly atomicEntryRemoval?: boolean;
|
|
57
65
|
readonly atomicDirectoryMetadata?: boolean;
|
|
58
66
|
readonly atomicRenameNoReplace?: boolean;
|
|
59
67
|
readonly snapshotRmdir?: boolean;
|
|
@@ -151,10 +159,11 @@ export interface ConditionalWriteFileOptions extends FsOptions {
|
|
|
151
159
|
readonly append?: boolean;
|
|
152
160
|
readonly mode?: number;
|
|
153
161
|
}
|
|
154
|
-
export interface
|
|
162
|
+
export interface ConditionalRemoveEntryOptions extends FsOptions {
|
|
155
163
|
readonly parent: FileStat;
|
|
156
164
|
readonly expected: FileStat;
|
|
157
165
|
}
|
|
166
|
+
export type ConditionalRemoveFileOptions = ConditionalRemoveEntryOptions;
|
|
158
167
|
export interface FileStagingEntry {
|
|
159
168
|
readonly path: string;
|
|
160
169
|
readonly stat: FileStat;
|
|
@@ -190,8 +199,10 @@ export interface PrepareDirectoryOptions extends FsOptions {
|
|
|
190
199
|
}
|
|
191
200
|
export interface FileSystem {
|
|
192
201
|
writeFileConditional?(path: string, data: Uint8Array, options: ConditionalWriteFileOptions): Promise<FileStat>;
|
|
202
|
+
removeEntryConditional?(path: string, options: ConditionalRemoveEntryOptions): Promise<void>;
|
|
193
203
|
removeFileConditional?(path: string, options: ConditionalRemoveFileOptions): Promise<void>;
|
|
194
204
|
readonly capabilities: FileSystemCapabilities;
|
|
205
|
+
open?(path: string, options: OpenFileOptions): Promise<FileDescriptor>;
|
|
195
206
|
prepareDirectory?(path: string, options: PrepareDirectoryOptions): Promise<FileStat>;
|
|
196
207
|
createStagedFile?(directoryPath: string, name: string, content: StagedFileContent, options: CreateStagedFileOptions): Promise<FileStaging>;
|
|
197
208
|
publishStagedFile?(staging: FileStaging, destination: string, options: PublishStagedFileOptions): Promise<void>;
|
|
@@ -211,6 +222,8 @@ export interface FileSystem {
|
|
|
211
222
|
readdir(path: string, options?: ReadDirectoryOptions): Promise<DirectoryEntry[]>;
|
|
212
223
|
mkdir(path: string, options?: MkdirOptions): Promise<void>;
|
|
213
224
|
rm(path: string, options?: RemoveOptions): Promise<void>;
|
|
225
|
+
/** Atomic final-entry removal; every directory is refused, including raced replacements. */
|
|
226
|
+
unlink?(path: string, options?: FsOptions): Promise<void>;
|
|
214
227
|
rmdir?(path: string, options?: FsOptions): Promise<void>;
|
|
215
228
|
rename(source: string, destination: string, options?: RenameOptions): Promise<void>;
|
|
216
229
|
copyFile(source: string, destination: string, options?: CopyFileOptions): Promise<void>;
|
package/dist/safe-fs/core.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export * from "./contracts/errors.js";
|
|
2
2
|
export * from "./contracts/filesystem.js";
|
|
3
3
|
export * from "./contracts/io.js";
|
|
4
|
+
export { openFileDescriptor } from "./fs/descriptor.js";
|
|
5
|
+
export type { DescriptorBackend, DescriptorOpenOptions } from "./fs/descriptor.js";
|
|
4
6
|
export { assertPathWithin, isPathWithin, normalizePath, relativePath, resolvePath, validatePath } from "./contracts/virtual-path.js";
|
|
5
7
|
export { basename, dirname, extname, isAbsolutePath, joinPath, posixPath } from "./contracts/portable-path.js";
|
|
6
8
|
export * from "./fs/memory/index.js";
|
|
@@ -13,6 +15,7 @@ export { scopeFileSystem, retainFileSystemCleanup } from "./fs/scoped.js";
|
|
|
13
15
|
export type { RetainedFileSystemCleanupView, RetainedFileSystemCleanupOptions } from "./fs/scoped.js";
|
|
14
16
|
export * from "./fs/webdav/index.js";
|
|
15
17
|
export * from "./bridge/index.js";
|
|
18
|
+
export * from "./python/index.js";
|
|
16
19
|
export { compareEntries } from "./fs/mount/comparison.js";
|
|
17
20
|
export { parseXml, parseXmlSteps, XmlLimitError } from "./xml.js";
|
|
18
21
|
export type { XmlName, XmlAttribute, XmlContent, XmlElement, XmlLimits } from "./xml.js";
|
package/dist/safe-fs/core.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export * from "./contracts/errors.js";
|
|
2
2
|
export * from "./contracts/filesystem.js";
|
|
3
3
|
export * from "./contracts/io.js";
|
|
4
|
+
export { openFileDescriptor } from "./fs/descriptor.js";
|
|
4
5
|
export { assertPathWithin, isPathWithin, normalizePath, relativePath, resolvePath, validatePath } from "./contracts/virtual-path.js";
|
|
5
6
|
export { basename, dirname, extname, isAbsolutePath, joinPath, posixPath } from "./contracts/portable-path.js";
|
|
6
7
|
export * from "./fs/memory/index.js";
|
|
@@ -12,5 +13,6 @@ export * from "./fs/quota/index.js";
|
|
|
12
13
|
export { scopeFileSystem, retainFileSystemCleanup } from "./fs/scoped.js";
|
|
13
14
|
export * from "./fs/webdav/index.js";
|
|
14
15
|
export * from "./bridge/index.js";
|
|
16
|
+
export * from "./python/index.js";
|
|
15
17
|
export { compareEntries } from "./fs/mount/comparison.js";
|
|
16
18
|
export { parseXml, parseXmlSteps, XmlLimitError } from "./xml.js";
|
|
@@ -7,4 +7,4 @@ export declare function openRetainedResizeFile(filesystem: FileSystem, path: str
|
|
|
7
7
|
export declare function readOnlyCapabilities(capabilities: FileSystemCapabilities): FileSystemCapabilities;
|
|
8
8
|
export declare function quotaCapabilities(capabilities: FileSystemCapabilities): FileSystemCapabilities;
|
|
9
9
|
export declare function ownedMutationCapabilities(filesystem: FileSystem, capabilities?: FileSystemCapabilities): FileSystemCapabilities;
|
|
10
|
-
export declare function requireOwnedMutation(filesystem: FileSystem, path: string, capability: "atomicFileMutation" | "atomicFileStaging" | "atomicDirectoryMetadata", options: FsOptions, create?: boolean): Promise<void>;
|
|
10
|
+
export declare function requireOwnedMutation(filesystem: FileSystem, path: string, capability: "atomicEntryRemoval" | "atomicFileMutation" | "atomicFileStaging" | "atomicDirectoryMetadata", options: FsOptions, create?: boolean): Promise<void>;
|
|
@@ -101,14 +101,14 @@ export async function openRetainedResizeFile(filesystem, path, options) {
|
|
|
101
101
|
export function readOnlyCapabilities(capabilities) {
|
|
102
102
|
const inspection = Object.fromEntries([
|
|
103
103
|
"read", "stat", "readdir", "realpath", "access", "readlink", "explicitDirectories", "implicitDirectories",
|
|
104
|
-
"symlinks", "streamingRead", "retainedRead",
|
|
104
|
+
"symlinks", "streamingRead", "open", "retainedRead",
|
|
105
105
|
].filter(name => capabilities[name] !== undefined).map(name => [name, capabilities[name]]));
|
|
106
106
|
return Object.freeze({
|
|
107
107
|
...inspection, readOnly: true, write: false, append: false, exclusiveCreate: false,
|
|
108
108
|
mkdir: false, recursiveMkdir: false, remove: false, removeDirectory: false, recursiveRemove: false,
|
|
109
109
|
rename: false, copy: false, exclusiveCopy: false, truncate: false, streamingAppend: false,
|
|
110
110
|
randomAccessWrite: false, hardlinks: false, permissions: false, timestamps: false,
|
|
111
|
-
descriptorWriteStream: false, atomicResize: false, retainedResize: false, atomicFileMutation: false, atomicFileStaging: false, atomicDirectoryMetadata: false,
|
|
111
|
+
descriptorWriteStream: false, atomicResize: false, retainedResize: false, atomicFileMutation: false, atomicEntryRemoval: false, atomicFileStaging: false, atomicDirectoryMetadata: false,
|
|
112
112
|
atomicRename: false, atomicRenameNoReplace: false, streamingWrite: false,
|
|
113
113
|
});
|
|
114
114
|
}
|
|
@@ -116,13 +116,15 @@ export function quotaCapabilities(capabilities) {
|
|
|
116
116
|
const streamingWrite = requireCapabilities(capabilities.write, capabilities.append, !capabilities.readOnly);
|
|
117
117
|
const streamingAppend = requireCapabilities(capabilities.append, !capabilities.readOnly);
|
|
118
118
|
const { streamingWrite: ignoredWrite, streamingAppend: ignoredAppend, ...rest } = capabilities;
|
|
119
|
-
return Object.freeze({ ...rest, descriptorWriteStream: false, atomicResize: false, atomicFileMutation: false, atomicFileStaging: false, atomicDirectoryMetadata: false,
|
|
119
|
+
return Object.freeze({ ...rest, open: false, descriptorWriteStream: false, atomicResize: false, atomicFileMutation: false, atomicEntryRemoval: false, atomicFileStaging: false, atomicDirectoryMetadata: false,
|
|
120
120
|
...(streamingWrite === undefined ? {} : { streamingWrite }),
|
|
121
121
|
...(streamingAppend === undefined ? {} : { streamingAppend }),
|
|
122
122
|
});
|
|
123
123
|
}
|
|
124
124
|
export function ownedMutationCapabilities(filesystem, capabilities = filesystem.capabilities) {
|
|
125
125
|
const unavailable = {};
|
|
126
|
+
if (capabilities.atomicEntryRemoval === true && (capabilities.readOnly === true || typeof filesystem.removeEntryConditional !== "function"))
|
|
127
|
+
unavailable.atomicEntryRemoval = false;
|
|
126
128
|
if (capabilities.atomicFileMutation === true && (capabilities.readOnly === true || typeof filesystem.writeFileConditional !== "function" || typeof filesystem.removeFileConditional !== "function"))
|
|
127
129
|
unavailable.atomicFileMutation = false;
|
|
128
130
|
if (capabilities.atomicFileStaging === true && (capabilities.readOnly === true
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { FileDescriptor, FileDescriptorCapabilities, FileStat, FsOptions, OpenFileOptions } from "../contracts/filesystem.js";
|
|
2
|
+
export interface DescriptorOpenOptions extends OpenFileOptions {
|
|
3
|
+
readonly creation: "never" | "ifMissing" | "exclusive";
|
|
4
|
+
readonly truncate: boolean;
|
|
5
|
+
readonly append: boolean;
|
|
6
|
+
readonly mode: number;
|
|
7
|
+
}
|
|
8
|
+
export interface DescriptorBackend<Resource> {
|
|
9
|
+
readonly resource: Resource;
|
|
10
|
+
readonly capabilities?: FileDescriptorCapabilities;
|
|
11
|
+
getPosition?(resource: Resource, options: FsOptions): Promise<number>;
|
|
12
|
+
probeRead?(resource: Resource, options: FsOptions): Promise<"ready" | "blocked" | "unknown">;
|
|
13
|
+
stat(resource: Resource, options: FsOptions): Promise<FileStat>;
|
|
14
|
+
read(resource: Resource, buffer: Uint8Array, position: number | null, options: FsOptions): Promise<number>;
|
|
15
|
+
write(resource: Resource, buffer: Uint8Array, position: number | null, options: FsOptions): Promise<number>;
|
|
16
|
+
truncate(resource: Resource, length: number, options: FsOptions): Promise<void>;
|
|
17
|
+
sync(resource: Resource, dataOnly: boolean, options: FsOptions): Promise<void>;
|
|
18
|
+
close(resource: Resource): Promise<void>;
|
|
19
|
+
}
|
|
20
|
+
export declare function forwardFileDescriptor(descriptor: FileDescriptor, operation: <Result>(syscall: string, options: FsOptions, action: () => Promise<Result>) => Promise<Result>, capabilities?: FileDescriptorCapabilities, snapshot?: (stat: FileStat) => FileStat): DescriptorBackend<FileDescriptor>;
|
|
21
|
+
export declare function openFileDescriptor<Resource>(path: string, options: OpenFileOptions, capabilities: FileDescriptorCapabilities, acquire: (options: DescriptorOpenOptions) => Promise<DescriptorBackend<Resource>>): Promise<FileDescriptor>;
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import { FsError } from "../contracts/errors.js";
|
|
2
|
+
import { finishCleanup } from "../contracts/cleanup.js";
|
|
3
|
+
function admitCapabilities(path, options, capabilities) {
|
|
4
|
+
if (![capabilities.positionedRead, capabilities.positionedWrite, capabilities.truncate].every(value => typeof value === "boolean")
|
|
5
|
+
|| capabilities.position !== undefined && typeof capabilities.position !== "boolean"
|
|
6
|
+
|| capabilities.readObservation !== undefined && typeof capabilities.readObservation !== "boolean"
|
|
7
|
+
|| capabilities.openTruncate !== undefined && typeof capabilities.openTruncate !== "boolean"
|
|
8
|
+
|| capabilities.positionedAppendWrite !== undefined && typeof capabilities.positionedAppendWrite !== "boolean"
|
|
9
|
+
|| capabilities.delegateZeroLengthWrite !== undefined && typeof capabilities.delegateZeroLengthWrite !== "boolean"
|
|
10
|
+
|| !["none", "volatile", "storage"].includes(capabilities.synchronization))
|
|
11
|
+
throw new FsError("EINVAL", { syscall: "open", path });
|
|
12
|
+
if (options.truncate && !(capabilities.openTruncate ?? capabilities.truncate) || options.synchronization !== undefined && capabilities.synchronization === "none") {
|
|
13
|
+
throw new FsError("ENOTSUP", { syscall: "open", path });
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export function forwardFileDescriptor(descriptor, operation, capabilities, snapshot = stat => ({ ...stat })) {
|
|
17
|
+
const retainedCapabilities = Object.freeze({ ...descriptor.capabilities });
|
|
18
|
+
const selected = Object.freeze({ ...(capabilities ?? retainedCapabilities) });
|
|
19
|
+
const getPosition = selected.position === true && retainedCapabilities.position === true ? descriptor.getPosition : undefined;
|
|
20
|
+
if (selected.position === true && typeof getPosition !== "function")
|
|
21
|
+
throw new FsError("ENOTSUP", { syscall: "getPosition" });
|
|
22
|
+
const probeRead = selected.readObservation === true && retainedCapabilities.readObservation === true ? descriptor.probeRead : undefined;
|
|
23
|
+
if (selected.readObservation === true && typeof probeRead !== "function")
|
|
24
|
+
throw new FsError("ENOTSUP", { syscall: "probeRead" });
|
|
25
|
+
return {
|
|
26
|
+
resource: descriptor, capabilities: selected,
|
|
27
|
+
...(getPosition === undefined ? {} : {
|
|
28
|
+
getPosition: (retained, options) => operation("getPosition", options, () => getPosition.call(retained, options)),
|
|
29
|
+
}),
|
|
30
|
+
...(probeRead === undefined ? {} : {
|
|
31
|
+
probeRead: (retained, options) => operation("probeRead", options, () => probeRead.call(retained, options)),
|
|
32
|
+
}),
|
|
33
|
+
stat: (retained, options) => operation("fstat", options, async () => snapshot(await retained.stat(options))),
|
|
34
|
+
read: (retained, buffer, position, options) => operation("read", options, () => retained.read(buffer, position, options)),
|
|
35
|
+
write: (retained, buffer, position, options) => operation("write", options, () => retained.write(buffer, position, options)),
|
|
36
|
+
truncate: (retained, length, options) => operation("ftruncate", options, () => retained.truncate(length, options)),
|
|
37
|
+
sync: (retained, dataOnly, options) => operation(dataOnly ? "fdatasync" : "fsync", options, () => retained.sync(dataOnly, options)),
|
|
38
|
+
close: retained => operation("close", {}, () => retained.close()),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function integer(value, syscall, path) {
|
|
42
|
+
if (!Number.isSafeInteger(value) || value < 0)
|
|
43
|
+
throw new FsError("EINVAL", { syscall, path });
|
|
44
|
+
}
|
|
45
|
+
class ManagedFileDescriptor {
|
|
46
|
+
capabilities;
|
|
47
|
+
#path;
|
|
48
|
+
#access;
|
|
49
|
+
#append;
|
|
50
|
+
#getPosition;
|
|
51
|
+
#probeRead;
|
|
52
|
+
#backend;
|
|
53
|
+
#pending = Promise.resolve();
|
|
54
|
+
#closing;
|
|
55
|
+
constructor(path, options, capabilities, backend) {
|
|
56
|
+
this.#path = path;
|
|
57
|
+
this.#access = options.access;
|
|
58
|
+
this.#append = options.append;
|
|
59
|
+
const getPosition = capabilities.position === true ? backend.getPosition : undefined;
|
|
60
|
+
if (capabilities.position === true && typeof getPosition !== "function")
|
|
61
|
+
throw new FsError("ENOTSUP", { syscall: "getPosition", path });
|
|
62
|
+
this.#getPosition = getPosition?.bind(backend);
|
|
63
|
+
const probeRead = capabilities.readObservation === true ? backend.probeRead : undefined;
|
|
64
|
+
if (capabilities.readObservation === true && typeof probeRead !== "function")
|
|
65
|
+
throw new FsError("ENOTSUP", { syscall: "probeRead", path });
|
|
66
|
+
this.#probeRead = probeRead?.bind(backend);
|
|
67
|
+
this.#backend = backend;
|
|
68
|
+
const positionedAppendWrite = capabilities.positionedAppendWrite === true && capabilities.positionedWrite && options.access !== "read";
|
|
69
|
+
this.capabilities = Object.freeze({
|
|
70
|
+
...(capabilities.position === undefined ? {} : { position: capabilities.position }),
|
|
71
|
+
...(capabilities.readObservation === undefined ? {} : { readObservation: capabilities.readObservation }),
|
|
72
|
+
...(capabilities.openTruncate === undefined ? {} : { openTruncate: capabilities.openTruncate }),
|
|
73
|
+
positionedRead: capabilities.positionedRead && options.access !== "write",
|
|
74
|
+
positionedWrite: capabilities.positionedWrite && options.access !== "read" && (!options.append || positionedAppendWrite),
|
|
75
|
+
...(capabilities.positionedAppendWrite === undefined ? {} : { positionedAppendWrite }),
|
|
76
|
+
...(capabilities.delegateZeroLengthWrite === undefined ? {} : { delegateZeroLengthWrite: capabilities.delegateZeroLengthWrite && options.access !== "read" }),
|
|
77
|
+
truncate: capabilities.truncate && options.access !== "read",
|
|
78
|
+
synchronization: capabilities.synchronization,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
#run(syscall, options, action) {
|
|
82
|
+
const signal = options.signal;
|
|
83
|
+
try {
|
|
84
|
+
signal?.throwIfAborted();
|
|
85
|
+
if (this.#closing || !this.#backend)
|
|
86
|
+
throw new FsError("EBADF", { syscall, path: this.#path });
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
return Promise.reject(error);
|
|
90
|
+
}
|
|
91
|
+
const forwarded = signal === undefined ? {} : { signal };
|
|
92
|
+
const operation = this.#pending.then(async () => {
|
|
93
|
+
signal?.throwIfAborted();
|
|
94
|
+
try {
|
|
95
|
+
const result = await action(this.#backend, forwarded);
|
|
96
|
+
signal?.throwIfAborted();
|
|
97
|
+
return result;
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
signal?.throwIfAborted();
|
|
101
|
+
throw error;
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
this.#pending = operation.then(() => { }, () => { });
|
|
105
|
+
return operation;
|
|
106
|
+
}
|
|
107
|
+
#position(buffer, position, syscall) {
|
|
108
|
+
if (!(buffer instanceof Uint8Array))
|
|
109
|
+
throw new FsError("EINVAL", { syscall, path: this.#path });
|
|
110
|
+
if (position !== null) {
|
|
111
|
+
integer(position, syscall, this.#path);
|
|
112
|
+
if (syscall === "write" && this.#append && this.capabilities.positionedAppendWrite !== true)
|
|
113
|
+
throw new FsError("EINVAL", { syscall, path: this.#path });
|
|
114
|
+
if (!(syscall === "read" ? this.capabilities.positionedRead : this.capabilities.positionedWrite)) {
|
|
115
|
+
throw new FsError("ESPIPE", { syscall, path: this.#path });
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
stat(options = {}) {
|
|
120
|
+
return this.#run("fstat", options, (backend, forwarded) => backend.stat(backend.resource, forwarded));
|
|
121
|
+
}
|
|
122
|
+
getPosition(options = {}) {
|
|
123
|
+
return this.#run("getPosition", options, async (backend, forwarded) => {
|
|
124
|
+
if (!this.capabilities.position || !this.#getPosition)
|
|
125
|
+
throw new FsError("ENOTSUP", { syscall: "getPosition", path: this.#path });
|
|
126
|
+
const position = await this.#getPosition(backend.resource, forwarded);
|
|
127
|
+
if (!Number.isSafeInteger(position) || position < 0)
|
|
128
|
+
throw new FsError("EIO", { syscall: "getPosition", path: this.#path });
|
|
129
|
+
return position;
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
probeRead(options = {}) {
|
|
133
|
+
return this.#run("probeRead", options, async (backend, forwarded) => {
|
|
134
|
+
if (!this.capabilities.readObservation || !this.#probeRead)
|
|
135
|
+
throw new FsError("ENOTSUP", { syscall: "probeRead", path: this.#path });
|
|
136
|
+
const readiness = await this.#probeRead(backend.resource, forwarded);
|
|
137
|
+
if (readiness !== "ready" && readiness !== "blocked" && readiness !== "unknown")
|
|
138
|
+
throw new FsError("EIO", { syscall: "probeRead", path: this.#path });
|
|
139
|
+
return readiness;
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
read(buffer, position, options = {}) {
|
|
143
|
+
return this.#run("read", options, async (backend, forwarded) => {
|
|
144
|
+
if (this.#access === "write")
|
|
145
|
+
throw new FsError("EBADF", { syscall: "read", path: this.#path });
|
|
146
|
+
this.#position(buffer, position, "read");
|
|
147
|
+
if (buffer.byteLength === 0)
|
|
148
|
+
return 0;
|
|
149
|
+
const count = await backend.read(backend.resource, buffer, position, forwarded);
|
|
150
|
+
if (!Number.isSafeInteger(count) || count < 0 || count > buffer.byteLength)
|
|
151
|
+
throw new FsError("EIO", { syscall: "read", path: this.#path });
|
|
152
|
+
return count;
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
write(buffer, position, options = {}) {
|
|
156
|
+
return this.#run("write", options, async (backend, forwarded) => {
|
|
157
|
+
if (this.#access === "read")
|
|
158
|
+
throw new FsError("EBADF", { syscall: "write", path: this.#path });
|
|
159
|
+
this.#position(buffer, position, "write");
|
|
160
|
+
if (buffer.byteLength === 0 && this.capabilities.delegateZeroLengthWrite !== true)
|
|
161
|
+
return 0;
|
|
162
|
+
const count = await backend.write(backend.resource, buffer, position, forwarded);
|
|
163
|
+
if (!Number.isSafeInteger(count) || count < 0 || count > buffer.byteLength)
|
|
164
|
+
throw new FsError("EIO", { syscall: "write", path: this.#path });
|
|
165
|
+
return count;
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
truncate(length, options = {}) {
|
|
169
|
+
return this.#run("ftruncate", options, async (backend, forwarded) => {
|
|
170
|
+
if (this.#access === "read")
|
|
171
|
+
throw new FsError("EBADF", { syscall: "ftruncate", path: this.#path });
|
|
172
|
+
integer(length, "ftruncate", this.#path);
|
|
173
|
+
if (!this.capabilities.truncate)
|
|
174
|
+
throw new FsError("ENOTSUP", { syscall: "ftruncate", path: this.#path });
|
|
175
|
+
await backend.truncate(backend.resource, length, forwarded);
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
sync(dataOnly, options = {}) {
|
|
179
|
+
return this.#run(dataOnly ? "fdatasync" : "fsync", options, async (backend, forwarded) => {
|
|
180
|
+
if (typeof dataOnly !== "boolean")
|
|
181
|
+
throw new FsError("EINVAL", { syscall: "fsync", path: this.#path });
|
|
182
|
+
if (this.capabilities.synchronization === "none")
|
|
183
|
+
throw new FsError("ENOTSUP", { syscall: dataOnly ? "fdatasync" : "fsync", path: this.#path });
|
|
184
|
+
await backend.sync(backend.resource, dataOnly, forwarded);
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
close() {
|
|
188
|
+
this.#closing ??= this.#pending.then(async () => {
|
|
189
|
+
try {
|
|
190
|
+
await this.#backend.close(this.#backend.resource);
|
|
191
|
+
}
|
|
192
|
+
finally {
|
|
193
|
+
this.#backend = undefined;
|
|
194
|
+
this.#getPosition = undefined;
|
|
195
|
+
this.#probeRead = undefined;
|
|
196
|
+
this.#pending = Promise.resolve();
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
return this.#closing;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
export async function openFileDescriptor(path, options, capabilities, acquire) {
|
|
203
|
+
if (!options || typeof options !== "object")
|
|
204
|
+
throw new FsError("EINVAL", { syscall: "open", path });
|
|
205
|
+
const signal = options.signal;
|
|
206
|
+
signal?.throwIfAborted();
|
|
207
|
+
const keys = ["access", "creation", "truncate", "append", "mode", "synchronization", "signal"];
|
|
208
|
+
const { access, creation = "never", truncate = false, append = false, mode = 0o666, synchronization } = options;
|
|
209
|
+
if (Object.keys(options).some(key => !keys.includes(key))
|
|
210
|
+
|| !["read", "write", "readwrite"].includes(access)
|
|
211
|
+
|| !["never", "ifMissing", "exclusive"].includes(creation)
|
|
212
|
+
|| typeof truncate !== "boolean" || typeof append !== "boolean"
|
|
213
|
+
|| access === "read" && (truncate || append)
|
|
214
|
+
|| synchronization !== undefined && !["data", "all"].includes(synchronization)
|
|
215
|
+
|| !Number.isSafeInteger(mode) || mode < 0 || mode > 0o7777) {
|
|
216
|
+
throw new FsError("EINVAL", { syscall: "open", path });
|
|
217
|
+
}
|
|
218
|
+
const admitted = Object.freeze({ access, creation, truncate, append, mode,
|
|
219
|
+
...(signal === undefined ? {} : { signal }), ...(synchronization === undefined ? {} : { synchronization }) });
|
|
220
|
+
const admittedCapabilities = Object.freeze({ ...capabilities });
|
|
221
|
+
admitCapabilities(path, admitted, admittedCapabilities);
|
|
222
|
+
let backend;
|
|
223
|
+
try {
|
|
224
|
+
backend = await acquire(admitted);
|
|
225
|
+
}
|
|
226
|
+
catch (error) {
|
|
227
|
+
signal?.throwIfAborted();
|
|
228
|
+
throw error;
|
|
229
|
+
}
|
|
230
|
+
try {
|
|
231
|
+
signal?.throwIfAborted();
|
|
232
|
+
const selected = Object.freeze({ ...(backend.capabilities ?? admittedCapabilities) });
|
|
233
|
+
signal?.throwIfAborted();
|
|
234
|
+
admitCapabilities(path, admitted, selected);
|
|
235
|
+
const descriptor = new ManagedFileDescriptor(path, admitted, selected, backend);
|
|
236
|
+
signal?.throwIfAborted();
|
|
237
|
+
return descriptor;
|
|
238
|
+
}
|
|
239
|
+
catch (error) {
|
|
240
|
+
await finishCleanup(() => backend.close(backend.resource), true);
|
|
241
|
+
signal?.throwIfAborted();
|
|
242
|
+
throw error;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { FileDescriptor, OpenFileOptions } from "../../contracts/descriptor.js";
|
|
2
|
+
import type { AppendFileOptions, CapabilityQueryOptions, CopyFileOptions, DirectoryEntry, FileReadHandle, FileResizeHandle, FileResizeOperation, FileResizeOptions, FileStat, FileSystem, OpenReadFileOptions, OpenResizeFileOptions, FileSystemCapabilities, FsOptions, RenameOptions, MkdirOptions, ReadDirectoryOptions, ReadFileOptions, ReadStreamOptions, RemoveOptions, WriteFileOptions, ConditionalWriteFileOptions, ConditionalRemoveFileOptions, ConditionalRemoveEntryOptions, CreateStagedFileOptions, FileStaging, PublishStagedFileOptions, PrepareDirectoryOptions, StagedFileContent } from "../../contracts/filesystem.js";
|
|
2
3
|
import type { ByteSource } from "../../contracts/io.js";
|
|
3
4
|
export declare class DeviceFileSystem implements FileSystem {
|
|
4
5
|
#private;
|
|
@@ -13,6 +14,7 @@ export declare class DeviceFileSystem implements FileSystem {
|
|
|
13
14
|
readFile(path: string, options?: ReadFileOptions): Promise<Uint8Array>;
|
|
14
15
|
readStream(path: string, options?: ReadStreamOptions): ByteSource;
|
|
15
16
|
openReadFile(path: string, options?: OpenReadFileOptions): Promise<FileReadHandle>;
|
|
17
|
+
open(path: string, options: OpenFileOptions): Promise<FileDescriptor>;
|
|
16
18
|
resizeFile(path: string, operation: FileResizeOperation, options?: FileResizeOptions): Promise<void>;
|
|
17
19
|
openResizeFile(path: string, options?: OpenResizeFileOptions): Promise<FileResizeHandle>;
|
|
18
20
|
writeFile(path: string, data: Uint8Array, options?: WriteFileOptions): Promise<void>;
|
|
@@ -21,8 +23,10 @@ export declare class DeviceFileSystem implements FileSystem {
|
|
|
21
23
|
readdir(path: string, options?: ReadDirectoryOptions): Promise<DirectoryEntry[]>;
|
|
22
24
|
mkdir(path: string, options?: MkdirOptions): Promise<void>;
|
|
23
25
|
rm(path: string, options?: RemoveOptions): Promise<void>;
|
|
26
|
+
unlink(path: string, options?: FsOptions): Promise<void>;
|
|
24
27
|
rmdir(path: string, options?: FsOptions): Promise<void>;
|
|
25
28
|
writeFileConditional(path: string, data: Uint8Array, options: ConditionalWriteFileOptions): Promise<FileStat>;
|
|
29
|
+
removeEntryConditional(path: string, options: ConditionalRemoveEntryOptions): Promise<void>;
|
|
26
30
|
removeFileConditional(path: string, options: ConditionalRemoveFileOptions): Promise<void>;
|
|
27
31
|
createStagedFile(path: string, name: string, content: StagedFileContent, options: CreateStagedFileOptions): Promise<FileStaging>;
|
|
28
32
|
publishStagedFile(staging: FileStaging, destination: string, options: PublishStagedFileOptions): Promise<void>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { FsError, isFsError } from "../../contracts/errors.js";
|
|
2
|
+
import { finishCleanup } from "../../contracts/cleanup.js";
|
|
2
3
|
import { admitDirectoryEntries, directoryEntryLimit } from "../directory-admission.js";
|
|
3
4
|
import { compareEntries, registerEntryAuthority, registerEntryView } from "../mount/comparison.js";
|
|
4
5
|
import { deviceDirectory, lexicalDevicePath, nullPath, resolveDevicePath } from "./path.js";
|
|
@@ -9,17 +10,18 @@ const views = new WeakMap();
|
|
|
9
10
|
const deviceCapabilities = Object.freeze({
|
|
10
11
|
readOnly: false, read: true, stat: true, realpath: true, access: true, readdir: false,
|
|
11
12
|
write: true, append: true, exclusiveCreate: true, streamingRead: true, retainedRead: true,
|
|
12
|
-
streamingWrite: true, streamingAppend: true, copy: true, exclusiveCopy: true,
|
|
13
|
+
streamingWrite: true, streamingAppend: true, independentWriteStreams: true, copy: true, exclusiveCopy: true,
|
|
13
14
|
remove: false, removeDirectory: false, recursiveRemove: false, rename: false,
|
|
14
15
|
mkdir: false, recursiveMkdir: false, symlinks: false, hardlinks: false, readlink: false,
|
|
15
16
|
permissions: false, timestamps: false, truncate: false, randomAccessWrite: false,
|
|
16
|
-
atomicFileMutation: false, atomicFileStaging: false, atomicDirectoryMetadata: false,
|
|
17
|
+
open: false, atomicFileMutation: false, atomicEntryRemoval: false, atomicFileStaging: false, atomicDirectoryMetadata: false,
|
|
17
18
|
atomicRename: false, atomicRenameNoReplace: false, descriptorWriteStream: true, retainedResize: true, atomicResize: false,
|
|
18
19
|
});
|
|
19
20
|
function globalCapabilities(filesystem) {
|
|
20
21
|
const capabilities = { readOnly: false };
|
|
21
22
|
const optional = {
|
|
22
|
-
|
|
23
|
+
open: ["open"],
|
|
24
|
+
atomicEntryRemoval: ["removeEntryConditional"], atomicFileMutation: ["writeFileConditional", "removeFileConditional"], atomicFileStaging: ["createStagedFile", "publishStagedFile", "removeStagedFile"], atomicDirectoryMetadata: ["prepareDirectory"],
|
|
23
25
|
streamingRead: ["readStream"], streamingWrite: ["writeStream"], retainedRead: ["openReadFile"],
|
|
24
26
|
streamingAppend: ["writeStream"], descriptorWriteStream: ["writeStream"], retainedResize: ["openResizeFile"], atomicResize: ["resizeFile"],
|
|
25
27
|
symlinks: ["symlink", "readlink"], hardlinks: ["link"], permissions: ["chmod"],
|
|
@@ -124,7 +126,7 @@ export class DeviceFileSystem {
|
|
|
124
126
|
return deviceCapabilities;
|
|
125
127
|
if (resolved === deviceDirectory)
|
|
126
128
|
return { ...deviceCapabilities, readdir: true, write: false, append: false,
|
|
127
|
-
exclusiveCreate: false, streamingWrite: false, streamingAppend: false, descriptorWriteStream: false,
|
|
129
|
+
exclusiveCreate: false, streamingWrite: false, streamingAppend: false, independentWriteStreams: false, descriptorWriteStream: false,
|
|
128
130
|
retainedRead: false, retainedResize: false, streamingRead: false, copy: false, exclusiveCopy: false };
|
|
129
131
|
const query = this.#filesystem.capabilitiesFor;
|
|
130
132
|
options.signal?.throwIfAborted();
|
|
@@ -134,6 +136,8 @@ export class DeviceFileSystem {
|
|
|
134
136
|
options.signal?.throwIfAborted();
|
|
135
137
|
const capabilities = observed.retainedResize === true ? retainedResizeCapabilities(this.#filesystem, observed) : observed;
|
|
136
138
|
const unavailable = {};
|
|
139
|
+
if (typeof this.#filesystem.open !== "function")
|
|
140
|
+
unavailable.open = false;
|
|
137
141
|
if (typeof this.#filesystem.readStream !== "function")
|
|
138
142
|
unavailable.streamingRead = false;
|
|
139
143
|
if (typeof this.#filesystem.writeStream !== "function") {
|
|
@@ -229,6 +233,37 @@ export class DeviceFileSystem {
|
|
|
229
233
|
async close() { closed = true; },
|
|
230
234
|
};
|
|
231
235
|
}
|
|
236
|
+
async open(path, options) {
|
|
237
|
+
options?.signal?.throwIfAborted();
|
|
238
|
+
if (!options || typeof options !== "object")
|
|
239
|
+
throw new FsError("EINVAL", { syscall: "open", path });
|
|
240
|
+
const resolved = await this.#resolve(path, options, options.creation !== "exclusive");
|
|
241
|
+
options.signal?.throwIfAborted();
|
|
242
|
+
if (resolved === nullPath || resolved === deviceDirectory)
|
|
243
|
+
throw new FsError("ENOTSUP", { syscall: "open", path });
|
|
244
|
+
const query = this.#filesystem.capabilitiesFor;
|
|
245
|
+
options.signal?.throwIfAborted();
|
|
246
|
+
const capabilities = query && options.creation !== "exclusive" ? await Reflect.apply(query, this.#filesystem, [path, options]) : this.#filesystem.capabilities;
|
|
247
|
+
options.signal?.throwIfAborted();
|
|
248
|
+
const open = this.#filesystem.open;
|
|
249
|
+
options.signal?.throwIfAborted();
|
|
250
|
+
if (capabilities.readOnly === true && (options.access === "write" || options.access === "readwrite" || options.creation === "ifMissing" || options.creation === "exclusive" || options.truncate === true || options.append === true))
|
|
251
|
+
throw new FsError("EROFS", { syscall: "open", path });
|
|
252
|
+
if (capabilities.open === false || typeof open !== "function")
|
|
253
|
+
throw new FsError("ENOTSUP", { syscall: "open", path });
|
|
254
|
+
let descriptor;
|
|
255
|
+
try {
|
|
256
|
+
descriptor = await Reflect.apply(open, this.#filesystem, [path, options]);
|
|
257
|
+
options.signal?.throwIfAborted();
|
|
258
|
+
return descriptor;
|
|
259
|
+
}
|
|
260
|
+
catch (error) {
|
|
261
|
+
if (descriptor)
|
|
262
|
+
await finishCleanup(() => descriptor.close(), true);
|
|
263
|
+
options.signal?.throwIfAborted();
|
|
264
|
+
throw error;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
232
267
|
async resizeFile(path, operation, options = {}) {
|
|
233
268
|
await this.#mutable(path, options);
|
|
234
269
|
const capabilities = await this.capabilitiesFor(path, options);
|
|
@@ -343,6 +378,13 @@ export class DeviceFileSystem {
|
|
|
343
378
|
await this.#mutable(path, options, false);
|
|
344
379
|
await this.#filesystem.rm(path, options);
|
|
345
380
|
}
|
|
381
|
+
async unlink(path, options = {}) {
|
|
382
|
+
await this.#mutable(path, options, false);
|
|
383
|
+
if (!this.#filesystem.unlink)
|
|
384
|
+
throw new FsError("ENOTSUP", { syscall: "unlink", path });
|
|
385
|
+
options.signal?.throwIfAborted();
|
|
386
|
+
await this.#filesystem.unlink(path, options);
|
|
387
|
+
}
|
|
346
388
|
async rmdir(path, options = {}) {
|
|
347
389
|
const resolved = await this.#resolve(path, options, false);
|
|
348
390
|
if (resolved === nullPath)
|
|
@@ -360,6 +402,13 @@ export class DeviceFileSystem {
|
|
|
360
402
|
throw new FsError("ENOTSUP", { path });
|
|
361
403
|
return this.#filesystem.writeFileConditional(path, data, options);
|
|
362
404
|
}
|
|
405
|
+
async removeEntryConditional(path, options) {
|
|
406
|
+
await this.#mutable(path, options, false);
|
|
407
|
+
await requireOwnedMutation(this.#filesystem, path, "atomicEntryRemoval", options);
|
|
408
|
+
if (!this.#filesystem.removeEntryConditional)
|
|
409
|
+
throw new FsError("ENOTSUP", { path });
|
|
410
|
+
await this.#filesystem.removeEntryConditional(path, options);
|
|
411
|
+
}
|
|
363
412
|
async removeFileConditional(path, options) {
|
|
364
413
|
await this.#mutable(path, options, false);
|
|
365
414
|
await requireOwnedMutation(this.#filesystem, path, "atomicFileMutation", options);
|