diff options
Diffstat (limited to 'rainbow/util.py')
-rw-r--r-- | rainbow/util.py | 505 |
1 files changed, 505 insertions, 0 deletions
diff --git a/rainbow/util.py b/rainbow/util.py new file mode 100644 index 0000000..3679c20 --- /dev/null +++ b/rainbow/util.py @@ -0,0 +1,505 @@ +import os + +from stat import ST_MODE, ST_UID, ST_GID, S_ISREG + +from os import strerror, stat, mkdir, chown, walk, listdir, fstat +from os.path import realpath, split, join, exists, isdir + +from errno import errorcode + +from ctypes import CDLL, POINTER, Structure, c_int, c_char_p, c_uint32, c_long +from ctypes.util import find_library + +from cStringIO import StringIO as stringio +from subprocess import Popen, PIPE + +### errno manipulationA + +libc = CDLL(find_library('c')) +libc.__errno_location.restype = POINTER(c_int) + +def get_errno(): + return libc.__errno_location().contents.value + +class CError(Exception): + """Base class for VServer syscall errors.""" + + def __init__(self, errno=None): + Exception.__init__(self) + if errno is None: + errno = get_errno() + self.errno = errno + self.errsym = errorcode[errno] + self.errstring = strerror(errno) + + def __repr__(self): + return '<%s errno=%s>'%(self.__class__.__name__, self.errno) + + def __str__(self): + return '%s: %s'%(self.errsym, self.errstring) + +def errno_errcheck(errval, exception): + """Assign to the 'errcheck' attr of a ctypes._CFuncPtr. + :errval: a return code indicating error, and + :exception: an exception type like CError. + >>> func.errcheck = errno_errcheck(errval, excpt)""" + def _inner(result, func, args): + if result == errval: + errno = get_errno() + raise exception(errno) + return result + return _inner + +libc_errcheck = errno_errcheck(-1, CError) + +### Logging, profiling, and error reporting + +def make_reporter(verbosity, quiet, filelike): + if not quiet: + def report(level, msg, *args): + if level <= verbosity: + if len(args): + filelike.write(msg % args + '\n') + else: + filelike.write(msg + '\n') + else: + def report(level, msg, *args): pass + return report + +def profile(profiler): + def wrapper(fn): + def inner(*args, **kwargs): + profiler.runcall(fn, *args, **kwargs) + return inner + return wrapper + +def trace(etype=None, value=None, tb=None): + try: + from IPython.ultraTB import AutoFormattedTB + trace_any = AutoFormattedTB(mode='Verbose', color_scheme='NoColor', call_pdb=0) + trace_exact = trace_any + except: + from traceback import print_exc, print_exception + trace_any = print_exc + trace_exact = print_exception + if etype or value or tb: + trace_exact(etype, value, tb) + else: + trace_any() + +### File mode checking. + +class Checker(object): + """Sanity-check file-system permissions. + Note: This class should NOT to be used for security-critical tests. + In particular, this class does not know the special rules that adhere to + uid 0. + """ + def __init__(self, path, uid, gid, groups=None): + self.path = path + self.uid = uid + self.gid = gid + self.groups = set(groups or []).union(set([gid])) + self.observation = stat(self.path) + + def __repr__(self): + return "Checker(%r, %r, %r, groups=%r)" % (self.path, self.uid, self.gid, self.groups) + + def __str__(self): + return ("Checker(%s, %s, %s, %s) -> mode: %o uid: %s, gid: %s" % + (self.path, self.uid, self.gid, self.groups, self.observation[ST_MODE], + self.observation[ST_UID], self.observation[ST_GID])) + + def positive(self, needed, mask): + # I'm going to try to turn off bits in mode as I verify that they can + # be satisfied. + o = self.observation + real_mode = o[ST_MODE] + if self.uid == o[ST_UID]: + needed &= ~((real_mode & 0700) >> 6) + if o[ST_GID] in self.groups: + needed &= ~((real_mode & 0070) >> 3) + needed &= ~(real_mode & 0007) + # Make sure needed is empty and real_mode satisfies stat. + return (needed == 0) and ((mask & real_mode) == mask) + + def negative(self, avoid, mask): + # I'm going to try to turn on bits for things we're permitted to do, + # then check if any of them are disallowed. + o = self.observation + real_mode = o[ST_MODE] + allowed = 0 + if self.uid == o[ST_UID]: + allowed |= (real_mode & 0700) >> 6 + if o[ST_GID] in self.groups: + allowed |= (real_mode & 0070) >> 3 + allowed |= (real_mode & 0007) + # Did we avoid everything? + return ((avoid & allowed) == 0) and (mask & real_mode == 0) + +### Paths + +def components(p): + """Take a path and return a list of its components: + e.g. /abc/de or /abc/de/ ---> ['abc', 'de']""" + path = realpath(p) + (head, tail) = split(path) + if tail == '': + (head, tail) = split(head) + cpts = [] + while tail != '': + cpts = [tail] + cpts + (head, tail) = split(head) + return cpts + +def make_dirs(pth, uid, gid, mode): + """A replacement for os.makedirs that sets ownership as it creates + directories.""" + if exists(pth): + return + root = '/' + for cpt in components(pth): + root = join(root, cpt) + if not exists(root): + #print "Made %s for (%d, %d)" % (root, uid, gid) + # XXX: A picky person would be worried about races here. + mkdir(root, mode) + chown(root, uid, gid) + +def get_mounted_paths(): + mounts = [] + procfile = open('/proc/mounts') + for line in procfile.readlines(): + mounts.append(line.split(' ')[1]) + procfile.close() + return mounts + +### Processes + +def tokenize(data): + """Take a string and split it on whitespace-strings not contained in + matched quotes. + """ + quotes = ''''"''' + words = [] + state = '\0' + current_word = stringio() + c = '' + for c in data: + if state == '\0': + if c in quotes: + state = c + continue + if c.isspace(): + val = current_word.getvalue() + if val != '': + words.append(val) + current_word.truncate(0) + continue + + if state in quotes: + if c == state: + state = '\0' + continue + + current_word.write(c) + + if state in quotes: + current_word.write(c) + + words.append(current_word.getvalue()) + + return words + +def lout(cmd): + if isinstance(cmd, basestring): + cmd = tokenize(cmd) + return Popen(cmd, stdout=PIPE).stdout.readlines() + +def get_fds(): + for _, _, files in walk("/proc/self/fd"): + for name in files: + yield int(name) + +### syscall(), clone(), daemon(), faccessat(), unshare() + +CSIGNAL = 0xff # /* Signal mask to be sent at exit. */ +CLONE_VM = 0x100 # /* Set if VM shared between processes. */ +CLONE_FS = 0x200 # /* Set if fs info shared between processes. */ +CLONE_FILES = 0x400 # /* Set if open files shared between processes. */ +CLONE_SIGHAND = 0x800 # /* Set if signal handlers shared. */ +CLONE_PTRACE = 0x2000 # /* Set if tracing continues on the child. */ +CLONE_VFORK = 0x4000 # /* Set if the parent wants the child to wake it up on mm_release. */ +CLONE_PARENT = 0x8000 # /* Set if we want to have the same parent as the cloner. */ +CLONE_THREAD = 0x10000 # /* Set to add to same thread group. */ +CLONE_NEWNS = 0x20000 # /* Set to create new namespace. */ +CLONE_SYSVSEM = 0x40000 # /* Set to shared SVID SEM_UNDO semantics. */ +CLONE_SETTLS = 0x80000 # /* Set TLS info. */ +CLONE_PARENT_SETTID = 0x100000 # /* Store TID in userlevel buffer before MM copy. */ +CLONE_CHILD_CLEARTID = 0x200000 # /* Register exit futex and memory location to clear. */ +CLONE_DETACHED = 0x400000 # /* Create clone detached. */ +CLONE_UNTRACED = 0x800000 # /* Set if the tracing process can't force CLONE_PTRACE on this clone. */ +CLONE_CHILD_SETTID = 0x1000000 # /* Store TID in userlevel buffer in the child. */ +CLONE_STOPPED = 0x2000000 # /* Start in stopped state. */ +CLONE_NEWUTS = 0x04000000# /* New utsname group? */ +CLONE_NEWIPC = 0x08000000# /* New ipcs */ +CLONE_NEWUSER = 0x10000000# /* New user namespace */ +CLONE_NEWPID = 0x20000000# /* New pid namespace */ +CLONE_NEWNET = 0x40000000# /* New network namespace */ +CLONE_IO = 0x80000000# /* Clone io context */ + +syscall = libc.syscall + +# We want the simple one. <Bertl> +# int clone(int number, int flags, void *child_stack) +# int clone(int number, int flags, void *child_stack, int *parent_tidptr, int *child_tidptr) + +SYS_clone = 120 + +def clone(flags): + """Perform sys_clone() system call, which creates a new process in + a manner similar to fork(). If flags is SIGCHLD, then this is + identical to fork(). + XXX: i386 wrapper. <MS>""" + return libc_errcheck(syscall(SYS_clone, flags, None), None, None) + +# int unshare(int flags); +unshare = libc.unshare +unshare.__doc__ = "Libc's unshare call; see unshare (2)" +unshare.argtypes = [c_int] +unshare.restype = c_int +unshare.errcheck = libc_errcheck + +# int daemon(int nochdir, int noclose); +daemon = libc.daemon +daemon.__doc__ = "Libc's daemon call; see daemon (3)." +daemon.argtypes = [c_int, c_int] +daemon.restype = c_int +daemon.errcheck = libc_errcheck + +AT_FDCWD = -100 # Special value used to indicate the at functions should use the current working directory. +AT_SYMLINK_NOFOLLOW = 0x100 # Do not follow symbolic links. +AT_REMOVEDIR = 0x200 # Remove directory instead of unlinking file. +AT_SYMLINK_FOLLOW = 0x400 # Follow symbolic links. +AT_EACCESS = 0x200 # Test access permitted for effective IDs, not real IDs. + +# int faccessat(int fd, const char* path, int mode, int flags); +faccessat = libc.faccessat +faccessat.__doc__ = "Libc's faccessat call; see faccessat (3)." +faccessat.argtypes = [c_int, c_char_p, c_int, c_int] +faccessat.restype = c_int +faccessat.errcheck = libc_errcheck + +### reboot(), shutdown(), and poweroff() + +_reboot = libc.reboot +_reboot.__doc__ = "Libc's reboot call; see reboot (2)." +_reboot.argtypes = [c_int] +_reboot.restype = c_int +# XXX: What is reboot()'s errval? +_reboot.errcheck = libc_errcheck + +RB_AUTOBOOT = 0x01234567 +RB_HALT_SYSTEM = 0xcdef0123 +RB_ENABLE_CAD = 0x89abcdef +RB_DISABLE_CAD = 0 +RB_POWER_OFF = 0x4321fedc + +def reboot(): + """Reboot the machine immediately. + If no sync is done first, data will be lost.""" + return _reboot(RB_AUTOBOOT) + +def halt(): + """Halt the machine immediately. + If no sync is done first, data will be lost.""" + return _reboot(RB_HALT_SYSTEM) + +def poweroff(): + """Poweroff the machine immediately. + If no sync is done first, data will be lost.""" + return _reboot(RB_POWER_OFF) + +### statfs(), fstatfs() + +# XXX: The LSB has deprecated statfs() and recommends statvfs() instead. +# Unfortunately, python's os.statvfs() doesn't give any information about file +# system time. <MS> + +# XXX: This is a very 32-bit Linux-only python module. <MS> + +# struct statfs { +# long f_type; /* type of filesystem (see below) */ +# long f_bsize; /* optimal transfer block size */ +# long f_blocks; /* total data blocks in file system */ +# long f_bfree; /* free blocks in fs */ +# long f_bavail; /* free blocks avail to non-superuser */ +# long f_files; /* total file nodes in file system */ +# long f_ffree; /* free file nodes in fs */ +# fsid_t f_fsid; /* file system id */ +# long f_namelen; /* maximum length of filenames */ +# }; + +class c_fsid_t(Structure): + _fields_ = [('val', c_int*3)] + +class c_statfs(Structure): + _fields_ = [('f_type', c_long), + ('f_bsize', c_long), + ('f_blocks', c_long), + ('f_bfree', c_long), + ('f_bavail', c_long), + ('f_files', c_long), + ('f_ffree', c_long), + ('f_fsid', c_fsid_t), + ('f_namelen', c_long),] + +c_statfs_p = POINTER(c_statfs) + +# The function statfs() returns information about a mounted file system. +# path is the pathname of any file within the mounted filesystem. buf is a +# pointer to a statfs structure defined approximately as follows: + +# include <sys/vfs.h> /* or <sys/statfs.h> */ + +# int statfs(const char *path, struct statfs *buf); +statfs = libc.statfs +statfs.__doc__ = "Libc's statfs call; see statfs(2)" +statfs.argtypes = [c_char_p, c_statfs_p] +statfs.restype = c_int +statfs.errcheck = libc_errcheck + +# int fstatfs(int fd, struct statfs *buf); +fstatfs = libc.fstatfs +fstatfs.__doc__ = "Libc's fstatfs call; see fstatfs(2)" +fstatfs.argtypes = [c_int, c_statfs_p] +fstatfs.restype = c_int +fstatfs.errcheck = libc_errcheck + +# File system types: + +ADFS_SUPER_MAGIC = 0xadf5 +AFFS_SUPER_MAGIC = 0xADFF +BEFS_SUPER_MAGIC = 0x42465331 +BFS_MAGIC = 0x1BADFACE +CIFS_MAGIC_NUMBER = 0xFF534D42 +CODA_SUPER_MAGIC = 0x73757245 +COH_SUPER_MAGIC = 0x012FF7B7 +CRAMFS_MAGIC = 0x28cd3d45 +DEVFS_SUPER_MAGIC = 0x1373 +EFS_SUPER_MAGIC = 0x00414A53 +EXT_SUPER_MAGIC = 0x137D +EXT2_OLD_SUPER_MAGIC = 0xEF51 +EXT2_SUPER_MAGIC = 0xEF53 +EXT3_SUPER_MAGIC = 0xEF53 +HFS_SUPER_MAGIC = 0x4244 +HPFS_SUPER_MAGIC = 0xF995E849 +HUGETLBFS_MAGIC = 0x958458f6 +ISOFS_SUPER_MAGIC = 0x9660 +JFFS2_SUPER_MAGIC = 0x72b6 +JFS_SUPER_MAGIC = 0x3153464a +MINIX_SUPER_MAGIC = 0x137F # /* orig. minix */ +MINIX_SUPER_MAGIC2 = 0x138F # /* 30 char minix */ +MINIX2_SUPER_MAGIC = 0x2468 # /* minix V2 */ +MINIX2_SUPER_MAGIC2 = 0x2478 # /* minix V2, 30 char names */ +MSDOS_SUPER_MAGIC = 0x4d44 +NCP_SUPER_MAGIC = 0x564c +NFS_SUPER_MAGIC = 0x6969 +NTFS_SB_MAGIC = 0x5346544e +OPENPROM_SUPER_MAGIC = 0x9fa1 +PROC_SUPER_MAGIC = 0x9fa0 +QNX4_SUPER_MAGIC = 0x002f +REISERFS_SUPER_MAGIC = 0x52654973 +ROMFS_MAGIC = 0x7275 +SMB_SUPER_MAGIC = 0x517B +SYSV2_SUPER_MAGIC = 0x012FF7B6 +SYSV4_SUPER_MAGIC = 0x012FF7B5 +TMPFS_MAGIC = 0x01021994 +UDF_SUPER_MAGIC = 0x15013346 +UFS_MAGIC = 0x00011954 +USBDEVICE_SUPER_MAGIC = 0x9fa2 +VXFS_SUPER_MAGIC = 0xa501FCF5 +XENIX_SUPER_MAGIC = 0x012FF7B4 +XFS_SUPER_MAGIC = 0x58465342 +_XIAFS_SUPER_MAGIC = 0x012FD16D + +### mount(), umount(), and bindmount() + +class MountError(CError): + pass + + +MS_RDONLY = 1 # Mount read-only. +MS_NOSUID = 2 # Ignore suid and sgid bits. +MS_NODEV = 4 # Disallow access to device special files. +MS_NOEXEC = 8 # Disallow program execution. +MS_SYNCHRONOUS = 16 # Writes are synced at once. +MS_REMOUNT = 32 # Alter flags of a mounted FS. +MS_MANDLOCK = 64 # Allow mandatory locks on an FS. +S_WRITE = 128 # Write on file/directory/symlink. +S_APPEND = 256 # Append-only file. +S_IMMUTABLE = 512 # Immutable file. +MS_NOATIME = 1024 # Do not update access times. +MS_NODIRATIME = 2048 # Do not update directory access times. +MS_BIND = 4096 # Bind directory at different place. +MS_REC = 16384 # Recursive mount (use with MS_BIN). + +MS_MGC_VAL = 0xc0ed0000 # Magic flag number to indicate "new" flags +MS_MGC_MSK = 0xffff0000 # Magic flag number mask + +#extern int mount (__const char *__special_file, __const char *__dir, +# __const char *__fstype, unsigned long int __rwflag, +# __const void *__data) __THROW; + +mount = libc.mount +mount.__doc__ = "Libc's mount call; see mount (2)." +mount.argtypes = [c_char_p, c_char_p, c_char_p, c_uint32, c_char_p] +mount.restype = c_int +mount.errcheck = libc_errcheck + +#extern int umount (__const char *__special_file) __THROW; +umount = libc.umount +umount.__doc__ = "Libc's umount call; see umount (2)." +umount.argtypes = [c_char_p] +umount.restype = c_int +umount.errcheck = libc_errcheck + +def bindmount(src, tgt, read_only, recursive): + """Calls mount with appropriate arguments. + """ + flags = MS_BIND + if read_only: + flags |= MS_RDONLY + if recursive: + flags |= MS_REC + mount(src, tgt, '', flags, '') + + +def read_envdir(envdir, overrides=None): + # We're going to read an envdir. + + # We'll add k->v pairs to our dictionary for every + # regular file or symlink to a regular file in opts.envdir. + + # XXX: Several of these calls can block. Do we care? + # XXX: How many bytes should we read? + # XXX: Any issues with special characters? + + env = {} + d = envdir + if d and isdir(d): + for p in listdir(d): + f = None + try: + f = os.open(join(d, p), os.O_RDONLY) + s = fstat(f) + if S_ISREG(s[ST_MODE]): + env[p] = os.read(f, 1024) + finally: + if f is not None: + os.close(f) + if overrides: + for binding in overrides: + k, v = binding.split('=',1) + env[k] = v + return env |