#!/usr/bin/env python3 # coding=utf-8 import sys import struct import ctypes import tarfile import platform # class for manipulating mode bits stored in lxattrb class stmode: # file types IFMT = 0o170000 SOCK = 0o140000 FLNK = 0o120000 FREG = 0o100000 BLCK = 0o60000 FDIR = 0o40000 FCHR = 0o20000 FIFO = 0o10000 # protection bits SUID = 0o4000 SGID = 0o2000 SVTX = 0o1000 # permissions # owner RWXU = 0o700 RUSR = 0o400 WUSR = 0o200 XUSR = 0o100 # group RWXG = 0o70 RGRP = 0o40 WGRP = 0o20 XGRP = 0o10 # others RWXO = 0o7 ROTH = 0o4 WOTH = 0o2 XOTH = 0o1 # methods follow the same naming convention as TarInfo @staticmethod def issock(mode): """ Determine whether mode bits indicate a socket. :param mode: Mode bits. :return: Evaluation result. """ return mode & stmode.IFMT == stmode.SOCK @staticmethod def issym(mode): """ Determine whether mode bits indicate a symbolic link. :param mode: Mode bits. :return: Evaluation result. """ return mode & stmode.IFMT == stmode.FLNK @staticmethod def isblk(mode): """ Determine whether mode bits indicate a block device. :param mode: Mode bits. :return: Evaluation result. """ return mode & stmode.IFMT == stmode.BLCK @staticmethod def isdir(mode): """ Determine whether mode bits indicate a directory. :param mode: Mode bits. :return: Evaluation result. """ return mode & stmode.IFMT == stmode.FDIR @staticmethod def isfile(mode): """ Determine whether mode bits indicate a regular file. :param mode: Mode bits. :return: Evaluation result. """ return mode & stmode.IFMT == stmode.FREG @staticmethod def ischr(mode): """ Determine whether mode bits indicate a character device. :param mode: Mode bits. :return: Evaluation result. """ return mode & stmode.IFMT == stmode.FCHR @staticmethod def isfifo(mode): """ Determine whether mode bits indicate a FIFO device. :param mode: Mode bits. :return: Evaluation result. """ return mode & stmode.IFMT == stmode.FIFO @staticmethod def isdev(mode): """ Determine whether mode bits indicate a character, block or FIFO device. :param mode: Mode bits. :return: Evaluation result. """ return stmode.ischr(mode) or stmode.isblk(mode) or stmode.isfifo(mode) @staticmethod def getperms(mode): """ Extract the permission bits from the full bitset. :param mode: Mode bits. :return: Permission bits. """ return mode & ~stmode.IFMT # class for parsing and generating lxattrb entries class lxattrb: structure = 'HHIIIIIIIQQQ' def __init__(self, mode = 0, uid = 0, gid = 0, drive = 0, atime = 0, mtime = 0, ctime = 0): self.flags = 0 self.version = 1 self.mode = mode self.uid = uid self.gid = gid self.drive = drive self.atime = atime self.mtime = mtime self.ctime = ctime def generate(self): """ Generate an lxattrb entry using the currently set values. :return: Entry bytes. """ return struct.pack(lxattrb.structure, self.flags, self.version, self.mode, self.uid, self.gid, self.drive, 0, 0, 0, self.atime, self.mtime, self.ctime) @staticmethod def parse(value): """ Parse an existing lxattrb entry byte array. :param value: Entry bytes. :return: An instance of this class with the data members filled accordingly. """ ret = lxattrb() ret.flags, ret.version, ret.mode, ret.uid, ret.gid, ret.drive, _, _, _, ret.atime, ret.mtime, ret.ctime = struct.unpack(lxattrb.structure, value) return ret @staticmethod def fromtar(tar): """ Converts a TarInfo instance to its equivalent Lxattrb instance. :param tar: TarInfo instance. :return: An instance of this class with the data members filled accordingly. """ ret = lxattrb() ret.uid = tar.uid ret.gid = tar.gid ret.drive = 0 # float 2 int ret.atime = int(getattr(tar.pax_headers, "atime", tar.mtime)) ret.mtime = int(tar.mtime) ret.ctime = int(getattr(tar.pax_headers, "ctime", tar.mtime)) # set file type if tar.isfile(): ret.mode |= stmode.FREG elif tar.isdir(): ret.mode |= stmode.FDIR elif tar.issym() or tar.islnk(): ret.mode |= stmode.FLNK elif tar.ischr(): ret.mode |= stmode.FCHR elif tar.isblk(): ret.mode |= stmode.BLCK elif tar.isfifo(): ret.mode |= stmode.FIFO # apply permissions ret.mode |= tar.mode return ret @staticmethod def fromsfs(sfs): """ Converts a SquashedFile instance to its equivalent Lxattrb instance. :param sfs: SquashedFile instance. :return: An instance of this class with the data members filled accordingly. """ ret = lxattrb() ret.uid = sfs.inode.uid ret.gid = sfs.inode.gid ret.drive = 0 ret.atime = sfs.inode.time ret.mtime = sfs.inode.time ret.ctime = sfs.inode.time ret.mode = sfs.inode.mode return ret # internal structures of the ntfsea.dll for ctypes class ntfsea_Ea(ctypes.Structure): _fields_ = [('Name', ctypes.c_char * 256), ('ValueLength', ctypes.c_uint), ('Value', ctypes.c_ubyte * 256)] class ntfsea_EaList(ctypes.Structure): _fields_ = [('ListSize', ctypes.c_uint), ('List', ntfsea_Ea * 4096)] # class for interfacing with the ntfsea.dll library class ntfsea: lib = None pwstr = ctypes.c_wchar_p pstr = lambda str: ctypes.c_char_p(str.encode('utf-8')) pbytes = lambda str: ctypes.create_string_buffer(str, len(str)) @staticmethod def init(): """ Initializes the ntfsea library. """ if ntfsea.lib is None: if hasattr(ctypes, 'WinDLL'): loader = ctypes.WinDLL else: loader = ctypes.CDLL ntfsea.lib = loader('ntfsea_%s.dll' % ('x64' if platform.architecture()[0] == '64bit' else 'x86')) ntfsea.lib.GetEaList.restype = ctypes.POINTER(ntfsea_EaList) ntfsea.lib.GetEa.restype = ctypes.POINTER(ntfsea_Ea) ntfsea.lib.WriteEa.restype = ctypes.c_int @staticmethod def getattrlist(file): """ Fetches the list of extended attributes available on the requested file. :param file: Path to the file. :return: List of extended attributes or None. """ ret = ntfsea.lib.GetEaList(ntfsea.pwstr(file)) if ret.contents.ListSize > 0: eas = [] for i in range(0, ret.contents.ListSize): try: eas += [(ret.contents.List[i].Name.decode('utf-8'), bytes(ret.contents.List[i].Value[:ret.contents.List[i].ValueLength]))] except Exception: pass return eas else: return None @staticmethod def getattr(file, name): """ Fetches the specified extended attribute and its value from the requested file. :param file: Path to the file. :param name: Name of the extended attribute. :return: Extended attribute information or None. """ ret = ntfsea.lib.GetEa(ntfsea.pwstr(file), ntfsea.pstr(name)) if 0 < ret.contents.ValueLength <= 256: try: return bytes(ret.contents.Value[:ret.contents.ValueLength]) except Exception: return None else: return None @staticmethod def writeattr(file, name, value): """ Writes the specified extended attribute and its value to the requested file. :param file: Path to the file. :param name: Name of the extended attribute. :param value: Value of the extended attribute. :return: Number of bytes written (should match EaValueLength) or -1 on failure. """ ret = ntfsea.lib.WriteEa(ntfsea.pwstr(file), ntfsea.pstr(name), ntfsea.pbytes(value), len(value)) return ret