1/* 2 * Based upon original code by Ric Wheeler and Mark Lord. 3 * 4 * Copyright (c) EMC Corporation 2008 5 * Copyright (c) Mark Lord 2008 6 * 7 * You may use/distribute this freely, under the terms of either 8 * (your choice) the GNU General Public License version 2, 9 * or a BSD style license. 10 */ 11#define _FILE_OFFSET_BITS 64 12#include <unistd.h> 13#include <string.h> 14#include <stdlib.h> 15#include <stdio.h> 16#include <fcntl.h> 17#include <errno.h> 18#include <sys/ioctl.h> 19#include <sys/stat.h> 20#include <linux/types.h> 21#include <linux/fs.h> 22 23#include "hdparm.h" 24 25static const unsigned int sector_bytes = 512; // FIXME someday 26 27struct file_extent { 28 __u64 byte_offset; 29 __u64 first_block; 30 __u64 last_block; 31 __u64 block_count; 32}; 33 34static void handle_extent (struct file_extent ext, unsigned int sectors_per_block, __u64 start_lba) 35{ 36 char lba_info[64], len_info[32]; 37 __u64 begin_lba, end_lba; 38 __u64 nsectors = ext.block_count * sectors_per_block; 39 40 if (ext.first_block) { 41 begin_lba = start_lba + ( ext.first_block * sectors_per_block); 42 end_lba = start_lba + ((ext.last_block + 1) * sectors_per_block) - 1; 43 } else { 44 begin_lba = end_lba = 0; 45 } 46 47 if (ext.first_block) 48 sprintf(lba_info, "%10llu %10llu", begin_lba, end_lba); 49 else 50 strcpy(lba_info, " - - "); 51 if (!ext.first_block && !nsectors) 52 strcpy(len_info, " - "); 53 else 54 sprintf(len_info, "%10llu", nsectors); 55 printf("%12llu %s %s\n", ext.byte_offset, lba_info, len_info); 56} 57 58static int walk_fibmap (int fd, struct stat *st, unsigned int sectors_per_block, __u64 start_lba) 59{ 60 struct file_extent ext; 61 unsigned long num_blocks; 62 __u64 blk_idx, hole = ~0ULL; 63 64 /* 65 * How many calls to FIBMAP do we need? 66 * FIBMAP returns a filesystem block number (counted from the start of the device) 67 * for each file block. This can be converted to a disk LBA using the filesystem 68 * blocksize and LBA offset obtained earlier. 69 */ 70 num_blocks = (st->st_size + st->st_blksize - 1) / st->st_blksize; 71 memset(&ext, 0, sizeof(ext)); 72 73 /* 74 * Loop through the file, building a map of the extents. 75 * All of this is done in filesystem blocks size (fs_blksize) units. 76 * 77 * Assumptions: 78 * Throughout the file, there can be any number of blocks backed by holes 79 * or by allocated blocks. Tail-packed files are special - if we find a file 80 * that has a size and has no allocated blocks, we could flag it as a "tail-packed" 81 * file if we cared: data is packed into the tail space of the inode block. 82 */ 83 for (blk_idx = 0; blk_idx < num_blocks; blk_idx++) { 84 unsigned int blknum = blk_idx; 85 __u64 blknum64; 86 /* 87 * FIBMAP takes a block index as input and on return replaces it with a 88 * block number relative to the beginning of the filesystem/partition. 89 * An output value of zero means "unallocated", or a "hole" in a sparse file. 90 * Note that this is a 32-bit value, so it will not work properly on 91 * files/filesystems with more than 4 billion blocks (~16TB), 92 */ 93 if (ioctl(fd, FIBMAP, &blknum) == -1) { 94 int err = errno; 95 perror("ioctl(FIBMAP)"); 96 return err; 97 } 98 blknum64 = blknum; /* work in 64-bits as much as possible */ 99 100 if (blk_idx && blknum64 == (ext.last_block + 1)) { 101 /* 102 * Continuation of extent: Bump last_block and block_count. 103 */ 104 ext.last_block = blknum64 ? blknum64 : hole; 105 ext.block_count++; 106 } else { 107 /* 108 * New extent: print previous extent (if any), and re-init the extent record. 109 */ 110 if (blk_idx) 111 handle_extent(ext, sectors_per_block, start_lba); 112 ext.first_block = blknum64; 113 ext.last_block = blknum64 ? blknum64 : hole; 114 ext.block_count = 1; 115 ext.byte_offset = blk_idx * st->st_blksize; 116 } 117 } 118 handle_extent(ext, sectors_per_block, start_lba); 119 return 0; 120} 121 122#define FE_COUNT 8000 123#define FE_FLAG_LAST (1 << 0) 124#define FE_FLAG_UNKNOWN (1 << 1) 125#define FE_FLAG_UNALLOC (1 << 2) 126#define FE_FLAG_NOALIGN (1 << 8) 127 128#define EXTENT_UNKNOWN (FE_FLAG_UNKNOWN | FE_FLAG_UNALLOC | FE_FLAG_NOALIGN) 129 130struct fe_s { 131 __u64 logical; 132 __u64 physical; 133 __u64 length; 134 __u64 reserved64[2]; 135 __u32 flags; 136 __u32 reserved32[3]; 137}; 138 139struct fm_s { 140 __u64 start; 141 __u64 length; 142 __u32 flags; 143 __u32 mapped_extents; 144 __u32 extent_count; 145 __u32 reserved; 146}; 147 148struct fs_s { 149 struct fm_s fm; 150 struct fe_s fe[FE_COUNT]; 151}; 152 153#define FIEMAP _IOWR('f', 11, struct fm_s) 154 155static int walk_fiemap (int fd, unsigned int sectors_per_block, __u64 start_lba) 156{ 157 unsigned int i, done = 0; 158 unsigned int blksize = sectors_per_block * sector_bytes; 159 struct fs_s fs; 160 161 memset(&fs, 0, sizeof(fs)); 162 do { 163 fs.fm.length = ~0ULL; 164 fs.fm.flags = 0; 165 fs.fm.extent_count = FE_COUNT; 166 167 if (-1 == ioctl(fd, FIEMAP, &fs)) { 168 int err = errno; 169 //perror("ioctl(FIEMAP)"); 170 return err; 171 } 172 173 if (0) fprintf(stderr, "ioctl(FIEMAP) returned %llu extents\n", (__u64)fs.fm.mapped_extents); 174 if (!fs.fm.mapped_extents) { 175 done = 1; 176 } else { 177 struct file_extent ext; 178 memset(&ext, 0, sizeof(ext)); 179 for (i = 0; i < fs.fm.mapped_extents; i++) { 180 __u64 phy_blk, ext_len; 181 182 ext.byte_offset = fs.fe[i].logical; 183 if (0) fprintf(stderr, "log=%llu phy=%llu len=%llu flags=0x%x\n", fs.fe[i].logical, 184 fs.fe[i].physical, fs.fe[i].length, fs.fe[i].flags); 185 if (fs.fe[i].flags & EXTENT_UNKNOWN) { 186 ext.first_block = 0; 187 ext.last_block = 0; 188 ext.block_count = 0; /* FIEMAP returns garbage for this. Ugh. */ 189 } else { 190 phy_blk = fs.fe[i].physical / blksize; 191 ext_len = fs.fe[i].length / blksize; 192 193 ext.first_block = phy_blk; 194 ext.last_block = phy_blk + ext_len - 1; 195 ext.block_count = ext_len; 196 } 197 handle_extent(ext, sectors_per_block, start_lba); 198 199 if (fs.fe[i].flags & FE_FLAG_LAST) { 200 /* 201 * Hit an ext4 bug in 2.6.29.4, where some FIEMAP calls 202 * had the LAST flag set in the final returned extent, 203 * even though there were *plenty* more extents to be had 204 * from continued FIEMAP calls. 205 * 206 * So, we'll ignore it here, and instead rely on getting 207 * a zero count back from fs.fm.mapped_extents at the end. 208 */ 209 if (0) fprintf(stderr, "%s: ignoring LAST bit\n", __func__); 210 //done = 1; 211 } 212 213 } 214 fs.fm.start = (fs.fe[i-1].logical + fs.fe[i-1].length); 215 } 216 } while (!done); 217 return 0; 218} 219 220int do_filemap (const char *file_name) 221{ 222 int fd, err; 223 struct stat st; 224 __u64 start_lba = 0; 225 unsigned int sectors_per_block, blksize; 226 227 if ((fd = open(file_name, O_RDONLY)) == -1) { 228 err = errno; 229 perror(file_name); 230 return err; 231 } 232 if (fstat(fd, &st) == -1) { 233 err = errno; 234 perror(file_name); 235 return err; 236 } 237 if (!S_ISREG(st.st_mode)) { 238 fprintf(stderr, "%s: not a regular file\n", file_name); 239 close(fd); 240 return EINVAL; 241 } 242 243 /* 244 * Get the filesystem starting LBA: 245 */ 246 err = get_dev_t_geometry(st.st_dev, NULL, NULL, NULL, &start_lba, NULL); 247 if (err) { 248 close(fd); 249 return err; 250 } 251 if (start_lba == START_LBA_UNKNOWN) { 252 fprintf(stderr, "Unable to determine start offset LBA for device, aborting.\n"); 253 close(fd); 254 return EIO; 255 } 256 if((err=ioctl(fd,FIGETBSZ,&blksize))){ 257 fprintf(stderr, "Unable to determine block size, aborting.\n"); 258 close(fd); 259 return err; 260 }; 261 sectors_per_block = blksize / sector_bytes; 262 printf("\n%s:\n filesystem blocksize %u, begins at LBA %llu;" 263 " assuming %u byte sectors.\n", 264 file_name, blksize, start_lba, sector_bytes); 265 printf("%12s %10s %10s %10s\n", "byte_offset", "begin_LBA", "end_LBA", "sectors"); 266 267 if (st.st_size == 0) { 268 struct file_extent ext; 269 memset(&ext, 0, sizeof(ext)); 270 handle_extent(ext, sectors_per_block, start_lba); 271 close(fd); 272 return 0; 273 } 274 275 err = walk_fiemap(fd, sectors_per_block, start_lba); 276 if (err) 277 err = walk_fibmap(fd, &st, sectors_per_block, start_lba); 278 close (fd); 279 return 0; 280} 281