from_symbolic.js (5920B)
1 /** 2 * @license Apache-2.0 3 * 4 * Copyright (c) 2018 The Stdlib Authors. 5 * 6 * Licensed under the Apache License, Version 2.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * See the License for the specific language governing permissions and 16 * limitations under the License. 17 */ 18 19 'use strict'; 20 21 // MODULES // 22 23 var lpad = require( '@stdlib/string/left-pad' ); 24 25 26 // VARIABLES // 27 28 // Regular expression to parse a mask expression: 29 var RE_MASK_EXPRESSION = /^(u{0,1}g{0,1}o{0,1}a{0,1}|)([+\-=])(r{0,1}w{0,1}x{0,1})$/; 30 31 // Table of permission bit mask offsets: 32 var PERMS = { 33 'r': 2, // read 34 'w': 1, // write 35 'x': 0 // execute 36 }; 37 38 // Table of class indices in the octal format (e.g., `0o0077`): 39 var WHO = { 40 's': 0, // special mode (ignored; see http://man7.org/linux/man-pages/man2/umask.2.html) 41 'u': 1, // user 42 'g': 2, // group 43 'o': 3 // other/non-group 44 }; 45 46 47 // FUNCTIONS // 48 49 /** 50 * Returns a bit mask. 51 * 52 * @private 53 * @param {NonNegativeInteger} offset - bit offset (right-to-left) 54 * @returns {NonNegativeInteger} bit mask 55 * 56 * @example 57 * var y = bitMask( 3 ); 58 * // returns 8 59 */ 60 function bitMask( offset ) { 61 return ( 1 << offset )>>>0; // asm type annotation 62 } 63 64 /** 65 * Sets a bit. 66 * 67 * @private 68 * @param {NonNegativeInteger} value - value 69 * @param {NonNegativeInteger} offset - bit offset (right-to-left) 70 * @returns {NonNegativeInteger} updated value 71 * 72 * @example 73 * var y = setBit( 8, 2 ); 74 */ 75 function setBit( value, offset ) { 76 return ( value | bitMask( offset ) )>>>0; // asm type annotation 77 } 78 79 /** 80 * Clears a bit. 81 * 82 * @private 83 * @param {NonNegativeInteger} value - value 84 * @param {NonNegativeInteger} offset - bit offset (right-to-left) 85 * @returns {NonNegativeInteger} updated value 86 */ 87 function clearBit( value, offset ) { 88 return ( value & ~bitMask( offset ) )>>>0; // asm type annotation 89 } 90 91 92 // MAIN // 93 94 /** 95 * Converts a mask expression in symbolic notation to an integer. 96 * 97 * @private 98 * @param {NonNegativeInteger} mask - current mask 99 * @param {string} expr - mask expression 100 * @returns {(NonNegativeInteger|Error)} integer mask or parse error 101 */ 102 function fromSymbolic( mask, expr ) { 103 var digits; 104 var parts; 105 var perm; 106 var who; 107 var tmp; 108 var idx; 109 var op; 110 var w; 111 var o; 112 var i; 113 var j; 114 var k; 115 116 // Split the mask into octal digits (e.g., [ '0', '0', '7', '7' ]): 117 digits = lpad( mask.toString( 8 ), 4, '0' ).split( '' ); 118 119 // Convert each octal digit to an integer value: 120 for ( i = 0; i < digits.length; i++ ) { 121 digits[ i ] = parseInt( digits[ i ], 10 ); 122 } 123 124 // See if we can easily split the mask into separate mask expressions (e.g., `u+x,g=rw,o=` => [ 'u+x', 'g=rw', 'o=' ] ): 125 parts = expr.split( ',' ); 126 127 // For each expression, split into "class", "operator", and "symbols" and update the mask octal digits: 128 for ( i = 0; i < parts.length; i++ ) { 129 tmp = parts[ i ].match( RE_MASK_EXPRESSION ); 130 if ( tmp === null ) { 131 return new Error( 'invalid argument. Unable to parse mask expression. Ensure the expression is properly formatted, only uses the class letters "u", "g", "o", and "a", only uses the operators "+", "-", and "=", and only uses the permission symbols "r", "w", and "x". Value: `' + expr + '`.' ); 132 } 133 // Extract the expression parts: 134 who = tmp[ 1 ]; 135 if ( who === '' ) { 136 // If a user class is not specified (e.g., `+x`), "ugo" (user, group, other) is implied... 137 who = 'ugo'; 138 } else { 139 // Replace `a` (all) user class letter with "ugo" (user, group, other) equivalent... 140 w = ''; 141 for ( k = 0; k < who.length; k++ ) { 142 if ( who[ k ] === 'a' ) { 143 w += 'ugo'; 144 } else { 145 w += who[ k ]; 146 } 147 } 148 who = w; 149 } 150 op = tmp[ 2 ]; 151 perm = tmp[ 3 ]; 152 153 // NOTE: the algorithm below is from the perspective of the mask. If implemented for, say, `chmod`, the "disabling"/"enabling" logic would be reversed. Recall that a "1" in the mask, serves to **disable** a permission setting, not enable. 154 155 // Disable permissions... 156 if ( op === '-' ) { 157 if ( perm === '' ) { 158 // The `-` operation by itself does not change any bits... 159 continue; 160 } 161 for ( j = 0; j < perm.length; j++ ) { 162 o = PERMS[ perm[j] ]; 163 for ( k = 0; k < who.length; k++ ) { 164 idx = WHO[ who[k] ]; 165 digits[ idx ] = setBit( digits[ idx ], o ); // to disable, we flip on mask bits 166 } 167 } 168 } 169 // Enable permissions... 170 else if ( op === '+' ) { 171 if ( perm === '' ) { 172 // The `+` operation by itself does not change any bits... 173 continue; 174 } 175 for ( j = 0; j < perm.length; j++ ) { 176 o = PERMS[ perm[j] ]; 177 for ( k = 0; k < who.length; k++ ) { 178 idx = WHO[ who[k] ]; 179 digits[ idx ] = clearBit( digits[ idx ], o ); // to enable, we clear mask bits 180 } 181 } 182 } 183 // Disable all permissions by flipping on all permission mask bits... 184 else if ( perm === '' ) { // op === '=' 185 for ( k = 0; k < who.length; k++ ) { 186 idx = WHO[ who[k] ]; 187 digits[ idx ] = 7; 188 } 189 } 190 // Explicitly set permissions... 191 else { // op === '=' 192 // First, disable all permissions by flipping on all permission mask bits... 193 for ( k = 0; k < who.length; k++ ) { 194 idx = WHO[ who[k] ]; 195 digits[ idx ] = 7; 196 } 197 // Then, explicitly enable permissions by clearing mask bits... 198 for ( j = 0; j < perm.length; j++ ) { 199 o = PERMS[ perm[j] ]; 200 for ( k = 0; k < who.length; k++ ) { 201 idx = WHO[ who[k] ]; 202 digits[ idx ] = clearBit( digits[ idx ], o ); 203 } 204 } 205 } 206 } 207 // Convert the digits to an integer value... 208 for ( i = 0; i < digits.length; i++ ) { 209 digits[ i ] = digits[ i ].toString(); 210 } 211 return parseInt( digits.join( '' ), 8 ); 212 } 213 214 215 // EXPORTS // 216 217 module.exports = fromSymbolic;