"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 parseMemparseValue = require ( "./memparse" ) ;
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 ) => parseMemparseValue ( 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 : [ ] } ) ;
} ;