mac-alias 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mac_alias-1.0.0/PKG-INFO +22 -0
- mac_alias-1.0.0/mac_alias/__init__.py +14 -0
- mac_alias-1.0.0/mac_alias/alias.py +586 -0
- mac_alias-1.0.0/mac_alias/osx.py +823 -0
- mac_alias-1.0.0/mac_alias/utils.py +17 -0
- mac_alias-1.0.0/mac_alias.egg-info/PKG-INFO +22 -0
- mac_alias-1.0.0/mac_alias.egg-info/SOURCES.txt +9 -0
- mac_alias-1.0.0/mac_alias.egg-info/dependency_links.txt +1 -0
- mac_alias-1.0.0/mac_alias.egg-info/top_level.txt +1 -0
- mac_alias-1.0.0/setup.cfg +5 -0
- mac_alias-1.0.0/setup.py +27 -0
mac_alias-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Metadata-Version: 1.1
|
|
2
|
+
Name: mac_alias
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Generate/parse Mac OS Alias records from Python
|
|
5
|
+
Home-page: http://alastairs-place.net/projects/mac_alias
|
|
6
|
+
Author: Alastair Houghton
|
|
7
|
+
Author-email: alastair@alastairs-place.net
|
|
8
|
+
License: MIT License
|
|
9
|
+
Description:
|
|
10
|
+
``mac_alias`` lets you generate or read binary Alias records from Python code.
|
|
11
|
+
While it is written in pure Python, some OS X specific code is required to
|
|
12
|
+
generate a proper Alias record for a given file, so this module currently
|
|
13
|
+
is not portable to other platforms.
|
|
14
|
+
|
|
15
|
+
Credit is due to Wim Lewis <wiml@hhhh.org>, whose work on reverse engineering
|
|
16
|
+
the alias format was extremely useful in writing this code.
|
|
17
|
+
Platform: darwin
|
|
18
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
19
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
20
|
+
Classifier: Topic :: Desktop Environment
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
22
|
+
Provides: mac_alias
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from .alias import *
|
|
2
|
+
|
|
3
|
+
__all__ = [ 'ALIAS_KIND_FILE', 'ALIAS_KIND_FOLDER',
|
|
4
|
+
'ALIAS_HFS_VOLUME_SIGNATURE',
|
|
5
|
+
'ALIAS_FIXED_DISK', 'ALIAS_NETWORK_DISK', 'ALIAS_400KB_FLOPPY_DISK',
|
|
6
|
+
'ALIAS_800KB_FLOPPY_DISK', 'ALIAS_1_44MB_FLOPPY_DISK',
|
|
7
|
+
'ALIAS_EJECTABLE_DISK',
|
|
8
|
+
'ALIAS_NO_CNID',
|
|
9
|
+
'AppleShareInfo',
|
|
10
|
+
'VolumeInfo',
|
|
11
|
+
'TargetInfo',
|
|
12
|
+
'Alias' ]
|
|
13
|
+
|
|
14
|
+
|
|
@@ -0,0 +1,586 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
from __future__ import unicode_literals
|
|
3
|
+
|
|
4
|
+
import struct
|
|
5
|
+
import datetime
|
|
6
|
+
import io
|
|
7
|
+
import re
|
|
8
|
+
import os
|
|
9
|
+
import os.path
|
|
10
|
+
import stat
|
|
11
|
+
import sys
|
|
12
|
+
|
|
13
|
+
if sys.platform == 'darwin':
|
|
14
|
+
from . import osx
|
|
15
|
+
|
|
16
|
+
from .utils import *
|
|
17
|
+
|
|
18
|
+
ALIAS_KIND_FILE = 0
|
|
19
|
+
ALIAS_KIND_FOLDER = 1
|
|
20
|
+
|
|
21
|
+
ALIAS_HFS_VOLUME_SIGNATURE = b'H+'
|
|
22
|
+
|
|
23
|
+
ALIAS_FIXED_DISK = 0
|
|
24
|
+
ALIAS_NETWORK_DISK = 1
|
|
25
|
+
ALIAS_400KB_FLOPPY_DISK = 2
|
|
26
|
+
ALIAS_800KB_FLOPPY_DISK = 3
|
|
27
|
+
ALIAS_1_44MB_FLOPPY_DISK = 4
|
|
28
|
+
ALIAS_EJECTABLE_DISK = 5
|
|
29
|
+
|
|
30
|
+
ALIAS_NO_CNID = 0xffffffff
|
|
31
|
+
|
|
32
|
+
class AppleShareInfo (object):
|
|
33
|
+
def __init__(self, zone=None, server=None, user=None):
|
|
34
|
+
#: The AppleShare zone
|
|
35
|
+
self.zone = zone
|
|
36
|
+
#: The AFP server
|
|
37
|
+
self.server = server
|
|
38
|
+
#: The username
|
|
39
|
+
self.user = user
|
|
40
|
+
|
|
41
|
+
def __repr__(self):
|
|
42
|
+
return 'AppleShareInfo(%r,%r,%r)' % (self.zone, self.server, self.user)
|
|
43
|
+
|
|
44
|
+
class VolumeInfo (object):
|
|
45
|
+
def __init__(self, name, creation_date, fs_type, disk_type,
|
|
46
|
+
attribute_flags, fs_id, appleshare_info=None,
|
|
47
|
+
driver_name=None, posix_path=None, disk_image_alias=None,
|
|
48
|
+
dialup_info=None, network_mount_info=None):
|
|
49
|
+
#: The name of the volume on which the target resides
|
|
50
|
+
self.name = name
|
|
51
|
+
|
|
52
|
+
#: The creation date of the target's volume
|
|
53
|
+
self.creation_date = creation_date
|
|
54
|
+
|
|
55
|
+
#: The filesystem type (a two character code, e.g. ``b'H+'`` for HFS+)
|
|
56
|
+
self.fs_type = fs_type
|
|
57
|
+
|
|
58
|
+
#: The type of disk; should be one of
|
|
59
|
+
#:
|
|
60
|
+
#: * ALIAS_FIXED_DISK
|
|
61
|
+
#: * ALIAS_NETWORK_DISK
|
|
62
|
+
#: * ALIAS_400KB_FLOPPY_DISK
|
|
63
|
+
#: * ALIAS_800KB_FLOPPY_DISK
|
|
64
|
+
#: * ALIAS_1_44MB_FLOPPY_DISK
|
|
65
|
+
#: * ALIAS_EJECTABLE_DISK
|
|
66
|
+
self.disk_type = disk_type
|
|
67
|
+
|
|
68
|
+
#: Filesystem attribute flags (from HFS volume header)
|
|
69
|
+
self.attribute_flags = attribute_flags
|
|
70
|
+
|
|
71
|
+
#: Filesystem identifier
|
|
72
|
+
self.fs_id = fs_id
|
|
73
|
+
|
|
74
|
+
#: AppleShare information (for automatic remounting of network shares)
|
|
75
|
+
#: *(optional)*
|
|
76
|
+
self.appleshare_info = appleshare_info
|
|
77
|
+
|
|
78
|
+
#: Driver name (*probably* contains a disk driver name on older Macs)
|
|
79
|
+
#: *(optional)*
|
|
80
|
+
self.driver_name = driver_name
|
|
81
|
+
|
|
82
|
+
#: POSIX path of the mount point of the target's volume
|
|
83
|
+
#: *(optional)*
|
|
84
|
+
self.posix_path = posix_path
|
|
85
|
+
|
|
86
|
+
#: :class:`Alias` object pointing at the disk image on which the
|
|
87
|
+
#: target's volume resides *(optional)*
|
|
88
|
+
self.disk_image_alias = disk_image_alias
|
|
89
|
+
|
|
90
|
+
#: Dialup information (for automatic establishment of dialup connections)
|
|
91
|
+
self.dialup_info = dialup_info
|
|
92
|
+
|
|
93
|
+
#: Network mount information (for automatic remounting)
|
|
94
|
+
self.network_mount_info = network_mount_info
|
|
95
|
+
|
|
96
|
+
def __repr__(self):
|
|
97
|
+
args = ['name', 'creation_date', 'fs_type', 'disk_type',
|
|
98
|
+
'attribute_flags', 'fs_id']
|
|
99
|
+
values = []
|
|
100
|
+
for a in args:
|
|
101
|
+
v = getattr(self, a)
|
|
102
|
+
values.append(repr(v))
|
|
103
|
+
|
|
104
|
+
kwargs = ['appleshare_info', 'driver_name', 'posix_path',
|
|
105
|
+
'disk_image_alias', 'dialup_info', 'network_mount_info']
|
|
106
|
+
for a in kwargs:
|
|
107
|
+
v = getattr(self, a)
|
|
108
|
+
if v is not None:
|
|
109
|
+
values.append('%s=%r' % (a, v))
|
|
110
|
+
return 'VolumeInfo(%s)' % ','.join(values)
|
|
111
|
+
|
|
112
|
+
class TargetInfo (object):
|
|
113
|
+
def __init__(self, kind, filename, folder_cnid, cnid, creation_date,
|
|
114
|
+
creator_code, type_code, levels_from=-1, levels_to=-1,
|
|
115
|
+
folder_name=None, cnid_path=None, carbon_path=None,
|
|
116
|
+
posix_path=None, user_home_prefix_len=None):
|
|
117
|
+
#: Either ALIAS_KIND_FILE or ALIAS_KIND_FOLDER
|
|
118
|
+
self.kind = kind
|
|
119
|
+
|
|
120
|
+
#: The filename of the target
|
|
121
|
+
self.filename = filename
|
|
122
|
+
|
|
123
|
+
#: The CNID (Catalog Node ID) of the target's containing folder;
|
|
124
|
+
#: CNIDs are similar to but different than traditional UNIX inode
|
|
125
|
+
#: numbers
|
|
126
|
+
self.folder_cnid = folder_cnid
|
|
127
|
+
|
|
128
|
+
#: The CNID (Catalog Node ID) of the target
|
|
129
|
+
self.cnid = cnid
|
|
130
|
+
|
|
131
|
+
#: The target's *creation* date.
|
|
132
|
+
self.creation_date = creation_date
|
|
133
|
+
|
|
134
|
+
#: The target's Mac creator code (a four-character binary string)
|
|
135
|
+
self.creator_code = creator_code
|
|
136
|
+
|
|
137
|
+
#: The target's Mac type code (a four-character binary string)
|
|
138
|
+
self.type_code = type_code
|
|
139
|
+
|
|
140
|
+
#: The depth of the alias? Always seems to be -1 on OS X.
|
|
141
|
+
self.levels_from = -1
|
|
142
|
+
|
|
143
|
+
#: The depth of the target? Always seems to be -1 on OS X.
|
|
144
|
+
self.levels_to = -1
|
|
145
|
+
|
|
146
|
+
#: The (POSIX) name of the target's containing folder. *(optional)*
|
|
147
|
+
self.folder_name = None
|
|
148
|
+
|
|
149
|
+
#: The path from the volume root as a sequence of CNIDs. *(optional)*
|
|
150
|
+
self.cnid_path = None
|
|
151
|
+
|
|
152
|
+
#: The Carbon path of the target *(optional)*
|
|
153
|
+
self.carbon_path = None
|
|
154
|
+
|
|
155
|
+
#: The POSIX path of the target relative to the volume root. Note
|
|
156
|
+
#: that this may or may not have a leading '/' character, but it is
|
|
157
|
+
#: always relative to the containing volume. *(optional)*
|
|
158
|
+
self.posix_path = None
|
|
159
|
+
|
|
160
|
+
#: If the path points into a user's home folder, the number of folders
|
|
161
|
+
#: deep that we go before we get to that home folder. *(optional)*
|
|
162
|
+
self.user_home_prefix_len = None
|
|
163
|
+
|
|
164
|
+
def __repr__(self):
|
|
165
|
+
args = ['kind', 'filename', 'folder_cnid', 'cnid', 'creation_date',
|
|
166
|
+
'creator_code', 'type_code']
|
|
167
|
+
values = []
|
|
168
|
+
for a in args:
|
|
169
|
+
v = getattr(self, a)
|
|
170
|
+
values.append(repr(v))
|
|
171
|
+
|
|
172
|
+
if self.levels_from != -1:
|
|
173
|
+
values.append('levels_from=%r' % self.levels_from)
|
|
174
|
+
if self.levels_to != -1:
|
|
175
|
+
values.append('levels_to=%r' % self.levels_to)
|
|
176
|
+
|
|
177
|
+
kwargs = ['folder_name', 'cnid_path', 'carbon_path',
|
|
178
|
+
'posix_path', 'user_home_prefix_len']
|
|
179
|
+
for a in kwargs:
|
|
180
|
+
v = getattr(self, a)
|
|
181
|
+
values.append('%s=%r' % (a, v))
|
|
182
|
+
|
|
183
|
+
return 'TargetInfo(%s)' % ','.join(values)
|
|
184
|
+
|
|
185
|
+
TAG_CARBON_FOLDER_NAME = 0
|
|
186
|
+
TAG_CNID_PATH = 1
|
|
187
|
+
TAG_CARBON_PATH = 2
|
|
188
|
+
TAG_APPLESHARE_ZONE = 3
|
|
189
|
+
TAG_APPLESHARE_SERVER_NAME = 4
|
|
190
|
+
TAG_APPLESHARE_USERNAME = 5
|
|
191
|
+
TAG_DRIVER_NAME = 6
|
|
192
|
+
TAG_NETWORK_MOUNT_INFO = 9
|
|
193
|
+
TAG_DIALUP_INFO = 10
|
|
194
|
+
TAG_UNICODE_FILENAME = 14
|
|
195
|
+
TAG_UNICODE_VOLUME_NAME = 15
|
|
196
|
+
TAG_HIGH_RES_VOLUME_CREATION_DATE = 16
|
|
197
|
+
TAG_HIGH_RES_CREATION_DATE = 17
|
|
198
|
+
TAG_POSIX_PATH = 18
|
|
199
|
+
TAG_POSIX_PATH_TO_MOUNTPOINT = 19
|
|
200
|
+
TAG_RECURSIVE_ALIAS_OF_DISK_IMAGE = 20
|
|
201
|
+
TAG_USER_HOME_LENGTH_PREFIX = 21
|
|
202
|
+
|
|
203
|
+
class Alias (object):
|
|
204
|
+
def __init__(self, appinfo=b'\0\0\0\0', version=2, volume=None,
|
|
205
|
+
target=None, extra=[]):
|
|
206
|
+
"""Construct a new :class:`Alias` object with the specified
|
|
207
|
+
contents."""
|
|
208
|
+
|
|
209
|
+
#: Application specific information (four byte byte-string)
|
|
210
|
+
self.appinfo = appinfo
|
|
211
|
+
|
|
212
|
+
#: Version (we support only version 2)
|
|
213
|
+
self.version = version
|
|
214
|
+
|
|
215
|
+
#: A :class:`VolumeInfo` object describing the target's volume
|
|
216
|
+
self.volume = volume
|
|
217
|
+
|
|
218
|
+
#: A :class:`TargetInfo` object describing the target
|
|
219
|
+
self.target = target
|
|
220
|
+
|
|
221
|
+
#: A list of extra `(tag, value)` pairs
|
|
222
|
+
self.extra = list(extra)
|
|
223
|
+
|
|
224
|
+
@classmethod
|
|
225
|
+
def _from_fd(cls, b):
|
|
226
|
+
appinfo, recsize, version = struct.unpack(b'>4shh', b.read(8))
|
|
227
|
+
|
|
228
|
+
if recsize < 150:
|
|
229
|
+
raise ValueError('Incorrect alias length')
|
|
230
|
+
|
|
231
|
+
if version != 2:
|
|
232
|
+
raise ValueError('Unsupported alias version %u' % version)
|
|
233
|
+
|
|
234
|
+
kind, volname, voldate, fstype, disktype, \
|
|
235
|
+
folder_cnid, filename, cnid, crdate, creator_code, type_code, \
|
|
236
|
+
levels_from, levels_to, volattrs, volfsid, reserved = \
|
|
237
|
+
struct.unpack(b'>h28pI2shI64pII4s4shhI2s10s', b.read(142))
|
|
238
|
+
|
|
239
|
+
voldate = mac_epoch + datetime.timedelta(seconds=voldate)
|
|
240
|
+
crdate = mac_epoch + datetime.timedelta(seconds=crdate)
|
|
241
|
+
|
|
242
|
+
alias = Alias()
|
|
243
|
+
alias.appinfo = appinfo
|
|
244
|
+
|
|
245
|
+
alias.volume = VolumeInfo (volname.replace('/',':'),
|
|
246
|
+
voldate, fstype, disktype,
|
|
247
|
+
volattrs, volfsid)
|
|
248
|
+
alias.target = TargetInfo (kind, filename.replace('/',':'),
|
|
249
|
+
folder_cnid, cnid,
|
|
250
|
+
crdate, creator_code, type_code)
|
|
251
|
+
alias.target.levels_from = levels_from
|
|
252
|
+
alias.target.levels_to = levels_to
|
|
253
|
+
|
|
254
|
+
tag = struct.unpack(b'>h', b.read(2))[0]
|
|
255
|
+
|
|
256
|
+
while tag != -1:
|
|
257
|
+
length = struct.unpack(b'>h', b.read(2))[0]
|
|
258
|
+
value = b.read(length)
|
|
259
|
+
if length & 1:
|
|
260
|
+
b.read(1)
|
|
261
|
+
|
|
262
|
+
if tag == TAG_CARBON_FOLDER_NAME:
|
|
263
|
+
alias.target.folder_name = value.replace('/',':')
|
|
264
|
+
elif tag == TAG_CNID_PATH:
|
|
265
|
+
alias.target.cnid_path = struct.unpack(b'>%uI' % (length / 4),
|
|
266
|
+
value)
|
|
267
|
+
elif tag == TAG_CARBON_PATH:
|
|
268
|
+
alias.target.carbon_path = value
|
|
269
|
+
elif tag == TAG_APPLESHARE_ZONE:
|
|
270
|
+
if alias.volume.appleshare_info is None:
|
|
271
|
+
alias.volume.appleshare_info = AppleShareInfo()
|
|
272
|
+
alias.volume.appleshare_info.zone = value
|
|
273
|
+
elif tag == TAG_APPLESHARE_SERVER_NAME:
|
|
274
|
+
if alias.volume.appleshare_info is None:
|
|
275
|
+
alias.volume.appleshare_info = AppleShareInfo()
|
|
276
|
+
alias.volume.appleshare_info.server = value
|
|
277
|
+
elif tag == TAG_APPLESHARE_USERNAME:
|
|
278
|
+
if alias.volume.appleshare_info is None:
|
|
279
|
+
alias.volume.appleshare_info = AppleShareInfo()
|
|
280
|
+
alias.volume.appleshare_info.user = value
|
|
281
|
+
elif tag == TAG_DRIVER_NAME:
|
|
282
|
+
alias.volume.driver_name = value
|
|
283
|
+
elif tag == TAG_NETWORK_MOUNT_INFO:
|
|
284
|
+
alias.volume.network_mount_info = value
|
|
285
|
+
elif tag == TAG_DIALUP_INFO:
|
|
286
|
+
alias.volume.dialup_info = value
|
|
287
|
+
elif tag == TAG_UNICODE_FILENAME:
|
|
288
|
+
alias.target.filename = value[2:].decode('utf-16be')
|
|
289
|
+
elif tag == TAG_UNICODE_VOLUME_NAME:
|
|
290
|
+
alias.volume.name = value[2:].decode('utf-16be')
|
|
291
|
+
elif tag == TAG_HIGH_RES_VOLUME_CREATION_DATE:
|
|
292
|
+
seconds = struct.unpack(b'>Q', value)[0] / 65536.0
|
|
293
|
+
alias.volume.creation_date \
|
|
294
|
+
= mac_epoch + datetime.timedelta(seconds=seconds)
|
|
295
|
+
elif tag == TAG_HIGH_RES_CREATION_DATE:
|
|
296
|
+
seconds = struct.unpack(b'>Q', value)[0] / 65536.0
|
|
297
|
+
alias.target.creation_date \
|
|
298
|
+
= mac_epoch + datetime.timedelta(seconds=seconds)
|
|
299
|
+
elif tag == TAG_POSIX_PATH:
|
|
300
|
+
alias.target.posix_path = value
|
|
301
|
+
elif tag == TAG_POSIX_PATH_TO_MOUNTPOINT:
|
|
302
|
+
alias.volume.posix_path = value
|
|
303
|
+
elif tag == TAG_RECURSIVE_ALIAS_OF_DISK_IMAGE:
|
|
304
|
+
alias.volume.disk_image_alias = Alias.from_bytes(value)
|
|
305
|
+
elif tag == TAG_USER_HOME_LENGTH_PREFIX:
|
|
306
|
+
alias.target.user_home_prefix_len = struct.unpack(b'>h', value)[0]
|
|
307
|
+
else:
|
|
308
|
+
alias.extra.append((tag, value))
|
|
309
|
+
|
|
310
|
+
tag = struct.unpack(b'>h', b.read(2))[0]
|
|
311
|
+
|
|
312
|
+
return alias
|
|
313
|
+
|
|
314
|
+
@classmethod
|
|
315
|
+
def from_bytes(cls, bytes):
|
|
316
|
+
"""Construct an :class:`Alias` object given binary Alias data."""
|
|
317
|
+
with io.BytesIO(bytes) as b:
|
|
318
|
+
return cls._from_fd(b)
|
|
319
|
+
|
|
320
|
+
@classmethod
|
|
321
|
+
def for_file(cls, path):
|
|
322
|
+
"""Create an :class:`Alias` that points at the specified file."""
|
|
323
|
+
if sys.platform != 'darwin':
|
|
324
|
+
raise Exception('Not implemented (requires special support)')
|
|
325
|
+
|
|
326
|
+
a = Alias()
|
|
327
|
+
|
|
328
|
+
# Find the filesystem
|
|
329
|
+
st = osx.statfs(path)
|
|
330
|
+
vol_path = st.f_mntonname
|
|
331
|
+
|
|
332
|
+
# Grab its attributes
|
|
333
|
+
attrs = [osx.ATTR_CMN_CRTIME,
|
|
334
|
+
osx.ATTR_VOL_NAME,
|
|
335
|
+
0, 0, 0]
|
|
336
|
+
volinfo = osx.getattrlist(vol_path, attrs, 0)
|
|
337
|
+
|
|
338
|
+
vol_crtime = volinfo[0]
|
|
339
|
+
vol_name = volinfo[1]
|
|
340
|
+
|
|
341
|
+
# Also grab various attributes of the file
|
|
342
|
+
attrs = [(osx.ATTR_CMN_OBJTYPE
|
|
343
|
+
| osx.ATTR_CMN_CRTIME
|
|
344
|
+
| osx.ATTR_CMN_FNDRINFO
|
|
345
|
+
| osx.ATTR_CMN_FILEID
|
|
346
|
+
| osx.ATTR_CMN_PARENTID), 0, 0, 0, 0]
|
|
347
|
+
info = osx.getattrlist(path, attrs, osx.FSOPT_NOFOLLOW)
|
|
348
|
+
|
|
349
|
+
if info[0] == osx.VDIR:
|
|
350
|
+
kind = ALIAS_KIND_FOLDER
|
|
351
|
+
else:
|
|
352
|
+
kind = ALIAS_KIND_FILE
|
|
353
|
+
|
|
354
|
+
cnid = info[3]
|
|
355
|
+
folder_cnid = info[4]
|
|
356
|
+
|
|
357
|
+
dirname, filename = os.path.split(path)
|
|
358
|
+
|
|
359
|
+
if dirname == '' or dirname == '.':
|
|
360
|
+
dirname = os.getcwd()
|
|
361
|
+
|
|
362
|
+
foldername = os.path.basename(dirname)
|
|
363
|
+
|
|
364
|
+
creation_date = info[1]
|
|
365
|
+
|
|
366
|
+
if kind == ALIAS_KIND_FILE:
|
|
367
|
+
creator_code = struct.pack(b'I', info[2].fileInfo.fileCreator)
|
|
368
|
+
type_code = struct.pack(b'I', info[2].fileInfo.fileType)
|
|
369
|
+
else:
|
|
370
|
+
creator_code = b'\0\0\0\0'
|
|
371
|
+
type_code = b'\0\0\0\0'
|
|
372
|
+
|
|
373
|
+
a.target = TargetInfo(kind, filename, folder_cnid, cnid, creation_date,
|
|
374
|
+
creator_code, type_code)
|
|
375
|
+
a.volume = VolumeInfo(vol_name, vol_crtime, b'H+',
|
|
376
|
+
ALIAS_FIXED_DISK, 0, b'\0\0')
|
|
377
|
+
|
|
378
|
+
a.target.folder_name = foldername
|
|
379
|
+
a.volume.posix_path = vol_path
|
|
380
|
+
|
|
381
|
+
rel_path = os.path.relpath(path, vol_path)
|
|
382
|
+
|
|
383
|
+
# Leave off the initial '/' if vol_path is '/' (no idea why)
|
|
384
|
+
if vol_path == '/':
|
|
385
|
+
a.target.posix_path = rel_path
|
|
386
|
+
else:
|
|
387
|
+
a.target.posix_path = '/' + rel_path
|
|
388
|
+
|
|
389
|
+
# Construct the Carbon and CNID paths
|
|
390
|
+
carbon_path = []
|
|
391
|
+
cnid_path = []
|
|
392
|
+
head, tail = os.path.split(rel_path)
|
|
393
|
+
if not tail:
|
|
394
|
+
head, tail = os.path.split(head)
|
|
395
|
+
while head or tail:
|
|
396
|
+
if head:
|
|
397
|
+
attrs = [osx.ATTR_CMN_FILEID, 0, 0, 0, 0]
|
|
398
|
+
info = osx.getattrlist(os.path.join(vol_path, head), attrs, 0)
|
|
399
|
+
cnid_path.append(info[0])
|
|
400
|
+
carbon_tail = tail.replace(':','/')
|
|
401
|
+
carbon_path.insert(0, carbon_tail)
|
|
402
|
+
head, tail = os.path.split(head)
|
|
403
|
+
carbon_path = vol_name + ':' + ':\0'.join(carbon_path)
|
|
404
|
+
|
|
405
|
+
a.target.carbon_path = carbon_path
|
|
406
|
+
a.target.cnid_path = cnid_path
|
|
407
|
+
|
|
408
|
+
return a
|
|
409
|
+
|
|
410
|
+
def _to_fd(self, b):
|
|
411
|
+
# We'll come back and fix the length when we're done
|
|
412
|
+
pos = b.tell()
|
|
413
|
+
b.write(struct.pack(b'>4shh', self.appinfo, 0, self.version))
|
|
414
|
+
|
|
415
|
+
carbon_volname = self.volume.name.replace(':','/').encode('utf-8')
|
|
416
|
+
carbon_filename = self.target.filename.replace(':','/').encode('utf-8')
|
|
417
|
+
voldate = (self.volume.creation_date - mac_epoch).total_seconds()
|
|
418
|
+
crdate = (self.target.creation_date - mac_epoch).total_seconds()
|
|
419
|
+
|
|
420
|
+
# NOTE: crdate should be in local time, but that's system dependent
|
|
421
|
+
# (so doing so is ridiculous, and nothing could rely on it).
|
|
422
|
+
b.write(struct.pack(b'>h28pI2shI64pII4s4shhI2s10s',
|
|
423
|
+
self.target.kind,
|
|
424
|
+
carbon_volname, voldate,
|
|
425
|
+
self.volume.fs_type,
|
|
426
|
+
self.volume.disk_type,
|
|
427
|
+
self.target.folder_cnid,
|
|
428
|
+
carbon_filename,
|
|
429
|
+
self.target.cnid,
|
|
430
|
+
crdate,
|
|
431
|
+
self.target.creator_code,
|
|
432
|
+
self.target.type_code,
|
|
433
|
+
self.target.levels_from,
|
|
434
|
+
self.target.levels_to,
|
|
435
|
+
self.volume.attribute_flags,
|
|
436
|
+
self.volume.fs_id,
|
|
437
|
+
b'\0'*10))
|
|
438
|
+
|
|
439
|
+
# Excuse the odd order; we're copying Finder
|
|
440
|
+
if self.target.folder_name:
|
|
441
|
+
carbon_foldername = self.target.folder_name.replace(':','/')\
|
|
442
|
+
.encode('utf-8')
|
|
443
|
+
b.write(struct.pack(b'>hh', TAG_CARBON_FOLDER_NAME,
|
|
444
|
+
len(carbon_foldername)))
|
|
445
|
+
b.write(carbon_foldername)
|
|
446
|
+
if len(carbon_foldername) & 1:
|
|
447
|
+
b.write(b'\0')
|
|
448
|
+
|
|
449
|
+
b.write(struct.pack(b'>hhQhhQ',
|
|
450
|
+
TAG_HIGH_RES_VOLUME_CREATION_DATE,
|
|
451
|
+
8, long(voldate * 65536),
|
|
452
|
+
TAG_HIGH_RES_CREATION_DATE,
|
|
453
|
+
8, long(crdate * 65536)))
|
|
454
|
+
|
|
455
|
+
if self.target.cnid_path:
|
|
456
|
+
cnid_path = struct.pack(b'>%uI' % len(self.target.cnid_path),
|
|
457
|
+
*self.target.cnid_path)
|
|
458
|
+
b.write(struct.pack(b'>hh', TAG_CNID_PATH,
|
|
459
|
+
len(cnid_path)))
|
|
460
|
+
b.write(cnid_path)
|
|
461
|
+
|
|
462
|
+
if self.target.carbon_path:
|
|
463
|
+
carbon_path=self.target.carbon_path.encode('utf-8')
|
|
464
|
+
b.write(struct.pack(b'>hh', TAG_CARBON_PATH,
|
|
465
|
+
len(carbon_path)))
|
|
466
|
+
b.write(carbon_path)
|
|
467
|
+
if len(carbon_path) & 1:
|
|
468
|
+
b.write(b'\0')
|
|
469
|
+
|
|
470
|
+
if self.volume.appleshare_info:
|
|
471
|
+
ai = self.volume.appleshare_info
|
|
472
|
+
if ai.zone:
|
|
473
|
+
b.write(struct.pack(b'>hh', TAG_APPLESHARE_ZONE,
|
|
474
|
+
len(ai.zone)))
|
|
475
|
+
b.write(ai.zone)
|
|
476
|
+
if len(ai.zone) & 1:
|
|
477
|
+
b.write(b'\0')
|
|
478
|
+
if ai.server:
|
|
479
|
+
b.write(struct.pack(b'>hh', TAG_APPLESHARE_SERVER_NAME,
|
|
480
|
+
len(ai.server)))
|
|
481
|
+
b.write(ai.server)
|
|
482
|
+
if len(ai.server) & 1:
|
|
483
|
+
b.write(b'\0')
|
|
484
|
+
if ai.username:
|
|
485
|
+
b.write(struct.pack(b'>hh', TAG_APPLESHARE_SERVER_NAME,
|
|
486
|
+
len(ai.username)))
|
|
487
|
+
b.write(ai.username)
|
|
488
|
+
if len(ai.username) & 1:
|
|
489
|
+
b.write(b'\0')
|
|
490
|
+
|
|
491
|
+
if self.volume.driver_name:
|
|
492
|
+
driver_name = self.volume.driver_name.encode('utf-8')
|
|
493
|
+
b.write(struct.pack(b'>hh', TAG_DRIVER_NAME,
|
|
494
|
+
len(driver_name)))
|
|
495
|
+
b.write(driver_name)
|
|
496
|
+
if len(driver_name) & 1:
|
|
497
|
+
b.write(b'\0')
|
|
498
|
+
|
|
499
|
+
if self.volume.network_mount_info:
|
|
500
|
+
b.write(struct.pack(b'>hh', TAG_NETWORK_MOUNT_INFO,
|
|
501
|
+
len(self.volume.network_mount_info)))
|
|
502
|
+
b.write(self.volume.network_mount_info)
|
|
503
|
+
if len(self.volume.network_mount_info) & 1:
|
|
504
|
+
b.write(b'\0')
|
|
505
|
+
|
|
506
|
+
if self.volume.dialup_info:
|
|
507
|
+
b.write(struct.pack(b'>hh', TAG_DIALUP_INFO,
|
|
508
|
+
len(self.volume.network_mount_info)))
|
|
509
|
+
b.write(self.volume.network_mount_info)
|
|
510
|
+
if len(self.volume.network_mount_info) & 1:
|
|
511
|
+
b.write(b'\0')
|
|
512
|
+
|
|
513
|
+
utf16 = self.target.filename.replace(':','/').encode('utf-16-be')
|
|
514
|
+
b.write(struct.pack(b'>hhh', TAG_UNICODE_FILENAME,
|
|
515
|
+
len(utf16) + 2,
|
|
516
|
+
len(utf16) / 2))
|
|
517
|
+
b.write(utf16)
|
|
518
|
+
|
|
519
|
+
utf16 = self.volume.name.replace(':','/').encode('utf-16-be')
|
|
520
|
+
b.write(struct.pack(b'>hhh', TAG_UNICODE_VOLUME_NAME,
|
|
521
|
+
len(utf16) + 2,
|
|
522
|
+
len(utf16) / 2))
|
|
523
|
+
b.write(utf16)
|
|
524
|
+
|
|
525
|
+
if self.target.posix_path:
|
|
526
|
+
posix_path = self.target.posix_path.encode('utf-8')
|
|
527
|
+
b.write(struct.pack(b'>hh', TAG_POSIX_PATH,
|
|
528
|
+
len(posix_path)))
|
|
529
|
+
b.write(posix_path)
|
|
530
|
+
if len(posix_path) & 1:
|
|
531
|
+
b.write(b'\0')
|
|
532
|
+
|
|
533
|
+
if self.volume.posix_path:
|
|
534
|
+
posix_path = self.volume.posix_path.encode('utf-8')
|
|
535
|
+
b.write(struct.pack(b'>hh', TAG_POSIX_PATH_TO_MOUNTPOINT,
|
|
536
|
+
len(posix_path)))
|
|
537
|
+
b.write(posix_path)
|
|
538
|
+
if len(posix_path) & 1:
|
|
539
|
+
b.write(b'\0')
|
|
540
|
+
|
|
541
|
+
if self.volume.disk_image_alias:
|
|
542
|
+
d = self.volume.disk_image_alias.to_bytes()
|
|
543
|
+
b.write(struct.pack(b'>hh', TAG_RECURSIVE_ALIAS_OF_DISK_IMAGE,
|
|
544
|
+
len(d)))
|
|
545
|
+
b.write(d)
|
|
546
|
+
if len(d) & 1:
|
|
547
|
+
b.write(b'\0')
|
|
548
|
+
|
|
549
|
+
if self.target.user_home_prefix_len is not None:
|
|
550
|
+
b.write(struct.pack(b'>hhh', TAG_USER_HOME_LENGTH_PREFIX,
|
|
551
|
+
2, self.target.user_home_prefix_len))
|
|
552
|
+
|
|
553
|
+
for t,v in self.extra:
|
|
554
|
+
b.write(struct.pack(b'>hh', t, len(v)))
|
|
555
|
+
b.write(v)
|
|
556
|
+
if len(v) & 1:
|
|
557
|
+
b.write(b'\0')
|
|
558
|
+
|
|
559
|
+
b.write(struct.pack(b'>hh', -1, 0))
|
|
560
|
+
|
|
561
|
+
blen = b.tell() - pos
|
|
562
|
+
b.seek(pos + 4, os.SEEK_SET)
|
|
563
|
+
b.write(struct.pack(b'>h', blen))
|
|
564
|
+
|
|
565
|
+
def to_bytes(self):
|
|
566
|
+
"""Returns the binary representation for this :class:`Alias`."""
|
|
567
|
+
with io.BytesIO() as b:
|
|
568
|
+
self._to_fd(b)
|
|
569
|
+
return b.getvalue()
|
|
570
|
+
|
|
571
|
+
def __str__(self):
|
|
572
|
+
return '<Alias target=%s>' % self.target.filename
|
|
573
|
+
|
|
574
|
+
def __repr__(self):
|
|
575
|
+
values = []
|
|
576
|
+
if self.appinfo != b'\0\0\0\0':
|
|
577
|
+
values.append('appinfo=%r' % self.appinfo)
|
|
578
|
+
if self.version != 2:
|
|
579
|
+
values.append('version=%r' % self.version)
|
|
580
|
+
if self.volume is not None:
|
|
581
|
+
values.append('volume=%r' % self.volume)
|
|
582
|
+
if self.target is not None:
|
|
583
|
+
values.append('target=%r' % self.target)
|
|
584
|
+
if self.extra:
|
|
585
|
+
values.append('extra=%r' % self.extra)
|
|
586
|
+
return 'Alias(%s)' % ','.join(values)
|