1 /* 2 * e2freefrag - report filesystem free-space fragmentation 3 * 4 * Copyright (C) 2009 Sun Microsystems, Inc. 5 * 6 * Author: Rupesh Thakare <rupesh (at) sun.com> 7 * Andreas Dilger <adilger (at) sun.com> 8 * 9 * %Begin-Header% 10 * This file may be redistributed under the terms of the GNU Public 11 * License version 2. 12 * %End-Header% 13 */ 14 #include "config.h" 15 #include <stdio.h> 16 #ifdef HAVE_UNISTD_H 17 #include <unistd.h> 18 #endif 19 #ifdef HAVE_STDLIB_H 20 #include <stdlib.h> 21 #endif 22 #ifdef HAVE_GETOPT_H 23 #include <getopt.h> 24 #else 25 extern char *optarg; 26 extern int optind; 27 #endif 28 #if defined(HAVE_EXT2_IOCTLS) && !defined(DEBUGFS) 29 # include <sys/ioctl.h> 30 # include <sys/types.h> 31 # include <sys/stat.h> 32 # include <fcntl.h> 33 # include <limits.h> 34 #endif 35 36 #include "ext2fs/ext2_fs.h" 37 #include "ext2fs/ext2fs.h" 38 #include "e2freefrag.h" 39 40 #if defined(HAVE_EXT2_IOCTLS) && !defined(DEBUGFS) 41 # ifdef HAVE_LINUX_FSMAP_H 42 # include <linux/fsmap.h> 43 # endif 44 # include "fsmap.h" 45 #endif 46 47 #ifndef PATH_MAX 48 #define PATH_MAX 4096 49 #endif 50 51 static void usage(const char *prog) 52 { 53 fprintf(stderr, "usage: %s [-c chunksize in kb] [-h] " 54 "device_name\n", prog); 55 #ifndef DEBUGFS 56 exit(1); 57 #endif 58 } 59 60 static int ul_log2(unsigned long arg) 61 { 62 int l = 0; 63 64 arg >>= 1; 65 while (arg) { 66 l++; 67 arg >>= 1; 68 } 69 return l; 70 } 71 72 static void init_chunk_info(ext2_filsys fs, struct chunk_info *info) 73 { 74 int i; 75 76 info->blocksize_bits = ul_log2((unsigned long)fs->blocksize); 77 if (info->chunkbytes) { 78 info->chunkbits = ul_log2(info->chunkbytes); 79 info->blks_in_chunk = info->chunkbytes >> info->blocksize_bits; 80 } else { 81 info->chunkbits = ul_log2(DEFAULT_CHUNKSIZE); 82 info->blks_in_chunk = DEFAULT_CHUNKSIZE >> info->blocksize_bits; 83 } 84 85 info->min = ~0UL; 86 info->max = info->avg = 0; 87 info->real_free_chunks = 0; 88 89 for (i = 0; i < MAX_HIST; i++) { 90 info->histogram.fc_chunks[i] = 0; 91 info->histogram.fc_blocks[i] = 0; 92 } 93 } 94 95 static void update_chunk_stats(struct chunk_info *info, 96 unsigned long chunk_size) 97 { 98 unsigned long idx; 99 100 idx = ul_log2(chunk_size) + 1; 101 if (idx >= MAX_HIST) 102 idx = MAX_HIST-1; 103 info->histogram.fc_chunks[idx]++; 104 info->histogram.fc_blocks[idx] += chunk_size; 105 106 if (chunk_size > info->max) 107 info->max = chunk_size; 108 if (chunk_size < info->min) 109 info->min = chunk_size; 110 info->avg += chunk_size; 111 info->real_free_chunks++; 112 } 113 114 static void scan_block_bitmap(ext2_filsys fs, struct chunk_info *info) 115 { 116 unsigned long long blocks_count = ext2fs_blocks_count(fs->super); 117 unsigned long long chunks = (blocks_count + info->blks_in_chunk) >> 118 (info->chunkbits - info->blocksize_bits); 119 unsigned long long chunk_num; 120 unsigned long last_chunk_size = 0; 121 unsigned long long chunk_start_blk = 0; 122 int used; 123 124 for (chunk_num = 0; chunk_num < chunks; chunk_num++) { 125 unsigned long long blk, num_blks; 126 int chunk_free; 127 128 /* Last chunk may be smaller */ 129 if (chunk_start_blk + info->blks_in_chunk > blocks_count) 130 num_blks = blocks_count - chunk_start_blk; 131 else 132 num_blks = info->blks_in_chunk; 133 134 chunk_free = 0; 135 136 /* Initialize starting block for first chunk correctly else 137 * there is a segfault when blocksize = 1024 in which case 138 * block_map->start = 1 */ 139 for (blk = 0; blk < num_blks; blk++, chunk_start_blk++) { 140 if (chunk_num == 0 && blk == 0) { 141 blk = fs->super->s_first_data_block; 142 chunk_start_blk = blk; 143 } 144 used = ext2fs_fast_test_block_bitmap2(fs->block_map, 145 chunk_start_blk >> fs->cluster_ratio_bits); 146 if (!used) { 147 last_chunk_size++; 148 chunk_free++; 149 } 150 151 if (used && last_chunk_size != 0) { 152 update_chunk_stats(info, last_chunk_size); 153 last_chunk_size = 0; 154 } 155 } 156 157 if (chunk_free == info->blks_in_chunk) 158 info->free_chunks++; 159 } 160 if (last_chunk_size != 0) 161 update_chunk_stats(info, last_chunk_size); 162 } 163 164 #if defined(HAVE_EXT2_IOCTLS) && !defined(DEBUGFS) 165 # define FSMAP_EXTENTS 1024 166 static int scan_online(ext2_filsys fs, struct chunk_info *info) 167 { 168 struct fsmap_head *fsmap; 169 struct fsmap *extent; 170 struct fsmap *p; 171 char mntpoint[PATH_MAX + 1]; 172 errcode_t retval; 173 int mount_flags; 174 int fd; 175 int ret; 176 unsigned int i; 177 178 /* Try to open the mountpoint for a live query. */ 179 retval = ext2fs_check_mount_point(fs->device_name, &mount_flags, 180 mntpoint, PATH_MAX); 181 if (retval) { 182 com_err(fs->device_name, retval, "while checking mount status"); 183 return 0; 184 } 185 if (!(mount_flags & EXT2_MF_MOUNTED)) 186 return 0; 187 fd = open(mntpoint, O_RDONLY); 188 if (fd < 0) { 189 com_err(mntpoint, errno, "while opening mount point"); 190 return 0; 191 } 192 193 fsmap = malloc(fsmap_sizeof(FSMAP_EXTENTS)); 194 if (!fsmap) { 195 com_err(fs->device_name, errno, "while allocating memory"); 196 return 0; 197 } 198 199 memset(fsmap, 0, sizeof(*fsmap)); 200 fsmap->fmh_count = FSMAP_EXTENTS; 201 fsmap->fmh_keys[1].fmr_device = UINT_MAX; 202 fsmap->fmh_keys[1].fmr_physical = ULLONG_MAX; 203 fsmap->fmh_keys[1].fmr_owner = ULLONG_MAX; 204 fsmap->fmh_keys[1].fmr_offset = ULLONG_MAX; 205 fsmap->fmh_keys[1].fmr_flags = UINT_MAX; 206 207 /* Fill the extent histogram with live data */ 208 while (1) { 209 ret = ioctl(fd, FS_IOC_GETFSMAP, fsmap); 210 if (ret < 0) { 211 com_err(fs->device_name, errno, "while calling fsmap"); 212 free(fsmap); 213 return 0; 214 } 215 216 /* No more extents to map, exit */ 217 if (!fsmap->fmh_entries) 218 break; 219 220 for (i = 0, extent = fsmap->fmh_recs; 221 i < fsmap->fmh_entries; 222 i++, extent++) { 223 if (!(extent->fmr_flags & FMR_OF_SPECIAL_OWNER) || 224 extent->fmr_owner != FMR_OWN_FREE) 225 continue; 226 update_chunk_stats(info, 227 extent->fmr_length / fs->blocksize); 228 } 229 230 p = &fsmap->fmh_recs[fsmap->fmh_entries - 1]; 231 if (p->fmr_flags & FMR_OF_LAST) 232 break; 233 fsmap_advance(fsmap); 234 } 235 236 return 1; 237 } 238 #else 239 # define scan_online(fs, info) (0) 240 #endif /* HAVE_EXT2_IOCTLS */ 241 242 static errcode_t scan_offline(ext2_filsys fs, struct chunk_info *info) 243 { 244 errcode_t retval; 245 246 retval = ext2fs_read_block_bitmap(fs); 247 if (retval) 248 return retval; 249 scan_block_bitmap(fs, info); 250 return 0; 251 } 252 253 static errcode_t dump_chunk_info(ext2_filsys fs, struct chunk_info *info, 254 FILE *f) 255 { 256 unsigned long total_chunks; 257 const char *unitp = "KMGTPEZY"; 258 int units = 10; 259 unsigned long start = 0, end; 260 int i, retval = 0; 261 262 fprintf(f, "Total blocks: %llu\nFree blocks: %llu (%0.1f%%)\n", 263 ext2fs_blocks_count(fs->super), 264 ext2fs_free_blocks_count(fs->super), 265 (double)ext2fs_free_blocks_count(fs->super) * 100 / 266 ext2fs_blocks_count(fs->super)); 267 268 if (info->chunkbytes) { 269 fprintf(f, "\nChunksize: %lu bytes (%u blocks)\n", 270 info->chunkbytes, info->blks_in_chunk); 271 total_chunks = (ext2fs_blocks_count(fs->super) + 272 info->blks_in_chunk) >> 273 (info->chunkbits - info->blocksize_bits); 274 fprintf(f, "Total chunks: %lu\nFree chunks: %lu (%0.1f%%)\n", 275 total_chunks, info->free_chunks, 276 (double)info->free_chunks * 100 / total_chunks); 277 } 278 279 /* Display chunk information in KB */ 280 if (info->real_free_chunks) { 281 unsigned int scale = fs->blocksize >> 10; 282 info->min = info->min * scale; 283 info->max = info->max * scale; 284 info->avg = info->avg / info->real_free_chunks * scale; 285 } else { 286 info->min = 0; 287 } 288 289 fprintf(f, "\nMin. free extent: %lu KB \nMax. free extent: %lu KB\n" 290 "Avg. free extent: %lu KB\n", info->min, info->max, info->avg); 291 fprintf(f, "Num. free extent: %lu\n", info->real_free_chunks); 292 293 fprintf(f, "\nHISTOGRAM OF FREE EXTENT SIZES:\n"); 294 fprintf(f, "%s : %12s %12s %7s\n", "Extent Size Range", 295 "Free extents", "Free Blocks", "Percent"); 296 for (i = 0; i < MAX_HIST; i++) { 297 end = 1 << (i + info->blocksize_bits - units); 298 if (info->histogram.fc_chunks[i] != 0) { 299 char end_str[32]; 300 301 sprintf(end_str, "%5lu%c-", end, *unitp); 302 if (i == MAX_HIST-1) 303 strcpy(end_str, "max "); 304 fprintf(f, "%5lu%c...%7s : %12lu %12lu %6.2f%%\n", 305 start, *unitp, end_str, 306 info->histogram.fc_chunks[i], 307 info->histogram.fc_blocks[i], 308 (double)info->histogram.fc_blocks[i] * 100 / 309 ext2fs_free_blocks_count(fs->super)); 310 } 311 start = end; 312 if (start == 1<<10) { 313 start = 1; 314 units += 10; 315 unitp++; 316 } 317 } 318 319 return retval; 320 } 321 322 static void close_device(char *device_name, ext2_filsys fs) 323 { 324 int retval = ext2fs_close_free(&fs); 325 326 if (retval) 327 com_err(device_name, retval, "while closing the filesystem.\n"); 328 } 329 330 static void collect_info(ext2_filsys fs, struct chunk_info *chunk_info, FILE *f) 331 { 332 unsigned int retval = 0; 333 334 fprintf(f, "Device: %s\n", fs->device_name); 335 fprintf(f, "Blocksize: %u bytes\n", fs->blocksize); 336 337 init_chunk_info(fs, chunk_info); 338 if (!scan_online(fs, chunk_info)) { 339 init_chunk_info(fs, chunk_info); 340 retval = scan_offline(fs, chunk_info); 341 } 342 if (retval) { 343 com_err(fs->device_name, retval, "while reading block bitmap"); 344 close_device(fs->device_name, fs); 345 exit(1); 346 } 347 348 retval = dump_chunk_info(fs, chunk_info, f); 349 if (retval) { 350 com_err(fs->device_name, retval, "while dumping chunk info"); 351 close_device(fs->device_name, fs); 352 exit(1); 353 } 354 } 355 356 #ifndef DEBUGFS 357 static void open_device(char *device_name, ext2_filsys *fs) 358 { 359 int retval; 360 int flag = EXT2_FLAG_FORCE | EXT2_FLAG_64BITS; 361 362 retval = ext2fs_open(device_name, flag, 0, 0, unix_io_manager, fs); 363 if (retval) { 364 com_err(device_name, retval, "while opening filesystem"); 365 exit(1); 366 } 367 (*fs)->default_bitmap_type = EXT2FS_BMAP64_RBTREE; 368 } 369 #endif 370 371 #ifdef DEBUGFS 372 #include "debugfs.h" 373 374 void do_freefrag(int argc, char **argv, int sci_idx EXT2FS_ATTR((unused)), 375 void *infop EXT2FS_ATTR((unused))) 376 #else 377 int main(int argc, char *argv[]) 378 #endif 379 { 380 struct chunk_info chunk_info; 381 ext2_filsys fs = NULL; 382 char *progname; 383 char *end; 384 int c; 385 386 #ifdef DEBUGFS 387 if (check_fs_open(argv[0])) 388 return; 389 reset_getopt(); 390 #else 391 char *device_name; 392 393 add_error_table(&et_ext2_error_table); 394 #endif 395 progname = argv[0]; 396 memset(&chunk_info, 0, sizeof(chunk_info)); 397 398 while ((c = getopt(argc, argv, "c:h")) != EOF) { 399 switch (c) { 400 case 'c': 401 chunk_info.chunkbytes = strtoull(optarg, &end, 0); 402 if (*end != '\0') { 403 fprintf(stderr, "%s: bad chunk size '%s'\n", 404 progname, optarg); 405 usage(progname); 406 } 407 if (chunk_info.chunkbytes & 408 (chunk_info.chunkbytes - 1)) { 409 fprintf(stderr, "%s: chunk size must be a " 410 "power of 2.\n", argv[0]); 411 usage(progname); 412 } 413 chunk_info.chunkbytes *= 1024; 414 break; 415 case 'h': 416 default: 417 usage(progname); 418 break; 419 } 420 } 421 422 #ifndef DEBUGFS 423 if (optind == argc) { 424 fprintf(stderr, "%s: missing device name.\n", progname); 425 usage(progname); 426 } 427 428 device_name = argv[optind]; 429 430 open_device(device_name, &fs); 431 #else 432 fs = current_fs; 433 #endif 434 435 if (chunk_info.chunkbytes && (chunk_info.chunkbytes < fs->blocksize)) { 436 fprintf(stderr, "%s: chunksize must be greater than or equal " 437 "to filesystem blocksize.\n", progname); 438 exit(1); 439 } 440 collect_info(fs, &chunk_info, stdout); 441 #ifndef DEBUGFS 442 close_device(device_name, fs); 443 444 return 0; 445 #endif 446 } 447