-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathfiemap.py
175 lines (133 loc) · 5.06 KB
/
fiemap.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
import array
import fcntl
import struct
import collections
# Public API
# From linux/fiemap.h
FIEMAP_FLAG_SYNC = 0x0001
FIEMAP_FLAG_XATTR = 0x0002
FIEMAP_FLAGS_COMPAT = FIEMAP_FLAG_SYNC | FIEMAP_FLAG_XATTR
FIEMAP_EXTENT_LAST = 0x0001
FIEMAP_EXTENT_UNKNOWN = 0x0002
FIEMAP_EXTENT_DELALLOC = 0x0004
FIEMAP_EXTENT_ENCODED = 0x0008
FIEMAP_EXTENT_DATA_ENCRYPTED = 0x0080
FIEMAP_EXTENT_NOT_ALIGNED = 0x0100
FIEMAP_EXTENT_DATA_INLINE = 0x0200
FIEMAP_EXTENT_DATA_TAIL = 0x0400
FIEMAP_EXTENT_UNWRITTEN = 0x0800
FIEMAP_EXTENT_MERGED = 0x1000
FIEMAP_EXTENT_SHARED = 0x2000
# Internals and plumbing
# From asm-generic/ioctl.h
_IOC_NRBITS = 8
_IOC_TYPEBITS = 8
_IOC_SIZEBITS = 14
_IOC_NRSHIFT = 0
_IOC_TYPESHIFT = _IOC_NRSHIFT + _IOC_NRBITS
_IOC_SIZESHIFT = _IOC_TYPESHIFT + _IOC_TYPEBITS
_IOC_DIRSHIFT = _IOC_SIZESHIFT + _IOC_SIZEBITS
_IOC_TYPECHECK = lambda struct: struct.size
_IOC = lambda dir_, type_, nr, size: \
(dir_ << _IOC_DIRSHIFT) | (type_ << _IOC_TYPESHIFT) \
| (nr << _IOC_NRSHIFT) | (size << _IOC_SIZESHIFT)
_IOC_WRITE = 1
_IOC_READ = 2
_IOWR = lambda type_, nr, size: \
_IOC(_IOC_READ | _IOC_WRITE, type_, nr, _IOC_TYPECHECK(size))
# Derived from linux/fiemap.h
_struct_fiemap = struct.Struct('=QQLLLL')
_struct_fiemap_extent = struct.Struct('=QQQQQLLLL')
# From linux/fs.h
_FS_IOC_FIEMAP = _IOWR(ord('f'), 11, _struct_fiemap)
_UINT64_MAX = (2 ** 64) - 1
_fiemap = collections.namedtuple('fiemap',
'start length flags mapped_extents extent_count extents')
_fiemap_extent = collections.namedtuple('fiemap_extent',
'logical physical length flags')
# Public API, part 2
def fiemap(fd, start=0, length=_UINT64_MAX, flags=0, count=0):
'''Retrieve extent mappings of a file using a given file descriptor
This uses the *fiemap* ioctl implemented in the Linux kernel. See
`Documentation/fiemap.txt`_ for more information.
This procedure returns a named tuple containing all non-reserved fields as
exposed by the fiemap and fiemap_extent structs. See
:py:func:get_all_mappings if you want to retrieve all mappings of a given
file.
Note the attributes of the result don't have an *fm_* or *fe_* prefix.
.. _Documentation/fiemap.txt: http://www.mjmwired.net/kernel/Documentation/filesystems/fiemap.txt
:param fd: File descriptor to use
:type fd: File-like object or `int`
:param start: Start offset
:type start: `int`
:param length: Query length
:type length: `int`
:param flags: Flags
:type flags: `int`
:param count: Number of extents to request
:type count: `int`
:return: Mapping information
:rtype: `_fiemap`
'''
fiemap_buffer = '%s%s' % (
_struct_fiemap.pack(start, length, flags, 0, count, 0),
'\0' * (_struct_fiemap_extent.size * count))
# Turn into mutable C-level array of chars
buffer_ = array.array('c', fiemap_buffer)
# Syscall
ret = fcntl.ioctl(fd, _FS_IOC_FIEMAP, buffer_)
if ret < 0:
raise IOError('ioctl')
# Read out fiemap struct
fm_start, fm_length, fm_flags, fm_mapped_extents, fm_extent_count, \
fm_reserved = _struct_fiemap.unpack_from(buffer_)
# Read out fiemap_extent structs
fm_extents = []
offset = _struct_fiemap.size
for i in xrange(fm_extent_count):
fe_logical, fe_physical, fe_length, _1, _2, fe_flags, _3, _4, _5 = \
_struct_fiemap_extent.unpack_from(
buffer_[offset:offset + _struct_fiemap_extent.size])
fm_extents.append(
_fiemap_extent(fe_logical, fe_physical, fe_length, fe_flags))
del fe_logical, fe_physical, fe_length, fe_flags
offset += _struct_fiemap_extent.size
if fm_extents:
assert fm_extents[-1].flags | FIEMAP_EXTENT_LAST == FIEMAP_EXTENT_LAST
return _fiemap(
fm_start, fm_length, fm_flags, fm_mapped_extents, fm_extent_count,
fm_extents)
def get_all_mappings(fd, start=0, length=_UINT64_MAX, flags=0):
'''Retrieve all extent mappings of a file using a given file descriptor
This uses :py:func:`fiemap` to retrieve the mappings, twice.
This procedure returns a named tuple containing all non-reserved fields as
exposed by the fiemap and fiemap_extent structs.
Note the attributes of the result don't have an *fm_* or *fe_* prefix.
:param fd: File descriptor to use
:type fd: File-like object or `int`
:param start: Start offset
:type start: `int`
:param length: Query length
:type length: `int`
:param flags: Flags
:type flags: `int`
:return: Mapping information
:rtype: `_fiemap`
'''
map1 = fiemap(fd, start=start, length=length, flags=flags, count=0)
map2 = fiemap(fd, start=start, length=length, flags=flags,
count=map1.mapped_extents)
return map2
if __name__ == '__main__':
import sys
import pprint
if len(sys.argv) < 2:
sys.stderr.write('No filename(s) given')
sys.exit(1)
for file_ in sys.argv[1:]:
with open(file_, 'r') as fd:
print file_
print '-' * len(file_)
map_ = get_all_mappings(fd)
pprint.pprint(map_)
print