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 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296
|
#!/usr/bin/env python2
#
# Simple python program to print out all the extents of a single file
# LGPLv2 license
# Copyright Facebook 2014
import sys,os,struct,fcntl,ctypes,stat
# helpers for max ints
maxu64 = (1L << 64) - 1
maxu32 = (1L << 32) - 1
# the inode (like form stat)
BTRFS_INODE_ITEM_KEY = 1
# backref to the directory
BTRFS_INODE_REF_KEY = 12
# backref to the directory v2
BTRFS_INODE_EXTREF_KEY = 13
# xattr items
BTRFS_XATTR_ITEM_KEY = 24
# orphans for list files
BTRFS_ORPHAN_ITEM_KEY = 48
# treelog items for dirs
BTRFS_DIR_LOG_ITEM_KEY = 60
BTRFS_DIR_LOG_INDEX_KEY = 72
# dir items and dir indexes both hold filenames
BTRFS_DIR_ITEM_KEY = 84
BTRFS_DIR_INDEX_KEY = 96
# these are the file extent pointers
BTRFS_EXTENT_DATA_KEY = 108
# csums
BTRFS_EXTENT_CSUM_KEY = 128
# root item for subvols and snapshots
BTRFS_ROOT_ITEM_KEY = 132
# root item backrefs
BTRFS_ROOT_BACKREF_KEY = 144
BTRFS_ROOT_REF_KEY = 156
# each allocated extent has an extent item
BTRFS_EXTENT_ITEM_KEY = 168
# optimized extents for metadata only
BTRFS_METADATA_ITEM_KEY = 169
# backrefs for extents
BTRFS_TREE_BLOCK_REF_KEY = 176
BTRFS_EXTENT_DATA_REF_KEY = 178
BTRFS_EXTENT_REF_V0_KEY = 180
BTRFS_SHARED_BLOCK_REF_KEY = 182
BTRFS_SHARED_DATA_REF_KEY = 184
# one of these for each block group
BTRFS_BLOCK_GROUP_ITEM_KEY = 192
# dev extents records which part of each device is allocated
BTRFS_DEV_EXTENT_KEY = 204
# dev items describe devs
BTRFS_DEV_ITEM_KEY = 216
# one for each chunk
BTRFS_CHUNK_ITEM_KEY = 228
# qgroup info
BTRFS_QGROUP_STATUS_KEY = 240
BTRFS_QGROUP_INFO_KEY = 242
BTRFS_QGROUP_LIMIT_KEY = 244
BTRFS_QGROUP_RELATION_KEY = 246
# records balance progress
BTRFS_BALANCE_ITEM_KEY = 248
# stats on device errors
BTRFS_DEV_STATS_KEY = 249
BTRFS_DEV_REPLACE_KEY = 250
BTRFS_STRING_ITEM_KEY = 253
# in the kernel sources, this is flattened
# btrfs_ioctl_search_args_v2. It includes both the btrfs_ioctl_search_key
# and the buffer. We're using a 64K buffer size.
#
args_buffer_size = 65536
class btrfs_ioctl_search_args(ctypes.Structure):
_pack_ = 1
_fields_ = [ ("tree_id", ctypes.c_ulonglong),
("min_objectid", ctypes.c_ulonglong),
("max_objectid", ctypes.c_ulonglong),
("min_offset", ctypes.c_ulonglong),
("max_offset", ctypes.c_ulonglong),
("min_transid", ctypes.c_ulonglong),
("max_transid", ctypes.c_ulonglong),
("min_type", ctypes.c_uint),
("max_type", ctypes.c_uint),
("nr_items", ctypes.c_uint),
("unused", ctypes.c_uint),
("unused1", ctypes.c_ulonglong),
("unused2", ctypes.c_ulonglong),
("unused3", ctypes.c_ulonglong),
("unused4", ctypes.c_ulonglong),
("buf_size", ctypes.c_ulonglong),
("buf", ctypes.c_ubyte * args_buffer_size),
]
# the search ioctl resturns one header for each item
#
class btrfs_ioctl_search_header(ctypes.Structure):
_pack_ = 1
_fields_ = [ ("transid", ctypes.c_ulonglong),
("objectid", ctypes.c_ulonglong),
("offset", ctypes.c_ulonglong),
("type", ctypes.c_uint),
("len", ctypes.c_uint),
]
# the type field in btrfs_file_extent_item
BTRFS_FILE_EXTENT_INLINE = 0
BTRFS_FILE_EXTENT_REG = 1
BTRFS_FILE_EXTENT_PREALLOC = 2
class btrfs_file_extent_item(ctypes.LittleEndianStructure):
_pack_ = 1
_fields_ = [ ("generation", ctypes.c_ulonglong),
("ram_bytes", ctypes.c_ulonglong),
("compression", ctypes.c_ubyte),
("encryption", ctypes.c_ubyte),
("other_encoding", ctypes.c_ubyte * 2),
("type", ctypes.c_ubyte),
("disk_bytenr", ctypes.c_ulonglong),
("disk_num_bytes", ctypes.c_ulonglong),
("offset", ctypes.c_ulonglong),
("num_bytes", ctypes.c_ulonglong),
]
class btrfs_ioctl_search():
def __init__(self):
self.args = btrfs_ioctl_search_args()
self.args.tree_id = 0
self.args.min_objectid = 0
self.args.max_objectid = maxu64
self.args.min_offset = 0
self.args.max_offset = maxu64
self.args.min_transid = 0
self.args.max_transid = maxu64
self.args.min_type = 0
self.args.max_type = maxu32
self.args.nr_items = 0
self.args.buf_size = args_buffer_size
# magic encoded for x86_64 this is the v2 search ioctl
self.ioctl_num = 3228603409L
# the results of the search get stored into args.buf
def search(self, fd, nritems=65536):
self.args.nr_items = nritems
fcntl.ioctl(fd, self.ioctl_num, self.args, 1)
# this moves the search key forward by one. If the end result is
# still a valid search key (all mins less than all maxes), we return
# True. Otherwise False
#
def advance_search(search):
if search.args.min_offset < maxu64:
search.args.min_offset += 1
elif search.args.min_type < 255:
search.args.min_type += 1
elif search.args.min_objectid < maxu64:
search.args.min_objectid += 1
else:
return False
if search.args.min_offset > search.args.max_offset:
return False
if search.args.min_type > search.args.max_type:
return False
if search.args.min_objectid > search.args.max_objectid:
return False
return True
# given one search_header and one file_item, print the details. This
# also tosses the [disk_bytenr,disk_num_bytes] into extent_hash to record
# which extents were used by this file
#
def print_one_extent(header, fi, extent_hash):
# we're ignoring inline items for now
if fi.type == BTRFS_FILE_EXTENT_INLINE:
# header.len is the length of the item returned. We subtract
# the part of the file item header that is actually used (21 bytes)
# and we get the length of the inlined data.
# this may or may not be compressed
inline_len = header.len - 21
if fi.compression:
ram_bytes = fi.ram_bytes
else:
ram_bytes = inline_len
print "(%Lu %Lu): ram %Lu disk 0 disk_size %Lu -- inline" % \
(header.objectid, header.offset, ram_bytes, inline_len)
extent_hash[-1] = inline_len
return
if fi.disk_bytenr == 0:
tag = " -- hole"
else:
tag = ""
print "(%Lu %Lu): ram %Lu disk %Lu disk_size %Lu%s" % (header.objectid,
header.offset, fi.num_bytes, fi.disk_bytenr, fi.disk_num_bytes, tag)
if fi.disk_bytenr:
extent_hash[fi.disk_bytenr] = fi.disk_num_bytes
# open 'filename' and run the search ioctl against it, printing all the extents
# we find
def print_file_extents(filename):
extent_hash = {}
s = btrfs_ioctl_search()
s.args.min_type = BTRFS_EXTENT_DATA_KEY
s.args.max_type = BTRFS_EXTENT_DATA_KEY
try:
fd = os.open(filename, os.O_RDONLY)
st = os.fstat(fd)
except Exception, e:
sys.stderr.write("Failed to open %s (%s)\n" % (filename, e))
return -1
if not stat.S_ISREG(st.st_mode):
sys.stderr.write("%s not a regular file\n" % filename)
return 0
s.args.min_objectid = st.st_ino
s.args.max_objectid = st.st_ino
size = st.st_size
while True:
try:
s.search(fd)
except Exception, e:
sys.stderr.write("Search ioctl failed for %s (%s)\n" % (filename, e))
return -1
if s.args.nr_items == 0:
break
# p is the results buffer from the kernel
p = ctypes.addressof(s.args.buf)
header = btrfs_ioctl_search_header()
header_size = ctypes.sizeof(header)
h = ctypes.addressof(header)
p_left = args_buffer_size
for x in xrange(0, s.args.nr_items):
# for each item, copy the header from the buffer into
# our header struct.
ctypes.memmove(h, p, header_size)
p += header_size
p_left -= header_size
# this would be a kernel bug it shouldn't be sending malformed
# items
if p_left <= 0:
break
if header.type == BTRFS_EXTENT_DATA_KEY:
fi = btrfs_file_extent_item()
# this would also be a kernel bug
if p_left < ctypes.sizeof(fi):
break
# Copy the file item out of the results buffer
ctypes.memmove(ctypes.addressof(fi), p, ctypes.sizeof(fi))
print_one_extent(header, fi, extent_hash)
p += header.len
p_left -= header.len
if p_left <= 0:
break
s.args.min_offset = header.offset
if not advance_search(s):
break
total_on_disk = 0
total_extents = 0
for x in extent_hash.itervalues():
total_on_disk += x
total_extents += 1
# don't divide by zero
if total_on_disk == 0:
total_on_disk = 1
print "file: %s extents %Lu disk size %Lu logical size %Lu ratio %.2f" % \
(filename, total_extents, total_on_disk, st.st_size,
float(st.st_size) / float(total_on_disk))
return 0
if len(sys.argv) == 1:
sys.stderr.write("Usage: btrfs-debug filename ...\n")
sys.exit(1)
for f in sys.argv[1:]:
print_file_extents(f)
|