filefrag.c revision e39082e2e81641eb8021eb6ddc969731537c79a8
1/* 2 * filefrag.c -- report if a particular file is fragmented 3 * 4 * Copyright 2003 by Theodore Ts'o. 5 * 6 * %Begin-Header% 7 * This file may be redistributed under the terms of the GNU Public 8 * License. 9 * %End-Header% 10 */ 11 12#include "config.h" 13#ifndef __linux__ 14#include <stdio.h> 15#include <stdlib.h> 16#include <unistd.h> 17 18int main(void) { 19 fputs("This program is only supported on Linux!\n", stderr); 20 exit(EXIT_FAILURE); 21} 22#else 23#define _LARGEFILE64_SOURCE 24 25#include <stdio.h> 26#include <stdlib.h> 27#include <unistd.h> 28#include <string.h> 29#include <time.h> 30#include <fcntl.h> 31#include <errno.h> 32#ifdef HAVE_GETOPT_H 33#include <getopt.h> 34#else 35extern char *optarg; 36extern int optind; 37#endif 38#include <sys/types.h> 39#include <sys/stat.h> 40#include <sys/vfs.h> 41#include <sys/ioctl.h> 42#include <linux/fd.h> 43#include <ext2fs/ext2fs.h> 44#include <ext2fs/ext2_types.h> 45#include <ext2fs/fiemap.h> 46 47int verbose = 0; 48int blocksize; /* Use specified blocksize (default 1kB) */ 49int sync_file = 0; /* fsync file before getting the mapping */ 50int xattr_map = 0; /* get xattr mapping */ 51int force_bmap; /* force use of FIBMAP instead of FIEMAP */ 52int force_extent; /* print output in extent format always */ 53int logical_width = 8; 54int physical_width = 10; 55const char *ext_fmt = "%4d: %*llu..%*llu: %*llu..%*llu: %6llu: %s\n"; 56const char *hex_fmt = "%4d: %*llx..%*llx: %*llx..%*llx: %6llx: %s\n"; 57 58#define FILEFRAG_FIEMAP_FLAGS_COMPAT (FIEMAP_FLAG_SYNC | FIEMAP_FLAG_XATTR) 59 60#define FIBMAP _IO(0x00, 1) /* bmap access */ 61#define FIGETBSZ _IO(0x00, 2) /* get the block size used for bmap */ 62 63#define LUSTRE_SUPER_MAGIC 0x0BD00BD0 64 65#define EXT4_EXTENTS_FL 0x00080000 /* Inode uses extents */ 66#define EXT3_IOC_GETFLAGS _IOR('f', 1, long) 67 68static int int_log2(int arg) 69{ 70 int l = 0; 71 72 arg >>= 1; 73 while (arg) { 74 l++; 75 arg >>= 1; 76 } 77 return l; 78} 79 80static int int_log10(unsigned long long arg) 81{ 82 int l = 0; 83 84 arg = arg / 10; 85 while (arg) { 86 l++; 87 arg = arg / 10; 88 } 89 return l; 90} 91 92static unsigned int div_ceil(unsigned int a, unsigned int b) 93{ 94 if (!a) 95 return 0; 96 return ((a - 1) / b) + 1; 97} 98 99static int get_bmap(int fd, unsigned long block, unsigned long *phy_blk) 100{ 101 int ret; 102 unsigned int b; 103 104 b = block; 105 ret = ioctl(fd, FIBMAP, &b); /* FIBMAP takes pointer to integer */ 106 if (ret < 0) { 107 if (errno == EPERM) { 108 fprintf(stderr, "No permission to use FIBMAP ioctl; " 109 "must have root privileges\n"); 110 } 111 } 112 *phy_blk = b; 113 114 return ret; 115} 116 117static void print_extent_header(void) 118{ 119 printf(" ext: %*s %*s length: %*s flags:\n", 120 logical_width * 2 + 3, 121 "logical_offset:", 122 physical_width * 2 + 3, "physical_offset:", 123 physical_width + 1, 124 "expected:"); 125} 126 127static void print_extent_info(struct fiemap_extent *fm_extent, int cur_ex, 128 unsigned long long expected, int blk_shift, 129 ext2fs_struct_stat *st) 130{ 131 unsigned long long physical_blk; 132 unsigned long long logical_blk; 133 unsigned long long ext_len; 134 unsigned long long ext_blks; 135 char flags[256] = ""; 136 137 /* For inline data all offsets should be in bytes, not blocks */ 138 if (fm_extent->fe_flags & FIEMAP_EXTENT_DATA_INLINE) 139 blk_shift = 0; 140 141 ext_len = fm_extent->fe_length >> blk_shift; 142 ext_blks = (fm_extent->fe_length - 1) >> blk_shift; 143 logical_blk = fm_extent->fe_logical >> blk_shift; 144 physical_blk = fm_extent->fe_physical >> blk_shift; 145 146 if (expected) 147 sprintf(flags, ext_fmt == hex_fmt ? "%*llx: " : "%*llu: ", 148 physical_width, expected >> blk_shift); 149 else 150 sprintf(flags, "%.*s ", physical_width, " "); 151 152 if (fm_extent->fe_flags & FIEMAP_EXTENT_UNKNOWN) 153 strcat(flags, "unknown,"); 154 if (fm_extent->fe_flags & FIEMAP_EXTENT_DELALLOC) 155 strcat(flags, "delalloc,"); 156 if (fm_extent->fe_flags & FIEMAP_EXTENT_DATA_ENCRYPTED) 157 strcat(flags, "encrypted,"); 158 if (fm_extent->fe_flags & FIEMAP_EXTENT_NOT_ALIGNED) 159 strcat(flags, "not_aligned,"); 160 if (fm_extent->fe_flags & FIEMAP_EXTENT_DATA_INLINE) 161 strcat(flags, "inline,"); 162 if (fm_extent->fe_flags & FIEMAP_EXTENT_DATA_TAIL) 163 strcat(flags, "tail_packed,"); 164 if (fm_extent->fe_flags & FIEMAP_EXTENT_UNWRITTEN) 165 strcat(flags, "unwritten,"); 166 if (fm_extent->fe_flags & FIEMAP_EXTENT_MERGED) 167 strcat(flags, "merged,"); 168 169 if (fm_extent->fe_logical + fm_extent->fe_length >= (__u64) st->st_size) 170 strcat(flags, "eof,"); 171 172 /* Remove trailing comma, if any */ 173 if (flags[0]) 174 flags[strlen(flags) - 1] = '\0'; 175 176 printf(ext_fmt, cur_ex, logical_width, logical_blk, 177 logical_width, logical_blk + ext_blks, 178 physical_width, physical_blk, 179 physical_width, physical_blk + ext_blks, 180 ext_len, flags); 181} 182 183static int filefrag_fiemap(int fd, int blk_shift, int *num_extents, 184 ext2fs_struct_stat *st) 185{ 186 char buf[16384]; 187 struct fiemap *fiemap = (struct fiemap *)buf; 188 struct fiemap_extent *fm_ext = &fiemap->fm_extents[0]; 189 int count = (sizeof(buf) - sizeof(*fiemap)) / 190 sizeof(struct fiemap_extent); 191 unsigned long long expected = 0; 192 unsigned long flags = 0; 193 unsigned int i; 194 static int fiemap_incompat_printed; 195 int fiemap_header_printed = 0; 196 int tot_extents = 0, n = 0; 197 int last = 0; 198 int rc; 199 200 memset(fiemap, 0, sizeof(struct fiemap)); 201 202 if (sync_file) 203 flags |= FIEMAP_FLAG_SYNC; 204 205 if (xattr_map) 206 flags |= FIEMAP_FLAG_XATTR; 207 208 do { 209 fiemap->fm_length = ~0ULL; 210 fiemap->fm_flags = flags; 211 fiemap->fm_extent_count = count; 212 rc = ioctl(fd, FS_IOC_FIEMAP, (unsigned long) fiemap); 213 if (rc < 0) { 214 if (errno == EBADR && fiemap_incompat_printed == 0) { 215 printf("FIEMAP failed with unsupported " 216 "flags %x\n", fiemap->fm_flags); 217 fiemap_incompat_printed = 1; 218 } 219 return rc; 220 } 221 222 /* If 0 extents are returned, then more ioctls are not needed */ 223 if (fiemap->fm_mapped_extents == 0) 224 break; 225 226 if (verbose && !fiemap_header_printed) { 227 print_extent_header(); 228 fiemap_header_printed = 1; 229 } 230 231 for (i = 0; i < fiemap->fm_mapped_extents; i++) { 232 if (fm_ext[i].fe_logical != 0 && 233 fm_ext[i].fe_physical != expected) { 234 tot_extents++; 235 } else { 236 expected = 0; 237 if (!tot_extents) 238 tot_extents = 1; 239 } 240 if (verbose) 241 print_extent_info(&fm_ext[i], n, expected, 242 blk_shift, st); 243 244 expected = fm_ext[i].fe_physical + fm_ext[i].fe_length; 245 if (fm_ext[i].fe_flags & FIEMAP_EXTENT_LAST) 246 last = 1; 247 n++; 248 } 249 250 fiemap->fm_start = (fm_ext[i - 1].fe_logical + 251 fm_ext[i - 1].fe_length); 252 } while (last == 0); 253 254 *num_extents = tot_extents; 255 256 return 0; 257} 258 259#define EXT2_DIRECT 12 260 261static int filefrag_fibmap(int fd, int blk_shift, int *num_extents, 262 ext2fs_struct_stat *st, 263 unsigned long numblocks, int is_ext2) 264{ 265 struct fiemap_extent fm_ext; 266 unsigned long i, last_block; 267 unsigned long long logical; 268 /* Blocks per indirect block */ 269 const long bpib = st->st_blksize / 4; 270 int count; 271 272 if (force_extent) { 273 memset(&fm_ext, 0, sizeof(fm_ext)); 274 fm_ext.fe_flags = FIEMAP_EXTENT_MERGED; 275 } 276 277 if (sync_file) 278 fsync(fd); 279 280 for (i = 0, logical = 0, *num_extents = 0, count = last_block = 0; 281 i < numblocks; 282 i++, logical += st->st_blksize) { 283 unsigned long block = 0; 284 int rc; 285 286 if (is_ext2 && last_block) { 287 if (((i - EXT2_DIRECT) % bpib) == 0) 288 last_block++; 289 if (((i - EXT2_DIRECT - bpib) % (bpib * bpib)) == 0) 290 last_block++; 291 if (((i - EXT2_DIRECT - bpib - bpib * bpib) % 292 (((unsigned long long)bpib) * bpib * bpib)) == 0) 293 last_block++; 294 } 295 rc = get_bmap(fd, i, &block); 296 if (rc < 0) 297 return rc; 298 if (block == 0) 299 continue; 300 if (*num_extents == 0) { 301 (*num_extents)++; 302 if (force_extent) { 303 print_extent_header(); 304 fm_ext.fe_physical = block * st->st_blksize; 305 } 306 } 307 count++; 308 if (force_extent && last_block != 0 && 309 (block != last_block + 1 || 310 fm_ext.fe_logical + fm_ext.fe_length != logical)) { 311 print_extent_info(&fm_ext, *num_extents - 1, 312 (last_block + 1) * st->st_blksize, 313 blk_shift, st); 314 fm_ext.fe_logical = logical; 315 fm_ext.fe_physical = block * st->st_blksize; 316 fm_ext.fe_length = 0; 317 (*num_extents)++; 318 } else if (verbose && last_block && (block != last_block + 1)) { 319 printf("Discontinuity: Block %ld is at %lu (was %lu)\n", 320 i, block, last_block + 1); 321 (*num_extents)++; 322 } 323 fm_ext.fe_length += st->st_blksize; 324 last_block = block; 325 } 326 327 if (force_extent) 328 print_extent_info(&fm_ext, *num_extents - 1, 329 last_block * st->st_blksize, blk_shift, st); 330 331 return count; 332} 333 334static void frag_report(const char *filename) 335{ 336 static struct statfs fsinfo; 337 ext2fs_struct_stat st; 338 int blk_shift; 339 long fd; 340 unsigned long numblocks; 341 int data_blocks_per_cyl = 1; 342 int num_extents = 1, expected = ~0; 343 int is_ext2 = 0; 344 static dev_t last_device; 345 unsigned int flags; 346 int width; 347 348#if defined(HAVE_OPEN64) && !defined(__OSX_AVAILABLE_BUT_DEPRECATED) 349 fd = open64(filename, O_RDONLY); 350#else 351 fd = open(filename, O_RDONLY); 352#endif 353 if (fd < 0) { 354 perror("open"); 355 return; 356 } 357 358#if defined(HAVE_FSTAT64) && !defined(__OSX_AVAILABLE_BUT_DEPRECATED) 359 if (fstat64(fd, &st) < 0) { 360#else 361 if (fstat(fd, &st) < 0) { 362#endif 363 close(fd); 364 perror("stat"); 365 return; 366 } 367 368 if (last_device != st.st_dev) { 369 if (fstatfs(fd, &fsinfo) < 0) { 370 close(fd); 371 perror("fstatfs"); 372 return; 373 } 374 if (verbose) 375 printf("Filesystem type is: %lx\n", 376 (unsigned long) fsinfo.f_type); 377 } 378 st.st_blksize = fsinfo.f_bsize; 379 if (ioctl(fd, EXT3_IOC_GETFLAGS, &flags) < 0) 380 flags = 0; 381 if (!(flags & EXT4_EXTENTS_FL) && 382 ((fsinfo.f_type == 0xef51) || (fsinfo.f_type == 0xef52) || 383 (fsinfo.f_type == 0xef53))) 384 is_ext2++; 385 386 if (is_ext2) { 387 long cylgroups = div_ceil(fsinfo.f_blocks, fsinfo.f_bsize * 8); 388 389 if (verbose && last_device != st.st_dev) 390 printf("Filesystem cylinder groups approximately %ld\n", 391 cylgroups); 392 393 data_blocks_per_cyl = fsinfo.f_bsize * 8 - 394 (fsinfo.f_files / 8 / cylgroups) - 3; 395 } 396 last_device = st.st_dev; 397 398 width = int_log10(fsinfo.f_blocks); 399 if (width > physical_width) 400 physical_width = width; 401 402 numblocks = (st.st_size + fsinfo.f_bsize - 1) / fsinfo.f_bsize; 403 if (blocksize != 0) 404 blk_shift = int_log2(blocksize); 405 else 406 blk_shift = int_log2(fsinfo.f_bsize); 407 408 width = int_log10(numblocks); 409 if (width > logical_width) 410 logical_width = width; 411 if (verbose) 412 printf("File size of %s is %llu (%lu block%s of %d bytes)\n", 413 filename, (unsigned long long)st.st_size, 414 numblocks * fsinfo.f_bsize >> blk_shift, 415 numblocks == 1 ? "" : "s", 1 << blk_shift); 416 417 if (force_bmap || 418 filefrag_fiemap(fd, blk_shift, &num_extents, &st) != 0) { 419 expected = filefrag_fibmap(fd, blk_shift, &num_extents, 420 &st, numblocks, is_ext2); 421 if (expected < 0) { 422 if (errno == EINVAL || errno == ENOTTY) { 423 fprintf(stderr, "%s: FIBMAP unsupported\n", 424 filename); 425 } else if (errno != EPERM) { 426 fprintf(stderr, "%s: FIBMAP error: %s", 427 filename, strerror(errno)); 428 } 429 goto out_close; 430 } 431 expected = expected / data_blocks_per_cyl + 1; 432 } 433 434 if (num_extents == 1) 435 printf("%s: 1 extent found", filename); 436 else 437 printf("%s: %d extents found", filename, num_extents); 438 /* count, and thus expected, only set for indirect FIBMAP'd files */ 439 if (is_ext2 && expected && expected < num_extents) 440 printf(", perfection would be %d extent%s\n", expected, 441 (expected > 1) ? "s" : ""); 442 else 443 fputc('\n', stdout); 444out_close: 445 close(fd); 446} 447 448static void usage(const char *progname) 449{ 450 fprintf(stderr, "Usage: %s [-b{blocksize}] [-BeklsvxX] file ...\n", 451 progname); 452 exit(1); 453} 454 455int main(int argc, char**argv) 456{ 457 char **cpp; 458 int c; 459 460 while ((c = getopt(argc, argv, "Bb::eksvxX")) != EOF) 461 switch (c) { 462 case 'B': 463 force_bmap++; 464 break; 465 case 'b': 466 if (optarg) { 467 char *end; 468 blocksize = strtoul(optarg, &end, 0); 469 if (end) { 470 switch (end[0]) { 471 case 'g': 472 case 'G': 473 blocksize *= 1024; 474 /* no break */ 475 case 'm': 476 case 'M': 477 blocksize *= 1024; 478 /* no break */ 479 case 'k': 480 case 'K': 481 blocksize *= 1024; 482 break; 483 default: 484 break; 485 } 486 } 487 } else { /* Allow -b without argument for compat. Remove 488 * this eventually so "-b {blocksize}" works */ 489 fprintf(stderr, "%s: -b needs a blocksize " 490 "option, assuming 1024-byte blocks.\n", 491 argv[0]); 492 blocksize = 1024; 493 } 494 break; 495 case 'e': 496 force_extent++; 497 if (!verbose) 498 verbose++; 499 break; 500 case 'k': 501 blocksize = 1024; 502 break; 503 case 's': 504 sync_file++; 505 break; 506 case 'v': 507 verbose++; 508 break; 509 case 'x': 510 xattr_map++; 511 break; 512 case 'X': 513 ext_fmt = hex_fmt; 514 break; 515 default: 516 usage(argv[0]); 517 break; 518 } 519 if (optind == argc) 520 usage(argv[0]); 521 for (cpp=argv+optind; *cpp; cpp++) 522 frag_report(*cpp); 523 return 0; 524} 525#endif 526