@zenfs/core
Version:
A filesystem, anywhere
122 lines (121 loc) • 4.54 kB
JavaScript
// 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);
}