UNPKG

@zenfs/core

Version:

A filesystem, anywhere

364 lines (312 loc) 8.98 kB
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); }