3 # Simple python program to print out all the extents of a single file
5 # Copyright Facebook 2014
7 import sys,os,struct,fcntl,ctypes,stat
10 maxu64 = (1L << 64) - 1
11 maxu32 = (1L << 32) - 1
13 # the inode (like form stat)
14 BTRFS_INODE_ITEM_KEY = 1
15 # backref to the directory
16 BTRFS_INODE_REF_KEY = 12
17 # backref to the directory v2
18 BTRFS_INODE_EXTREF_KEY = 13
20 BTRFS_XATTR_ITEM_KEY = 24
21 # orphans for list files
22 BTRFS_ORPHAN_ITEM_KEY = 48
23 # treelog items for dirs
24 BTRFS_DIR_LOG_ITEM_KEY = 60
25 BTRFS_DIR_LOG_INDEX_KEY = 72
26 # dir items and dir indexes both hold filenames
27 BTRFS_DIR_ITEM_KEY = 84
28 BTRFS_DIR_INDEX_KEY = 96
29 # these are the file extent pointers
30 BTRFS_EXTENT_DATA_KEY = 108
32 BTRFS_EXTENT_CSUM_KEY = 128
33 # root item for subvols and snapshots
34 BTRFS_ROOT_ITEM_KEY = 132
36 BTRFS_ROOT_BACKREF_KEY = 144
37 BTRFS_ROOT_REF_KEY = 156
38 # each allocated extent has an extent item
39 BTRFS_EXTENT_ITEM_KEY = 168
40 # optimized extents for metadata only
41 BTRFS_METADATA_ITEM_KEY = 169
42 # backrefs for extents
43 BTRFS_TREE_BLOCK_REF_KEY = 176
44 BTRFS_EXTENT_DATA_REF_KEY = 178
45 BTRFS_EXTENT_REF_V0_KEY = 180
46 BTRFS_SHARED_BLOCK_REF_KEY = 182
47 BTRFS_SHARED_DATA_REF_KEY = 184
48 # one of these for each block group
49 BTRFS_BLOCK_GROUP_ITEM_KEY = 192
50 # dev extents records which part of each device is allocated
51 BTRFS_DEV_EXTENT_KEY = 204
52 # dev items describe devs
53 BTRFS_DEV_ITEM_KEY = 216
55 BTRFS_CHUNK_ITEM_KEY = 228
57 BTRFS_QGROUP_STATUS_KEY = 240
58 BTRFS_QGROUP_INFO_KEY = 242
59 BTRFS_QGROUP_LIMIT_KEY = 244
60 BTRFS_QGROUP_RELATION_KEY = 246
61 # records balance progress
62 BTRFS_BALANCE_ITEM_KEY = 248
63 # stats on device errors
64 BTRFS_DEV_STATS_KEY = 249
65 BTRFS_DEV_REPLACE_KEY = 250
66 BTRFS_STRING_ITEM_KEY = 253
68 # in the kernel sources, this is flattened
69 # btrfs_ioctl_search_args_v2. It includes both the btrfs_ioctl_search_key
70 # and the buffer. We're using a 64K buffer size.
72 args_buffer_size = 65536
73 class btrfs_ioctl_search_args(ctypes.Structure):
75 _fields_ = [ ("tree_id", ctypes.c_ulonglong),
76 ("min_objectid", ctypes.c_ulonglong),
77 ("max_objectid", ctypes.c_ulonglong),
78 ("min_offset", ctypes.c_ulonglong),
79 ("max_offset", ctypes.c_ulonglong),
80 ("min_transid", ctypes.c_ulonglong),
81 ("max_transid", ctypes.c_ulonglong),
82 ("min_type", ctypes.c_uint),
83 ("max_type", ctypes.c_uint),
84 ("nr_items", ctypes.c_uint),
85 ("unused", ctypes.c_uint),
86 ("unused1", ctypes.c_ulonglong),
87 ("unused2", ctypes.c_ulonglong),
88 ("unused3", ctypes.c_ulonglong),
89 ("unused4", ctypes.c_ulonglong),
90 ("buf_size", ctypes.c_ulonglong),
91 ("buf", ctypes.c_ubyte * args_buffer_size),
94 # the search ioctl resturns one header for each item
96 class btrfs_ioctl_search_header(ctypes.Structure):
98 _fields_ = [ ("transid", ctypes.c_ulonglong),
99 ("objectid", ctypes.c_ulonglong),
100 ("offset", ctypes.c_ulonglong),
101 ("type", ctypes.c_uint),
102 ("len", ctypes.c_uint),
105 # the type field in btrfs_file_extent_item
106 BTRFS_FILE_EXTENT_INLINE = 0
107 BTRFS_FILE_EXTENT_REG = 1
108 BTRFS_FILE_EXTENT_PREALLOC = 2
110 class btrfs_file_extent_item(ctypes.LittleEndianStructure):
112 _fields_ = [ ("generation", ctypes.c_ulonglong),
113 ("ram_bytes", ctypes.c_ulonglong),
114 ("compression", ctypes.c_ubyte),
115 ("encryption", ctypes.c_ubyte),
116 ("other_encoding", ctypes.c_ubyte * 2),
117 ("type", ctypes.c_ubyte),
118 ("disk_bytenr", ctypes.c_ulonglong),
119 ("disk_num_bytes", ctypes.c_ulonglong),
120 ("offset", ctypes.c_ulonglong),
121 ("num_bytes", ctypes.c_ulonglong),
124 class btrfs_ioctl_search():
126 self.args = btrfs_ioctl_search_args()
127 self.args.tree_id = 0
128 self.args.min_objectid = 0
129 self.args.max_objectid = maxu64
130 self.args.min_offset = 0
131 self.args.max_offset = maxu64
132 self.args.min_transid = 0
133 self.args.max_transid = maxu64
134 self.args.min_type = 0
135 self.args.max_type = maxu32
136 self.args.nr_items = 0
137 self.args.buf_size = args_buffer_size
139 # magic encoded for x86_64 this is the v2 search ioctl
140 self.ioctl_num = 3228603409L
142 # the results of the search get stored into args.buf
143 def search(self, fd, nritems=65536):
144 self.args.nr_items = nritems
145 fcntl.ioctl(fd, self.ioctl_num, self.args, 1)
147 # this moves the search key forward by one. If the end result is
148 # still a valid search key (all mins less than all maxes), we return
149 # True. Otherwise False
151 def advance_search(search):
152 if search.args.min_offset < maxu64:
153 search.args.min_offset += 1
154 elif search.args.min_type < 255:
155 search.args.min_type += 1
156 elif search.args.min_objectid < maxu64:
157 search.args.min_objectid += 1
161 if search.args.min_offset > search.args.max_offset:
163 if search.args.min_type > search.args.max_type:
165 if search.args.min_objectid > search.args.max_objectid:
170 # given one search_header and one file_item, print the details. This
171 # also tosses the [disk_bytenr,disk_num_bytes] into extent_hash to record
172 # which extents were used by this file
174 def print_one_extent(header, fi, extent_hash):
175 # we're ignoring inline items for now
176 if fi.type == BTRFS_FILE_EXTENT_INLINE:
177 # header.len is the length of the item returned. We subtract
178 # the part of the file item header that is actually used (21 bytes)
179 # and we get the length of the inlined data.
180 # this may or may not be compressed
181 inline_len = header.len - 21
183 ram_bytes = fi.ram_bytes
185 ram_bytes = inline_len
186 print "(%Lu %Lu): ram %Lu disk 0 disk_size %Lu -- inline" % \
187 (header.objectid, header.offset, ram_bytes, inline_len)
188 extent_hash[-1] = inline_len
191 if fi.disk_bytenr == 0:
195 print "(%Lu %Lu): ram %Lu disk %Lu disk_size %Lu%s" % (header.objectid,
196 header.offset, fi.num_bytes, fi.disk_bytenr, fi.disk_num_bytes, tag)
199 extent_hash[fi.disk_bytenr] = fi.disk_num_bytes
201 # open 'filename' and run the search ioctl against it, printing all the extents
203 def print_file_extents(filename):
206 s = btrfs_ioctl_search()
207 s.args.min_type = BTRFS_EXTENT_DATA_KEY
208 s.args.max_type = BTRFS_EXTENT_DATA_KEY
211 fd = os.open(filename, os.O_RDONLY)
214 sys.stderr.write("Failed to open %s (%s)\n" % (filename, e))
217 if not stat.S_ISREG(st.st_mode):
218 sys.stderr.write("%s not a regular file\n" % filename)
221 s.args.min_objectid = st.st_ino
222 s.args.max_objectid = st.st_ino
230 sys.stderr.write("Search ioctl failed for %s (%s)\n" % (filename, e))
233 if s.args.nr_items == 0:
236 # p is the results buffer from the kernel
237 p = ctypes.addressof(s.args.buf)
238 header = btrfs_ioctl_search_header()
239 header_size = ctypes.sizeof(header)
240 h = ctypes.addressof(header)
241 p_left = args_buffer_size
243 for x in xrange(0, s.args.nr_items):
244 # for each item, copy the header from the buffer into
246 ctypes.memmove(h, p, header_size)
248 p_left -= header_size
250 # this would be a kernel bug it shouldn't be sending malformed
255 if header.type == BTRFS_EXTENT_DATA_KEY:
256 fi = btrfs_file_extent_item()
258 # this would also be a kernel bug
259 if p_left < ctypes.sizeof(fi):
262 # Copy the file item out of the results buffer
263 ctypes.memmove(ctypes.addressof(fi), p, ctypes.sizeof(fi))
264 print_one_extent(header, fi, extent_hash)
271 s.args.min_offset = header.offset
273 if not advance_search(s):
278 for x in extent_hash.itervalues():
282 # don't divide by zero
283 if total_on_disk == 0:
286 print "file: %s extents %Lu disk size %Lu logical size %Lu ratio %.2f" % \
287 (filename, total_extents, total_on_disk, st.st_size,
288 float(st.st_size) / float(total_on_disk))
291 if len(sys.argv) == 1:
292 sys.stderr.write("Usage: btrfs-debug filename ...\n")
295 for f in sys.argv[1:]:
296 print_file_extents(f)