#-*- coding: utf-8 -*-

# Copyright 2008-2013 Calculate Ltd. http://www.calculate-linux.org
#
#  Licensed under the Apache License, Version 2.0 (the "License");
#  you may not use this file except in compliance with the License.
#  You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.

import os
from os import path
import subprocess
from subprocess import Popen,PIPE,STDOUT
import stat
from shutil import copytree, rmtree,copy2
from calculate.lib import cl_overriding
import re
import sys
from itertools import *
import tarfile
import select
import fcntl

from contextlib import contextmanager
import signal

@contextmanager
def timeout(seconds):
    def timeout_handler(signum,frame):
        pass

    original_handler = signal.signal(signal.SIGALRM, timeout_handler)

    try:
        signal.alarm(seconds)
        yield
    finally:
        signal.alarm(0)
        signal.signal(signal.SIGALRM, original_handler)

try:
    from magic import (open as type_file, MAGIC_NONE, MAGIC_CONTINUE,
                        MAGIC_MIME_TYPE, MAGIC_COMPRESS,
                        MAGIC_MIME_ENCODING, MAGIC_SYMLINK)
except ImportError as e:
    try:
        from magic import (open as type_file, NONE as MAGIC_NONE,
                CONTINUE as MAGIC_CONTNUE, MIME_TYPE as MAGIC_MIME_TYPE,
                COMPRESS as MAGIC_COMPRESS,MIME_ENCODING as MIME_ENCODING,
                SYMLINK as MAGIC_SYMLINK)
    except:
        type_file = None
        MAGIC_NONE = 0
        MAGIC_SYMLINK = 0x002
        MAGIC_COMPRESS = 0x004
        MAGIC_CONTINUE = 0x020
        MAGIC_MIME_TYPE = 0x010
        MAGIC_MIME_ENCODING = 0x400

from calculate.lib.cl_lang import setLocalTranslate
setLocalTranslate('cl_lib3',sys.modules[__name__])

reSearch = lambda pattern,listar:map(lambda x:x.groups(),
                        filter(None,
                        map(pattern.search,listar)))

class FilesError(Exception):
    pass

class process:
    """Execute system command by Popen
    
    Examples:
    
    execute program and get result:
    if process("/bin/gzip","/boot/somefile").success():
        print "Gzip success"

    unzip and process unzip data by cpio (list files):
    processGzip = process("/bin/gzip","-dc","/boot/initrd")
    processCpio = process("/bin/cpio","-tf",stdin=processGzip)
    filelist = processCpio.readlines()

    execute command and send data:
    processGrub = process("/sbin/grub")
    processGrub.write("root (hd0,0)\n")
    processGrub.write("setup (hd0)\n")
    processGrub.write("quit\n")
    isok = processGrub.success()

    union stdout and stderr:
    process("/bin/ls","/",stderr=STDOUT)
    
    result to stdout:
    process("/bin/ls",stdout=None)

    get data from keyboard:
    process("/bin/cat",stdin=None)
    """
    def __init__(self,command,*params,**kwarg):
        if not "stdin" in kwarg:
            stdin=self._defaultStdin
        else:
            if kwarg["stdin"] == None:
                stdin = self._keyboardStdin
            else:
                stdin=kwarg["stdin"].getStdout
        self.stdout = kwarg.get("stdout",PIPE)
        self.envdict = kwarg.get("envdict",os.environ.copy())
        self.envdict["LANG"] = kwarg.get('lang','C')
        self.langc = "langc" in kwarg

        self.stderr = kwarg.get("stderr",PIPE)
        self.cwd = kwarg.get("cwd",None)
        self.command = getProgPath(command)
        if not self.command:
            raise FilesError(_("Command not found '%s'")%
                             command)
        self.command = [self.command] + list(params)
        self.stdin = stdin
        self.iter = None
        self.pipe = None
        self.cacheresult = None
        self.cacheerr = None
        self.communicated = False

    def _open(self):
        """Open pipe if it not open"""
        if not self.pipe:
            self.pipe = Popen(self.command,
                              stdout=self.stdout,
                              stdin=self.stdin(),
                              stderr=self.stderr,
                              cwd=self.cwd,
                              close_fds=True,
                              env=self.envdict)

    def _defaultStdin(self):
        """Return default stdin"""
        return PIPE

    def _keyboardStdin(self):
        """Return keyboard stdin"""
        return None

    def getStdout(self):
        """Get current stdout"""
        self.close()
        self._open()
        return self.pipe.stdout

    def write(self,data):
        """Write to process stdin"""
        self._open()
        self.pipe.stdin.write(data)
        self.pipe.stdin.flush()

    def close(self):
        """Close stdin"""
        if self.pipe:
            self.pipe.stdin.close()

    def readerr(self):
        self.read()
        return self.cacheerr

    def readByLine(self):
        
        try:
            self._open()
            if self.cacheresult is None:
                self.cacheresult = []
                s = self.pipe.stdout.readline()
                while s:
                    yield s
                    s = self.pipe.stdout.readline()
                if self.pipe.stderr:
                    self.cacheerr = self.pipe.stderr.read()
                else:
                    self.cacheerr = ""

        except KeyboardInterrupt:
            self.kill()
            raise KeyboardInterrupt
        finally:
            self.cacheresult = "\n".join(self.cacheresult)

    def read(self):
        """Read all data"""
        try:
            self._open()
            if self.cacheresult is None:
                self.cacheresult,self.cacheerr = self.pipe.communicate()
            return self.cacheresult
        except KeyboardInterrupt:
            self.kill()
            raise KeyboardInterrupt

    def readlines(self):
        """Read lines"""
        return self.read().split('\n')

    def __iter__(self):
        """Get iterator"""
        if not self.iter:
            self.iter = iter(self.readlines())
        return self.iter

    def kill(self):
        """Kill this process"""
        if self.pipe:
            self.pipe.kill()

    def next(self):
        """Next string from stdout"""
        return self.__iter__().next()

    def returncode(self):
        """Get return code"""
        self.read()
        return self.pipe.returncode

    def success(self):
        """Success or not"""
        return self.returncode() == 0

    def failed(self):
        """Failed or not"""
        return self.returncode() != 0


def getModeFile(nameFile, mode="all"):
    """Выдает информацию о файле
    mode=="all"
        права файла, владелец, группа файла
    mode=="mode"
        права файла
    mode=="owner"
        владелец, группа файла
    """
    fst = os.lstat(nameFile)
    if mode == "all":
        return (stat.S_IMODE(fst.st_mode), fst.st_uid, fst.st_gid)
    if mode == "mode":
        return stat.S_IMODE(fst.st_mode)
    if mode == "owner":
        return fst.st_uid, fst.st_gid

def chownR(directory, uid, gid):
    """Recusive chown"""
    def chownPaths(rootPath, listPath, uid, gid):
        for chPath in listPath:
            chownPath = path.join(rootPath, chPath)
            statInfo = os.lstat(chownPath)[stat.ST_MODE]
            if stat.S_ISLNK(statInfo):
                os.lchown(chownPath, uid, gid)
            else:
                os.chown(chownPath, uid, gid)

    for root, dirs, files in os.walk(directory):
        # меняем владельца директории
        os.chown(root, uid, gid)
        # Меняем владельца директорий
        chownPaths(root, dirs, uid, gid)
        # Меняем владельца файлов
        chownPaths(root, files, uid, gid)
    return True

class proxy_type_file:
    def __init__(self,flags):
        self.flags = flags

    def load(self):
        pass

    def setflags(self,flags):
        self.flags = flags

    def close(self):
        pass

    def _get_cmd_by_flags(self,flags):
        """
        Получить команду для выполнения по флагам
        """
        paramdict = {MAGIC_CONTINUE:"-k",
                     MAGIC_SYMLINK:"-L",
                     MAGIC_MIME_TYPE:"--mime-type",
                     MAGIC_MIME_ENCODING:"--mime-encoding",
                     MAGIC_COMPRESS:"-z"}
        appendParam = map(lambda x:paramdict[x],
                      filter(lambda x:flags & x,
                      sorted(paramdict.keys())))
        fileCmd = getProgPath('/usr/bin/file')
        return [fileCmd,'-b']+appendParam

    def file(self,filename):
        if path.exists(filename):
            processFile = \
                process(*(self._get_cmd_by_flags(self.flags)+[filename]))
            if processFile.success():
                return processFile.read().rstrip()
        return None

class typeFile:
    """Получение типа файла"""

    def __init__(self, magic=MAGIC_MIME_ENCODING|MAGIC_MIME_TYPE):
        if type_file is None:
            self.magicObject = proxy_type_file(MAGIC_NONE)
        else:
            self.magicObject = type_file(MAGIC_NONE)
        self.magicObject.load()
        self.magicObject.setflags(magic)

    def __del__(self):
        """Закрываем magic"""
        self.magicObject.close()

    def getMType(self, filename):
        """Информация о типе файла"""
        try:
            ret = self.magicObject.file(filename)
        except UnicodeDecodeError:
            try:
                ret = self.magicObject.file(filename.decode('utf-8'))
            except UnicodeDecodeError:
                return None
        # fix for kernel 3.7.7 (bad work samba)
        if ret is None and self.magicObject.errno() == 5:
            r,w = os.pipe()
            devnull = os.open(os.devnull,os.O_WRONLY)
            cat = subprocess.Popen(['/bin/cat',filename],stdout=w,
                    stderr=devnull,close_fds=True)
            ret = self.magicObject.descriptor(r)
            os.close(w)
            os.close(devnull)
            cat.wait()
        return ret

    def isBinary(self, filename):
        """является ли файл бинарным"""
        mime = self.getMType(filename)
        if mime:
            if mime.startswith("text"):
                return False
            else:
                return True
        return None

class scanDirectory:
    """Класс для cканирования директории"""

    def processingFile(self, pathname, prefix):
        """Обработка в случае файла"""
        return True

    def processingDirectory(self, pathname, prefix):
        """Обработка в случае директории если возвращаем None то пропуск дир."""
        return True

    def scanningDirectory(self, scanDir, skipFile=[], skipDir=[],
                          prefix=None, flagDir=False):
        """Сканирование и обработка шаблонов в директории scanDir"""
        ret = True
        if not prefix:
            prefix = path.join(scanDir,"")[:-1]
        if not flagDir:
            # проверка корневой директории
            retDir = self.processingDirectory(scanDir, scanDir)
            if retDir is None:
                return None
            elif retDir is False:
                return False
        if flagDir or stat.S_ISDIR(os.lstat(scanDir)[stat.ST_MODE]):
            for absPath in sorted(listDirectory(scanDir,fullPath=True)):
                relPath = absPath.split(prefix)[1]
                stInfo = os.lstat(absPath)
                statInfo = stInfo[stat.ST_MODE]
                if stat.S_ISREG(statInfo):
                    # Обработка файла
                    if relPath in skipFile:
                        continue
                    if not self.processingFile(absPath, prefix):
                        return False
                elif stat.S_ISDIR(statInfo):
                    # Обработка директории
                    if relPath in skipDir:
                        continue
                    retDir = self.processingDirectory(absPath, prefix)
                    if retDir is None:
                        continue
                    elif retDir is False:
                        return False
                    ret = self.scanningDirectory(absPath, skipFile,
                                                 skipDir, prefix, True)
                    if ret is False:
                        return False
        return ret

class processProgress(process):
    """
    Generator like process progress
    """
    def __init__(self,command,*params,**kwarg):
        process.__init__(self,command,*params,**kwarg)
        self.readsize = kwarg.get("readsize",10)
        self.delimeter = re.compile("\n")
        self.init(**kwarg)

    def init(self,*args,**kwarg):
        pass

    def processInit(self):
        pass

    def processEnd(self):
        pass

    def processString(self,strdata):
        self.cacheresult.append(strdata)
        return strdata

    def processBuffer(self,buf):
        pass

    def progress(self):
        try:
            res = self.processInit()
            if res != None: yield res
            self._open()
            if self.cacheresult is None:
                self.cacheresult = []
                self.buf = ""
                fd = self.pipe.stdout.fileno()
                fl = fcntl.fcntl(fd,fcntl.F_GETFL)
                fcntl.fcntl(fd,fcntl.F_SETFL,fl | os.O_NONBLOCK)
                hasData = True
                while hasData or self.pipe.poll() is None:
                    hasData = False
                    try:
                        part = self.pipe.stdout.read(self.readsize)
                        if not part:
                            continue
                    except IOError as e:
                        # re-raise not "Resource temporary unavailable"
                        if e.errno != 11:
                            raise
                        continue
                    hasData = True
                    if self.buf:
                        self.buf += part
                    else:
                        self.buf = part
                    res = self.processBuffer(part)
                    if res:
                        yield res
                    while self.delimeter.search(self.buf):
                        strdata, self.buf = self.delimeter.split(self.buf,1)
                        res = self.processString(strdata)
                        if res:
                            yield res
            res = self.processEnd()
            if res != None: yield res
        except KeyboardInterrupt:
            self.pipe.kill()
            raise
        finally:
            if self.cacheresult:
                self.cacheresult = "\n".join(self.cacheresult)
            else:
                self.cacheresult = ""

def countFiles(dirpath,onefilesystem=True):
    """
    Count files in specified dirpath
    """
    num = 1
    for dirpath,dirnames,filenames in os.walk(dirpath):
        num += len(set(dirnames) | set(filenames))
        if onefilesystem:
            mountDirs = filter(lambda x:path.ismount(path.join(dirpath,x)),
                        dirnames)
            for dirname in mountDirs:
                dirnames.remove(dirname)
    return num

def getFilesCount(directory):
    """Alias for compatibility"""
    return countFiles(directory,onefilesystem=False)


def runOsCommand(cmd,in_str=None, env_dict=None):
    """Run system command

    Параметры:
    cmd            внешняя программа
    in_str         данные передаваемые программе на страндартный вход.
    env_dict       словарь переменных окружения
    Возвращает (код возврата, список stdout+stderr)
    """
    pipe = subprocess.Popen(cmd, stdin=subprocess.PIPE,
                            stdout=subprocess.PIPE,
                            stderr=subprocess.PIPE,
                            env=env_dict,
                            close_fds=True,
                            shell=True)
    fout, fin, ferr = (pipe.stdout, pipe.stdin, pipe.stderr)
    # если есть данные на вход, передать их
    if in_str:
        fin.write(in_str)
    fin.close()
    res = map(lambda x: x.rstrip(), fout.readlines())
    fout.close()
    res += map(lambda x: x.rstrip(), ferr.readlines())
    ferr.close()
    # Код возврата
    retcode = pipe.wait()
    return retcode, res

def copyDir(srcDir, destDir):
    """Копируем директорию srcDir в destDir

    При копировании сохраняются владелец, группа, права
    """
    def ignoreFile(pathname, names):
        """Игнорирование сокетов при копировании"""
        ignore = []
        for name in names:
            if stat.S_ISSOCK(os.lstat(path.join(pathname, name))[stat.ST_MODE]):
                ignore.append(name)
        return ignore

    copytree(srcDir, destDir, ignore=ignoreFile)
    return True

def removeDir(rmDir):
        """Рекурсивное удаление директории"""
        rmtree(rmDir)
        return True

def getRunCommands(not_chroot=False):
    """List run program"""
    def getCmd(procNum):
        cmdLineFile = '/proc/%s/cmdline'%procNum
        try:
            if path.exists(cmdLineFile):
                if not_chroot:
                    root_link = '/proc/%s/root'%procNum
                    if os.readlink(root_link) != "/":
                        return ""
                return open(cmdLineFile,'r').read().strip()
        except Exception as e:
            pass
        return ""
    if not os.access('/proc',os.R_OK):
        return []
    return filter(None,
           map(getCmd,
           filter(lambda x:x.isdigit(),
           listDirectory('/proc'))))


def isMount(pathname):
    """В случае монтирования директории выдает другой примонтированный путь"""
    findPath = [path.abspath(pathname)]
    if findPath[0].startswith('/dev'):
        info = device.getUdevDeviceInfo(name=findPath[0])
        if 'DM_VG_NAME' in info and 'DM_LV_NAME' in info:
            lvmDeviceName = \
                '/dev/mapper/{vg}-{lv}'.format(vg=info['DM_VG_NAME'],
                                               lv=info['DM_LV_NAME'])
            findPath.append(lvmDeviceName)
    mtabFile = '/etc/mtab'
    if not os.access(mtabFile,os.R_OK):
        return ""
    return filter(lambda x: x!=findPath,
           reduce(lambda x,y: y,
           ifilter(lambda x: any(p in x for p in findPath),
           imap(lambda x: [x[0], x[1]],
           imap(lambda x: x.split(" "),
           open(mtabFile)))), [""]))[0]

def commonPath(*paths):
    """Return common path from list of paths"""
    paths = map(lambda x:path.normpath(x).split('/'),paths)
    res = map(lambda x:x[0],
        filter(lambda x:filter(lambda y:x[0]==y,x[1:]),zip(*paths)))
    return "/".join(res)

def childMounts(pathname):
    """Get all mount points which contain path"""
    if pathname != "none":
        absPath = path.abspath(pathname)
    else:
        absPath = pathname
    mtabFile = '/etc/mtab'
    if not os.access(mtabFile,os.R_OK):
        return ""
    return reduce(lambda x,y: x + [y],
                  filter(lambda x: commonPath(absPath,x[0])==absPath or \
                                   commonPath(absPath,x[1])==absPath,
                  map(lambda x: [x[0], x[1]],
                  map(lambda x: x.split(" "),
                  open(mtabFile)))),
                  [])


def pathJoin(*paths):
    """Складывает пути, в отличии от os.path.join, складывает абсолютные пути"""
    if len(paths)==1:
        return paths[0]
    return reduce(path.join,
                  filter(lambda x:x and x != "/",
                  map(lambda x: x.startswith("/") and x[1:] or x,
                  paths[1:])),paths[0])

def checkDigestFile(digestfile):
    """Check digest by digestfile"""
    reEntry = re.compile(r"# (\S+) HASH\n(\S+)  (\S+)",re.S)
    result = []
    for alg,hashdata,filename in \
        reEntry.findall(open(digestfile,'r').read()):
        if hasattr(hashlib,alg.lower()):
            hashobj = getattr(hashlib,alg.lower())
            filename = path.join(path.dirname(digestfile),filename)
            if os.path.exists(filename):
                digest = hashobj(open(filename,'r').read())
                result.append((alg,
                    digest.hexdigest().upper() == hashdata.upper()))
    return result

def listDirectory(directory,fullPath=False,onlyDir=False):
    """Get files from directory, if it exists"""
    if not path.exists(directory):
        return []
    try:
        if fullPath:
            if onlyDir:
                return filter(lambda x:path.isdir(x),
                       map(lambda x:path.join(directory,x),
                       os.listdir(directory)))
            else:
                return map(lambda x:path.join(directory,x),
                       os.listdir(directory))
        else:
            if onlyDir:
                return filter(lambda x:path.isdir(path.join(directory,x)),
                       os.listdir(directory))
            else:
                return os.listdir(directory)
    except OSError:
        pass
    return []

def makeDirectory(pathname):
    """Make directory and parent.

    Return False on error"""
    try:
        parent = path.split(path.normpath(pathname))[0]
        if not path.exists(parent):
            makeDirectory(parent)
        else:
            if path.exists(pathname):
                return True
        os.mkdir(pathname)
        return True
    except Exception, e:
        return False

def readLinesFile(filename):
    """Read file by line"""
    try:
        if path.exists(filename):
            for line in open(filename,'r'):
                yield line.rstrip('\n')
    except (OSError,IOError):
        pass
    finally:
        raise StopIteration

def readFile(filename,tailbyte=None):
    """
    Прочитать целый файл или вернуть пустую строку.
    tailbyte: прочитать только последнее указанное количество байт
    """
    try:
        if path.exists(filename):
            with open(filename,'r') as f:
                if tailbyte:
                    seeksize = max(0,os.stat(filename).st_size-tailbyte)
                    if seeksize:
                        f.seek(seeksize)
                return f.read()
    except (OSError,IOError):
        pass
    return ""

def writeFile(filename):
    """
    Открыть файл на запись и создать необходимые каталоги
    """
    dn = path.dirname(filename)
    if not path.exists(dn):
        os.makedirs(dn)
    return open(filename,'w')

import common

class getProgPath:
    """Get full path of program or False"""
    cache = {}

    def __call__(self,progname):
        baseprogname = path.basename(progname)
        if baseprogname in self.cache:
            return self.cache[baseprogname]
        env = {"LANG":"C"}
        env.update(os.environ.items() + [("PATH",common.getpathenv())] +\
                   env.items())
        res = runOsCommand("which %s"%baseprogname,env_dict=env)
        if path.isabs(progname) and path.exists(progname):
            self.cache[baseprogname] = progname
            return self.cache[baseprogname]
        elif res[0] == 0:
            self.cache[baseprogname] = res[1][0].strip()
            return self.cache[baseprogname]
        else:
            return False

getProgPath = getProgPath()

def checkUtils(*utils):
    """Check utils, exit if it not found and return fullpath"""
    retval = []
    for util in utils:
        utilPath = getProgPath(util)
        if not utilPath:
            raise FilesError(_("Command not found '%s'")%
                                     path.basename(util))
        retval.append(utilPath)
    if len(retval) == 1:
        return retval[0]
    else:
        return retval

def getCmdLineParam(paramName):
    """Get value of param /proc/cmdline. If param not found then empty.
    """
    cmdLine = '/proc/cmdline'
    paramName = "%s="%paramName
    params = \
        map(lambda x:x.partition('=')[2],
        filter(lambda x:x.startswith(paramName),
        readFile(cmdLine).split(' ')))
    if params:
        return params[-1]
    else:
        return ""

from device import getUUIDDict

def isFstabMount(pathname,mapDevUuid={},listFstab=[]):
    """Get mount point or device from fstab"""
    def removeQuotes(s):
        return s.replace('"','').replace("'","")
    if pathname == "swap":
        absPath = "swap"
    else:
        absPath = path.abspath(pathname)
    if not mapDevUuid:
        mapDevUuid.update(getUUIDDict())
    # convert fstab to
    # [['/dev/sda3', '/', '', 'reiserfs', 'noatime', '', '', '0', '2\n'],
    # ['/dev/sda5', '/var/calculate', 'reiserfs', 'noatime', '0', '0\n']]
    #if not listFstab:
    if not listFstab:
        listFstab.extend(
                map(lambda x: [mapDevUuid.get(removeQuotes(x[0]),x[0]),
                          x[1] if x[2] != "swap" else "swap"],
                filter(lambda x: len(x) >= 4,
                map(lambda x: filter(lambda x: x,
                              x.replace('\t',' ').split(' ')),
                filter(lambda x: not x.startswith('#') and x.strip(),
                open("/etc/fstab"))))))
    # get mount point or device or dir
    return filter(lambda x: x!=absPath,
           reduce(lambda x,y: y,
           filter(lambda x: absPath in x and x[1] != "none",
           listFstab),[""]))[0]

class SingletonParam(type):
    def __init__(cls, name, bases, dict):
        super(SingletonParam, cls).__init__(name, bases, dict)
        cls.instance = {}

    def __call__(cls,*args,**kw):
        keyarg = args[0] if args else ""
        if not keyarg in cls.instance:
            cls.instance[keyarg] = \
                super(SingletonParam, cls).__call__(*args, **kw)
        return cls.instance[keyarg]

import device

class FStab(object):
    """Data reader for fstab"""
    __metaclass__ = SingletonParam
    fstab_file = '/etc/fstab'
    NAME, DIR, TYPE, OPTS, FREQ, PASSNO = range(0,6)

    def __init__(self,fstab_file=None,devs=[]):
        if fstab_file:
            self.fstab_file = fstab_file
        self.cache = []
        self.rotateCache = []
        self.dictUUID = getUUIDDict(devs=devs)
        self.rebuildCache()

    def rebuildCache(self):
        """Rebuild cache from fstab file"""
        def setlen(ar):
            return ar[:6]+[""]*(6-len(ar))
        self.cache = \
            map(lambda x:setlen(map(lambda y:y.strip(),x.split())),
            filter(lambda x:x.strip() and not x.lstrip().startswith("#"),
            open(self.fstab_file,'r').read().split('\n')))
        for data in self.cache:
            convertDev = lambda x: path.realpath(x) if x.startswith('/') else x
            data[0] = device.getUdevDeviceInfo(
                       name=convertDev(self.dictUUID.get(data[0],data[0]))
                      ).get('DEVNAME',data[0])
            data[1] = data[1] if data[2] != "swap" else "swap"
        self.rotateCache = zip(*self.cache)

    def getBy(self,what=DIR,where=NAME,_in=None,eq=None,
              noteq=None,allentry=False):
        """Get data from fstab"""
        if not eq is None:
            filterfunc = lambda x: x[where] == eq
        elif not _in is None:
            filterfunc = lambda x: _in in x[where]
        else:
            filterfunc = lambda x: x[where] != noteq
        res = map(lambda x:x[what],filter(filterfunc,self.cache))
        if allentry:
            return res
        else:
            return "" if not res else res[-1]

    def getFields(self,*fields):
        """Get all data by specifie fields"""
        return zip(*reduce(lambda x,y:x+[self.rotateCache[y]],fields,[]))

    def isExists(self,what=DIR,eq=None,noteq=None):
        """Field with condition exist in fstab"""
        if not eq is None:
            filterfunc = lambda x: x[what] == eq
        else:
            filterfunc = lambda x: x[what] != noteq
        return bool(filter(filterfunc,self.cache))

def tarLinks(rootpath,archpath,skip=[]):
    """
    Поместить все симлинки в архив
    """
    links = []
    lenprefix = len(path.normpath(rootpath))+1
    if path.exists(archpath):
        os.unlink(archpath)
    # create arch
    tar = tarfile.open(archpath,"w:bz2")
    # find links
    if skip:
        reSkip = re.compile("|".join(map(lambda x:x.replace("*",".*"),
            skip))).search
    else:
        reSkip = lambda x:False
    for link in ifilterfalse(reSkip,
                find(rootpath,filetype="l",onefilesystem=True)):
        ti = tar.gettarinfo(link)
        ti.name = link[lenprefix:]
        tar.addfile(ti)
        links.append(link)
    tar.close()
    return links

def getch():
    """
    Get char from keyboard
    """
    import sys, tty, termios

    try:
        fd = sys.stdin.fileno()
        old_settings = termios.tcgetattr(fd)
        try:
            tty.setraw(sys.stdin.fileno())
            ch = sys.stdin.read(1)
        finally:
            termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
        return ch
    except:
        return ""

def find(directory,onefilesystem=False,filetype=None,
         fullpath=True, depth=None, downfilter=None):
    """
    Поиск файлов в каталоге
    """
    ldirectory = len(directory)
    if downfilter or onefilesystem or depth:
        _downfilter = lambda dp: (
            (depth is None or dp[ldirectory:].count('/') < depth) and
            (not downfilter or downfiler(dp)) and
            (not onefilesystem or not path.ismount(dp)))
    else:
        _downfilter = None

    if filetype:
        def dirfilter(dp):
            link = path.islink(dp)
            return ("l" in filetype and link) or ("d" in filetype and not link)
        _dirfilter = dirfilter
    else:
        _dirfilter = None

    if fullpath or _dirfilter or _downfilter:
        _dirfullpath = lambda root,fn: path.join(root,fn)
    else:
        _dirfullpath = lambda root,fn:fn

    if filetype:
        def filefilter(fp):
            link = path.islink(fp)
            return ("l" in filetype and link) or ("f" in filetype and not link)
        _filefilter = filefilter
    else:
        _filefilter = None

    if fullpath or _filefilter:
        _filefullpath = lambda root,fn: path.join(root,fn)
    else:
        _filefullpath = lambda root,fn:fn

    for root,directories,files in os.walk(directory):
        for fn in files:
            fp = _filefullpath(root,fn)
            if not _filefilter or _filefilter(fp):
                yield fp if fullpath else fn
        for dn in directories[:]:
            dp = _dirfullpath(root,dn)
            if not _dirfilter or _dirfilter(dp):
                yield dp if fullpath else dn
            if _downfilter and not _downfilter(dp):
                directories.remove(dn)

def copyWithPath(fn,dest,prefix=None):
    """
    Копировать файл в dst с созданием каталогов

    source: префикс исходного файла
    dest: каталог для копирывания
    fn: имя файла
    """
    try:
        if prefix:
            destFile = pathJoin(dest,fn[len(prefix):])
        else:
            destFile = pathJoin(dest,path.basename(fn))
        destDir = path.dirname(destFile)
        if not path.exists(destDir):
            os.makedirs(destDir)
        copy2(fn,destFile)
    except Exception as e:
        raise FilesError(
            _("Failed to copy '%(src)s' to '%(dst)s'")%
            {'src':fn,
             'dst':destFile})

def getLoopFromPath(directory):
    """
    Получить loop, использующие файлы в данной директории
    """
    losetup = getProgPath('losetup')
    p = process(losetup,'-a')
    rePattern = re.compile('(^\/dev\/loop[^:]+)[^(]+\(([^)]+)\).*')
    return map(lambda x:x[0],
            filter(lambda x:x[1].startswith(directory),
            reSearch(rePattern,
            p.readlines())))

def getMdRaidDevices():
    """
    Получить словарь: какой mdraid какие использует устройства
    """
    reMdInfo = re.compile('^(\w+)\s+:\s+active.*?raid\d+\s+(.*)')
    return dict(map(lambda x:(x[0],map(lambda x:x.partition('[')[0],
                            x[1].split())),
           reSearch(reMdInfo,readLinesFile('/proc/mdstat'))))

def sambaPasswordCheck(username,password,server,resource):
    """
    Подключиться к указанному samba ресурсу сервера по логину паролю
    """
    smbclient = getProgPath('/usr/sbin/smbclient')
    if not smbclient:
        raise FilesError(_("Command not found '%s'")%"smbclint")
    p = process(smbclient,"-U",username,"//%s/%s"%(server,resource),
                envdict={'PASSWD':password})
    return p.success()

class PercentProgress(processProgress):
    """
    Объект выдает прогресс, ища в выводе \d+%
    
    Args:
        part: количество прогрессов в программе
        delimeter: разделители строк по умолчанию \n и \r
        cachefilter: фильтр вывода программы (регулярная строка)
        startpart: используется для вывода процентов прогрессбар
                   с определенного места (0 по умолчанию)
        end:       конечный прогрессбар (по умолчанию)
        atty: для получения данных создается pty
    """
    def init(self,*args,**kwargs):
        self.rePerc = re.compile("(\d+)%",re.S)
        self.part = kwargs.get("part",1)
        if self.part < 1:
            self.part = 1
        self.add_offset = 100 / self.part
        self.offset = 0 + kwargs.get("startpart",0)*self.add_offset
        self.is_end = kwargs.get("end",True)
        self.stderr = STDOUT
        self.delimeter = re.compile("[%s]"%kwargs.get("delimeter","\n\r"))
        self.cachedata = re.compile(kwargs.get("cachefilter",
                "((?:\[31;01m\*|\[33;01m\*|error:|warning:|fatal:).*)"))
        self.atty = kwargs.get("atty",False)
        self.alldata = ""

    def _open(self):
        self.master,self.slave = None,None
        if self.atty:
            if not self.pipe:
                self.master,self.slave = os.openpty()
                self.pipe = Popen(self.command,
                                  stdout=self.slave,
                                  stderr=self.slave,
                                  cwd=self.cwd,
                                  close_fds=True,
                                  env=self.envdict)
                self.pipe.stdout = os.fdopen(self.master,'r')
        else:
            process._open(self)

    def processInit(self):
        self.percent = 0
        self.showval = 0
        if not self.offset:
            return 0

    def processEnd(self):
        if not self.slave is None:
            os.close(self.slave)
        if self.is_end:
            self.percent = 100
            return 100

    def processString(self, strdata):
        self.alldata += strdata
        match = self.rePerc.search(strdata)
        resSearch = self.cachedata.search(strdata)
        if resSearch:
            self.cacheresult.append(
                re.sub("\[31;01m\*|\[33;01m\*|\[.*?m",
                       "", resSearch.group(1)))
        if match:
            percent = int(match.group(1))
            if percent < self.percent:
                self.offset = self.offset + self.add_offset
            percent = percent / self.part
            if percent != self.percent:
                self.percent = percent
                showval = min(99,self.percent + self.offset)
                if showval != self.showval:
                    self.showval = showval
                    return self.showval


def set_active_tty(ttynum):
    """
    Установить активный терминал
    """
    chvt = getProgPath("/usr/bin/chvt")
    if chvt:
        os.system('chvt %d &>/dev/null' % ttynum)

def get_active_tty():
    """
    Получить активный терминал
    """
    getvt = getProgPath("/usr/bin/fgconsole")
    if getvt:
        p = process(getvt)
        if p.success():
            return p.read().strip()
    return None
