@zenfs/core
Version:
A filesystem, anywhere
364 lines (312 loc) • 8.98 kB
text/typescript
import type * as Node from 'node:fs';
import { credentials, type Credentials } from './credentials.js';
import { S_IFBLK, S_IFCHR, S_IFDIR, S_IFIFO, S_IFLNK, S_IFMT, S_IFREG, S_IFSOCK, S_IRWXG, S_IRWXO, S_IRWXU, size_max } from './emulation/constants.js';
/**
* Indicates the type of a file. Applied to 'mode'.
*/
export type FileType = typeof S_IFREG | typeof S_IFDIR | typeof S_IFLNK;
export interface StatsLike<T extends number | bigint = number | bigint> {
/**
* Size of the item in bytes.
* For directories/symlinks, this is normally the size of the struct that represents the item.
*/
size: T;
/**
* Unix-style file mode (e.g. 0o644) that includes the item type
*/
mode: T;
/**
* Time of last access, since epoch
*/
atimeMs: T;
/**
* Time of last modification, since epoch
*/
mtimeMs: T;
/**
* Time of last time file status was changed, since epoch
*/
ctimeMs: T;
/**
* Time of file creation, since epoch
*/
birthtimeMs: T;
/**
* The id of the user that owns the file
*/
uid: T;
/**
* The id of the group that owns the file
*/
gid: T;
/**
* Inode number
*/
ino: T;
}
/**
* Provides information about a particular entry in the file system.
* Common code used by both Stats and BigIntStats.
*/
export abstract class StatsCommon<T extends number | bigint> implements Node.StatsBase<T>, StatsLike {
protected abstract _isBigint: T extends bigint ? true : false;
protected _convert(arg: number | bigint | string | boolean): T {
return (this._isBigint ? BigInt(arg) : Number(arg)) as T;
}
public get blocks(): T {
return this._convert(Math.ceil(Number(this.size) / 512));
}
/**
* Unix-style file mode (e.g. 0o644) that includes the type of the item.
* Type of the item can be FILE, DIRECTORY, SYMLINK, or SOCKET
*/
public mode: T;
/**
* ID of device containing file
*/
public dev: T = this._convert(0);
/**
* Inode number
*/
public ino: T = this._convert(0);
/**
* Device ID (if special file)
*/
public rdev: T = this._convert(0);
/**
* Number of hard links
*/
public nlink: T = this._convert(1);
/**
* Block size for file system I/O
*/
public blksize: T = this._convert(4096);
/**
* User ID of owner
*/
public uid: T = this._convert(0);
/**
* Group ID of owner
*/
public gid: T = this._convert(0);
/**
* Some file systems stash data on stats objects.
*/
public fileData?: Uint8Array;
/**
* Time of last access, since epoch
*/
public atimeMs: T;
public get atime(): Date {
return new Date(Number(this.atimeMs));
}
public set atime(value: Date) {
this.atimeMs = this._convert(value.getTime());
}
/**
* Time of last modification, since epoch
*/
public mtimeMs: T;
public get mtime(): Date {
return new Date(Number(this.mtimeMs));
}
public set mtime(value: Date) {
this.mtimeMs = this._convert(value.getTime());
}
/**
* Time of last time file status was changed, since epoch
*/
public ctimeMs: T;
public get ctime(): Date {
return new Date(Number(this.ctimeMs));
}
public set ctime(value: Date) {
this.ctimeMs = this._convert(value.getTime());
}
/**
* Time of file creation, since epoch
*/
public birthtimeMs: T;
public get birthtime(): Date {
return new Date(Number(this.birthtimeMs));
}
public set birthtime(value: Date) {
this.birthtimeMs = this._convert(value.getTime());
}
/**
* Size of the item in bytes.
* For directories/symlinks, this is normally the size of the struct that represents the item.
*/
public size: T;
/**
* Creates a new stats instance from a stats-like object. Can be used to copy stats (note)
*/
public constructor({ atimeMs, mtimeMs, ctimeMs, birthtimeMs, uid, gid, size, mode, ino }: Partial<StatsLike> = {}) {
const now = Date.now();
this.atimeMs = this._convert(atimeMs ?? now);
this.mtimeMs = this._convert(mtimeMs ?? now);
this.ctimeMs = this._convert(ctimeMs ?? now);
this.birthtimeMs = this._convert(birthtimeMs ?? now);
this.uid = this._convert(uid ?? 0);
this.gid = this._convert(gid ?? 0);
this.size = this._convert(size ?? 0);
this.ino = this._convert(ino ?? 0);
this.mode = this._convert(mode ?? 0o644 & S_IFREG);
if ((this.mode & S_IFMT) == 0) {
this.mode = (this.mode | this._convert(S_IFREG)) as T;
}
}
public isFile(): boolean {
return (this.mode & S_IFMT) === S_IFREG;
}
public isDirectory(): boolean {
return (this.mode & S_IFMT) === S_IFDIR;
}
public isSymbolicLink(): boolean {
return (this.mode & S_IFMT) === S_IFLNK;
}
public isSocket(): boolean {
return (this.mode & S_IFMT) === S_IFSOCK;
}
public isBlockDevice(): boolean {
return (this.mode & S_IFMT) === S_IFBLK;
}
public isCharacterDevice(): boolean {
return (this.mode & S_IFMT) === S_IFCHR;
}
public isFIFO(): boolean {
return (this.mode & S_IFMT) === S_IFIFO;
}
/**
* Checks if a given user/group has access to this item
* @param mode The requested access, combination of W_OK, R_OK, and X_OK
* @returns True if the request has access, false if the request does not
* @internal
*/
public hasAccess(mode: number): boolean {
if (credentials.euid === 0 || credentials.egid === 0) {
//Running as root
return true;
}
// Mask for
const adjusted = (credentials.uid == this.uid ? S_IRWXU : 0) | (credentials.gid == this.gid ? S_IRWXG : 0) | S_IRWXO;
return (mode & this.mode & adjusted) == mode;
}
/**
* Convert the current stats object into a credentials object
* @internal
*/
public cred(uid: number = Number(this.uid), gid: number = Number(this.gid)): Credentials {
return {
uid,
gid,
suid: Number(this.uid),
sgid: Number(this.gid),
euid: uid,
egid: gid,
};
}
/**
* Change the mode of the file.
* We use this helper function to prevent messing up the type of the file.
* @internal
*/
public chmod(mode: number): void {
this.mode = this._convert((this.mode & S_IFMT) | mode);
}
/**
* Change the owner user/group of the file.
* This function makes sure it is a valid UID/GID (that is, a 32 unsigned int)
* @internal
*/
public chown(uid: number | bigint, gid: number | bigint): void {
uid = Number(uid);
gid = Number(gid);
if (!isNaN(uid) && 0 <= uid && uid < 2 ** 32) {
this.uid = this._convert(uid);
}
if (!isNaN(gid) && 0 <= gid && gid < 2 ** 32) {
this.gid = this._convert(gid);
}
}
public get atimeNs(): bigint {
return BigInt(this.atimeMs) * 1000n;
}
public get mtimeNs(): bigint {
return BigInt(this.mtimeMs) * 1000n;
}
public get ctimeNs(): bigint {
return BigInt(this.ctimeMs) * 1000n;
}
public get birthtimeNs(): bigint {
return BigInt(this.birthtimeMs) * 1000n;
}
}
/**
* Implementation of Node's `Stats`.
*
* Attribute descriptions are from `man 2 stat'
* @see http://nodejs.org/api/fs.html#fs_class_fs_stats
* @see http://man7.org/linux/man-pages/man2/stat.2.html
*/
export class Stats extends StatsCommon<number> implements Node.Stats, StatsLike {
protected _isBigint = false as const;
}
Stats satisfies typeof Node.Stats;
/**
* Stats with bigint
*/
export class BigIntStats extends StatsCommon<bigint> implements Node.BigIntStats, StatsLike {
protected _isBigint = true as const;
}
/**
* Determines if the file stats have changed by comparing relevant properties.
*
* @param left The previous stats.
* @param right The current stats.
* @returns `true` if stats have changed; otherwise, `false`.
* @internal
*/
export function isStatsEqual<T extends number | bigint>(left: StatsCommon<T>, right: StatsCommon<T>): boolean {
return left.size == right.size && +left.atime == +right.atime && +left.mtime == +right.mtime && +left.ctime == +right.ctime && left.mode == right.mode;
}
/** @internal */
export const ZenFsType = 0x7a656e6673; // 'z' 'e' 'n' 'f' 's'
/**
* @hidden
*/
export class StatsFs implements Node.StatsFsBase<number> {
/** Type of file system. */
public type: number = 0x7a656e6673;
/** Optimal transfer block size. */
public bsize: number = 4096;
/** Total data blocks in file system. */
public blocks: number = 0;
/** Free blocks in file system. */
public bfree: number = 0;
/** Available blocks for unprivileged users */
public bavail: number = 0;
/** Total file nodes in file system. */
public files: number = size_max;
/** Free file nodes in file system. */
public ffree: number = size_max;
}
/**
* @hidden
*/
export class BigIntStatsFs implements Node.StatsFsBase<bigint> {
/** Type of file system. */
public type: bigint = 0x7a656e6673n;
/** Optimal transfer block size. */
public bsize: bigint = 4096n;
/** Total data blocks in file system. */
public blocks: bigint = 0n;
/** Free blocks in file system. */
public bfree: bigint = 0n;
/** Available blocks for unprivileged users */
public bavail: bigint = 0n;
/** Total file nodes in file system. */
public files: bigint = BigInt(size_max);
/** Free file nodes in file system. */
public ffree: bigint = BigInt(size_max);
}