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.
486 lines
14 KiB
JavaScript
486 lines
14 KiB
JavaScript
"use strict";
|
|
|
|
const mapObj = require("map-obj");
|
|
const matchValue = require("match-value");
|
|
|
|
const {B, KiB} = require("../unit-bytes-iec");
|
|
const {minutes, seconds, microseconds} = require("../unit-time");
|
|
const parseOctalMode = require("../parse-octal-mode");
|
|
const parseIECBytes = require("../parse-bytes-iec");
|
|
const parseMemparseValue = require("../parse-memparse-value");
|
|
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) => matchValue(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).subMatches;
|
|
return { sizeAsPoolPercentage: parseFloat(percentage) };
|
|
} else {
|
|
return { size: parseIECBytes(value) };
|
|
}
|
|
},
|
|
min_size: (value) => {
|
|
if (value.includes("%")) {
|
|
let [ percentage ] = matchOrError(/^([0-9]+(?:\.[0-9]+))%$/, value).subMatches;
|
|
return { minimumSizeAsPoolPercentage: parseFloat(percentage) };
|
|
} else {
|
|
return { minimumSize: parseIECBytes(value) };
|
|
}
|
|
},
|
|
nr_inodes: { maximumInodes: (value) => parseMemparseValue(value) },
|
|
},
|
|
cgroup: {
|
|
/* TODO */
|
|
},
|
|
cgroup2: {
|
|
/* TODO */
|
|
},
|
|
bpf: {
|
|
/* TODO */
|
|
},
|
|
pstore: {
|
|
/* TODO */
|
|
},
|
|
fuse: {
|
|
/* TODO
|
|
http://man7.org/linux/man-pages/man8/mount.fuse.8.html
|
|
https://www.kernel.org/doc/Documentation/filesystems/fuse.txt
|
|
*/
|
|
user_id: { mountOwnerId: Value },
|
|
group_id: { mountGroupId: Value },
|
|
},
|
|
"fuse.sshfs": {
|
|
// TODO
|
|
[Include]: [ "fuse" ]
|
|
},
|
|
fusectl: {
|
|
// 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: [] });
|
|
};
|