Commit 27a26f51 authored by adam j hartz's avatar adam j hartz

first attempt at umask command

parent 0d9180a1
......@@ -19,4 +19,4 @@
# xonsh is Copyright (c) 2015-2016 the xonsh developers and is licensed under
# the 2-Clause BSD license.
__version__ = '0.2.2'
__version__ = '0.2.3'
......@@ -36,7 +36,7 @@ from argparse import ArgumentParser
from takoshell.dirstack import cd, pushd, popd, dirs
from takoshell.jobs import jobs, fg, bg, clean_jobs, disown
from takoshell.coreutils import _which, _echo
from takoshell.coreutils import _which, _echo, _umask
from takoshell.completers._aliases import completer_alias
......@@ -374,4 +374,5 @@ default_aliases = {
'ls': ['ls', '--color=auto', '-v'],
'suppress_tako_welcome_message': suppress_welcome,
'echo': _echo.echo,
'umask': _umask.umask,
}
# This file is part of tako
# Copyright (c) 2015-2017 Adam Hartz <hartz@mit.edu> and contributors
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program. If not, see <http://www.gnu.org/licenses/>.
"""Implements a umask command for tako."""
import re
import os
symbolic_matcher = re.compile(r'([ugo]*|a)([+-=])([^\s,]*)')
order = 'rwx'
name_to_value = {'x': 1, 'w': 2, 'r': 4}
value_to_name = {v: k for k, v in name_to_value.items()}
class_to_loc = {'u': 6, 'g': 3, 'o': 0} # how many bits to shift this class by
loc_to_class = {v: k for k, v in class_to_loc.items()}
function_map = {
'+': lambda orig, new: orig | new, # add the given permission
'-': lambda orig, new: orig & ~new, # remove the given permission
'=': lambda orig, new: new, # set the permissions exactly
}
def current_mask():
out = os.umask(0)
os.umask(out)
return out
def invert(perms):
return 0o777 - perms
def get_oct_digits(mode):
"""
Separate a given integer into its three components
"""
if not 0 <= mode <= 0o777:
raise ValueError("expected a value between 000 and 777")
return {'u': (mode & 0o700) >> 6,
'g': (mode & 0o070) >> 3,
'o': mode & 0o007}
def from_oct_digits(digits):
o = 0
for c, m in digits.items():
o |= (m << class_to_loc[c])
return o
def get_symbolic_rep_single(digit):
"""
Given a single octal digit, return the appropriate string representation.
For example, 6 becomes "rw".
"""
o = ''
for sym in 'rwx':
num = name_to_value[sym]
if digit & num:
o += sym
digit -= num
return o
def get_symbolic_rep(number):
digits = get_oct_digits(number)
return ','.join('%s=%s' % (class_, get_symbolic_rep_single(digits[class_]))
for class_ in 'ugo')
def get_numeric_rep_single(rep):
"""
Given a string representation, return the appropriate octal digit.
For example, "rw" becomes 6.
"""
o = 0
for sym in set(rep):
o += name_to_value[sym]
return o
def single_symbolic_arg(arg, old=None):
# we'll assume this always operates in the "forward" direction (on the
# current permissions) rather than on the mask directly.
if old is None:
old = invert(current_mask())
match = symbolic_matcher.match(arg)
if not match:
raise ValueError('could not parse argument %r' % arg)
class_, op, mask = match.groups()
if class_ == 'a':
class_ = 'ugo'
invalid_chars = [i for i in mask if i not in name_to_value]
if invalid_chars:
raise ValueError('invalid mask %r' % mask)
digits = get_oct_digits(old)
new_num = get_numeric_rep_single(mask)
for c in set(class_):
digits[c] = function_map[op](digits[c], new_num)
return from_oct_digits(digits)
def valid_numeric_argument(x):
try:
return len(x) == 3 and all(0 <= int(i) <= 7 for i in x)
except:
return False
def umask(args, stdin, stdout, stderr):
if '-h' in args:
print(UMASK_HELP, file=stdout)
return 0
symbolic = False
while '-S' in args:
symbolic = True
args.remove('-S')
cur = current_mask()
if len(args) == 0:
# just print the current mask
if symbolic:
to_print = get_symbolic_rep(invert(cur))
else:
to_print = oct(cur)[2:]
while len(to_print) < 3:
to_print = '0%s' % to_print
print(to_print, file=stdout)
return 0
else:
num = [valid_numeric_argument(i) for i in args]
if any(num):
if not all(num):
print("error: can't mix numeric and symbolic arguments", file=stderr)
return 1
if len(num) != 1:
print("error: can't have more than one numeric argument", file=stderr)
return 1
for arg, isnum in zip(args, num):
if isnum:
cur = int(arg, 8)
else:
# this mode operates not on the mask, but on the current
# _permissions_. so invert first, operate, then invert back.
cur = invert(cur)
for subarg in arg.split(','):
try:
cur = single_symbolic_arg(subarg, cur)
except:
print('OOPS!', file=stderr)
return 1
cur = invert(cur)
os.umask(cur)
UMASK_HELP = """Usage: umask [-p] [-S] [mode]...
View or set the file creation mask.
-S when printing, show output in symbolic format
-h --help display this message and exit
This version of umask was written in Python for tako: https://takoshell.org
Based on the umask command from Bash:
https://www.gnu.org/software/bash/manual/html_node/Bourne-Shell-Builtins.html"""
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment