filefrag.c revision e3507739e4185bdb2394928eb6479e48f4e690a8
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; 55char *ext_fmt = "%4d: %*llu..%*llu: %*llu..%*llu: %6llu: %s\n"; 56char *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 >= 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 perror("stat"); 364 return; 365 } 366 367 if (last_device != st.st_dev) { 368 if (fstatfs(fd, &fsinfo) < 0) { 369 perror("fstatfs"); 370 return; 371 } 372 if (verbose) 373 printf("Filesystem type is: %lx\n", 374 (unsigned long) fsinfo.f_type); 375 } 376 st.st_blksize = fsinfo.f_bsize; 377 if (ioctl(fd, EXT3_IOC_GETFLAGS, &flags) < 0) 378 flags = 0; 379 if (!(flags & EXT4_EXTENTS_FL) && 380 ((fsinfo.f_type == 0xef51) || (fsinfo.f_type == 0xef52) || 381 (fsinfo.f_type == 0xef53))) 382 is_ext2++; 383 384 if (is_ext2) { 385 long cylgroups = div_ceil(fsinfo.f_blocks, fsinfo.f_bsize * 8); 386 387 if (verbose && last_device != st.st_dev) 388 printf("Filesystem cylinder groups approximately %ld\n", 389 cylgroups); 390 391 data_blocks_per_cyl = fsinfo.f_bsize * 8 - 392 (fsinfo.f_files / 8 / cylgroups) - 3; 393 } 394 last_device = st.st_dev; 395 396 width = int_log10(fsinfo.f_blocks); 397 if (width > physical_width) 398 physical_width = width; 399 400 numblocks = (st.st_size + fsinfo.f_bsize - 1) / fsinfo.f_bsize; 401 if (blocksize != 0) 402 blk_shift = int_log2(blocksize); 403 else 404 blk_shift = int_log2(fsinfo.f_bsize); 405 406 width = int_log10(numblocks); 407 if (width > logical_width) 408 logical_width = width; 409 if (verbose) 410 printf("File size of %s is %llu (%lu block%s of %d bytes)\n", 411 filename, (unsigned long long)st.st_size, 412 numblocks * fsinfo.f_bsize >> blk_shift, 413 numblocks == 1 ? "" : "s", 1 << blk_shift); 414 415 if (force_bmap || 416 filefrag_fiemap(fd, blk_shift, &num_extents, &st) != 0) { 417 expected = filefrag_fibmap(fd, blk_shift, &num_extents, 418 &st, numblocks, is_ext2); 419 if (expected < 0) { 420 if (errno == EINVAL || errno == ENOTTY) { 421 fprintf(stderr, "%s: FIBMAP unsupported\n", 422 filename); 423 } else if (errno != EPERM) { 424 fprintf(stderr, "%s: FIBMAP error: %s", 425 filename, strerror(errno)); 426 } 427 goto out_close; 428 } 429 expected = expected / data_blocks_per_cyl + 1; 430 } 431 432 if (num_extents == 1) 433 printf("%s: 1 extent found", filename); 434 else 435 printf("%s: %d extents found", filename, num_extents); 436 /* count, and thus expected, only set for indirect FIBMAP'd files */ 437 if (is_ext2 && expected && expected < num_extents) 438 printf(", perfection would be %d extent%s\n", expected, 439 (expected > 1) ? "s" : ""); 440 else 441 fputc('\n', stdout); 442out_close: 443 close(fd); 444} 445 446static void usage(const char *progname) 447{ 448 fprintf(stderr, "Usage: %s [-b{blocksize}] [-BeklsvxX] file ...\n", 449 progname); 450 exit(1); 451} 452 453int main(int argc, char**argv) 454{ 455 char **cpp; 456 int c; 457 458 while ((c = getopt(argc, argv, "Bb::eksvxX")) != EOF) 459 switch (c) { 460 case 'B': 461 force_bmap++; 462 break; 463 case 'b': 464 if (optarg) { 465 char *end; 466 blocksize = strtoul(optarg, &end, 0); 467 if (end) { 468 switch (end[0]) { 469 case 'g': 470 case 'G': 471 blocksize *= 1024; 472 /* no break */ 473 case 'm': 474 case 'M': 475 blocksize *= 1024; 476 /* no break */ 477 case 'k': 478 case 'K': 479 blocksize *= 1024; 480 break; 481 default: 482 break; 483 } 484 } 485 } else { /* Allow -b without argument for compat. Remove 486 * this eventually so "-b {blocksize}" works */ 487 fprintf(stderr, "%s: -b needs a blocksize " 488 "option, assuming 1024-byte blocks.\n", 489 argv[0]); 490 blocksize = 1024; 491 } 492 break; 493 case 'e': 494 force_extent++; 495 if (!verbose) 496 verbose++; 497 break; 498 case 'k': 499 blocksize = 1024; 500 break; 501 case 's': 502 sync_file++; 503 break; 504 case 'v': 505 verbose++; 506 break; 507 case 'x': 508 xattr_map++; 509 break; 510 case 'X': 511 ext_fmt = hex_fmt; 512 break; 513 default: 514 usage(argv[0]); 515 break; 516 } 517 if (optind == argc) 518 usage(argv[0]); 519 for (cpp=argv+optind; *cpp; cpp++) 520 frag_report(*cpp); 521 return 0; 522} 523#endif 524