UNPKG

@zenfs/core

Version:

A filesystem, anywhere

122 lines (121 loc) 4.54 kB
// SPDX-License-Identifier: LGPL-3.0-or-later // Utilities and shared data import { Errno, Exception, UV, withErrno } from 'kerium'; import { alert, debug, err, info, notice, warn } from 'kerium/log'; import { InMemory } from '../backends/memory.js'; import { size_max } from '../constants.js'; import { contextOf } from '../internal/contexts.js'; import { credentialsAllowRoot } from '../internal/credentials.js'; import { withExceptionContext } from '../internal/error.js'; import { join, resolve } from '../path.js'; import { normalizePath } from '../utils.js'; /** * The map of mount points * @category Backends and Configuration * @internal */ export const mounts = new Map(); // Set a default root. mount('/', InMemory.create({ label: 'root' })); /** * Mounts the file system at `mountPoint`. * @category Backends and Configuration * @internal */ export function mount(mountPoint, fs) { if (mountPoint[0] != '/') mountPoint = '/' + mountPoint; mountPoint = resolve.call(this, mountPoint); if (mounts.has(mountPoint)) throw err(withErrno('EINVAL', 'Mount point is already in use: ' + mountPoint)); fs._mountPoint = mountPoint; mounts.set(mountPoint, fs); info(`Mounted ${fs.name} on ${mountPoint}`); debug(`${fs.name} attributes: ${[...fs.attributes].map(([k, v]) => (v !== undefined && v !== null ? k + '=' + v : k)).join(', ')}`); } /** * Unmounts the file system at `mountPoint`. * @category Backends and Configuration */ export function umount(mountPoint) { if (mountPoint[0] != '/') mountPoint = '/' + mountPoint; mountPoint = resolve.call(this, mountPoint); if (!mounts.has(mountPoint)) { warn(mountPoint + ' is already unmounted'); return; } mounts.delete(mountPoint); notice('Unmounted ' + mountPoint); } /** * Gets the internal `FileSystem` for the path, then returns it along with the path relative to the FS' root * @internal @hidden */ export function resolveMount(path, ctx, extra) { const { root } = contextOf(ctx); const _exceptionContext = { path, ...extra }; path = normalizePath(join(root, path), true); path = resolve.call(ctx, path); const sortedMounts = [...mounts].sort((a, b) => (a[0].length > b[0].length ? -1 : 1)); // descending order of the string length for (const [mountPoint, fs] of sortedMounts) { // We know path is normalized, so it would be a substring of the mount point. if (!_isParentOf(mountPoint, path)) continue; path = path.slice(mountPoint.length > 1 ? mountPoint.length : 0); // Resolve the path relative to the mount point if (path === '') path = '/'; const case_fold = fs.attributes.get('case_fold'); if (case_fold === 'lower') path = path.toLowerCase(); if (case_fold === 'upper') path = path.toUpperCase(); return { fs: withExceptionContext(fs, _exceptionContext), path, mountPoint, root }; } throw alert(new Exception(Errno.EIO, 'No file system for ' + path)); } /** * @internal @hidden */ export function _statfs(fs, bigint) { const md = fs.usage(); const bs = md.blockSize || 4096; return { type: (bigint ? BigInt : Number)(fs.type), bsize: (bigint ? BigInt : Number)(bs), ffree: (bigint ? BigInt : Number)(md.freeNodes || size_max), files: (bigint ? BigInt : Number)(md.totalNodes || size_max), bavail: (bigint ? BigInt : Number)(md.freeSpace / bs), bfree: (bigint ? BigInt : Number)(md.freeSpace / bs), blocks: (bigint ? BigInt : Number)(md.totalSpace / bs), }; } /** * Change the root path * @category Backends and Configuration */ export function chroot(path) { const $ = contextOf(this); if (!credentialsAllowRoot($.credentials)) throw withErrno('EPERM', 'Can not chroot() as non-root user'); $.root ??= '/'; const newRoot = join($.root, path); for (const handle of $.descriptors?.values() ?? []) { if (!handle.path.startsWith($.root)) throw UV('EBUSY', 'chroot', handle.path); handle.path = handle.path.slice($.root.length); } if (newRoot.length > $.root.length) throw withErrno('EPERM', 'Can not chroot() outside of current root'); $.root = newRoot; } /** * @internal @hidden */ function _isParentOf(parent, child) { if (parent === '/' || parent === child) return true; if (!parent.endsWith('/')) parent += '/'; return child.startsWith(parent); }