btrfs-progs: docs: new size options for fi show
[platform/upstream/btrfs-progs.git] / btrfs-debugfs
1 #!/usr/bin/env python2
2 #
3 # Simple python program to print out all the extents of a single file
4 # LGPLv2 license
5 # Copyright Facebook 2014
6
7 import sys,os,struct,fcntl,ctypes,stat
8
9 # helpers for max ints
10 maxu64 = (1L << 64) - 1
11 maxu32 = (1L << 32) - 1
12
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
19 # xattr items
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
31 # csums
32 BTRFS_EXTENT_CSUM_KEY = 128
33 # root item for subvols and snapshots
34 BTRFS_ROOT_ITEM_KEY = 132
35 # root item backrefs
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
54 # one for each chunk
55 BTRFS_CHUNK_ITEM_KEY = 228
56 # qgroup info
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
67
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.
71 #
72 args_buffer_size = 65536
73 class btrfs_ioctl_search_args(ctypes.Structure):
74     _pack_ = 1
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),
92                ]
93
94 # the search ioctl resturns one header for each item
95 #
96 class btrfs_ioctl_search_header(ctypes.Structure):
97     _pack_ = 1
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),
103                ]
104
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
109
110 class btrfs_file_extent_item(ctypes.LittleEndianStructure):
111     _pack_ = 1
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),
122                ]
123
124 class btrfs_ioctl_search():
125     def __init__(self):
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
138
139         # magic encoded for x86_64 this is the v2 search ioctl
140         self.ioctl_num = 3228603409L
141
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)
146
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
150 #
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
158     else:
159         return False
160
161     if search.args.min_offset > search.args.max_offset:
162         return False
163     if search.args.min_type > search.args.max_type:
164         return False
165     if search.args.min_objectid > search.args.max_objectid:
166         return False
167
168     return True
169
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
173 #
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
182         if fi.compression:
183             ram_bytes = fi.ram_bytes
184         else:
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
189         return
190
191     if fi.disk_bytenr == 0:
192         tag = " -- hole"
193     else:
194         tag = ""
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)
197
198     if fi.disk_bytenr:
199         extent_hash[fi.disk_bytenr] = fi.disk_num_bytes
200
201 # open 'filename' and run the search ioctl against it, printing all the extents
202 # we find
203 def print_file_extents(filename):
204     extent_hash = {}
205
206     s = btrfs_ioctl_search()
207     s.args.min_type = BTRFS_EXTENT_DATA_KEY
208     s.args.max_type = BTRFS_EXTENT_DATA_KEY
209
210     try:
211         fd = os.open(filename, os.O_RDONLY)
212         st = os.fstat(fd)
213     except Exception, e:
214         sys.stderr.write("Failed to open %s (%s)\n" % (filename, e))
215         return -1
216
217     if not stat.S_ISREG(st.st_mode):
218         sys.stderr.write("%s not a regular file\n" % filename)
219         return 0
220
221     s.args.min_objectid = st.st_ino
222     s.args.max_objectid = st.st_ino
223
224     size = st.st_size
225
226     while True:
227         try:
228             s.search(fd)
229         except Exception, e:
230             sys.stderr.write("Search ioctl failed for %s (%s)\n" % (filename, e))
231             return -1
232
233         if s.args.nr_items == 0:
234             break
235
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
242
243         for x in xrange(0, s.args.nr_items):
244             # for each item, copy the header from the buffer into
245             # our header struct.
246             ctypes.memmove(h, p, header_size)
247             p += header_size
248             p_left -= header_size
249
250             # this would be a kernel bug it shouldn't be sending malformed
251             # items
252             if p_left <= 0:
253                 break
254
255             if header.type == BTRFS_EXTENT_DATA_KEY:
256                 fi = btrfs_file_extent_item()
257
258                 # this would also be a kernel bug
259                 if p_left < ctypes.sizeof(fi):
260                     break
261
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)
265
266             p += header.len
267             p_left -= header.len
268             if p_left <= 0:
269                 break
270
271             s.args.min_offset = header.offset
272
273         if not advance_search(s):
274             break
275
276     total_on_disk = 0
277     total_extents = 0
278     for x in extent_hash.itervalues():
279         total_on_disk += x
280         total_extents += 1
281
282     # don't divide by zero
283     if total_on_disk == 0:
284         total_on_disk = 1
285
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))
289     return 0
290
291 if len(sys.argv) == 1:
292     sys.stderr.write("Usage: btrfs-debug filename ...\n")
293     sys.exit(1)
294
295 for f in sys.argv[1:]:
296     print_file_extents(f)