@zenfs/core
Advanced tools
Comparing version 0.5.4 to 0.5.5
@@ -83,5 +83,6 @@ /// <reference types="node" resolution-mode="require"/> | ||
message: string; | ||
path: string; | ||
path?: string; | ||
code: string; | ||
stack: string; | ||
syscall: string; | ||
} | ||
@@ -95,14 +96,6 @@ /** | ||
path?: string; | ||
syscall: string; | ||
static fromJSON(json: ApiErrorJSON): ApiError; | ||
static OnPath(code: ErrorCode, path: string): ApiError; | ||
static EACCES(path: string): ApiError; | ||
static ENOENT(path: string): ApiError; | ||
static EEXIST(path: string): ApiError; | ||
static EISDIR(path: string): ApiError; | ||
static ENOTDIR(path: string): ApiError; | ||
static EPERM(path: string): ApiError; | ||
static ENOTEMPTY(path: string): ApiError; | ||
static With(code: string, path: string, syscall?: string): ApiError; | ||
code: string; | ||
syscall: string; | ||
stack?: string; | ||
/** | ||
@@ -118,3 +111,3 @@ * Represents a ZenFS error. Passed back to applications after a failed | ||
*/ | ||
constructor(errno: ErrorCode, message?: string, path?: string); | ||
constructor(errno: ErrorCode, message?: string, path?: string, syscall?: string); | ||
/** | ||
@@ -121,0 +114,0 @@ * @return A friendly error message. |
@@ -101,3 +101,3 @@ /** | ||
static fromJSON(json) { | ||
const err = new ApiError(json.errno, json.message, json.path); | ||
const err = new ApiError(json.errno, json.message, json.path, json.syscall); | ||
err.code = json.code; | ||
@@ -107,26 +107,5 @@ err.stack = json.stack; | ||
} | ||
static OnPath(code, path) { | ||
return new ApiError(code, ErrorStrings[code], path); | ||
static With(code, path, syscall) { | ||
return new ApiError(ErrorCode[code], ErrorStrings[ErrorCode[code]], path, syscall); | ||
} | ||
static EACCES(path) { | ||
return this.OnPath(ErrorCode.EACCES, path); | ||
} | ||
static ENOENT(path) { | ||
return this.OnPath(ErrorCode.ENOENT, path); | ||
} | ||
static EEXIST(path) { | ||
return this.OnPath(ErrorCode.EEXIST, path); | ||
} | ||
static EISDIR(path) { | ||
return this.OnPath(ErrorCode.EISDIR, path); | ||
} | ||
static ENOTDIR(path) { | ||
return this.OnPath(ErrorCode.ENOTDIR, path); | ||
} | ||
static EPERM(path) { | ||
return this.OnPath(ErrorCode.EPERM, path); | ||
} | ||
static ENOTEMPTY(path) { | ||
return this.OnPath(ErrorCode.ENOTEMPTY, path); | ||
} | ||
/** | ||
@@ -142,8 +121,7 @@ * Represents a ZenFS error. Passed back to applications after a failed | ||
*/ | ||
constructor(errno, message = ErrorStrings[errno], path) { | ||
constructor(errno, message = ErrorStrings[errno], path, syscall = '') { | ||
super(message); | ||
this.errno = errno; | ||
this.path = path; | ||
// Unsupported. | ||
this.syscall = ''; | ||
this.syscall = syscall; | ||
this.code = ErrorCode[errno]; | ||
@@ -165,2 +143,3 @@ this.message = `${this.code}: ${message}${this.path ? `, '${this.path}'` : ''}`; | ||
message: this.message, | ||
syscall: this.syscall, | ||
}; | ||
@@ -167,0 +146,0 @@ } |
@@ -130,6 +130,6 @@ import { dirname, basename, join, resolve } from '../emulation/path.js'; | ||
if (!oldDirNode.toStats().hasAccess(W_OK, cred)) { | ||
throw ApiError.EACCES(oldPath); | ||
throw ApiError.With('EACCES', oldPath, 'rename'); | ||
} | ||
if (!oldDirList[oldName]) { | ||
throw ApiError.ENOENT(oldPath); | ||
throw ApiError.With('ENOENT', oldPath, 'rename'); | ||
} | ||
@@ -172,3 +172,3 @@ const nodeId = oldDirList[oldName]; | ||
// If it's a directory, throw a permissions error. | ||
throw ApiError.EPERM(newPath); | ||
throw ApiError.With('EPERM', newPath, 'rename'); | ||
} | ||
@@ -198,7 +198,7 @@ } | ||
if (!inode) { | ||
throw ApiError.ENOENT(p); | ||
throw ApiError.With('ENOENT', p, 'stat'); | ||
} | ||
const stats = inode.toStats(); | ||
if (!stats.hasAccess(R_OK, cred)) { | ||
throw ApiError.EACCES(p); | ||
throw ApiError.With('EACCES', p, 'stat'); | ||
} | ||
@@ -215,6 +215,6 @@ return stats; | ||
if (!node.toStats().hasAccess(flagToMode(flag), cred)) { | ||
throw ApiError.EACCES(p); | ||
throw ApiError.With('EACCES', p, 'openFile'); | ||
} | ||
if (!data) { | ||
throw ApiError.ENOENT(p); | ||
throw ApiError.With('ENOENT', p, 'openFile'); | ||
} | ||
@@ -230,3 +230,3 @@ return new AsyncFile(this, p, flag, node.toStats(), data); | ||
if (list.length > 0) { | ||
throw ApiError.ENOTEMPTY(p); | ||
throw ApiError.With('ENOTEMPTY', p, 'rmdir'); | ||
} | ||
@@ -243,3 +243,3 @@ await this.removeEntry(p, true, cred); | ||
if (!node.toStats().hasAccess(R_OK, cred)) { | ||
throw ApiError.EACCES(p); | ||
throw ApiError.With('EACCES', p, 'readdur'); | ||
} | ||
@@ -273,7 +273,7 @@ return Object.keys(await this.getDirListing(tx, node, p)); | ||
if (!existingDirNode.toStats().hasAccess(R_OK, cred)) { | ||
throw ApiError.EACCES(existingDir); | ||
throw ApiError.With('EACCES', existingDir, 'link'); | ||
} | ||
const newDir = dirname(newpath), newDirNode = await this.findINode(tx, newDir), newListing = await this.getDirListing(tx, newDirNode, newDir); | ||
if (!newDirNode.toStats().hasAccess(W_OK, cred)) { | ||
throw ApiError.EACCES(newDir); | ||
throw ApiError.With('EACCES', newDir, 'link'); | ||
} | ||
@@ -283,3 +283,3 @@ const ino = await this._findINode(tx, existingDir, basename(existing)); | ||
if (!node.toStats().hasAccess(W_OK, cred)) { | ||
throw ApiError.EACCES(newpath); | ||
throw ApiError.With('EACCES', newpath, 'link'); | ||
} | ||
@@ -352,3 +352,3 @@ node.nlink++; | ||
else { | ||
throw ApiError.ENOENT(resolve(parent, filename)); | ||
throw ApiError.With('ENOENT', resolve(parent, filename), '_findINode'); | ||
} | ||
@@ -370,3 +370,3 @@ } | ||
else { | ||
throw ApiError.ENOENT(resolve(parent, filename)); | ||
throw ApiError.With('ENOENT', resolve(parent, filename), '_findINode'); | ||
} | ||
@@ -393,3 +393,3 @@ } | ||
if (!data) { | ||
throw ApiError.ENOENT(p); | ||
throw ApiError.With('ENOENT', p, 'getINode'); | ||
} | ||
@@ -404,3 +404,3 @@ return new Inode(data.buffer); | ||
if (!inode.toStats().isDirectory()) { | ||
throw ApiError.ENOTDIR(p); | ||
throw ApiError.With('ENOTDIR', p, 'getDirListing'); | ||
} | ||
@@ -414,3 +414,3 @@ const data = await tx.get(inode.ino); | ||
*/ | ||
throw ApiError.ENOENT(p); | ||
throw ApiError.With('ENOENT', p, 'getDirListing'); | ||
} | ||
@@ -450,3 +450,3 @@ return decodeDirListing(data); | ||
if (!parentNode.toStats().hasAccess(W_OK, cred)) { | ||
throw ApiError.EACCES(p); | ||
throw ApiError.With('EACCES', p, 'commitNewFile'); | ||
} | ||
@@ -457,3 +457,3 @@ // Invariant: The root always exists. | ||
if (p === '/') { | ||
throw ApiError.EEXIST(p); | ||
throw ApiError.With('EEXIST', p, 'commitNewFile'); | ||
} | ||
@@ -463,3 +463,3 @@ // Check if file already exists. | ||
await tx.abort(); | ||
throw ApiError.EEXIST(p); | ||
throw ApiError.With('EEXIST', p, 'commitNewFile'); | ||
} | ||
@@ -503,3 +503,3 @@ try { | ||
if (!parentListing[fileName]) { | ||
throw ApiError.ENOENT(p); | ||
throw ApiError.With('ENOENT', p, 'removeEntry'); | ||
} | ||
@@ -510,3 +510,3 @@ const fileIno = parentListing[fileName]; | ||
if (!fileNode.toStats().hasAccess(W_OK, cred)) { | ||
throw ApiError.EACCES(p); | ||
throw ApiError.With('EACCES', p, 'removeEntry'); | ||
} | ||
@@ -516,6 +516,6 @@ // Remove from directory listing of parent. | ||
if (!isDir && fileNode.toStats().isDirectory()) { | ||
throw ApiError.EISDIR(p); | ||
throw ApiError.With('EISDIR', p, 'removeEntry'); | ||
} | ||
if (isDir && !fileNode.toStats().isDirectory()) { | ||
throw ApiError.ENOTDIR(p); | ||
throw ApiError.With('ENOTDIR', p, 'removeEntry'); | ||
} | ||
@@ -522,0 +522,0 @@ try { |
@@ -76,4 +76,4 @@ import { FileSystem } from '../filesystem.js'; | ||
*/ | ||
export interface BackendConfig { | ||
backend: Backend; | ||
export interface BackendConfig<FS extends FileSystem = FileSystem, OC extends BackendOptionsConfig = BackendOptionsConfig> { | ||
backend: Backend<FS, OC>; | ||
[key: string]: unknown; | ||
@@ -89,3 +89,3 @@ } | ||
*/ | ||
export declare function resolveBackend(options: BackendConfig, _depth?: number): Promise<FileSystem>; | ||
export declare function resolveBackend<FS extends FileSystem>(options: BackendConfig<FS>, _depth?: number): Promise<FS>; | ||
export {}; |
@@ -137,3 +137,3 @@ import { FileSystem } from '../filesystem.js'; | ||
if (this._deletedFiles.has(oldPath)) { | ||
throw ApiError.ENOENT(oldPath); | ||
throw ApiError.With('ENOENT', oldPath, 'rename'); | ||
} | ||
@@ -151,3 +151,3 @@ } | ||
if (this._deletedFiles.has(oldPath)) { | ||
throw ApiError.ENOENT(oldPath); | ||
throw ApiError.With('ENOENT', oldPath, 'renameSync'); | ||
} | ||
@@ -163,3 +163,3 @@ } | ||
if (this._deletedFiles.has(p)) { | ||
throw ApiError.ENOENT(p); | ||
throw ApiError.With('ENOENT', p, 'stat'); | ||
} | ||
@@ -179,3 +179,3 @@ const oldStat = new Stats(await this._readable.stat(p, cred)); | ||
if (this._deletedFiles.has(p)) { | ||
throw ApiError.ENOENT(p); | ||
throw ApiError.With('ENOENT', p, 'statSync'); | ||
} | ||
@@ -231,3 +231,3 @@ const oldStat = new Stats(this._readable.statSync(p, cred)); | ||
if (!(await this.exists(p, cred))) { | ||
throw ApiError.ENOENT(p); | ||
throw ApiError.With('ENOENT', p, 'unlink'); | ||
} | ||
@@ -246,3 +246,3 @@ if (await this._writable.exists(p, cred)) { | ||
if (!this.existsSync(p, cred)) { | ||
throw ApiError.ENOENT(p); | ||
throw ApiError.With('ENOENT', p, 'unlinkSync'); | ||
} | ||
@@ -260,3 +260,3 @@ if (this._writable.existsSync(p, cred)) { | ||
if (!(await this.exists(p, cred))) { | ||
throw ApiError.ENOENT(p); | ||
throw ApiError.With('ENOENT', p, 'rmdir'); | ||
} | ||
@@ -269,3 +269,3 @@ if (await this._writable.exists(p, cred)) { | ||
if ((await this.readdir(p, cred)).length > 0) { | ||
throw ApiError.ENOTEMPTY(p); | ||
throw ApiError.With('ENOTEMPTY', p, 'rmdir'); | ||
} | ||
@@ -280,3 +280,3 @@ else { | ||
if (!this.existsSync(p, cred)) { | ||
throw ApiError.ENOENT(p); | ||
throw ApiError.With('ENOENT', p, 'rmdirSync'); | ||
} | ||
@@ -289,3 +289,3 @@ if (this._writable.existsSync(p, cred)) { | ||
if (this.readdirSync(p, cred).length > 0) { | ||
throw ApiError.ENOTEMPTY(p); | ||
throw ApiError.With('ENOTEMPTY', p, 'rmdirSync'); | ||
} | ||
@@ -300,3 +300,3 @@ else { | ||
if (await this.exists(p, cred)) { | ||
throw ApiError.EEXIST(p); | ||
throw ApiError.With('EEXIST', p, 'mkdir'); | ||
} | ||
@@ -310,3 +310,3 @@ // The below will throw should any of the parent directories fail to exist on _writable. | ||
if (this.existsSync(p, cred)) { | ||
throw ApiError.EEXIST(p); | ||
throw ApiError.With('EEXIST', p, 'mkdirSync'); | ||
} | ||
@@ -321,3 +321,3 @@ // The below will throw should any of the parent directories fail to exist on _writable. | ||
if (!dirStats.isDirectory()) { | ||
throw ApiError.ENOTDIR(p); | ||
throw ApiError.With('ENOTDIR', p, 'readdir'); | ||
} | ||
@@ -349,3 +349,3 @@ // Readdir in both, check delete log on RO file system's listing, merge, return. | ||
if (!dirStats.isDirectory()) { | ||
throw ApiError.ENOTDIR(p); | ||
throw ApiError.With('ENOTDIR', p, 'readdirSync'); | ||
} | ||
@@ -422,3 +422,3 @@ // Readdir in both, check delete log on RO file system's listing, merge, return. | ||
if (path == deletionLogPath) { | ||
throw ApiError.EPERM(path); | ||
throw ApiError.With('EPERM', path, 'checkPath'); | ||
} | ||
@@ -460,3 +460,3 @@ } | ||
if (!this.existsSync(p, cred)) { | ||
throw ApiError.ENOENT(p); | ||
throw ApiError.With('ENOENT', p, 'operateOnWriteable'); | ||
} | ||
@@ -471,3 +471,3 @@ if (!this._writable.existsSync(p, cred)) { | ||
if (!(await this.exists(p, cred))) { | ||
throw ApiError.ENOENT(p); | ||
throw ApiError.With('ENOENT', p, 'operateOnWritableAsync'); | ||
} | ||
@@ -474,0 +474,0 @@ if (!(await this._writable.exists(p, cred))) { |
@@ -137,6 +137,6 @@ import { dirname, basename, join, resolve, sep } from '../emulation/path.js'; | ||
if (!oldDirNode.toStats().hasAccess(W_OK, cred)) { | ||
throw ApiError.EACCES(oldPath); | ||
throw ApiError.With('EACCES', oldPath, 'renameSync'); | ||
} | ||
if (!oldDirList[oldName]) { | ||
throw ApiError.ENOENT(oldPath); | ||
throw ApiError.With('ENOENT', oldPath, 'renameSync'); | ||
} | ||
@@ -179,3 +179,3 @@ const ino = oldDirList[oldName]; | ||
// If it's a directory, throw a permissions error. | ||
throw ApiError.EPERM(newPath); | ||
throw ApiError.With('EPERM', newPath, 'renameSync'); | ||
} | ||
@@ -199,3 +199,3 @@ } | ||
if (!stats.hasAccess(R_OK, cred)) { | ||
throw ApiError.EACCES(p); | ||
throw ApiError.With('EACCES', p, 'statSync'); | ||
} | ||
@@ -211,6 +211,6 @@ return stats; | ||
if (!node.toStats().hasAccess(flagToMode(flag), cred)) { | ||
throw ApiError.EACCES(p); | ||
throw ApiError.With('EACCES', p, 'openFileSync'); | ||
} | ||
if (data === null) { | ||
throw ApiError.ENOENT(p); | ||
throw ApiError.With('ENOENT', p, 'openFileSync'); | ||
} | ||
@@ -225,3 +225,3 @@ return new SyncStoreFile(this, p, flag, node.toStats(), data); | ||
if (this.readdirSync(p, cred).length > 0) { | ||
throw ApiError.ENOTEMPTY(p); | ||
throw ApiError.With('ENOTEMPTY', p, 'rmdirSync'); | ||
} | ||
@@ -239,3 +239,3 @@ else { | ||
if (!node.toStats().hasAccess(R_OK, cred)) { | ||
throw ApiError.EACCES(p); | ||
throw ApiError.With('EACCES', p, 'readdirSync'); | ||
} | ||
@@ -267,7 +267,7 @@ return Object.keys(this.getDirListing(tx, node, p)); | ||
if (!existingDirNode.toStats().hasAccess(R_OK, cred)) { | ||
throw ApiError.EACCES(existingDir); | ||
throw ApiError.With('EACCES', existingDir, 'linkSync'); | ||
} | ||
const newDir = dirname(newpath), newDirNode = this.findINode(tx, newDir), newListing = this.getDirListing(tx, newDirNode, newDir); | ||
if (!newDirNode.toStats().hasAccess(W_OK, cred)) { | ||
throw ApiError.EACCES(newDir); | ||
throw ApiError.With('EACCES', newDir, 'linkSync'); | ||
} | ||
@@ -277,3 +277,3 @@ const ino = this._findINode(tx, existingDir, basename(existing)); | ||
if (!node.toStats().hasAccess(W_OK, cred)) { | ||
throw ApiError.EACCES(newpath); | ||
throw ApiError.With('EACCES', newpath, 'linkSync'); | ||
} | ||
@@ -325,3 +325,3 @@ node.nlink++; | ||
if (!(filename in dir)) { | ||
throw ApiError.ENOENT(resolve(parent, filename)); | ||
throw ApiError.With('ENOENT', resolve(parent, filename), '_findINode'); | ||
} | ||
@@ -334,3 +334,3 @@ return dir[filename]; | ||
if (!(filename in dir)) { | ||
throw ApiError.ENOENT(resolve(parent, filename)); | ||
throw ApiError.With('ENOENT', resolve(parent, filename), '_findINode'); | ||
} | ||
@@ -361,3 +361,3 @@ return dir[filename]; | ||
if (!data) { | ||
throw ApiError.ENOENT(p); | ||
throw ApiError.With('ENOENT', p, 'getINode'); | ||
} | ||
@@ -372,7 +372,7 @@ const inode = new Inode(data.buffer); | ||
if (!inode.toStats().isDirectory()) { | ||
throw ApiError.ENOTDIR(p); | ||
throw ApiError.With('ENOTDIR', p, 'getDirListing'); | ||
} | ||
const data = tx.get(inode.ino); | ||
if (!data) { | ||
throw ApiError.ENOENT(p); | ||
throw ApiError.With('ENOENT', p, 'getDirListing'); | ||
} | ||
@@ -409,3 +409,3 @@ return decodeDirListing(data); | ||
if (!parentNode.toStats().hasAccess(W_OK, cred)) { | ||
throw ApiError.EACCES(p); | ||
throw ApiError.With('EACCES', p, 'commitNewFile'); | ||
} | ||
@@ -417,7 +417,7 @@ /* Invariant: The root always exists. | ||
if (p === '/') { | ||
throw ApiError.EEXIST(p); | ||
throw ApiError.With('EEXIST', p, 'commitNewFile'); | ||
} | ||
// Check if file already exists. | ||
if (dirListing[fname]) { | ||
throw ApiError.EEXIST(p); | ||
throw ApiError.With('EEXIST', p, 'commitNewFile'); | ||
} | ||
@@ -452,3 +452,3 @@ const fileNode = new Inode(); | ||
if (!fileIno) { | ||
throw ApiError.ENOENT(p); | ||
throw ApiError.With('ENOENT', p, 'removeEntry'); | ||
} | ||
@@ -458,3 +458,3 @@ // Get file inode. | ||
if (!fileNode.toStats().hasAccess(W_OK, cred)) { | ||
throw ApiError.EACCES(p); | ||
throw ApiError.With('EACCES', p, 'removeEntry'); | ||
} | ||
@@ -464,6 +464,6 @@ // Remove from directory listing of parent. | ||
if (!isDir && fileNode.toStats().isDirectory()) { | ||
throw ApiError.EISDIR(p); | ||
throw ApiError.With('EISDIR', p, 'removeEntry'); | ||
} | ||
if (isDir && !fileNode.toStats().isDirectory()) { | ||
throw ApiError.ENOTDIR(p); | ||
throw ApiError.With('ENOTDIR', p, 'removeEntry'); | ||
} | ||
@@ -470,0 +470,0 @@ try { |
@@ -217,6 +217,5 @@ /// <reference types="node" resolution-mode="require"/> | ||
}): Promise<Uint8Array>; | ||
export declare function readFile(filename: PathLike, options: { | ||
encoding?: BufferEncoding; | ||
export declare function readFile(filename: PathLike, options: (Node.BaseEncodingOptions & { | ||
flag?: Node.OpenMode; | ||
} | BufferEncoding): Promise<string>; | ||
}) | BufferEncoding): Promise<string>; | ||
/** | ||
@@ -223,0 +222,0 @@ * Synchronously writes data to a file, replacing the file if it already exists. |
@@ -9,2 +9,3 @@ import { ApiError, ErrorCode } from '../ApiError.js'; | ||
import { dirname, join } from './path.js'; | ||
import { F_OK } from './constants.js'; | ||
export class FileHandle { | ||
@@ -102,3 +103,3 @@ constructor( | ||
async readFile(_options) { | ||
const options = normalizeOptions(_options, null, 'r', null); | ||
const options = normalizeOptions(_options, null, 'r', 0o444); | ||
const flag = parseFlag(options.flag); | ||
@@ -213,11 +214,14 @@ if (!isReadable(flag)) { | ||
newPath = normalizePath(newPath); | ||
const { path: old } = resolveFS(oldPath); | ||
const { fs, path } = resolveFS(newPath); | ||
const src = resolveFS(oldPath); | ||
const dst = resolveFS(newPath); | ||
try { | ||
const data = await readFile(oldPath); | ||
await writeFile(newPath, data); | ||
if (src.mountPoint == dst.mountPoint) { | ||
await src.fs.rename(src.path, dst.path, cred); | ||
return; | ||
} | ||
await writeFile(newPath, await readFile(oldPath)); | ||
await unlink(oldPath); | ||
} | ||
catch (e) { | ||
throw fixError(e, { [old]: oldPath, [path]: newPath }); | ||
throw fixError(e, { [src.path]: oldPath, [dst.path]: newPath }); | ||
} | ||
@@ -285,3 +289,3 @@ } | ||
case ActionType.THROW: | ||
throw ApiError.EEXIST(path); | ||
throw ApiError.With('EEXIST', path, '_open'); | ||
case ActionType.TRUNCATE: | ||
@@ -314,7 +318,7 @@ /* | ||
if (parentStats && !parentStats.isDirectory()) { | ||
throw ApiError.ENOTDIR(dirname(path)); | ||
throw ApiError.With('ENOTDIR', dirname(path), '_open'); | ||
} | ||
return await doOp('createFile', resolveSymlinks, path, flag, mode, cred); | ||
case ActionType.THROW: | ||
throw ApiError.ENOENT(path); | ||
throw ApiError.With('ENOENT', path, '_open'); | ||
default: | ||
@@ -535,3 +539,3 @@ throw new ApiError(ErrorCode.EINVAL, 'Invalid file flag'); | ||
if (await exists(path)) { | ||
throw ApiError.EEXIST(path); | ||
throw ApiError.With('EEXIST', path, 'symlink'); | ||
} | ||
@@ -677,3 +681,3 @@ await writeFile(path, target); | ||
*/ | ||
export async function access(path, mode = 0o600) { | ||
export async function access(path, mode = F_OK) { | ||
const stats = await stat(path); | ||
@@ -680,0 +684,0 @@ if (!stats.hasAccess(mode, cred)) { |
/// <reference types="node" resolution-mode="require"/> | ||
/// <reference types="node" resolution-mode="require"/> | ||
import { Cred } from '../cred.js'; | ||
import { FileSystem } from '../filesystem.js'; | ||
import type { File } from '../file.js'; | ||
import type { BaseEncodingOptions, OpenMode, WriteFileOptions } from 'node:fs'; | ||
/** | ||
@@ -29,5 +31,11 @@ * converts Date or number to a integer UNIX timestamp | ||
* Normalizes options | ||
* @param options options to normalize | ||
* @param encoding default encoding | ||
* @param flag default flag | ||
* @param mode default mode | ||
* @internal | ||
*/ | ||
export declare function normalizeOptions(options: unknown, defEnc: string | null, defFlag: string, defMode: number | null): { | ||
export declare function normalizeOptions(options?: WriteFileOptions | (BaseEncodingOptions & { | ||
flag?: OpenMode; | ||
}), encoding?: BufferEncoding, flag?: string, mode?: number): { | ||
encoding: BufferEncoding; | ||
@@ -34,0 +42,0 @@ flag: string; |
@@ -26,13 +26,11 @@ // Utilities and shared data | ||
export function normalizeMode(mode, def) { | ||
switch (typeof mode) { | ||
case 'number': | ||
// (path, flag, mode, cb?) | ||
return mode; | ||
case 'string': | ||
// (path, flag, modeString, cb?) | ||
const trueMode = parseInt(mode, 8); | ||
if (!isNaN(trueMode)) { | ||
return trueMode; | ||
} | ||
if (typeof mode == 'number') { | ||
return mode; | ||
} | ||
if (typeof mode == 'string') { | ||
const parsed = parseInt(mode, 8); | ||
if (!isNaN(parsed)) { | ||
return parsed; | ||
} | ||
} | ||
if (typeof def == 'number') { | ||
@@ -65,41 +63,31 @@ return def; | ||
// Node doesn't allow null characters in paths. | ||
if (p.indexOf('\u0000') >= 0) { | ||
if (p.includes('\x00')) { | ||
throw new ApiError(ErrorCode.EINVAL, 'Path must be a string without null bytes.'); | ||
} | ||
if (p === '') { | ||
if (p.length == 0) { | ||
throw new ApiError(ErrorCode.EINVAL, 'Path must not be empty.'); | ||
} | ||
p = p.replaceAll(/\/+/g, '/'); | ||
return resolve(p); | ||
return resolve(p.replaceAll(/[/\\]+/g, '/')); | ||
} | ||
/** | ||
* Normalizes options | ||
* @param options options to normalize | ||
* @param encoding default encoding | ||
* @param flag default flag | ||
* @param mode default mode | ||
* @internal | ||
*/ | ||
export function normalizeOptions(options, defEnc, defFlag, defMode) { | ||
// typeof null === 'object' so special-case handing is needed. | ||
switch (options === null ? 'null' : typeof options) { | ||
case 'object': | ||
return { | ||
encoding: typeof options['encoding'] !== 'undefined' ? options['encoding'] : defEnc, | ||
flag: typeof options['flag'] !== 'undefined' ? options['flag'] : defFlag, | ||
mode: normalizeMode(options['mode'], defMode), | ||
}; | ||
case 'string': | ||
return { | ||
encoding: options, | ||
flag: defFlag, | ||
mode: defMode, | ||
}; | ||
case 'null': | ||
case 'undefined': | ||
case 'function': | ||
return { | ||
encoding: defEnc, | ||
flag: defFlag, | ||
mode: defMode, | ||
}; | ||
default: | ||
throw new TypeError(`"options" must be a string or an object, got ${typeof options} instead.`); | ||
export function normalizeOptions(options, encoding = 'utf8', flag, mode = 0) { | ||
if (typeof options != 'object' || options === null) { | ||
return { | ||
encoding: typeof options == 'string' ? options : encoding, | ||
flag, | ||
mode, | ||
}; | ||
} | ||
return { | ||
encoding: typeof options?.encoding == 'string' ? options.encoding : encoding, | ||
flag: typeof options?.flag == 'string' ? options.flag : flag, | ||
mode: normalizeMode('mode' in options ? options?.mode : null, mode), | ||
}; | ||
} | ||
@@ -106,0 +94,0 @@ /** |
@@ -79,7 +79,5 @@ /// <reference types="node" resolution-mode="require"/> | ||
}): Uint8Array; | ||
export declare function readFileSync(filename: string, options: { | ||
encoding: string; | ||
export declare function readFileSync(filename: string, options: (Node.BaseEncodingOptions & { | ||
flag?: string; | ||
}): string; | ||
export declare function readFileSync(filename: string, encoding: string): string; | ||
}) | BufferEncoding): string; | ||
/** | ||
@@ -110,4 +108,3 @@ * Synchronously writes data to a file, replacing the file if it already | ||
*/ | ||
export declare function appendFileSync(filename: string, data: FileContents, options?: Node.WriteFileOptions): void; | ||
export declare function appendFileSync(filename: string, data: FileContents, encoding?: string): void; | ||
export declare function appendFileSync(filename: string, data: FileContents, arg3?: Node.WriteFileOptions): void; | ||
/** | ||
@@ -114,0 +111,0 @@ * Synchronous `fstat`. |
@@ -34,4 +34,3 @@ import { ApiError, ErrorCode } from '../ApiError.js'; | ||
} | ||
const data = readFileSync(oldPath); | ||
writeFileSync(newPath, data); | ||
writeFileSync(newPath, readFileSync(oldPath)); | ||
unlinkSync(oldPath); | ||
@@ -109,7 +108,7 @@ } | ||
if (!parentStats.isDirectory()) { | ||
throw ApiError.ENOTDIR(dirname(path)); | ||
throw ApiError.With('ENOTDIR', dirname(path), '_openSync'); | ||
} | ||
return doOp('createFileSync', resolveSymlinks, path, flag, mode, cred); | ||
case ActionType.THROW: | ||
throw ApiError.ENOENT(path); | ||
throw ApiError.With('ENOENT', path, '_openSync'); | ||
default: | ||
@@ -120,3 +119,3 @@ throw new ApiError(ErrorCode.EINVAL, 'Invalid FileFlag object.'); | ||
if (!stats.hasAccess(mode, cred)) { | ||
throw ApiError.EACCES(path); | ||
throw ApiError.With('EACCES', path, '_openSync'); | ||
} | ||
@@ -126,3 +125,3 @@ // File exists. | ||
case ActionType.THROW: | ||
throw ApiError.EEXIST(path); | ||
throw ApiError.With('EEXIST', path, '_openSync'); | ||
case ActionType.TRUNCATE: | ||
@@ -235,2 +234,13 @@ // Delete file. | ||
} | ||
/** | ||
* Asynchronously append data to a file, creating the file if it not yet | ||
* exists. | ||
* | ||
* @param filename | ||
* @param data | ||
* @param options | ||
* @option options encoding Defaults to `'utf8'`. | ||
* @option options mode Defaults to `0644`. | ||
* @option options flag Defaults to `'a'`. | ||
*/ | ||
export function appendFileSync(filename, data, arg3) { | ||
@@ -422,3 +432,3 @@ const options = normalizeOptions(arg3, 'utf8', 'a', 0o644); | ||
if (existsSync(path)) { | ||
throw ApiError.EEXIST(path); | ||
throw ApiError.With('EEXIST', path, 'symlinkSync'); | ||
} | ||
@@ -425,0 +435,0 @@ writeFileSync(path, target); |
@@ -32,2 +32,6 @@ import type { FileSystem } from './filesystem.js'; | ||
export declare function flagToNumber(flag: string): number; | ||
/** | ||
* Parses a flag as a mode (W_OK, R_OK, and/or X_OK) | ||
* @param flag the flag to parse | ||
*/ | ||
export declare function flagToMode(flag: string): number; | ||
@@ -34,0 +38,0 @@ export declare function isReadable(flag: string): boolean; |
@@ -89,2 +89,6 @@ import { ApiError, ErrorCode } from './ApiError.js'; | ||
} | ||
/** | ||
* Parses a flag as a mode (W_OK, R_OK, and/or X_OK) | ||
* @param flag the flag to parse | ||
*/ | ||
export function flagToMode(flag) { | ||
@@ -327,11 +331,12 @@ let mode = 0; | ||
} | ||
this._buffer.set(buffer.slice(offset, offset + length), position); | ||
const len = this._buffer.byteOffset; | ||
const slice = buffer.slice(offset, offset + length); | ||
this._buffer.set(slice, position); | ||
const bytesWritten = slice.byteLength; | ||
this.stats.mtimeMs = Date.now(); | ||
if (isSynchronous(this.flag)) { | ||
this.syncSync(); | ||
return len; | ||
return bytesWritten; | ||
} | ||
this.position = position + len; | ||
return len; | ||
this.position = position + bytesWritten; | ||
return bytesWritten; | ||
} | ||
@@ -394,5 +399,2 @@ /** | ||
chmodSync(mode) { | ||
if (!this.fs.metadata().supportsProperties) { | ||
throw new ApiError(ErrorCode.ENOTSUP); | ||
} | ||
this._dirty = true; | ||
@@ -416,5 +418,2 @@ this.stats.chmod(mode); | ||
chownSync(uid, gid) { | ||
if (!this.fs.metadata().supportsProperties) { | ||
throw new ApiError(ErrorCode.ENOTSUP); | ||
} | ||
this._dirty = true; | ||
@@ -428,5 +427,2 @@ this.stats.chown(uid, gid); | ||
utimesSync(atime, mtime) { | ||
if (!this.fs.metadata().supportsProperties) { | ||
throw new ApiError(ErrorCode.ENOTSUP); | ||
} | ||
this._dirty = true; | ||
@@ -433,0 +429,0 @@ this.stats.atime = atime; |
@@ -309,3 +309,3 @@ import { ApiError, ErrorCode } from './ApiError.js'; | ||
if (!inode) { | ||
throw ApiError.ENOENT(path); | ||
throw ApiError.With('ENOENT', path, 'stat'); | ||
} | ||
@@ -323,3 +323,3 @@ if (inode.isDirectory()) { | ||
if (!inode) { | ||
throw ApiError.ENOENT(path); | ||
throw ApiError.With('ENOENT', path, 'statSync'); | ||
} | ||
@@ -342,6 +342,6 @@ if (inode.isDirectory()) { | ||
if (!inode) { | ||
throw ApiError.ENOENT(path); | ||
throw ApiError.With('ENOENT', path, 'openFile'); | ||
} | ||
if (!inode.toStats().hasAccess(flagToMode(flag), cred)) { | ||
throw ApiError.EACCES(path); | ||
throw ApiError.With('EACCESS', path, 'openFile'); | ||
} | ||
@@ -362,6 +362,6 @@ if (inode.isDirectory()) { | ||
if (!inode) { | ||
throw ApiError.ENOENT(path); | ||
throw ApiError.With('ENOENT', path, 'openFileSync'); | ||
} | ||
if (!inode.toStats().hasAccess(flagToMode(flag), cred)) { | ||
throw ApiError.EACCES(path); | ||
throw ApiError.With('EACCES', path, 'openFileSync'); | ||
} | ||
@@ -378,3 +378,3 @@ if (inode.isDirectory()) { | ||
if (!inode) { | ||
throw ApiError.ENOENT(path); | ||
throw ApiError.With('ENOENT', path, 'readdir'); | ||
} | ||
@@ -384,3 +384,3 @@ if (inode.isDirectory()) { | ||
} | ||
throw ApiError.ENOTDIR(path); | ||
throw ApiError.With('ENOTDIR', path, 'readdir'); | ||
} | ||
@@ -391,3 +391,3 @@ readdirSync(path) { | ||
if (!inode) { | ||
throw ApiError.ENOENT(path); | ||
throw ApiError.With('ENOENT', path, 'readdirSync'); | ||
} | ||
@@ -397,3 +397,3 @@ if (inode.isDirectory()) { | ||
} | ||
throw ApiError.ENOTDIR(path); | ||
throw ApiError.With('ENOTDIR', path, 'readdirSync'); | ||
} | ||
@@ -400,0 +400,0 @@ } |
@@ -149,5 +149,4 @@ /// <reference types="node" resolution-mode="require"/> | ||
* Checks if a given user/group has access to this item | ||
* @param mode The request access as 4 bits (unused, read, write, execute) | ||
* @param uid The requesting UID | ||
* @param gid The requesting GID | ||
* @param mode The requested access, combination of W_OK, R_OK, and X_OK | ||
* @param cred The requesting credentials | ||
* @returns True if the request has access, false if the request does not | ||
@@ -154,0 +153,0 @@ * @internal |
@@ -1,2 +0,2 @@ | ||
import { S_IFDIR, S_IFLNK, S_IFMT, S_IFREG } from './emulation/constants.js'; | ||
import { S_IFDIR, S_IFLNK, S_IFMT, S_IFREG, S_IRWXG, S_IRWXO, S_IRWXU } from './emulation/constants.js'; | ||
/** | ||
@@ -148,5 +148,4 @@ * Indicates the type of the given file. Applied to 'mode'. | ||
* Checks if a given user/group has access to this item | ||
* @param mode The request access as 4 bits (unused, read, write, execute) | ||
* @param uid The requesting UID | ||
* @param gid The requesting GID | ||
* @param mode The requested access, combination of W_OK, R_OK, and X_OK | ||
* @param cred The requesting credentials | ||
* @returns True if the request has access, false if the request does not | ||
@@ -160,20 +159,5 @@ * @internal | ||
} | ||
const perms = this.mode & ~S_IFMT; | ||
let uMode = 0xf, gMode = 0xf, wMode = 0xf; | ||
if (cred.euid == this.uid) { | ||
const uPerms = (0xf00 & perms) >> 8; | ||
uMode = (mode ^ uPerms) & mode; | ||
} | ||
if (cred.egid == this.gid) { | ||
const gPerms = (0xf0 & perms) >> 4; | ||
gMode = (mode ^ gPerms) & mode; | ||
} | ||
const wPerms = 0xf & perms; | ||
wMode = (mode ^ wPerms) & mode; | ||
/* | ||
Result = 0b0xxx (read, write, execute) | ||
If any bits are set that means the request does not have that permission. | ||
*/ | ||
const result = uMode & gMode & wMode; | ||
return !result; | ||
// Mask for | ||
const adjusted = (cred.uid == this.uid ? S_IRWXU : 0) | (cred.gid == this.gid ? S_IRWXG : 0) | S_IRWXO; | ||
return (mode & this.mode & adjusted) == mode; | ||
} | ||
@@ -180,0 +164,0 @@ /** |
@@ -106,2 +106,3 @@ import { ApiError, ErrorCode } from './ApiError.js'; | ||
case 'ascii': | ||
return new Uint8Array(Array.from(input).map(char => char.charCodeAt(0) & 0x7f)); | ||
case 'latin1': | ||
@@ -128,3 +129,3 @@ case 'binary': | ||
case 'base64': | ||
return encode(atob(input), 'utf-8'); | ||
return encode(atob(input), 'binary'); | ||
case 'base64url': | ||
@@ -156,2 +157,5 @@ return encode(input.replace('_', '/').replace('-', '+'), 'base64'); | ||
case 'ascii': | ||
return Array.from(input) | ||
.map(char => String.fromCharCode(char & 0x7f)) | ||
.join(''); | ||
case 'latin1': | ||
@@ -192,3 +196,3 @@ case 'binary': | ||
case 'base64': | ||
return btoa(decode(input, 'utf-8')); | ||
return btoa(decode(input, 'binary')); | ||
case 'base64url': | ||
@@ -195,0 +199,0 @@ return decode(input, 'base64').replace('/', '_').replace('+', '-'); |
{ | ||
"name": "@zenfs/core", | ||
"version": "0.5.4", | ||
"version": "0.5.5", | ||
"description": "A filesystem in your browser", | ||
@@ -5,0 +5,0 @@ "main": "dist/index.js", |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
1474716
11304