You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
cvm/src/parse/mount-options.js

469 lines
14 KiB
JavaScript

"use strict";
const mapObj = require("map-obj");
const {B, KiB} = require("../units/bytes/iec");
const {minutes, seconds, microseconds} = require("../units/time");
const mapValue = require("../map-value");
const parseOctalMode = require("./octal-mode");
const parseIECBytes = require("./bytes/iec");
const matchOrError = require("../match-or-error");
let Value = (value) => value;
let NumericValue = (value) => parseInt(value);
let ByteValue = (value) => B(parseInt(value));
let Include = Symbol("Include");
let All = Symbol("All");
function MappedValue(mapping) {
return (value) => mapValue(value, mapping);
}
let mountOptionMap = {
// https://www.systutorials.com/docs/linux/man/8-mount/
/* TODO: UDF / iso9660? */
/* TODO: sshfs, fuseiso, and other FUSE-y things? */
[All]: {
async: { asynchronous: true },
sync: { asynchronous: false },
atime: { updateAccessTime: true },
noatime: { updateAccessTime: false },
auto: { automaticallyMountable: true },
noauto: { automaticallyMountable: false },
dev: { allowDeviceNodes: true },
nodev: { allowDeviceNodes: false },
diratime: { updateDirectoryAccessTime: true },
nodiratime: { updateDirectoryAccessTime: false },
dirsync: { asynchronousDirectoryUpdates: false },
exec: { allowExecution: true },
noexec: { allowExecution: false },
iversion: { incrementIVersion: true },
noiversion: { incrementIVersion: false },
mand: { allowMandatoryLocks: true },
nomand: { allowMandatoryLocks: true },
_netdev: { requiresNetworkAccess: true },
nofail: { reportMountingErrors: false },
relatime: { updateAccessTimeRelatively: true },
norelatime: { updateAccessTimeRelatively: false },
strictatime: { updateAccessTimeStrictly: true },
nostrictatime: { updateAccessTimeStrictly: false },
suid: { allowSetUIDBits: true },
nosuid: { allowSetUIDBits: false },
rw: { writable: true },
ro: { writable: false },
user: {
userMountable: true,
allowExecution: false,
allowSetUIDBits: false,
allowDeviceNodes: false
},
nouser: { userMountable: false },
users: {
freelyMountable: true,
allowExecution: false,
allowSetUIDBits: false,
allowDeviceNodes: false
},
_rnetdev: {
requiresNetworkAccess: true,
allowOfflineChecks: true
},
owner: {
owner: Value,
allowSetUIDBits: false,
allowDeviceNodes: false
},
group: {
group: Value,
allowSetUIDBits: false,
allowDeviceNodes: false
},
defaults: {
writable: true,
allowSetUIDBits: true,
allowDeviceNodes: true,
allowExecution: true,
automaticallyMountable: true,
userMountable: false,
asynchronous: true,
updateAccessTimeRelatively: true
},
/* Various specific filesystems support the below options */
errors: { onError: Value },
},
_filesystemModes: {
uid: { filesystemOwnerId: Value },
gid: { filesystemGroupId: Value },
mode: { filesystemPermissions: (value) => parseOctalMode(value) },
},
tmpfs: {
uid: { rootOwnerId: Value },
gid: { rootGroupId: Value },
mode: { rootPermissions: (value) => parseOctalMode(value) },
size: { size: ByteValue },
nr_blocks: { blockCount: NumericValue },
nr_inodes: { maximumInodes: NumericValue },
mpol: { numaPolicy: Value },
},
devtmpfs: {
[Include]: ["tmpfs"]
},
// https://www.systutorials.com/docs/linux/man/5-ext4/
ext2: {
acl: { supportACL: true },
noacl: { supportACL: false },
bsddf: { showOnlyUsableSpace: true },
minixdf: { showOnlyUsableSpace: false },
check: (value) => {
if (value === "none") {
return { checkFilesystemOnMount: false };
}
},
nocheck: { checkFilesystemOnMount: false },
debug: { printDebugInformation: true },
grpid: { useGroupIDFromDirectory: true },
bsdgroups: { useGroupIDFromDirectory: true },
nogrpid: { useGroupIDFromDirectory: false },
sysvgroups: { useGroupIDFromDirectory: false },
grpquota: { enableGroupQuota: true },
usrquota: { enableUserQuota: true },
quota: { enableUserQuota: true },
noquota: {
enableUserQuota: false,
enableGroupQuota: false
},
bh: { attachBufferHeads: true },
nobh: { attachBufferHeads: false },
nouid32: { allow32bitIdentifiers: false },
oldalloc: { allocator: "old" },
orlov: { allocator: "orlov" },
resgid: { reservedSpaceForGroupID: NumericValue },
resuid: { reservedSpaceForUserID: NumericValue },
sb: { superblockIndex: NumericValue },
user_xattr: { allowExtendedAttributes: true },
nouser_xattr: { allowExtendedAttributes: false },
},
ext3: {
[Include]: ["ext2"],
journal: (value) => {
if (value === "update") {
return { updateJournalFormat: true };
} else {
return { journalInode: parseInt(value) };
}
},
journal_dev: { journalDeviceNumber: Value },
journal_path: { journalPath: Value },
norecovery: { processJournal: false },
noload: { processJournal: false },
data: { journalingMode: Value },
data_err: {
onBufferError: MappedValue({
ignore: "ignoreError",
abort: "abortJournal"
})
},
barrier: (value) => {
if (value === "0") {
return { enableWriteBarriers: false };
} else if (value === "1") {
return { enableWriteBarriers: true };
} else {
throw new Error(`Invalid value for 'barrier': ${value}`);
}
},
commit: (value) => {
if (value === "0") {
return { commitInterval: null };
} else {
return { commitInterval: seconds(parseInt(value)) };
}
},
jqfmt: { quotaSystem: Value },
usrjquota: { userQuotaFile: Value },
grpjquota: { groupQuotaFile: Value }
},
ext4: {
[Include]: ["ext3"],
journal_checksum: { enableJournalChecksumming: true },
nojournal_checksum: { enableJournalChecksumming: false },
journal_async_commit: {
asynchronousJournalCommits: true,
enableJournalChecksumming: true
},
barrier: (value) => {
if (value === "1" || value === true) {
return { enableWriteBarriers: true };
} else if (value === "0") {
return { enableWriteBarriers: false };
} else {
throw new Error(`Invalid value for 'barrier': ${value}`);
}
},
nobarrier: { enableWriteBarriers: false },
inode_readahead_blks: { inodeBlockReadAheadLimit: NumericValue },
stripe: { stripeBlocks: NumericValue },
delalloc: { allowDeferredAllocations: true },
nodelalloc: { allowDeferredAllocations: false },
max_batch_time: { batchingTimeLimit: (value) => microseconds(parseInt(value)) },
min_batch_time: { batchingTimeMinimum: (value) => microseconds(parseInt(value)) },
journal_ioprio: { journalIOPriority: NumericValue },
abort: { simulateAbort: true },
auto_da_alloc: { automaticallySynchronizeBeforeRename: true },
noauto_da_alloc: { automaticallySynchronizeBeforeRename: false },
noinit_itable: { enableInodeTableBlockInitialization: false },
init_itable: {
enableInodeTableBlockInitialization: true,
inodeTableBlockInitializationDelay: NumericValue
},
discard: { enableHardwareDeleteCalls: true },
nodiscard: { enableHardwareDeleteCalls: false },
resize: { allowAutomaticFilesystemResizing: true },
block_validity: { enableMetadataBlockTracking: true },
noblock_validity: { enableMetadataBlockTracking: false },
dioread_lock: { enableDirectIOReadLocking: true },
nodioread_lock: { enableDirectIOReadLocking: false },
max_dir_size_kb: { directorySizeLimit: (value) => KiB(parseInt(value)) },
i_version: { allow64bitInodeVersions: true },
nombcache: { enableMetadataBlockCache: false },
prjquota: { enableProjectQuota: true }
},
fat: {
[Include]: ["_filesystemModes"],
blocksize: { blockSize: ByteValue },
umask: {
defaultFileMode: (value) => parseOctalMode("666", { mask: value }),
defaultFolderMode: (value) => parseOctalMode("777", { mask: value }),
},
dmask: { defaultFolderMode: (value) => parseOctalMode("777", { mask: value }) },
fmask: { defaultFileMode: (value) => parseOctalMode("666", { mask: value }) },
/* TODO: Figure out a way to make the below nicer, interacting with dmask etc. */
allow_utime: (value) => {
if (value === "2") {
return { timestampChangesAllowedFrom: "everybody" };
} else if (value === "20") {
return { timestampChangesAllowedFrom: "group" };
}
},
check: {
nameStrictness: MappedValue({
r: "relaxed",
relaxed: "relaxed",
n: "normal",
normal: "normal",
s: "strict",
strict: "strict"
})
},
codepage: { codepage: NumericValue },
conv: {
lineEndingConversionPolicy: MappedValue({
b: "binary",
binary: "binary",
t: "text",
text: "text",
a: "auto",
auto: "auto"
})
},
cvf_format: { compressedVolumeFileModule: Value },
cvf_option: { compressedVolumeFileOption: Value },
debug: { printDebugInformation: true },
dos1xfloppy: { enableDOS1xFallbackConfiguration: true },
fat: { allocationTableBitness: NumericValue },
iocharset: { conversionCharacterSet: Value },
nfs: (value) => {
if (value === true || value === "stale_rw") {
return { enableNFSInodeCache: true };
} else if (value === "nostale_ro") {
return {
enableNFSInodeCache: false,
writable: false
};
} else {
throw new Error(`Unrecognized value for 'nfs' option: ${value}`);
}
},
tz: (value) => {
if (value === "UTC") {
return { enableTimezoneConversion: false };
} else {
throw new Error(`Unrecognized value for 'tz' option: ${value}`);
}
},
time_offset: { timestampOffset: (value) => minutes(parseInt(value)) },
quiet: { enableQuietModeFailures: true },
rodir: { enableReadOnlyFlagSupport: true },
showexec: { restrictExecutableModeToWindowsBinaries: true },
sys_immutable: { treatAttrSysFlagAsImmutable: true },
flush: { enableEagerFlushing: true },
usefree: { enableFreeClusterCache: true },
// omitted: dots, nodots, dotsOK=[yes|no]
},
vfat: {
[Include]: ["fat"],
uni_xlate: { enableUnicodeCharacterEscaping: true },
posix: { allowDifferentlyCasedNameConflicts: true },
nonumtail: { preferShortNamesWithoutSequenceNumber: true },
utf8: (value) => {
if (value === true) {
return { supportUtf8: true };
} else if (value === "false" || value === "0" || value === "no") {
return { supportUtf8: false };
} else {
throw new Error(`Unrecognized value for 'utf8' option: ${value}`);
}
},
shortname: { shortNameMode: Value }
},
msdos: {
[Include]: ["fat"]
/* FIXME */
},
umsdos: {
[Include]: ["fat"]
/* FIXME */
},
devpts: {
uid: { newPTYOwnerId: Value },
gid: { newPTYGroupId: Value },
mode: { newPTYPermissions: (value) => parseOctalMode(value) },
newinstance: { isolatePTYs: true },
ptmxmode: { ptmxPermissions: (value) => parseOctalMode(value) }
},
mqueue: {
/* This pseudo-filesystem does not appear to have any specific mount options. */
},
proc: {
/* This pseudo-filesystem does not appear to have any specific mount options. */
},
sysfs: {
// https://www.kernel.org/doc/Documentation/filesystems/sysfs.txt
/* This pseudo-filesystem does not appear to have any specific mount options. */
},
securityfs: {
// https://lwn.net/Articles/153366/
/* This pseudo-filesystem does not appear to have any specific mount options. */
},
efivarfs: {
// https://www.kernel.org/doc/Documentation/filesystems/efivarfs.txt
/* This pseudo-filesystem does not appear to have any specific mount options. */
},
ramfs: {
/* FILEBUG: manpage for `ramfs` incorrectly claims that it has no mount options; it has `mode` (https://github.com/torvalds/linux/blob/master/fs/ramfs/inode.c#L41) */
mode: { rootPermissions: (value) => parseOctalMode(value) },
},
debugfs: {
// https://www.kernel.org/doc/Documentation/filesystems/debugfs.txt
uid: { rootOwnerId: Value },
gid: { rootGroupId: Value },
mode: { rootPermissions: (value) => parseOctalMode(value) },
},
hugetlbfs: {
// https://www.kernel.org/doc/Documentation/vm/hugetlbpage.txt
uid: { rootOwnerId: Value },
gid: { rootGroupId: Value },
mode: { rootPermissions: (value) => parseOctalMode(value, { mask: "6000" }) },
pagesize: { pageSize: (value) => parseIECBytes(value) },
size: (value) => {
if (value.includes("%")) {
let [percentage] = matchOrError(/^([0-9]+(?:\.[0-9]+))%$/, value);
return { sizeAsPoolPercentage: parseFloat(percentage) };
} else {
return { size: parseIECBytes(value) };
}
},
min_size: (value) => {
if (value.includes("%")) {
let [percentage] = matchOrError(/^([0-9]+(?:\.[0-9]+))%$/, value);
return { minimumSizeAsPoolPercentage: parseFloat(percentage) };
} else {
return { minimumSize: parseIECBytes(value) };
}
},
nr_inodes: { maximumInodes: (value) => parseIECBytes(value) },
},
cgroup: {
/* TODO */
},
cgroup2: {
/* TODO */
},
bpf: {
/* TODO */
},
pstore: {
/* TODO */
},
};
function optionsForFilesystem(filesystem) {
let ownOptions = mountOptionMap[filesystem];
if (ownOptions == null) {
throw new Error(`No options found for filesystem '${filesystem}'`);
}
if (ownOptions[Include] != null) {
return ownOptions[Include]
.map((target) => {
return optionsForFilesystem(target);
})
.concat([ownOptions])
.reduce((combined, targetOptions) => {
return Object.assign(combined, targetOptions);
}, {});
} else {
return ownOptions;
}
}
function applyMapping(sourceValue, mapping) {
if (typeof mapping === "function") {
return mapping(sourceValue);
} else {
return mapObj(mapping, (key, value) => {
let mappedValue;
if (value === Value) {
mappedValue = sourceValue;
} else if (typeof value === "function") {
mappedValue = value(sourceValue);
} else {
mappedValue = value;
}
return [key, mappedValue];
});
}
}
module.exports = function parseOptions(filesystem, optionString) {
let optionMap = Object.assign({}, mountOptionMap[All], optionsForFilesystem(filesystem));
return optionString
.split(",")
.map((item) => {
if (item.includes("=")) {
let [key, value] = item.split("=");
return [ key, value ];
} else {
return [ item, true ];
}
})
.reduce(({ parsed, missing }, [key, value]) => {
let mapping = optionMap[key];
if (mapping != null) {
return {
parsed: Object.assign(parsed, applyMapping(value, mapping)),
missing: missing
};
} else {
return {
parsed: parsed,
missing: missing.concat(key)
};
}
}, { parsed: {}, missing: [] });
};