UNPKG

@zenfs/core

Version:

A filesystem, anywhere

197 lines (196 loc) 6.74 kB
// SPDX-License-Identifier: LGPL-3.0-or-later import { Buffer } from 'buffer'; import { rethrow, setUVMessage, UV } from 'kerium'; import { Attributes, hasAccess } from '../internal/inode.js'; import { normalizePath } from '../utils.js'; import { checkAccess } from './config.js'; import { R_OK, W_OK } from '../constants.js'; import { resolveMount } from './shared.js'; const _allowedRestrictedNames = []; /** * Check permission for the attribute name. * For now, only attributes in the 'user' namespace are supported. * @throws ENOTSUP for attributes in namespaces other than 'user' */ function checkName($, name, path, syscall) { if (!name.startsWith('user.') && !_allowedRestrictedNames.includes(name)) throw UV('ENOTSUP', syscall, path); } export async function get(path, name, opt = {}) { path = normalizePath(path); const { fs, path: resolved } = resolveMount(path, this); checkName(this, name, path, 'xattr.get'); const inode = await fs.stat(resolved).catch(rethrow('xattr.get', path)); if (checkAccess && !hasAccess(this, inode, R_OK)) throw UV('EACCES', 'xattr.get', path); inode.attributes ??= new Attributes(); const value = inode.attributes.get(name); if (!value) throw UV('ENODATA', 'xattr.get', path); const buffer = Buffer.from(value); return opt.encoding == 'buffer' || !opt.encoding ? buffer : buffer.toString(opt.encoding); } export function getSync(path, name, opt = {}) { path = normalizePath(path); checkName(this, name, path, 'xattr.get'); const { fs, path: resolved } = resolveMount(path, this); let inode; try { inode = fs.statSync(resolved); } catch (e) { throw setUVMessage(Object.assign(e, { path })); } if (checkAccess && !hasAccess(this, inode, R_OK)) throw UV('EACCES', 'xattr.get', path); inode.attributes ??= new Attributes(); const value = inode.attributes.get(name); if (!value) throw UV('ENODATA', 'xattr.get', path); const buffer = Buffer.from(value); return opt.encoding == 'buffer' || !opt.encoding ? buffer : buffer.toString(opt.encoding); } /** * Sets the value of an extended attribute. * * @param path Path to the file * @param name Name of the attribute to set * @param value Value to set * @param opt Options for the operation */ export async function set(path, name, value, opt = {}) { path = normalizePath(path); const { fs, path: resolved } = resolveMount(path, this); checkName(this, name, path, 'xattr.set'); const inode = await fs.stat(resolved).catch(rethrow('xattr.set', path)); if (checkAccess && !hasAccess(this, inode, W_OK)) throw UV('EACCES', 'xattr.set', path); inode.attributes ??= new Attributes(); const attr = inode.attributes.get(name); if (opt.create && attr) throw UV('EEXIST', 'xattr.set', path); if (opt.replace && !attr) throw UV('ENODATA', 'xattr.set', path); inode.attributes.set(name, Buffer.from(value)); await fs.touch(resolved, inode).catch(rethrow('xattr.set', path)); } /** * Synchronously sets the value of an extended attribute. * * @param path Path to the file * @param name Name of the attribute to set * @param value Value to set * @param opt Options for the operation */ export function setSync(path, name, value, opt = {}) { path = normalizePath(path); const { fs, path: resolved } = resolveMount(path, this); checkName(this, name, path, 'xattr.set'); let inode; try { inode = fs.statSync(resolved); } catch (e) { throw setUVMessage(Object.assign(e, { path })); } if (checkAccess && !hasAccess(this, inode, W_OK)) throw UV('EACCES', 'xattr.set', path); inode.attributes ??= new Attributes(); const attr = inode.attributes.get(name); if (opt.create && attr) throw UV('EEXIST', 'xattr.set', path); if (opt.replace && !attr) throw UV('ENODATA', 'xattr.set', path); inode.attributes.set(name, Buffer.from(value)); try { fs.touchSync(resolved, inode); } catch (e) { throw setUVMessage(Object.assign(e, { path })); } } /** * Removes an extended attribute from a file. * * @param path Path to the file * @param name Name of the attribute to remove */ export async function remove(path, name) { path = normalizePath(path); const { fs, path: resolved } = resolveMount(path, this); checkName(this, name, path, 'xattr.remove'); const inode = await fs.stat(resolved).catch(rethrow('xattr.remove', path)); if (checkAccess && !hasAccess(this, inode, W_OK)) throw UV('EACCES', 'xattr.remove', path); inode.attributes ??= new Attributes(); const attr = inode.attributes.get(name); if (!attr) throw UV('ENODATA', 'xattr.remove', path); inode.attributes.remove(name); await fs.touch(resolved, inode); } /** * Synchronously removes an extended attribute from a file. * * @param path Path to the file * @param name Name of the attribute to remove */ export function removeSync(path, name) { path = normalizePath(path); const { fs, path: resolved } = resolveMount(path, this); checkName(this, name, path, 'xattr.remove'); let inode; try { inode = fs.statSync(resolved); } catch (e) { throw setUVMessage(Object.assign(e, { path })); } if (checkAccess && !hasAccess(this, inode, W_OK)) throw UV('EACCES', 'xattr.remove', path); inode.attributes ??= new Attributes(); const attr = inode.attributes.get(name); if (!attr) throw UV('ENODATA', 'xattr.remove', path); inode.attributes.remove(name); try { fs.touchSync(resolved, inode); } catch (e) { throw setUVMessage(Object.assign(e, { path })); } } /** * Lists all extended attributes of a file. * * @param path Path to the file * @returns Array of attribute names */ export async function list(path) { path = normalizePath(path); const { fs, path: resolved } = resolveMount(path, this); const inode = await fs.stat(resolved).catch(rethrow('xattr.list', path)); if (!inode.attributes) return []; return inode.attributes.keys().toArray(); } /** * Synchronously lists all extended attributes of a file. * * @param path Path to the file * @returns Array of attribute names */ export function listSync(path) { path = normalizePath(path); const { fs, path: resolved } = resolveMount(path, this); let inode; try { inode = fs.statSync(resolved); } catch (e) { throw setUVMessage(Object.assign(e, { path })); } if (!inode.attributes) return []; return inode.attributes.keys().toArray(); }