This commit is contained in:
2022-09-30 05:39:11 +00:00
parent 41ee9463ae
commit 4687fa49bc
11418 changed files with 1312504 additions and 0 deletions

View File

@ -0,0 +1,27 @@
from .alias import *
from .bookmark import *
__all__ = [ 'ALIAS_KIND_FILE', 'ALIAS_KIND_FOLDER',
'ALIAS_HFS_VOLUME_SIGNATURE',
'ALIAS_FIXED_DISK', 'ALIAS_NETWORK_DISK', 'ALIAS_400KB_FLOPPY_DISK',
'ALIAS_800KB_FLOPPY_DISK', 'ALIAS_1_44MB_FLOPPY_DISK',
'ALIAS_EJECTABLE_DISK',
'ALIAS_NO_CNID',
'kBookmarkPath', 'kBookmarkCNIDPath', 'kBookmarkFileProperties',
'kBookmarkFileName', 'kBookmarkFileID', 'kBookmarkFileCreationDate',
'kBookmarkTOCPath', 'kBookmarkVolumePath',
'kBookmarkVolumeURL', 'kBookmarkVolumeName', 'kBookmarkVolumeUUID',
'kBookmarkVolumeSize', 'kBookmarkVolumeCreationDate',
'kBookmarkVolumeProperties', 'kBookmarkContainingFolder',
'kBookmarkUserName', 'kBookmarkUID', 'kBookmarkWasFileReference',
'kBookmarkCreationOptions', 'kBookmarkURLLengths',
'kBookmarkSecurityExtension',
'AppleShareInfo',
'VolumeInfo',
'TargetInfo',
'Alias',
'Bookmark',
'Data',
'URL' ]

Binary file not shown.

View File

@ -0,0 +1,612 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from __future__ import division
import struct
import datetime
import io
import re
import os
import os.path
import stat
import sys
from unicodedata import normalize
if sys.platform == 'darwin':
from . import osx
try:
long
except NameError:
long = int
from .utils import *
ALIAS_KIND_FILE = 0
ALIAS_KIND_FOLDER = 1
ALIAS_HFS_VOLUME_SIGNATURE = b'H+'
ALIAS_FIXED_DISK = 0
ALIAS_NETWORK_DISK = 1
ALIAS_400KB_FLOPPY_DISK = 2
ALIAS_800KB_FLOPPY_DISK = 3
ALIAS_1_44MB_FLOPPY_DISK = 4
ALIAS_EJECTABLE_DISK = 5
ALIAS_NO_CNID = 0xffffffff
def encode_utf8(s):
if isinstance(s, bytes):
return s
return s.encode('utf-8')
def decode_utf8(s):
if isinstance(s, bytes):
return s.decode('utf-8')
return s
class AppleShareInfo (object):
def __init__(self, zone=None, server=None, user=None):
#: The AppleShare zone
self.zone = zone
#: The AFP server
self.server = server
#: The username
self.user = user
def __repr__(self):
return 'AppleShareInfo(%r,%r,%r)' % (self.zone, self.server, self.user)
class VolumeInfo (object):
def __init__(self, name, creation_date, fs_type, disk_type,
attribute_flags, fs_id, appleshare_info=None,
driver_name=None, posix_path=None, disk_image_alias=None,
dialup_info=None, network_mount_info=None):
#: The name of the volume on which the target resides
self.name = name
#: The creation date of the target's volume
self.creation_date = creation_date
#: The filesystem type (a two character code, e.g. ``b'H+'`` for HFS+)
self.fs_type = fs_type
#: The type of disk; should be one of
#:
#: * ALIAS_FIXED_DISK
#: * ALIAS_NETWORK_DISK
#: * ALIAS_400KB_FLOPPY_DISK
#: * ALIAS_800KB_FLOPPY_DISK
#: * ALIAS_1_44MB_FLOPPY_DISK
#: * ALIAS_EJECTABLE_DISK
self.disk_type = disk_type
#: Filesystem attribute flags (from HFS volume header)
self.attribute_flags = attribute_flags
#: Filesystem identifier
self.fs_id = fs_id
#: AppleShare information (for automatic remounting of network shares)
#: *(optional)*
self.appleshare_info = appleshare_info
#: Driver name (*probably* contains a disk driver name on older Macs)
#: *(optional)*
self.driver_name = driver_name
#: POSIX path of the mount point of the target's volume
#: *(optional)*
self.posix_path = posix_path
#: :class:`Alias` object pointing at the disk image on which the
#: target's volume resides *(optional)*
self.disk_image_alias = disk_image_alias
#: Dialup information (for automatic establishment of dialup connections)
self.dialup_info = dialup_info
#: Network mount information (for automatic remounting)
self.network_mount_info = network_mount_info
def __repr__(self):
args = ['name', 'creation_date', 'fs_type', 'disk_type',
'attribute_flags', 'fs_id']
values = []
for a in args:
v = getattr(self, a)
values.append(repr(v))
kwargs = ['appleshare_info', 'driver_name', 'posix_path',
'disk_image_alias', 'dialup_info', 'network_mount_info']
for a in kwargs:
v = getattr(self, a)
if v is not None:
values.append('%s=%r' % (a, v))
return 'VolumeInfo(%s)' % ','.join(values)
class TargetInfo (object):
def __init__(self, kind, filename, folder_cnid, cnid, creation_date,
creator_code, type_code, levels_from=-1, levels_to=-1,
folder_name=None, cnid_path=None, carbon_path=None,
posix_path=None, user_home_prefix_len=None):
#: Either ALIAS_KIND_FILE or ALIAS_KIND_FOLDER
self.kind = kind
#: The filename of the target
self.filename = filename
#: The CNID (Catalog Node ID) of the target's containing folder;
#: CNIDs are similar to but different than traditional UNIX inode
#: numbers
self.folder_cnid = folder_cnid
#: The CNID (Catalog Node ID) of the target
self.cnid = cnid
#: The target's *creation* date.
self.creation_date = creation_date
#: The target's Mac creator code (a four-character binary string)
self.creator_code = creator_code
#: The target's Mac type code (a four-character binary string)
self.type_code = type_code
#: The depth of the alias? Always seems to be -1 on OS X.
self.levels_from = levels_from
#: The depth of the target? Always seems to be -1 on OS X.
self.levels_to = levels_to
#: The (POSIX) name of the target's containing folder. *(optional)*
self.folder_name = folder_name
#: The path from the volume root as a sequence of CNIDs. *(optional)*
self.cnid_path = cnid_path
#: The Carbon path of the target *(optional)*
self.carbon_path = carbon_path
#: The POSIX path of the target relative to the volume root. Note
#: that this may or may not have a leading '/' character, but it is
#: always relative to the containing volume. *(optional)*
self.posix_path = posix_path
#: If the path points into a user's home folder, the number of folders
#: deep that we go before we get to that home folder. *(optional)*
self.user_home_prefix_len = user_home_prefix_len
def __repr__(self):
args = ['kind', 'filename', 'folder_cnid', 'cnid', 'creation_date',
'creator_code', 'type_code']
values = []
for a in args:
v = getattr(self, a)
values.append(repr(v))
if self.levels_from != -1:
values.append('levels_from=%r' % self.levels_from)
if self.levels_to != -1:
values.append('levels_to=%r' % self.levels_to)
kwargs = ['folder_name', 'cnid_path', 'carbon_path',
'posix_path', 'user_home_prefix_len']
for a in kwargs:
v = getattr(self, a)
values.append('%s=%r' % (a, v))
return 'TargetInfo(%s)' % ','.join(values)
TAG_CARBON_FOLDER_NAME = 0
TAG_CNID_PATH = 1
TAG_CARBON_PATH = 2
TAG_APPLESHARE_ZONE = 3
TAG_APPLESHARE_SERVER_NAME = 4
TAG_APPLESHARE_USERNAME = 5
TAG_DRIVER_NAME = 6
TAG_NETWORK_MOUNT_INFO = 9
TAG_DIALUP_INFO = 10
TAG_UNICODE_FILENAME = 14
TAG_UNICODE_VOLUME_NAME = 15
TAG_HIGH_RES_VOLUME_CREATION_DATE = 16
TAG_HIGH_RES_CREATION_DATE = 17
TAG_POSIX_PATH = 18
TAG_POSIX_PATH_TO_MOUNTPOINT = 19
TAG_RECURSIVE_ALIAS_OF_DISK_IMAGE = 20
TAG_USER_HOME_LENGTH_PREFIX = 21
class Alias (object):
def __init__(self, appinfo=b'\0\0\0\0', version=2, volume=None,
target=None, extra=[]):
"""Construct a new :class:`Alias` object with the specified
contents."""
#: Application specific information (four byte byte-string)
self.appinfo = appinfo
#: Version (we support only version 2)
self.version = version
#: A :class:`VolumeInfo` object describing the target's volume
self.volume = volume
#: A :class:`TargetInfo` object describing the target
self.target = target
#: A list of extra `(tag, value)` pairs
self.extra = list(extra)
@classmethod
def _from_fd(cls, b):
appinfo, recsize, version = struct.unpack(b'>4shh', b.read(8))
if recsize < 150:
raise ValueError('Incorrect alias length')
if version != 2:
raise ValueError('Unsupported alias version %u' % version)
kind, volname, voldate, fstype, disktype, \
folder_cnid, filename, cnid, crdate, creator_code, type_code, \
levels_from, levels_to, volattrs, volfsid, reserved = \
struct.unpack(b'>h28pI2shI64pII4s4shhI2s10s', b.read(142))
voldate = mac_epoch + datetime.timedelta(seconds=voldate)
crdate = mac_epoch + datetime.timedelta(seconds=crdate)
alias = Alias()
alias.appinfo = appinfo
alias.volume = VolumeInfo (volname.replace('/',':'),
voldate, fstype, disktype,
volattrs, volfsid)
alias.target = TargetInfo (kind, filename.replace('/',':'),
folder_cnid, cnid,
crdate, creator_code, type_code)
alias.target.levels_from = levels_from
alias.target.levels_to = levels_to
tag = struct.unpack(b'>h', b.read(2))[0]
while tag != -1:
length = struct.unpack(b'>h', b.read(2))[0]
value = b.read(length)
if length & 1:
b.read(1)
if tag == TAG_CARBON_FOLDER_NAME:
alias.target.folder_name = value.replace('/',':')
elif tag == TAG_CNID_PATH:
alias.target.cnid_path = struct.unpack(b'>%uI' % (length // 4),
value)
elif tag == TAG_CARBON_PATH:
alias.target.carbon_path = value
elif tag == TAG_APPLESHARE_ZONE:
if alias.volume.appleshare_info is None:
alias.volume.appleshare_info = AppleShareInfo()
alias.volume.appleshare_info.zone = value
elif tag == TAG_APPLESHARE_SERVER_NAME:
if alias.volume.appleshare_info is None:
alias.volume.appleshare_info = AppleShareInfo()
alias.volume.appleshare_info.server = value
elif tag == TAG_APPLESHARE_USERNAME:
if alias.volume.appleshare_info is None:
alias.volume.appleshare_info = AppleShareInfo()
alias.volume.appleshare_info.user = value
elif tag == TAG_DRIVER_NAME:
alias.volume.driver_name = value
elif tag == TAG_NETWORK_MOUNT_INFO:
alias.volume.network_mount_info = value
elif tag == TAG_DIALUP_INFO:
alias.volume.dialup_info = value
elif tag == TAG_UNICODE_FILENAME:
alias.target.filename = value[2:].decode('utf-16be')
elif tag == TAG_UNICODE_VOLUME_NAME:
alias.volume.name = value[2:].decode('utf-16be')
elif tag == TAG_HIGH_RES_VOLUME_CREATION_DATE:
seconds = struct.unpack(b'>Q', value)[0] / 65536.0
alias.volume.creation_date \
= mac_epoch + datetime.timedelta(seconds=seconds)
elif tag == TAG_HIGH_RES_CREATION_DATE:
seconds = struct.unpack(b'>Q', value)[0] / 65536.0
alias.target.creation_date \
= mac_epoch + datetime.timedelta(seconds=seconds)
elif tag == TAG_POSIX_PATH:
alias.target.posix_path = value
elif tag == TAG_POSIX_PATH_TO_MOUNTPOINT:
alias.volume.posix_path = value
elif tag == TAG_RECURSIVE_ALIAS_OF_DISK_IMAGE:
alias.volume.disk_image_alias = Alias.from_bytes(value)
elif tag == TAG_USER_HOME_LENGTH_PREFIX:
alias.target.user_home_prefix_len = struct.unpack(b'>h', value)[0]
else:
alias.extra.append((tag, value))
tag = struct.unpack(b'>h', b.read(2))[0]
return alias
@classmethod
def from_bytes(cls, bytes):
"""Construct an :class:`Alias` object given binary Alias data."""
with io.BytesIO(bytes) as b:
return cls._from_fd(b)
@classmethod
def for_file(cls, path):
"""Create an :class:`Alias` that points at the specified file."""
if sys.platform != 'darwin':
raise Exception('Not implemented (requires special support)')
path = encode_utf8(path)
a = Alias()
# Find the filesystem
st = osx.statfs(path)
vol_path = st.f_mntonname
# File and folder names in HFS+ are normalized to a form similar to NFD.
# Must be normalized (NFD->NFC) before use to avoid unicode string comparison issues.
vol_path = normalize("NFC", vol_path.decode('utf-8')).encode('utf-8')
# Grab its attributes
attrs = [osx.ATTR_CMN_CRTIME,
osx.ATTR_VOL_NAME,
0, 0, 0]
volinfo = osx.getattrlist(vol_path, attrs, 0)
vol_crtime = volinfo[0]
vol_name = encode_utf8(volinfo[1])
# Also grab various attributes of the file
attrs = [(osx.ATTR_CMN_OBJTYPE
| osx.ATTR_CMN_CRTIME
| osx.ATTR_CMN_FNDRINFO
| osx.ATTR_CMN_FILEID
| osx.ATTR_CMN_PARENTID), 0, 0, 0, 0]
info = osx.getattrlist(path, attrs, osx.FSOPT_NOFOLLOW)
if info[0] == osx.VDIR:
kind = ALIAS_KIND_FOLDER
else:
kind = ALIAS_KIND_FILE
cnid = info[3]
folder_cnid = info[4]
dirname, filename = os.path.split(path)
if dirname == b'' or dirname == b'.':
dirname = os.getcwd()
foldername = os.path.basename(dirname)
creation_date = info[1]
if kind == ALIAS_KIND_FILE:
creator_code = struct.pack(b'I', info[2].fileInfo.fileCreator)
type_code = struct.pack(b'I', info[2].fileInfo.fileType)
else:
creator_code = b'\0\0\0\0'
type_code = b'\0\0\0\0'
a.target = TargetInfo(kind, filename, folder_cnid, cnid, creation_date,
creator_code, type_code)
a.volume = VolumeInfo(vol_name, vol_crtime, b'H+',
ALIAS_FIXED_DISK, 0, b'\0\0')
a.target.folder_name = foldername
a.volume.posix_path = vol_path
rel_path = os.path.relpath(path, vol_path)
# Leave off the initial '/' if vol_path is '/' (no idea why)
if vol_path == b'/':
a.target.posix_path = rel_path
else:
a.target.posix_path = b'/' + rel_path
# Construct the Carbon and CNID paths
carbon_path = []
cnid_path = []
head, tail = os.path.split(rel_path)
if not tail:
head, tail = os.path.split(head)
while head or tail:
if head:
attrs = [osx.ATTR_CMN_FILEID, 0, 0, 0, 0]
info = osx.getattrlist(os.path.join(vol_path, head), attrs, 0)
cnid_path.append(info[0])
carbon_tail = tail.replace(b':',b'/')
carbon_path.insert(0, carbon_tail)
head, tail = os.path.split(head)
carbon_path = vol_name + b':' + b':\0'.join(carbon_path)
a.target.carbon_path = carbon_path
a.target.cnid_path = cnid_path
return a
def _to_fd(self, b):
# We'll come back and fix the length when we're done
pos = b.tell()
b.write(struct.pack(b'>4shh', self.appinfo, 0, self.version))
carbon_volname = encode_utf8(self.volume.name).replace(b':',b'/')
carbon_filename = encode_utf8(self.target.filename).replace(b':',b'/')
voldate = (self.volume.creation_date - mac_epoch).total_seconds()
crdate = (self.target.creation_date - mac_epoch).total_seconds()
# NOTE: crdate should be in local time, but that's system dependent
# (so doing so is ridiculous, and nothing could rely on it).
b.write(struct.pack(b'>h28pI2shI64pII4s4shhI2s10s',
self.target.kind,
carbon_volname, int(voldate),
self.volume.fs_type,
self.volume.disk_type,
self.target.folder_cnid,
carbon_filename,
self.target.cnid,
int(crdate),
self.target.creator_code,
self.target.type_code,
self.target.levels_from,
self.target.levels_to,
self.volume.attribute_flags,
self.volume.fs_id,
b'\0'*10))
# Excuse the odd order; we're copying Finder
if self.target.folder_name:
carbon_foldername = encode_utf8(self.target.folder_name)\
.replace(b':',b'/')
b.write(struct.pack(b'>hh', TAG_CARBON_FOLDER_NAME,
len(carbon_foldername)))
b.write(carbon_foldername)
if len(carbon_foldername) & 1:
b.write(b'\0')
b.write(struct.pack(b'>hhQhhQ',
TAG_HIGH_RES_VOLUME_CREATION_DATE,
8, long(voldate * 65536),
TAG_HIGH_RES_CREATION_DATE,
8, long(crdate * 65536)))
if self.target.cnid_path:
cnid_path = struct.pack(b'>%uI' % len(self.target.cnid_path),
*self.target.cnid_path)
b.write(struct.pack(b'>hh', TAG_CNID_PATH,
len(cnid_path)))
b.write(cnid_path)
if self.target.carbon_path:
carbon_path=encode_utf8(self.target.carbon_path)
b.write(struct.pack(b'>hh', TAG_CARBON_PATH,
len(carbon_path)))
b.write(carbon_path)
if len(carbon_path) & 1:
b.write(b'\0')
if self.volume.appleshare_info:
ai = self.volume.appleshare_info
if ai.zone:
b.write(struct.pack(b'>hh', TAG_APPLESHARE_ZONE,
len(ai.zone)))
b.write(ai.zone)
if len(ai.zone) & 1:
b.write(b'\0')
if ai.server:
b.write(struct.pack(b'>hh', TAG_APPLESHARE_SERVER_NAME,
len(ai.server)))
b.write(ai.server)
if len(ai.server) & 1:
b.write(b'\0')
if ai.username:
b.write(struct.pack(b'>hh', TAG_APPLESHARE_USERNAME,
len(ai.username)))
b.write(ai.username)
if len(ai.username) & 1:
b.write(b'\0')
if self.volume.driver_name:
driver_name = encode_utf8(self.volume.driver_name)
b.write(struct.pack(b'>hh', TAG_DRIVER_NAME,
len(driver_name)))
b.write(driver_name)
if len(driver_name) & 1:
b.write(b'\0')
if self.volume.network_mount_info:
b.write(struct.pack(b'>hh', TAG_NETWORK_MOUNT_INFO,
len(self.volume.network_mount_info)))
b.write(self.volume.network_mount_info)
if len(self.volume.network_mount_info) & 1:
b.write(b'\0')
if self.volume.dialup_info:
b.write(struct.pack(b'>hh', TAG_DIALUP_INFO,
len(self.volume.network_mount_info)))
b.write(self.volume.network_mount_info)
if len(self.volume.network_mount_info) & 1:
b.write(b'\0')
utf16 = decode_utf8(self.target.filename)\
.replace(':','/').encode('utf-16-be')
b.write(struct.pack(b'>hhh', TAG_UNICODE_FILENAME,
len(utf16) + 2,
len(utf16) // 2))
b.write(utf16)
utf16 = decode_utf8(self.volume.name)\
.replace(':','/').encode('utf-16-be')
b.write(struct.pack(b'>hhh', TAG_UNICODE_VOLUME_NAME,
len(utf16) + 2,
len(utf16) // 2))
b.write(utf16)
if self.target.posix_path:
posix_path = encode_utf8(self.target.posix_path)
b.write(struct.pack(b'>hh', TAG_POSIX_PATH,
len(posix_path)))
b.write(posix_path)
if len(posix_path) & 1:
b.write(b'\0')
if self.volume.posix_path:
posix_path = encode_utf8(self.volume.posix_path)
b.write(struct.pack(b'>hh', TAG_POSIX_PATH_TO_MOUNTPOINT,
len(posix_path)))
b.write(posix_path)
if len(posix_path) & 1:
b.write(b'\0')
if self.volume.disk_image_alias:
d = self.volume.disk_image_alias.to_bytes()
b.write(struct.pack(b'>hh', TAG_RECURSIVE_ALIAS_OF_DISK_IMAGE,
len(d)))
b.write(d)
if len(d) & 1:
b.write(b'\0')
if self.target.user_home_prefix_len is not None:
b.write(struct.pack(b'>hhh', TAG_USER_HOME_LENGTH_PREFIX,
2, self.target.user_home_prefix_len))
for t,v in self.extra:
b.write(struct.pack(b'>hh', t, len(v)))
b.write(v)
if len(v) & 1:
b.write(b'\0')
b.write(struct.pack(b'>hh', -1, 0))
blen = b.tell() - pos
b.seek(pos + 4, os.SEEK_SET)
b.write(struct.pack(b'>h', blen))
def to_bytes(self):
"""Returns the binary representation for this :class:`Alias`."""
with io.BytesIO() as b:
self._to_fd(b)
return b.getvalue()
def __str__(self):
return '<Alias target=%s>' % self.target.filename
def __repr__(self):
values = []
if self.appinfo != b'\0\0\0\0':
values.append('appinfo=%r' % self.appinfo)
if self.version != 2:
values.append('version=%r' % self.version)
if self.volume is not None:
values.append('volume=%r' % self.volume)
if self.target is not None:
values.append('target=%r' % self.target)
if self.extra:
values.append('extra=%r' % self.extra)
return 'Alias(%s)' % ','.join(values)

Binary file not shown.

View File

@ -0,0 +1,665 @@
# -*- coding: utf-8 -*-
#
# This file implements the Apple "bookmark" format, which is the replacement
# for the old-fashioned alias format. The details of this format were
# reverse engineered; some things are still not entirely clear.
#
from __future__ import unicode_literals, print_function
import struct
import uuid
import datetime
import os
import sys
import pprint
try:
from urlparse import urljoin
except ImportError:
from urllib.parse import urljoin
if sys.platform == 'darwin':
from . import osx
def iteritems(x):
return x.iteritems()
try:
unicode
except NameError:
unicode = str
long = int
xrange = range
def iteritems(x):
return x.items()
from .utils import *
BMK_DATA_TYPE_MASK = 0xffffff00
BMK_DATA_SUBTYPE_MASK = 0x000000ff
BMK_STRING = 0x0100
BMK_DATA = 0x0200
BMK_NUMBER = 0x0300
BMK_DATE = 0x0400
BMK_BOOLEAN = 0x0500
BMK_ARRAY = 0x0600
BMK_DICT = 0x0700
BMK_UUID = 0x0800
BMK_URL = 0x0900
BMK_NULL = 0x0a00
BMK_ST_ZERO = 0x0000
BMK_ST_ONE = 0x0001
BMK_BOOLEAN_ST_FALSE = 0x0000
BMK_BOOLEAN_ST_TRUE = 0x0001
# Subtypes for BMK_NUMBER are really CFNumberType values
kCFNumberSInt8Type = 1
kCFNumberSInt16Type = 2
kCFNumberSInt32Type = 3
kCFNumberSInt64Type = 4
kCFNumberFloat32Type = 5
kCFNumberFloat64Type = 6
kCFNumberCharType = 7
kCFNumberShortType = 8
kCFNumberIntType = 9
kCFNumberLongType = 10
kCFNumberLongLongType = 11
kCFNumberFloatType = 12
kCFNumberDoubleType = 13
kCFNumberCFIndexType = 14
kCFNumberNSIntegerType = 15
kCFNumberCGFloatType = 16
# Resource property flags (from CFURLPriv.h)
kCFURLResourceIsRegularFile = 0x00000001
kCFURLResourceIsDirectory = 0x00000002
kCFURLResourceIsSymbolicLink = 0x00000004
kCFURLResourceIsVolume = 0x00000008
kCFURLResourceIsPackage = 0x00000010
kCFURLResourceIsSystemImmutable = 0x00000020
kCFURLResourceIsUserImmutable = 0x00000040
kCFURLResourceIsHidden = 0x00000080
kCFURLResourceHasHiddenExtension = 0x00000100
kCFURLResourceIsApplication = 0x00000200
kCFURLResourceIsCompressed = 0x00000400
kCFURLResourceIsSystemCompressed = 0x00000400
kCFURLCanSetHiddenExtension = 0x00000800
kCFURLResourceIsReadable = 0x00001000
kCFURLResourceIsWriteable = 0x00002000
kCFURLResourceIsExecutable = 0x00004000
kCFURLIsAliasFile = 0x00008000
kCFURLIsMountTrigger = 0x00010000
# Volume property flags (from CFURLPriv.h)
kCFURLVolumeIsLocal = 0x1 #
kCFURLVolumeIsAutomount = 0x2 #
kCFURLVolumeDontBrowse = 0x4 #
kCFURLVolumeIsReadOnly = 0x8 #
kCFURLVolumeIsQuarantined = 0x10
kCFURLVolumeIsEjectable = 0x20 #
kCFURLVolumeIsRemovable = 0x40 #
kCFURLVolumeIsInternal = 0x80 #
kCFURLVolumeIsExternal = 0x100 #
kCFURLVolumeIsDiskImage = 0x200 #
kCFURLVolumeIsFileVault = 0x400
kCFURLVolumeIsLocaliDiskMirror = 0x800
kCFURLVolumeIsiPod = 0x1000 #
kCFURLVolumeIsiDisk = 0x2000
kCFURLVolumeIsCD = 0x4000
kCFURLVolumeIsDVD = 0x8000
kCFURLVolumeIsDeviceFileSystem = 0x10000
kCFURLVolumeSupportsPersistentIDs = 0x100000000
kCFURLVolumeSupportsSearchFS = 0x200000000
kCFURLVolumeSupportsExchange = 0x400000000
# reserved 0x800000000
kCFURLVolumeSupportsSymbolicLinks = 0x1000000000
kCFURLVolumeSupportsDenyModes = 0x2000000000
kCFURLVolumeSupportsCopyFile = 0x4000000000
kCFURLVolumeSupportsReadDirAttr = 0x8000000000
kCFURLVolumeSupportsJournaling = 0x10000000000
kCFURLVolumeSupportsRename = 0x20000000000
kCFURLVolumeSupportsFastStatFS = 0x40000000000
kCFURLVolumeSupportsCaseSensitiveNames = 0x80000000000
kCFURLVolumeSupportsCasePreservedNames = 0x100000000000
kCFURLVolumeSupportsFLock = 0x200000000000
kCFURLVolumeHasNoRootDirectoryTimes = 0x400000000000
kCFURLVolumeSupportsExtendedSecurity = 0x800000000000
kCFURLVolumeSupports2TBFileSize = 0x1000000000000
kCFURLVolumeSupportsHardLinks = 0x2000000000000
kCFURLVolumeSupportsMandatoryByteRangeLocks = 0x4000000000000
kCFURLVolumeSupportsPathFromID = 0x8000000000000
# reserved 0x10000000000000
kCFURLVolumeIsJournaling = 0x20000000000000
kCFURLVolumeSupportsSparseFiles = 0x40000000000000
kCFURLVolumeSupportsZeroRuns = 0x80000000000000
kCFURLVolumeSupportsVolumeSizes = 0x100000000000000
kCFURLVolumeSupportsRemoteEvents = 0x200000000000000
kCFURLVolumeSupportsHiddenFiles = 0x400000000000000
kCFURLVolumeSupportsDecmpFSCompression = 0x800000000000000
kCFURLVolumeHas64BitObjectIDs = 0x1000000000000000
kCFURLVolumePropertyFlagsAll = 0xffffffffffffffff
BMK_URL_ST_ABSOLUTE = 0x0001
BMK_URL_ST_RELATIVE = 0x0002
# Bookmark keys
# = 0x1003
kBookmarkPath = 0x1004 # Array of path components
kBookmarkCNIDPath = 0x1005 # Array of CNIDs
kBookmarkFileProperties = 0x1010 # (CFURL rp flags,
# CFURL rp flags asked for,
# 8 bytes NULL)
kBookmarkFileName = 0x1020
kBookmarkFileID = 0x1030
kBookmarkFileCreationDate = 0x1040
# = 0x1054 # ?
# = 0x1055 # ?
# = 0x1056 # ?
# = 0x1101 # ?
# = 0x1102 # ?
kBookmarkTOCPath = 0x2000 # A list of (TOC id, ?) pairs
kBookmarkVolumePath = 0x2002
kBookmarkVolumeURL = 0x2005
kBookmarkVolumeName = 0x2010
kBookmarkVolumeUUID = 0x2011 # Stored (perversely) as a string
kBookmarkVolumeSize = 0x2012
kBookmarkVolumeCreationDate = 0x2013
kBookmarkVolumeProperties = 0x2020 # (CFURL vp flags,
# CFURL vp flags asked for,
# 8 bytes NULL)
kBookmarkVolumeIsRoot = 0x2030 # True if volume is FS root
kBookmarkVolumeBookmark = 0x2040 # Embedded bookmark for disk image (TOC id)
kBookmarkVolumeMountPoint = 0x2050 # A URL
# = 0x2070
kBookmarkContainingFolder = 0xc001 # Index of containing folder in path
kBookmarkUserName = 0xc011 # User that created bookmark
kBookmarkUID = 0xc012 # UID that created bookmark
kBookmarkWasFileReference = 0xd001 # True if the URL was a file reference
kBookmarkCreationOptions = 0xd010
kBookmarkURLLengths = 0xe003 # See below
# = 0xf017 # Localized name?
# = 0xf022
kBookmarkSecurityExtension = 0xf080
# = 0xf081
# kBookmarkURLLengths is an array that is set if the URL encoded by the
# bookmark had a base URL; in that case, each entry is the length of the
# base URL in question. Thus a URL
#
# file:///foo/bar/baz blam/blat.html
#
# will result in [3, 2], while the URL
#
# file:///foo bar/baz blam blat.html
#
# would result in [1, 2, 1, 1]
class Data (object):
def __init__(self, bytedata=None):
#: The bytes, stored as a byte string
self.bytes = bytes(bytedata)
def __repr__(self):
return 'Data(%r)' % self.bytes
class URL (object):
def __init__(self, base, rel=None):
if rel is not None:
#: The base URL, if any (a :class:`URL`)
self.base = base
#: The rest of the URL (a string)
self.relative = rel
else:
self.base = None
self.relative = base
@property
def absolute(self):
"""Return an absolute URL."""
if self.base is None:
return self.relative
else:
base_abs = self.base.absolute
return urljoin(self.base.absolute, self.relative)
def __repr__(self):
return 'URL(%r)' % self.absolute
class Bookmark (object):
def __init__(self, tocs=None):
if tocs is None:
#: The TOCs for this Bookmark
self.tocs = []
else:
self.tocs = tocs
@classmethod
def _get_item(cls, data, hdrsize, offset):
offset += hdrsize
if offset > len(data) - 8:
raise ValueError('Offset out of range')
length,typecode = struct.unpack(b'<II', data[offset:offset+8])
if len(data) - offset < 8 + length:
raise ValueError('Data item truncated')
databytes = data[offset+8:offset+8+length]
dsubtype = typecode & BMK_DATA_SUBTYPE_MASK
dtype = typecode & BMK_DATA_TYPE_MASK
if dtype == BMK_STRING:
return databytes.decode('utf-8')
elif dtype == BMK_DATA:
return Data(databytes)
elif dtype == BMK_NUMBER:
if dsubtype == kCFNumberSInt8Type:
return ord(databytes[0])
elif dsubtype == kCFNumberSInt16Type:
return struct.unpack(b'<h', databytes)[0]
elif dsubtype == kCFNumberSInt32Type:
return struct.unpack(b'<i', databytes)[0]
elif dsubtype == kCFNumberSInt64Type:
return struct.unpack(b'<q', databytes)[0]
elif dsubtype == kCFNumberFloat32Type:
return struct.unpack(b'<f', databytes)[0]
elif dsubtype == kCFNumberFloat64Type:
return struct.unpack(b'<d', databytes)[0]
elif dtype == BMK_DATE:
# Yes, dates really are stored as *BIG-endian* doubles; everything
# else is little-endian
secs = datetime.timedelta(seconds=struct.unpack(b'>d', databytes)[0])
return osx_epoch + secs
elif dtype == BMK_BOOLEAN:
if dsubtype == BMK_BOOLEAN_ST_TRUE:
return True
elif dsubtype == BMK_BOOLEAN_ST_FALSE:
return False
elif dtype == BMK_UUID:
return uuid.UUID(bytes=databytes)
elif dtype == BMK_URL:
if dsubtype == BMK_URL_ST_ABSOLUTE:
return URL(databytes.decode('utf-8'))
elif dsubtype == BMK_URL_ST_RELATIVE:
baseoff,reloff = struct.unpack(b'<II', databytes)
base = cls._get_item(data, hdrsize, baseoff)
rel = cls._get_item(data, hdrsize, reloff)
return URL(base, rel)
elif dtype == BMK_ARRAY:
result = []
for aoff in xrange(offset+8,offset+8+length,4):
eltoff, = struct.unpack(b'<I', data[aoff:aoff+4])
result.append(cls._get_item(data, hdrsize, eltoff))
return result
elif dtype == BMK_DICT:
result = {}
for eoff in xrange(offset+8,offset+8+length,8):
keyoff,valoff = struct.unpack(b'<II', data[eoff:eoff+8])
key = cls._get_item(data, hdrsize, keyoff)
val = cls._get_item(data, hdrsize, valoff)
result[key] = val
return result
elif dtype == BMK_NULL:
return None
print('Unknown data type %08x' % typecode)
return (typecode, databytes)
@classmethod
def from_bytes(cls, data):
"""Create a :class:`Bookmark` given byte data."""
if len(data) < 16:
raise ValueError('Not a bookmark file (too short)')
if isinstance(data, bytearray):
data = bytes(data)
magic,size,dummy,hdrsize = struct.unpack(b'<4sIII', data[0:16])
if magic != b'book':
raise ValueError('Not a bookmark file (bad magic) %r' % magic)
if hdrsize < 16:
raise ValueError('Not a bookmark file (header size too short)')
if hdrsize > size:
raise ValueError('Not a bookmark file (header size too large)')
if size != len(data):
raise ValueError('Not a bookmark file (truncated)')
tocoffset, = struct.unpack(b'<I', data[hdrsize:hdrsize+4])
tocs = []
while tocoffset != 0:
tocbase = hdrsize + tocoffset
if tocoffset > size - hdrsize \
or size - tocbase < 20:
raise ValueError('TOC offset out of range')
tocsize,tocmagic,tocid,nexttoc,toccount \
= struct.unpack(b'<IIIII',
data[tocbase:tocbase+20])
if tocmagic != 0xfffffffe:
break
tocsize += 8
if size - tocbase < tocsize:
raise ValueError('TOC truncated')
if tocsize < 12 * toccount:
raise ValueError('TOC entries overrun TOC size')
toc = {}
for n in xrange(0,toccount):
ebase = tocbase + 20 + 12 * n
eid,eoffset,edummy = struct.unpack(b'<III',
data[ebase:ebase+12])
if eid & 0x80000000:
eid = cls._get_item(data, hdrsize, eid & 0x7fffffff)
toc[eid] = cls._get_item(data, hdrsize, eoffset)
tocs.append((tocid, toc))
tocoffset = nexttoc
return cls(tocs)
def __getitem__(self, key):
for tid,toc in self.tocs:
if key in toc:
return toc[key]
raise KeyError('Key not found')
def __setitem__(self, key, value):
if len(self.tocs) == 0:
self.tocs = [(1, {})]
self.tocs[0][1][key] = value
def get(self, key, default=None):
"""Lookup the value for a given key, returning a default if not
present."""
for tid,toc in self.tocs:
if key in toc:
return toc[key]
return default
@classmethod
def _encode_item(cls, item, offset):
if item is True:
result = struct.pack(b'<II', 0, BMK_BOOLEAN | BMK_BOOLEAN_ST_TRUE)
elif item is False:
result = struct.pack(b'<II', 0, BMK_BOOLEAN | BMK_BOOLEAN_ST_FALSE)
elif isinstance(item, unicode):
encoded = item.encode('utf-8')
result = (struct.pack(b'<II', len(encoded), BMK_STRING | BMK_ST_ONE)
+ encoded)
elif isinstance(item, bytes):
result = (struct.pack(b'<II', len(item), BMK_STRING | BMK_ST_ONE)
+ item)
elif isinstance(item, Data):
result = (struct.pack(b'<II', len(item.bytes),
BMK_DATA | BMK_ST_ONE)
+ bytes(item.bytes))
elif isinstance(item, bytearray):
result = (struct.pack(b'<II', len(item),
BMK_DATA | BMK_ST_ONE)
+ bytes(item))
elif isinstance(item, int) or isinstance(item, long):
if item > -0x80000000 and item < 0x7fffffff:
result = struct.pack(b'<IIi', 4,
BMK_NUMBER | kCFNumberSInt32Type, item)
else:
result = struct.pack(b'<IIq', 8,
BMK_NUMBER | kCFNumberSInt64Type, item)
elif isinstance(item, float):
result = struct.pack(b'<IId', 8,
BMK_NUMBER | kCFNumberFloat64Type, item)
elif isinstance(item, datetime.datetime):
secs = item - osx_epoch
result = struct.pack(b'<II', 8, BMK_DATE | BMK_ST_ZERO) \
+ struct.pack(b'>d', float(secs.total_seconds()))
elif isinstance(item, uuid.UUID):
result = struct.pack(b'<II', 16, BMK_UUID | BMK_ST_ONE) \
+ item.bytes
elif isinstance(item, URL):
if item.base:
baseoff = offset + 16
reloff, baseenc = cls._encode_item(item.base, baseoff)
xoffset, relenc = cls._encode_item(item.relative, reloff)
result = b''.join([
struct.pack(b'<IIII', 8, BMK_URL | BMK_URL_ST_RELATIVE,
baseoff, reloff),
baseenc,
relenc])
else:
encoded = item.relative.encode('utf-8')
result = struct.pack(b'<II', len(encoded),
BMK_URL | BMK_URL_ST_ABSOLUTE) + encoded
elif isinstance(item, list):
ioffset = offset + 8 + len(item) * 4
result = [struct.pack(b'<II', len(item) * 4, BMK_ARRAY | BMK_ST_ONE)]
enc = []
for elt in item:
result.append(struct.pack(b'<I', ioffset))
ioffset, ienc = cls._encode_item(elt, ioffset)
enc.append(ienc)
result = b''.join(result + enc)
elif isinstance(item, dict):
ioffset = offset + 8 + len(item) * 8
result = [struct.pack(b'<II', len(item) * 8, BMK_DICT | BMK_ST_ONE)]
enc = []
for k,v in iteritems(item):
result.append(struct.pack(b'<I', ioffset))
ioffset, ienc = cls._encode_item(k, ioffset)
enc.append(ienc)
result.append(struct.pack(b'<I', ioffset))
ioffset, ienc = cls._encode_item(v, ioffset)
enc.append(ienc)
result = b''.join(result + enc)
elif item is None:
result = struct.pack(b'<II', 0, BMK_NULL | BMK_ST_ONE)
else:
raise ValueError('Unknown item type when encoding: %s' % item)
offset += len(result)
# Pad to a multiple of 4 bytes
if offset & 3:
extra = 4 - (offset & 3)
result += b'\0' * extra
offset += extra
return (offset, result)
def to_bytes(self):
"""Convert this :class:`Bookmark` to a byte representation."""
result = []
tocs = []
offset = 4 # For the offset to the first TOC
# Generate the data and build the TOCs
for tid,toc in self.tocs:
entries = []
for k,v in iteritems(toc):
if isinstance(k, (str, unicode)):
noffset = offset
voffset, enc = self._encode_item(k, offset)
result.append(enc)
offset, enc = self._encode_item(v, voffset)
result.append(enc)
entries.append((noffset | 0x80000000, voffset))
else:
entries.append((k, offset))
offset, enc = self._encode_item(v, offset)
result.append(enc)
# TOC entries must be sorted - CoreServicesInternal does a
# binary search to find data
entries.sort()
tocs.append((tid, b''.join([struct.pack(b'<III',k,o,0)
for k,o in entries])))
first_toc_offset = offset
# Now generate the TOC headers
for ndx,toc in enumerate(tocs):
tid, data = toc
if ndx == len(tocs) - 1:
next_offset = 0
else:
next_offset = offset + 20 + len(data)
result.append(struct.pack(b'<IIIII', len(data) - 8,
0xfffffffe,
tid,
next_offset,
len(data) // 12))
result.append(data)
offset += 20 + len(data)
# Finally, add the header (and the first TOC offset, which isn't part
# of the header, but goes just after it)
header = struct.pack(b'<4sIIIQQQQI', b'book',
offset + 48,
0x10040000,
48,
0, 0, 0, 0, first_toc_offset)
result.insert(0, header)
return b''.join(result)
@classmethod
def for_file(cls, path):
"""Construct a :class:`Bookmark` for a given file."""
# Find the filesystem
st = osx.statfs(path)
vol_path = st.f_mntonname.decode('utf-8')
# Grab its attributes
attrs = [osx.ATTR_CMN_CRTIME,
osx.ATTR_VOL_SIZE
| osx.ATTR_VOL_NAME
| osx.ATTR_VOL_UUID,
0, 0, 0]
volinfo = osx.getattrlist(vol_path, attrs, 0)
vol_crtime = volinfo[0]
vol_size = volinfo[1]
vol_name = volinfo[2]
vol_uuid = volinfo[3]
# Also grab various attributes of the file
attrs = [(osx.ATTR_CMN_OBJTYPE
| osx.ATTR_CMN_CRTIME
| osx.ATTR_CMN_FILEID), 0, 0, 0, 0]
info = osx.getattrlist(path, attrs, osx.FSOPT_NOFOLLOW)
cnid = info[2]
crtime = info[1]
if info[0] == osx.VREG:
flags = kCFURLResourceIsRegularFile
elif info[0] == osx.VDIR:
flags = kCFURLResourceIsDirectory
elif info[0] == osx.VLNK:
flags = kCFURLResourceIsSymbolicLink
else:
flags = kCFURLResourceIsRegularFile
dirname, filename = os.path.split(path)
relcount = 0
if not os.path.isabs(dirname):
curdir = os.getcwd()
head, tail = os.path.split(curdir)
relcount = 0
while head and tail:
relcount += 1
head, tail = os.path.split(head)
dirname = os.path.join(curdir, dirname)
foldername = os.path.basename(dirname)
rel_path = os.path.relpath(path, vol_path)
# Build the path arrays
name_path = []
cnid_path = []
head, tail = os.path.split(rel_path)
if not tail:
head, tail = os.path.split(head)
while head or tail:
if head:
attrs = [osx.ATTR_CMN_FILEID, 0, 0, 0, 0]
info = osx.getattrlist(os.path.join(vol_path, head), attrs, 0)
cnid_path.insert(0, info[0])
head, tail = os.path.split(head)
name_path.insert(0, tail)
else:
head, tail = os.path.split(head)
name_path.append(filename)
cnid_path.append(cnid)
url_lengths = [relcount, len(name_path) - relcount]
fileprops = Data(struct.pack(b'<QQQ', flags, 0x0f, 0))
volprops = Data(struct.pack(b'<QQQ', 0x81 | kCFURLVolumeSupportsPersistentIDs,
0x13ef | kCFURLVolumeSupportsPersistentIDs, 0))
toc = {
kBookmarkPath: name_path,
kBookmarkCNIDPath: cnid_path,
kBookmarkFileCreationDate: crtime,
kBookmarkFileProperties: fileprops,
kBookmarkContainingFolder: len(name_path) - 2,
kBookmarkVolumePath: vol_path,
kBookmarkVolumeIsRoot: vol_path == '/',
kBookmarkVolumeURL: URL('file://' + vol_path),
kBookmarkVolumeName: vol_name,
kBookmarkVolumeSize: vol_size,
kBookmarkVolumeCreationDate: vol_crtime,
kBookmarkVolumeUUID: str(vol_uuid).upper(),
kBookmarkVolumeProperties: volprops,
kBookmarkCreationOptions: 512,
kBookmarkWasFileReference: True,
kBookmarkUserName: 'unknown',
kBookmarkUID: 99,
}
if relcount:
toc[kBookmarkURLLengths] = url_lengths
return Bookmark([(1, toc)])
def __repr__(self):
result = ['Bookmark([']
for tid,toc in self.tocs:
result.append('(0x%x, {\n' % tid)
for k,v in iteritems(toc):
if isinstance(k, (str, unicode)):
kf = repr(k)
else:
kf = '0x%04x' % k
result.append(' %s: %r\n' % (kf, v))
result.append('}),\n')
result.append('])')
return ''.join(result)

Binary file not shown.

View File

@ -0,0 +1,827 @@
# -*- 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
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
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_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_RETURNED_ATTRS = 0x80000000
ATTR_CMN_ALL_ATTRS = 0x9fe7ffff
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_ATTRIBUTES = 0x40000000
ATTR_VOL_INFO = 0x80000000
ATTR_VOL_ALL_ATTRS = 0xc007ffff
ATTR_DIR_LINKCOUNT = 0x00000001
ATTR_DIR_ENTRYCOUNT = 0x00000002
ATTR_DIR_MOUNTSTATUS = 0x00000004
DIR_MNTSTATUS_MNTPOINT = 0x00000001
DIR_MNTSTATUS_TRIGGER = 0x00000002
ATTR_DIR_ALL_ATTRS = 0x00000007
ATTR_FILE_LINKCOUNT = 0x00000001
ATTR_FILE_TOTALSIZE = 0x00000002
ATTR_FILE_ALLOCSIZE = 0x00000004
ATTR_FILE_IOBLOCKSIZE = 0x00000008
ATTR_FILE_DEVTYPE = 0x00000020
ATTR_FILE_DATALENGTH = 0x00000200
ATTR_FILE_DATAALLOCSIZE = 0x00000400
ATTR_FILE_RSRCLENGTH = 0x00001000
ATTR_FILE_RSRCALLOCSIZE = 0x00002000
ATTR_FILE_ALL_ATTRS = 0x0000362f
ATTR_FORK_TOTALSIZE = 0x00000001
ATTR_FORK_ALLOCSIZE = 0x00000002
ATTR_FORK_ALL_ATTRS = 0x00000003
# These can't be used
ATTR_FILE_FORKCOUNT = 0x00000080
ATTR_FILE_FORKLIST = 0x00000100
ATTR_CMN_NAMEDATTRCOUNT = 0x00080000
ATTR_CMN_NAMEDATTRLIST = 0x00100000
ATTR_FILE_DATAEXTENTS = 0x00000800
ATTR_FILE_RSRCEXTENTS = 0x00004000
ATTR_FILE_CLUMPSIZE = 0x00000010
ATTR_FILE_FILETYPE = 0x00000040
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
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 * 16),
('f_mntonname', c_char * PATH_MAX),
('f_mntfromname', c_char * PATH_MAX),
('f_reserved', uint32_t * 8)]
# 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_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_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)),
# 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_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)),
# 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))
)
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
_statfs = libc['statfs$INODE64']
_statfs.argtypes = [c_char_p, POINTER(struct_statfs)]
_statfs.restype = c_int
_fstatfs = libc['fstatfs$INODE64']
_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_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))
# 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_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)
# 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)
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

Binary file not shown.

View File

@ -0,0 +1,18 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import datetime
ZERO = datetime.timedelta(0)
class UTC (datetime.tzinfo):
def utcoffset(self, dt):
return ZERO
def dst(self, dt):
return ZERO
def tzname(self, dt):
return 'UTC'
utc = UTC()
mac_epoch = datetime.datetime(1904,1,1,0,0,0,0,utc)
unix_epoch = datetime.datetime(1970,1,1,0,0,0,0,utc)
osx_epoch = datetime.datetime(2001,1,1,0,0,0,0,utc)

Binary file not shown.