# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from ctypes import *
import struct
import os
import datetime
import uuid

from .utils import *

libc = cdll.LoadLibrary('/usr/lib/libc.dylib')

# Constants
FSOPT_NOFOLLOW          = 0x00000001
FSOPT_NOINMEMUPDATE     = 0x00000002
FSOPT_REPORT_FULLSIZE   = 0x00000004
FSOPT_PACK_INVAL_ATTRS  = 0x00000008
FSOPT_ATTR_CMN_EXTENDED = 0x00000020
FSOPT_RETURN_REALDEV    = 0x00000200

VOL_CAPABILITIES_FORMAT     = 0
VOL_CAPABILITIES_INTERFACES = 1

VOL_CAP_FMT_PERSISTENTOBJECTIDS = 0x00000001
VOL_CAP_FMT_SYMBOLICLINKS       = 0x00000002
VOL_CAP_FMT_HARDLINKS           = 0x00000004
VOL_CAP_FMT_JOURNAL             = 0x00000008
VOL_CAP_FMT_JOURNAL_ACTIVE      = 0x00000010
VOL_CAP_FMT_NO_ROOT_TIMES       = 0x00000020
VOL_CAP_FMT_SPARSE_FILES        = 0x00000040
VOL_CAP_FMT_ZERO_RUNS           = 0x00000080
VOL_CAP_FMT_CASE_SENSITIVE      = 0x00000100
VOL_CAP_FMT_CASE_PRESERVING     = 0x00000200
VOL_CAP_FMT_FAST_STATFS         = 0x00000400
VOL_CAP_FMT_2TB_FILESIZE        = 0x00000800
VOL_CAP_FMT_OPENDENYMODES       = 0x00001000
VOL_CAP_FMT_HIDDEN_FILES        = 0x00002000
VOL_CAP_FMT_PATH_FROM_ID        = 0x00004000
VOL_CAP_FMT_NO_VOLUME_SIZES     = 0x00008000
VOL_CAP_FMT_DECMPFS_COMPRESSION = 0x00010000
VOL_CAP_FMT_64BIT_OBJECT_IDS    = 0x00020000

VOL_CAP_INT_SEARCHFS          = 0x00000001
VOL_CAP_INT_ATTRLIST          = 0x00000002
VOL_CAP_INT_NFSEXPORT         = 0x00000004
VOL_CAP_INT_READDIRATTR       = 0x00000008
VOL_CAP_INT_EXCHANGEDATA      = 0x00000010
VOL_CAP_INT_COPYFILE          = 0x00000020
VOL_CAP_INT_ALLOCATE          = 0x00000040
VOL_CAP_INT_VOL_RENAME        = 0x00000080
VOL_CAP_INT_ADVLOCK           = 0x00000100
VOL_CAP_INT_FLOCK             = 0x00000200
VOL_CAP_INT_EXTENDED_SECURITY = 0x00000400
VOL_CAP_INT_USERACCESS        = 0x00000800
VOL_CAP_INT_MANLOCK           = 0x00001000
VOL_CAP_INT_NAMEDSTREAMS      = 0x00002000
VOL_CAP_INT_EXTENDED_ATTR     = 0x00004000
VOL_CAP_INT_CLONE             = 0x00010000
VOL_CAP_INT_SNAPSHOT          = 0x00020000
VOL_CAP_INT_RENAME_SWAP       = 0x00040000
VOL_CAP_INT_RENAME_EXCL       = 0x00080000
VOL_CAP_INT_RENAME_OPENFAIL   = 0x00100000

ATTR_CMN_NAME               = 0x00000001
ATTR_CMN_DEVID              = 0x00000002
ATTR_CMN_FSID               = 0x00000004
ATTR_CMN_OBJTYPE            = 0x00000008
ATTR_CMN_OBJTAG             = 0x00000010
ATTR_CMN_OBJID              = 0x00000020
ATTR_CMN_OBJPERMANENTID     = 0x00000040
ATTR_CMN_PAROBJID           = 0x00000080
ATTR_CMN_SCRIPT             = 0x00000100
ATTR_CMN_CRTIME             = 0x00000200
ATTR_CMN_MODTIME            = 0x00000400
ATTR_CMN_CHGTIME            = 0x00000800
ATTR_CMN_ACCTIME            = 0x00001000
ATTR_CMN_BKUPTIME           = 0x00002000
ATTR_CMN_FNDRINFO           = 0x00004000
ATTR_CMN_OWNERID            = 0x00008000
ATTR_CMN_GRPID              = 0x00010000
ATTR_CMN_ACCESSMASK         = 0x00020000
ATTR_CMN_FLAGS              = 0x00040000
ATTR_CMN_GEN_COUNT          = 0x00080000
ATTR_CMN_DOCUMENT_ID        = 0x00100000
ATTR_CMN_USERACCESS         = 0x00200000
ATTR_CMN_EXTENDED_SECURITY  = 0x00400000
ATTR_CMN_UUID               = 0x00800000
ATTR_CMN_GRPUUID            = 0x01000000
ATTR_CMN_FILEID             = 0x02000000
ATTR_CMN_PARENTID           = 0x04000000
ATTR_CMN_FULLPATH           = 0x08000000
ATTR_CMN_ADDEDTIME          = 0x10000000
ATTR_CMN_ERROR              = 0x20000000
ATTR_CMN_DATA_PROTECT_FLAGS = 0x40000000
ATTR_CMN_RETURNED_ATTRS     = 0x80000000
ATTR_CMN_ALL_ATTRS          = 0xffffffff

ATTR_CMN_VALIDMASK          = 0xffffffff
ATTR_CMN_SETMASK            = 0x51c7ff00
ATTR_CMN_VOLSETMASK         = 0x00006700

ATTR_VOL_FSTYPE		 = 0x00000001
ATTR_VOL_SIGNATURE	 = 0x00000002
ATTR_VOL_SIZE		 = 0x00000004
ATTR_VOL_SPACEFREE	 = 0x00000008
ATTR_VOL_SPACEAVAIL	 = 0x00000010
ATTR_VOL_MINALLOCATION	 = 0x00000020
ATTR_VOL_ALLOCATIONCLUMP = 0x00000040
ATTR_VOL_IOBLOCKSIZE	 = 0x00000080
ATTR_VOL_OBJCOUNT	 = 0x00000100
ATTR_VOL_FILECOUNT	 = 0x00000200
ATTR_VOL_DIRCOUNT	 = 0x00000400
ATTR_VOL_MAXOBJCOUNT	 = 0x00000800
ATTR_VOL_MOUNTPOINT	 = 0x00001000
ATTR_VOL_NAME		 = 0x00002000
ATTR_VOL_MOUNTFLAGS	 = 0x00004000
ATTR_VOL_MOUNTEDDEVICE	 = 0x00008000
ATTR_VOL_ENCODINGSUSED	 = 0x00010000
ATTR_VOL_CAPABILITIES	 = 0x00020000
ATTR_VOL_UUID		 = 0x00040000
ATTR_VOL_QUOTA_SIZE      = 0x10000000
ATTR_VOL_RESERVED_SIZE   = 0x20000000
ATTR_VOL_ATTRIBUTES	 = 0x40000000
ATTR_VOL_INFO		 = 0x80000000
ATTR_VOL_ALL_ATTRS       = 0xf007ffff

ATTR_DIR_LINKCOUNT     = 0x00000001
ATTR_DIR_ENTRYCOUNT    = 0x00000002
ATTR_DIR_MOUNTSTATUS   = 0x00000004
DIR_MNTSTATUS_MNTPOINT   = 0x00000001
DIR_MNTSTATUS_TRIGGER    = 0x00000002
ATTR_DIR_ALLOCSIZE     = 0x00000008
ATTR_DIR_IOBLOCKSIZE   = 0x00000010
ATTR_DIR_DATALENGTH    = 0x00000020
ATTR_DIR_ALL_ATTRS     = 0x0000003f

ATTR_DIR_VALIDMASK     = 0x0000003f
ATTR_DIR_SETMASK       = 0x00000000

ATTR_FILE_LINKCOUNT	= 0x00000001
ATTR_FILE_TOTALSIZE	= 0x00000002
ATTR_FILE_ALLOCSIZE	= 0x00000004
ATTR_FILE_IOBLOCKSIZE	= 0x00000008
ATTR_FILE_DEVTYPE	= 0x00000020
ATTR_FILE_FORKCOUNT     = 0x00000080
ATTR_FILE_FORKLIST      = 0x00000100
ATTR_FILE_DATALENGTH	= 0x00000200
ATTR_FILE_DATAALLOCSIZE	= 0x00000400
ATTR_FILE_RSRCLENGTH	= 0x00001000
ATTR_FILE_RSRCALLOCSIZE	= 0x00002000
ATTR_FILE_ALL_ATTRS     = 0x000037ff

ATTR_FILE_VALIDMASK     = 0x000037ff
ATTR_FILE_SETMASK       = 0x00000020

# These are deprecated
ATTR_FORK_TOTALSIZE = 0x00000001
ATTR_FORK_ALLOCSIZE = 0x00000002
ATTR_FORK_ALL_ATTRS = 0x00000003

# These go in the fork attribute field
ATTR_CMNEXT_RELPATH            = 0x00000004
ATTR_CMNEXT_PRIVATESIZE        = 0x00000008
ATTR_CMNEXT_LINKID             = 0x0000010
ATTR_CMNEXT_NOFIRMLINKPATH     = 0x00000020
ATTR_CMNEXT_REALDEVID          = 0x00000040
ATTR_CMNEXT_REALFSID           = 0x00000080
ATTR_CMNEXT_CLONEID            = 0x00000100
ATTR_CMNEXT_EXT_FLAGS          = 0x00000200
ATTR_CMNEXT_RECURSIVE_GENCOUNT = 0x00000400
ATTR_CMNEXT_ALL_ATTRS          = 0x000007fc

ATTR_CMNEXT_VALIDMASK = 0x000007fc
ATTR_CMNEXT_SETMASK   = 0x00000000

ATTR_FORK_VALIDMASK  = 0x00000003
ATTR_FORK_SETMASK    = 0x00000000

# These can't be used
ATTR_CMN_NAMEDATTRCOUNT	= 0x00080000
ATTR_CMN_NAMEDATTRLIST	= 0x00100000
ATTR_FILE_CLUMPSIZE	= 0x00000010
ATTR_FILE_FILETYPE	= 0x00000040
ATTR_FILE_DATAEXTENTS	= 0x00000800
ATTR_FILE_RSRCEXTENTS	= 0x00004000

class attrlist(Structure):
    _fields_ = [('bitmapcount', c_ushort),
                ('reserved', c_ushort),
                ('commonattr', c_uint),
                ('volattr', c_uint),
                ('dirattr', c_uint),
                ('fileattr', c_uint),
                ('forkattr', c_uint)]

class attribute_set_t(Structure):
    _fields_ = [('commonattr', c_uint),
                ('volattr', c_uint),
                ('dirattr', c_uint),
                ('fileattr', c_uint),
                ('forkattr', c_uint)]

class fsobj_id_t(Structure):
    _fields_ = [('fid_objno', c_uint),
                ('fid_generation', c_uint)]

class timespec(Structure):
    _fields_ = [('tv_sec', c_long),
                ('tv_nsec', c_long)]

class attrreference_t(Structure):
    _fields_ = [('attr_dataoffset', c_int),
                ('attr_length', c_uint)]

class fsid_t(Structure):
    _fields_ = [('val', c_uint * 2)]

class guid_t(Structure):
    _fields_ = [('g_guid', c_byte*16)]

class kauth_ace(Structure):
    _fields_ = [('ace_applicable', guid_t),
                ('ace_flags', c_uint)]

class kauth_acl(Structure):
    _fields_ = [('acl_entrycount', c_uint),
                ('acl_flags', c_uint),
                ('acl_ace', kauth_ace * 128)]

class kauth_filesec(Structure):
    _fields_ = [('fsec_magic', c_uint),
                ('fsec_owner', guid_t),
                ('fsec_group', guid_t),
                ('fsec_acl', kauth_acl)]

class diskextent(Structure):
    _fields_ = [('startblock', c_uint),
                ('blockcount', c_uint)]

OSType = c_uint
UInt16 = c_ushort
SInt16 = c_short
SInt32 = c_int

class Point(Structure):
    _fields_ = [('x', SInt16),
                ('y', SInt16)]
class Rect(Structure):
    _fields_ = [('x', SInt16),
                ('y', SInt16),
                ('w', SInt16),
                ('h', SInt16)]
class FileInfo(Structure):
    _fields_ = [('fileType', OSType),
                ('fileCreator', OSType),
                ('finderFlags', UInt16),
                ('location', Point),
                ('reservedField', UInt16),
                ('reserved1', SInt16 * 4),
                ('extendedFinderFlags', UInt16),
                ('reserved2', SInt16),
                ('putAwayFolderID', SInt32)]
class FolderInfo(Structure):
    _fields_ = [('windowBounds', Rect),
                ('finderFlags', UInt16),
                ('location', Point),
                ('reservedField', UInt16),
                ('scrollPosition', Point),
                ('reserved1', SInt32),
                ('extendedFinderFlags', UInt16),
                ('reserved2', SInt16),
                ('putAwayFolderID', SInt32)]
class FinderInfo(Union):
    _fields_ = [('fileInfo', FileInfo),
                ('folderInfo', FolderInfo)]

extentrecord = diskextent * 8

vol_capabilities_set_t = c_uint * 4

class vol_capabilities_attr_t(Structure):
    _fields_ = [('capabilities', vol_capabilities_set_t),
                ('valid', vol_capabilities_set_t)]

class vol_attributes_attr_t(Structure):
    _fields_ = [('validattr', attribute_set_t),
                ('nativeattr', attribute_set_t)]

dev_t = c_uint

fsobj_type_t = c_uint

VNON = 0
VREG = 1
VDIR = 2
VBLK = 3
VCHR = 4
VLNK = 5
VSOCK = 6
VFIFO = 7
VBAD = 8
VSTR = 9
VCPLX = 10

fsobj_tag_t = c_uint

VT_NON = 0
VT_UFS = 1
VT_NFS = 2
VT_MFS = 3
VT_MSDOSFS = 4
VT_LFS = 5
VT_LOFS = 6
VT_FDESC = 7
VT_PORTAL = 8
VT_NULL = 9
VT_UMAP = 10
VT_KERNFS = 11
VT_PROCFS = 12
VT_AFS = 13
VT_ISOFS = 14
VT_UNION = 15
VT_HFS = 16
VT_ZFS = 17
VT_DEVFS = 18
VT_WEBDAV = 19
VT_UDF = 20
VT_AFP = 21
VT_CDDA = 22
VT_CIFS = 23
VT_OTHER = 24

fsfile_type_t = c_uint
fsvolid_t = c_uint
text_encoding_t = c_uint
uid_t = c_uint
gid_t = c_uint
int32_t = c_int
uint32_t = c_uint
int64_t = c_longlong
uint64_t = c_ulonglong
off_t = c_long
size_t = c_ulong
uuid_t = c_byte*16

NAME_MAX = 255
PATH_MAX = 1024
FSTYPE_MAX = 16

class struct_statfs(Structure):
    _fields_ = [('f_bsize', uint32_t),
                ('f_iosize', int32_t),
                ('f_blocks', uint64_t),
                ('f_bfree', uint64_t),
                ('f_bavail', uint64_t),
                ('f_files', uint64_t),
                ('f_ffree', uint64_t),
                ('f_fsid', fsid_t),
                ('f_owner', uid_t),
                ('f_type', uint32_t),
                ('f_flags', uint32_t),
                ('f_fssubtype', uint32_t),
                ('f_fstypename', c_char * FSTYPE_MAX),
                ('f_mntonname', c_char * PATH_MAX),
                ('f_mntfromname', c_char * PATH_MAX),
                ('f_flags_ext', uint32_t),
                ('f_reserved', uint32_t * 7)]

# Calculate the maximum number of bytes required for the attribute buffer
_attr_info = (
    # Common attributes
    (0, ATTR_CMN_RETURNED_ATTRS, sizeof(attribute_set_t)),
    (0, ATTR_CMN_NAME, sizeof(attrreference_t) + NAME_MAX * 3 + 1),
    (0, ATTR_CMN_DEVID, sizeof(dev_t)),
    (0, ATTR_CMN_FSID, sizeof(fsid_t)),
    (0, ATTR_CMN_OBJTYPE, sizeof(fsobj_type_t)),
    (0, ATTR_CMN_OBJTAG, sizeof(fsobj_tag_t)),
    (0, ATTR_CMN_OBJID, sizeof(fsobj_id_t)),
    (0, ATTR_CMN_OBJPERMANENTID, sizeof(fsobj_id_t)),
    (0, ATTR_CMN_PAROBJID, sizeof(fsobj_id_t)),
    (0, ATTR_CMN_SCRIPT, sizeof(text_encoding_t)),
    (0, ATTR_CMN_CRTIME, sizeof(timespec)),
    (0, ATTR_CMN_MODTIME, sizeof(timespec)),
    (0, ATTR_CMN_CHGTIME, sizeof(timespec)),
    (0, ATTR_CMN_ACCTIME, sizeof(timespec)),
    (0, ATTR_CMN_BKUPTIME, sizeof(timespec)),
    (0, ATTR_CMN_FNDRINFO, sizeof(FinderInfo)),
    (0, ATTR_CMN_OWNERID, sizeof(uid_t)),
    (0, ATTR_CMN_GRPID, sizeof(gid_t)),
    (0, ATTR_CMN_ACCESSMASK, sizeof(uint32_t)),
    (0, ATTR_CMN_NAMEDATTRCOUNT, None),
    (0, ATTR_CMN_NAMEDATTRLIST, None),
    (0, ATTR_CMN_FLAGS, sizeof(uint32_t)),
    (0, ATTR_CMN_GEN_COUNT, sizeof(uint32_t)),
    (0, ATTR_CMN_DOCUMENT_ID, sizeof(uint32_t)),
    (0, ATTR_CMN_USERACCESS, sizeof(uint32_t)),
    (0, ATTR_CMN_EXTENDED_SECURITY, sizeof(attrreference_t) + sizeof(kauth_filesec)),
    (0, ATTR_CMN_UUID, sizeof(guid_t)),
    (0, ATTR_CMN_GRPUUID, sizeof(guid_t)),
    (0, ATTR_CMN_FILEID, sizeof(uint64_t)),
    (0, ATTR_CMN_PARENTID, sizeof(uint64_t)),
    (0, ATTR_CMN_FULLPATH, sizeof(attrreference_t) + PATH_MAX),
    (0, ATTR_CMN_ADDEDTIME, sizeof(timespec)),
    (0, ATTR_CMN_DATA_PROTECT_FLAGS, sizeof(uint32_t)),

    # Volume attributes
    (1, ATTR_VOL_FSTYPE, sizeof(uint32_t)),
    (1, ATTR_VOL_SIGNATURE, sizeof(uint32_t)),
    (1, ATTR_VOL_SIZE, sizeof(off_t)),
    (1, ATTR_VOL_SPACEFREE, sizeof(off_t)),
    (1, ATTR_VOL_SPACEAVAIL, sizeof(off_t)),
    (1, ATTR_VOL_MINALLOCATION, sizeof(off_t)),
    (1, ATTR_VOL_ALLOCATIONCLUMP, sizeof(off_t)),
    (1, ATTR_VOL_IOBLOCKSIZE, sizeof(uint32_t)),
    (1, ATTR_VOL_OBJCOUNT, sizeof(uint32_t)),
    (1, ATTR_VOL_FILECOUNT, sizeof(uint32_t)),
    (1, ATTR_VOL_DIRCOUNT, sizeof(uint32_t)),
    (1, ATTR_VOL_MAXOBJCOUNT, sizeof(uint32_t)),
    (1, ATTR_VOL_MOUNTPOINT, sizeof(attrreference_t) + PATH_MAX),
    (1, ATTR_VOL_NAME, sizeof(attrreference_t) + NAME_MAX + 1),
    (1, ATTR_VOL_MOUNTFLAGS, sizeof(uint32_t)),
    (1, ATTR_VOL_MOUNTEDDEVICE, sizeof(attrreference_t) + PATH_MAX),
    (1, ATTR_VOL_ENCODINGSUSED, sizeof(c_ulonglong)),
    (1, ATTR_VOL_CAPABILITIES, sizeof(vol_capabilities_attr_t)),
    (1, ATTR_VOL_UUID, sizeof(uuid_t)),
    (1, ATTR_VOL_QUOTA_SIZE, sizeof(off_t)),
    (1, ATTR_VOL_RESERVED_SIZE, sizeof(off_t)),
    (1, ATTR_VOL_ATTRIBUTES, sizeof(vol_attributes_attr_t)),

    # Directory attributes
    (2, ATTR_DIR_LINKCOUNT, sizeof(uint32_t)),
    (2, ATTR_DIR_ENTRYCOUNT, sizeof(uint32_t)),
    (2, ATTR_DIR_MOUNTSTATUS, sizeof(uint32_t)),
    (2, ATTR_DIR_ALLOCSIZE, sizeof(off_t)),
    (2, ATTR_DIR_IOBLOCKSIZE, sizeof(uint32_t)),
    (2, ATTR_DIR_DATALENGTH, sizeof(off_t)),

    # File attributes
    (3, ATTR_FILE_LINKCOUNT, sizeof(uint32_t)),
    (3, ATTR_FILE_TOTALSIZE, sizeof(off_t)),
    (3, ATTR_FILE_ALLOCSIZE, sizeof(off_t)),
    (3, ATTR_FILE_IOBLOCKSIZE, sizeof(uint32_t)),
    (3, ATTR_FILE_CLUMPSIZE, sizeof(uint32_t)),
    (3, ATTR_FILE_DEVTYPE, sizeof(uint32_t)),
    (3, ATTR_FILE_FILETYPE, sizeof(uint32_t)),
    (3, ATTR_FILE_FORKCOUNT, sizeof(uint32_t)),
    (3, ATTR_FILE_FORKLIST, None),
    (3, ATTR_FILE_DATALENGTH, sizeof(off_t)),
    (3, ATTR_FILE_DATAALLOCSIZE, sizeof(off_t)),
    (3, ATTR_FILE_DATAEXTENTS, sizeof(extentrecord)),
    (3, ATTR_FILE_RSRCLENGTH, sizeof(off_t)),
    (3, ATTR_FILE_RSRCALLOCSIZE, sizeof(off_t)),
    (3, ATTR_FILE_RSRCEXTENTS, sizeof(extentrecord)),

    # Fork attributes
    (4, ATTR_FORK_TOTALSIZE, sizeof(off_t)),
    (4, ATTR_FORK_ALLOCSIZE, sizeof(off_t)),

    # Extended common attributes
    (4, ATTR_CMNEXT_RELPATH, sizeof(attrreference_t) + PATH_MAX),
    (4, ATTR_CMNEXT_PRIVATESIZE, sizeof(off_t)),
    (4, ATTR_CMNEXT_LINKID, sizeof(uint64_t)),
    (4, ATTR_CMNEXT_NOFIRMLINKPATH, sizeof(attrreference_t) + PATH_MAX),
    (4, ATTR_CMNEXT_REALDEVID, sizeof(dev_t)),
    (4, ATTR_CMNEXT_REALFSID, sizeof(fsid_t)),
    (4, ATTR_CMNEXT_CLONEID, sizeof(uint64_t)),
    (4, ATTR_CMNEXT_EXT_FLAGS, sizeof(uint64_t)),
    )

def _attrbuf_size(attrs):
    size = 4
    for entry in _attr_info:
        if attrs[entry[0]] & entry[1]:
            if entry[2] is None:
                raise ValueError('Unsupported attribute (%u, %x)'
                                 % (entry[0], entry[1]))
            size += entry[2]
    return size

_getattrlist = libc.getattrlist
_getattrlist.argtypes = [c_char_p, POINTER(attrlist), c_void_p, c_ulong, c_ulong]
_getattrlist.restype = c_int

_fgetattrlist = libc.fgetattrlist
_fgetattrlist.argtypes = [c_int, POINTER(attrlist), c_void_p, c_ulong, c_ulong]
_fgetattrlist.restype = c_int

try:
    _statfs = libc['statfs$INODE64']
except (KeyError, AttributeError):
    _statfs = libc['statfs']

_statfs.argtypes = [c_char_p, POINTER(struct_statfs)]
_statfs.restype = c_int

try:
    _fstatfs = libc['fstatfs$INODE64']
except (KeyError, AttributeError):
    _fstatfs = libc['fstatfs']

_fstatfs.argtypes = [c_int, POINTER(struct_statfs)]
_fstatfs.restype = c_int

def _datetime_from_timespec(ts):
    td = datetime.timedelta(seconds=ts.tv_sec + 1.0e-9 * ts.tv_nsec)
    return unix_epoch + td

def _decode_utf8_nul(sz):
    nul = sz.find(b'\0')
    if nul > -1:
        sz = sz[:nul]
    return sz.decode('utf-8')

def _decode_attrlist_result(buf, attrs, options):
    result = []

    assert len(buf) >= 4
    total_size = uint32_t.from_buffer(buf, 0).value
    assert total_size <= len(buf)

    offset = 4

    # Common attributes
    if attrs[0] & ATTR_CMN_RETURNED_ATTRS:
        a = attribute_set_t.from_buffer(buf, offset)
        result.append(a)
        offset += sizeof (attribute_set_t)
        if not (options & FSOPT_PACK_INVAL_ATTRS):
            attrs = [a.commonattr, a.volattr, a.dirattr, a.fileattr, a.forkattr]
    if attrs[0] & ATTR_CMN_NAME:
        a = attrreference_t.from_buffer(buf, offset)
        ofs = offset + a.attr_dataoffset
        name = _decode_utf8_nul(buf[ofs:ofs+a.attr_length])
        offset += sizeof (attrreference_t)
        result.append(name)
    if attrs[0] & ATTR_CMN_DEVID:
        a = dev_t.from_buffer(buf, offset)
        offset += sizeof(dev_t)
        result.append(a.value)
    if attrs[0] & ATTR_CMN_FSID:
        a = fsid_t.from_buffer(buf, offset)
        offset += sizeof(fsid_t)
        result.append(a)
    if attrs[0] & ATTR_CMN_OBJTYPE:
        a = fsobj_type_t.from_buffer(buf, offset)
        offset += sizeof(fsobj_type_t)
        result.append(a.value)
    if attrs[0] & ATTR_CMN_OBJTAG:
        a = fsobj_tag_t.from_buffer(buf, offset)
        offset += sizeof(fsobj_tag_t)
        result.append(a.value)
    if attrs[0] & ATTR_CMN_OBJID:
        a = fsobj_id_t.from_buffer(buf, offset)
        offset += sizeof(fsobj_id_t)
        result.append(a)
    if attrs[0] & ATTR_CMN_OBJPERMANENTID:
        a = fsobj_id_t.from_buffer(buf, offset)
        offset += sizeof(fsobj_id_t)
        result.append(a)
    if attrs[0] & ATTR_CMN_PAROBJID:
        a = fsobj_id_t.from_buffer(buf, offset)
        offset += sizeof(fsobj_id_t)
        result.append(a)
    if attrs[0] & ATTR_CMN_SCRIPT:
        a = text_encoding_t.from_buffer(buf, offset)
        offset += sizeof(text_encoding_t)
        result.append(a.value)
    if attrs[0] & ATTR_CMN_CRTIME:
        a = timespec.from_buffer(buf, offset)
        offset += sizeof(timespec)
        result.append(_datetime_from_timespec(a))
    if attrs[0] & ATTR_CMN_MODTIME:
        a = timespec.from_buffer(buf, offset)
        offset += sizeof(timespec)
        result.append(_datetime_from_timespec(a))
    if attrs[0] & ATTR_CMN_CHGTIME:
        a = timespec.from_buffer(buf, offset)
        offset += sizeof(timespec)
        result.append(_datetime_from_timespec(a))
    if attrs[0] & ATTR_CMN_ACCTIME:
        a = timespec.from_buffer(buf, offset)
        offset += sizeof(timespec)
        result.append(_datetime_from_timespec(a))
    if attrs[0] & ATTR_CMN_BKUPTIME:
        a = timespec.from_buffer(buf, offset)
        offset += sizeof(timespec)
        result.append(_datetime_from_timespec(a))
    if attrs[0] & ATTR_CMN_FNDRINFO:
        a = FinderInfo.from_buffer(buf, offset)
        offset += sizeof(FinderInfo)
        result.append(a)
    if attrs[0] & ATTR_CMN_OWNERID:
        a = uid_t.from_buffer(buf, offset)
        offset += sizeof(uid_t)
        result.append(a.value)
    if attrs[0] & ATTR_CMN_GRPID:
        a = gid_t.from_buffer(buf, offset)
        offset += sizeof(gid_t)
        result.append(a.value)
    if attrs[0] & ATTR_CMN_ACCESSMASK:
        a = uint32_t.from_buffer(buf, offset)
        offset += sizeof(uint32_t)
        result.append(a.value)
    if attrs[0] & ATTR_CMN_FLAGS:
        a = uint32_t.from_buffer(buf, offset)
        offset += sizeof(uint32_t)
        result.append(a.value)
    if attrs[0] & ATTR_CMN_GEN_COUNT:
        a = uint32_t.from_buffer(buf, offset)
        offset += sizeof(uint32_t)
        result.append(a.value)
    if attrs[0] & ATTR_CMN_DOCUMENT_ID:
        a = uint32_t.from_buffer(buf, offset)
        offset += sizeof(uint32_t)
        result.append(a.value)
    if attrs[0] & ATTR_CMN_USERACCESS:
        a = uint32_t.from_buffer(buf, offset)
        offset += sizeof(uint32_t)
        result.append(a.value)
    if attrs[0] & ATTR_CMN_EXTENDED_SECURITY:
        a = attrreference_t.from_buffer(buf, offset)
        ofs = offset + a.attr_dataoffset
        offset += sizeof(attrreference_t)
        ec = uint32_t.from_buffer(buf, ofs + 36).value
        class kauth_acl(Structure):
            _fields_ = [('acl_entrycount', c_uint),
                        ('acl_flags', c_uint),
                        ('acl_ace', kauth_ace * ec)]
        class kauth_filesec(Structure):
            _fields_ = [('fsec_magic', c_uint),
                        ('fsec_owner', guid_t),
                        ('fsec_group', guid_t),
                        ('fsec_acl', kauth_acl)]
        a = kauth_filesec.from_buffer(buf, ofs)
        result.append(a)
    if attrs[0] & ATTR_CMN_UUID:
        result.append(uuid.UUID(bytes=buf[offset:offset+16]))
        offset += sizeof(guid_t)
    if attrs[0] & ATTR_CMN_GRPUUID:
        result.append(uuid.UUID(bytes=buf[offset:offset+16]))
        offset += sizeof(guid_t)
    if attrs[0] & ATTR_CMN_FILEID:
        a = uint64_t.from_buffer(buf, offset)
        offset += sizeof(uint64_t)
        result.append(a.value)
    if attrs[0] & ATTR_CMN_PARENTID:
        a = uint64_t.from_buffer(buf, offset)
        offset += sizeof(uint64_t)
        result.append(a.value)
    if attrs[0] & ATTR_CMN_FULLPATH:
        a = attrreference_t.from_buffer(buf, offset)
        ofs = offset + a.attr_dataoffset
        path = _decode_utf8_nul(buf[ofs:ofs+a.attr_length])
        offset += sizeof (attrreference_t)
        result.append(path)
    if attrs[0] & ATTR_CMN_ADDEDTIME:
        a = timespec.from_buffer(buf, offset)
        offset += sizeof(timespec)
        result.append(_datetime_from_timespec(a))
    if attrs[0] & ATTR_CMN_DATA_PROTECT_FLAGS:
        a = uint32_t.from_buffer(buf, offset)
        offset += sizeof(uint32_t)
        result.append(a.value)

    # Volume attributes
    if attrs[1] & ATTR_VOL_FSTYPE:
        a = uint32_t.from_buffer(buf, offset)
        offset += sizeof(uint32_t)
        result.append(a.value)
    if attrs[1] & ATTR_VOL_SIGNATURE:
        a = uint32_t.from_buffer(buf, offset)
        offset += sizeof(uint32_t)
        result.append(a.value)
    if attrs[1] & ATTR_VOL_SIZE:
        a = off_t.from_buffer(buf, offset)
        offset += sizeof(off_t)
        result.append(a.value)
    if attrs[1] & ATTR_VOL_SPACEFREE:
        a = off_t.from_buffer(buf, offset)
        offset += sizeof(off_t)
        result.append(a.value)
    if attrs[1] & ATTR_VOL_SPACEAVAIL:
        a = off_t.from_buffer(buf, offset)
        offset += sizeof(off_t)
        result.append(a.value)
    if attrs[1] & ATTR_VOL_MINALLOCATION:
        a = off_t.from_buffer(buf, offset)
        offset += sizeof(off_t)
        result.append(a.value)
    if attrs[1] & ATTR_VOL_ALLOCATIONCLUMP:
        a = off_t.from_buffer(buf, offset)
        offset += sizeof(off_t)
        result.append(a.value)
    if attrs[1] & ATTR_VOL_IOBLOCKSIZE:
        a = uint32_t.from_buffer(buf, offset)
        offset += sizeof(uint32_t)
        result.append(a.value)
    if attrs[1] & ATTR_VOL_OBJCOUNT:
        a = uint32_t.from_buffer(buf, offset)
        offset += sizeof(uint32_t)
        result.append(a.value)
    if attrs[1] & ATTR_VOL_FILECOUNT:
        a = uint32_t.from_buffer(buf, offset)
        offset += sizeof(uint32_t)
        result.append(a.value)
    if attrs[1] & ATTR_VOL_DIRCOUNT:
        a = uint32_t.from_buffer(buf, offset)
        offset += sizeof(uint32_t)
        result.append(a.value)
    if attrs[1] & ATTR_VOL_MAXOBJCOUNT:
        a = uint32_t.from_buffer(buf, offset)
        offset += sizeof(uint32_t)
        result.append(a.value)
    if attrs[1] & ATTR_VOL_MOUNTPOINT:
        a = attrreference_t.from_buffer(buf, offset)
        ofs = offset + a.attr_dataoffset
        path = _decode_utf8_nul(buf[ofs:ofs+a.attr_length])
        offset += sizeof (attrreference_t)
        result.append(path)
    if attrs[1] & ATTR_VOL_NAME:
        a = attrreference_t.from_buffer(buf, offset)
        ofs = offset + a.attr_dataoffset
        name = _decode_utf8_nul(buf[ofs:ofs+a.attr_length])
        offset += sizeof (attrreference_t)
        result.append(name)
    if attrs[1] & ATTR_VOL_MOUNTFLAGS:
        a = uint32_t.from_buffer(buf, offset)
        offset += sizeof(uint32_t)
        result.append(a.value)
    if attrs[1] & ATTR_VOL_MOUNTEDDEVICE:
        a = attrreference_t.from_buffer(buf, offset)
        ofs = offset + a.attr_dataoffset
        path = _decode_utf8_nul(buf[ofs:ofs+a.attr_length])
        offset += sizeof (attrreference_t)
        result.append(path)
    if attrs[1] & ATTR_VOL_ENCODINGSUSED:
        a = c_ulonglong.from_buffer(buf, offset)
        offset += sizeof(c_ulonglong)
        result.append(a.value)
    if attrs[1] & ATTR_VOL_CAPABILITIES:
        a = vol_capabilities_attr_t.from_buffer(buf, offset)
        offset += sizeof(vol_capabilities_attr_t)
        result.append(a)
    if attrs[1] & ATTR_VOL_UUID:
        result.append(uuid.UUID(bytes=buf[offset:offset+16]))
        offset += sizeof(uuid_t)
    if attrs[1] & ATTR_VOL_QUOTA_SIZE:
        a = off_t.from_buffer(buf, offset)
        offset += sizeof(off_t)
        result.append(a.value)
    if attrs[1] & ATTR_VOL_RESERVED_SIZE:
        a = off_t.from_buffer(buf, offset)
        offset += sizeof(off_t)
        result.append(a.value)
    if attrs[1] & ATTR_VOL_ATTRIBUTES:
        a = vol_attributes_attr_t.from_buffer(buf, offset)
        offset += sizeof(vol_attributes_attr_t)
        result.append(a)

    # Directory attributes
    if attrs[2] & ATTR_DIR_LINKCOUNT:
        a = uint32_t.from_buffer(buf, offset)
        offset += sizeof(uint32_t)
        result.append(a.value)
    if attrs[2] & ATTR_DIR_ENTRYCOUNT:
        a = uint32_t.from_buffer(buf, offset)
        offset += sizeof(uint32_t)
        result.append(a.value)
    if attrs[2] & ATTR_DIR_MOUNTSTATUS:
        a = uint32_t.from_buffer(buf, offset)
        offset += sizeof(uint32_t)
        result.append(a.value)
    if attrs[2] & ATTR_DIR_ALLOCSIZE:
        a = off_t.from_buffer(buf, offset)
        offset += sizeof(off_t)
        result.append(a.value)
    if attrs[2] & ATTR_DIR_IOBLOCKSIZE:
        a = uint32_t.from_buffer(buf, offset)
        offset += sizeof(uint32_t)
        result.append(a.value)
    if attrs[2] & ATTR_DIR_DATALENGTH:
        a = off_t.from_buffer(buf, offset)
        offset += sizeof(off_t)
        result.append(a.value)

    # File attributes
    if attrs[3] & ATTR_FILE_LINKCOUNT:
        a = uint32_t.from_buffer(buf, offset)
        offset += sizeof(uint32_t)
        result.append(a.value)
    if attrs[3] & ATTR_FILE_TOTALSIZE:
        a = off_t.from_buffer(buf, offset)
        offset += sizeof(off_t)
        result.append(a.value)
    if attrs[3] & ATTR_FILE_ALLOCSIZE:
        a = off_t.from_buffer(buf, offset)
        offset += sizeof(off_t)
        result.append(a.value)
    if attrs[3] & ATTR_FILE_IOBLOCKSIZE:
        a = uint32_t.from_buffer(buf, offset)
        offset += sizeof(uint32_t)
        result.append(a.value)
    if attrs[3] & ATTR_FILE_CLUMPSIZE:
        a = uint32_t.from_buffer(buf, offset)
        offset += sizeof(uint32_t)
        result.append(a.value)
    if attrs[3] & ATTR_FILE_DEVTYPE:
        a = uint32_t.from_buffer(buf, offset)
        offset += sizeof(uint32_t)
        result.append(a.value)
    if attrs[3] & ATTR_FILE_FILETYPE:
        a = uint32_t.from_buffer(buf, offset)
        offset += sizeof(uint32_t)
        result.append(a.value)
    if attrs[3] & ATTR_FILE_FORKCOUNT:
        a = uint32_t.from_buffer(buf, offset)
        offset += sizeof(uint32_t)
        result.append(a.value)
    if attrs[3] & ATTR_FILE_DATALENGTH:
        a = off_t.from_buffer(buf, offset)
        offset += sizeof(off_t)
        result.append(a.value)
    if attrs[3] & ATTR_FILE_DATAALLOCSIZE:
        a = off_t.from_buffer(buf, offset)
        offset += sizeof(off_t)
        result.append(a.value)
    if attrs[3] & ATTR_FILE_DATAEXTENTS:
        a = extentrecord.from_buffer(buf, offset)
        offset += sizeof(extentrecord)
        result.append(a.value)
    if attrs[3] & ATTR_FILE_RSRCLENGTH:
        a = off_t.from_buffer(buf, offset)
        offset += sizeof(off_t)
        result.append(a.value)
    if attrs[3] & ATTR_FILE_RSRCALLOCSIZE:
        a = off_t.from_buffer(buf, offset)
        offset += sizeof(off_t)
        result.append(a.value)
    if attrs[3] & ATTR_FILE_RSRCEXTENTS:
        a = extentrecord.from_buffer(buf, offset)
        offset += sizeof(extentrecord)
        result.append(a.value)

    # Fork attributes
    if attrs[4] & ATTR_FORK_TOTALSIZE:
        a = off_t.from_buffer(buf, offset)
        offset += sizeof(off_t)
        result.append(a.value)
    if attrs[4] & ATTR_FORK_ALLOCSIZE:
        a = off_t.from_buffer(buf, offset)
        offset += sizeof(off_t)
        result.append(a.value)

    # Extended common attributes
    if attrs[4] & ATTR_CMNEXT_RELPATH:
        a = attrreference_t.from_buffer(buf, offset)
        ofs = offset + a.attr_dataoffset
        path = _decode_utf8_nul(buf[ofs:ofs+a.attr_length])
        offset += sizeof (attrreference_t)
        result.append(path)
    if attrs[4] & ATTR_CMNEXT_PRIVATESIZE:
        a = off_t.from_buffer(buf, offset)
        offset += sizeof(off_t)
        result.append(a.value)
    if attrs[4] & ATTR_CMNEXT_LINKID:
        a = uint64_t.from_buffer(buf, offset)
        offset += sizeof(uint64_t)
        result.append(a.value)
    if attrs[4] & ATTR_CMNEXT_NOFIRMLINKPATH:
        a = attrreference_t.from_buffer(buf, offset)
        ofs = offset + a.attr_dataoffset
        path = _decode_utf8_nul(buf[ofs:ofs+a.attr_length])
        offset += sizeof (attrreference_t)
        result.append(path)
    if attrs[4] & ATTR_CMNEXT_REALDEVID:
        a = dev_t.from_buffer(buf, offset)
        offset += sizeof(dev_t)
        result.append(a.value)
    if attrs[4] & ATTR_CMNEXT_REALFSID:
        a = fsid_t.from_buffer(buf, offset)
        offset += sizeof(fsid_t)
        result.append(a.value)
    if attrs[4] & ATTR_CMNEXT_CLONEID:
        a = uint64_t.from_buffer(buf, offset)
        offset += sizeof(uint64_t)
        result.append(a.value)
    if attrs[4] & ATTR_CMNEXT_EXT_FLAGS:
        a = uint64_t.from_buffer(buf, offset)
        offset += sizeof(uint64_t)
        result.append(a.value)

    return result

# Sadly, ctypes.get_errno() seems not to work
__error = libc.__error
__error.restype = POINTER(c_int)

def _get_errno():
    return __error().contents.value

def getattrlist(path, attrs, options):
    if not isinstance(path, bytes):
        path = path.encode('utf-8')
    attrs = list(attrs)
    if attrs[1]:
        attrs[1] |= ATTR_VOL_INFO
    alist = attrlist(bitmapcount=5,
                     commonattr=attrs[0],
                     volattr=attrs[1],
                     dirattr=attrs[2],
                     fileattr=attrs[3],
                     forkattr=attrs[4])

    bufsize = _attrbuf_size(attrs)
    buf = create_string_buffer(bufsize)

    ret = _getattrlist(path, byref(alist), buf, bufsize,
                       options | FSOPT_REPORT_FULLSIZE)

    if ret < 0:
        err = _get_errno()
        raise OSError(err, os.strerror(err), path)

    return _decode_attrlist_result(buf, attrs, options)

def fgetattrlist(fd, attrs, options):
    if hasattr(fd, 'fileno'):
        fd = fd.fileno()
    attrs = list(attrs)
    if attrs[1]:
        attrs[1] |= ATTR_VOL_INFO
    alist = attrlist(bitmapcount=5,
                     commonattr=attrs[0],
                     volattr=attrs[1],
                     dirattr=attrs[2],
                     fileattr=attrs[3],
                     forkattr=attrs[4])

    bufsize = _attrbuf_size(attrs)
    buf = create_string_buffer(bufsize)

    ret = _fgetattrlist(fd, byref(alist), buf, bufsize,
                        options | FSOPT_REPORT_FULLSIZE)

    if ret < 0:
        err = _get_errno()
        raise OSError(err, os.strerror(err))

    return _decode_attrlist_result(buf, attrs, options)

def statfs(path):
    if not isinstance(path, bytes):
        path = path.encode('utf-8')
    result = struct_statfs()
    ret = _statfs(path, byref(result))
    if ret < 0:
        err = _get_errno()
        raise OSError(err, os.strerror(err), path)
    return result

def fstatfs(fd):
    if hasattr(fd, 'fileno'):
        fd = fd.fileno()
    result = struct_statfs()
    ret = _fstatfs(fd, byref(result))
    if ret < 0:
        err = _get_errno()
        raise OSError(err, os.strerror(err))
    return result