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#ifndef __linux__ 13#include <stdio.h> 14#include <stdlib.h> 15 16int main(void) { 17 fputs("This program is only supported on Linux!\n", stderr); 18 exit(EXIT_FAILURE); 19} 20#else 21#define _LARGEFILE64_SOURCE 22 23#include <stdio.h> 24#include <stdlib.h> 25#include <unistd.h> 26#include <string.h> 27#include <time.h> 28#include <fcntl.h> 29#include <errno.h> 30#ifdef HAVE_GETOPT_H 31#include <getopt.h> 32#else 33extern char *optarg; 34extern int optind; 35#endif 36#include <sys/types.h> 37#include <sys/stat.h> 38#include <sys/vfs.h> 39#include <sys/ioctl.h> 40#include <linux/fd.h> 41 42int verbose = 0; 43 44#define FIBMAP _IO(0x00,1) /* bmap access */ 45#define FIGETBSZ _IO(0x00,2) /* get the block size used for bmap */ 46 47#define EXT4_EXTENTS_FL 0x00080000 /* Inode uses extents */ 48#define EXT3_IOC_GETFLAGS _IOR('f', 1, long) 49 50static unsigned int div_ceil(unsigned int a, unsigned int b) 51{ 52 if (!a) 53 return 0; 54 return ((a - 1) / b) + 1; 55} 56 57static unsigned long get_bmap(int fd, unsigned long block) 58{ 59 int ret; 60 unsigned int b; 61 62 b = block; 63 ret = ioctl(fd, FIBMAP, &b); /* FIBMAP takes a pointer to an integer */ 64 if (ret < 0) { 65 if (errno == EPERM) { 66 fprintf(stderr, "No permission to use FIBMAP ioctl; must have root privileges\n"); 67 exit(1); 68 } 69 perror("FIBMAP"); 70 } 71 return b; 72} 73 74#define EXT2_DIRECT 12 75 76static void frag_report(const char *filename) 77{ 78 struct statfs fsinfo; 79#ifdef HAVE_FSTAT64 80 struct stat64 fileinfo; 81#else 82 struct stat fileinfo; 83#endif 84 int bs; 85 long fd; 86 unsigned long block, last_block = 0, numblocks, i; 87 long bpib; /* Blocks per indirect block */ 88 long cylgroups; 89 int discont = 0, expected; 90 int is_ext2 = 0; 91 unsigned int flags; 92 93 if (statfs(filename, &fsinfo) < 0) { 94 perror("statfs"); 95 return; 96 } 97#ifdef HAVE_FSTAT64 98 if (stat64(filename, &fileinfo) < 0) { 99#else 100 if (stat(filename, &fileinfo) < 0) { 101#endif 102 perror("stat"); 103 return; 104 } 105 if (!S_ISREG(fileinfo.st_mode)) { 106 printf("%s: Not a regular file\n", filename); 107 return; 108 } 109 if ((fsinfo.f_type == 0xef51) || (fsinfo.f_type == 0xef52) || 110 (fsinfo.f_type == 0xef53)) 111 is_ext2++; 112 if (verbose) { 113 printf("Filesystem type is: %lx\n", 114 (unsigned long) fsinfo.f_type); 115 } 116 cylgroups = div_ceil(fsinfo.f_blocks, fsinfo.f_bsize*8); 117 if (verbose) { 118 printf("Filesystem cylinder groups is approximately %ld\n", 119 cylgroups); 120 } 121#ifdef HAVE_OPEN64 122 fd = open64(filename, O_RDONLY); 123#else 124 fd = open(filename, O_RDONLY); 125#endif 126 if (fd < 0) { 127 perror("open"); 128 return; 129 } 130 if (ioctl(fd, FIGETBSZ, &bs) < 0) { /* FIGETBSZ takes an int */ 131 perror("FIGETBSZ"); 132 close(fd); 133 return; 134 } 135 if (ioctl(fd, EXT3_IOC_GETFLAGS, &flags) < 0) 136 flags = 0; 137 if (flags & EXT4_EXTENTS_FL) { 138 printf("File is stored in extents format\n"); 139 is_ext2 = 0; 140 } 141 if (verbose) 142 printf("Blocksize of file %s is %d\n", filename, bs); 143 bpib = bs / 4; 144 numblocks = (fileinfo.st_size + (bs-1)) / bs; 145 if (verbose) { 146 printf("File size of %s is %lld (%ld blocks)\n", filename, 147 (long long) fileinfo.st_size, numblocks); 148 printf("First block: %lu\nLast block: %lu\n", 149 get_bmap(fd, 0), get_bmap(fd, numblocks - 1)); 150 } 151 for (i=0; i < numblocks; i++) { 152 if (is_ext2 && last_block) { 153 if (((i-EXT2_DIRECT) % bpib) == 0) 154 last_block++; 155 if (((i-EXT2_DIRECT-bpib) % (bpib*bpib)) == 0) 156 last_block++; 157 if (((i-EXT2_DIRECT-bpib-bpib*bpib) % (bpib*bpib*bpib)) == 0) 158 last_block++; 159 } 160 block = get_bmap(fd, i); 161 if (block == 0) 162 continue; 163 if (last_block && (block != last_block +1) ) { 164 if (verbose) 165 printf("Discontinuity: Block %ld is at %lu (was %lu)\n", 166 i, block, last_block); 167 discont++; 168 } 169 last_block = block; 170 } 171 if (discont==0) 172 printf("%s: 1 extent found", filename); 173 else 174 printf("%s: %d extents found", filename, discont+1); 175 expected = (numblocks/((bs*8)-(fsinfo.f_files/8/cylgroups)-3))+1; 176 if (is_ext2 && expected != discont+1) 177 printf(", perfection would be %d extent%s\n", expected, 178 (expected>1) ? "s" : ""); 179 else 180 fputc('\n', stdout); 181 close(fd); 182} 183 184static void usage(const char *progname) 185{ 186 fprintf(stderr, "Usage: %s [-v] file ...\n", progname); 187 exit(1); 188} 189 190int main(int argc, char**argv) 191{ 192 char **cpp; 193 int c; 194 195 while ((c = getopt(argc, argv, "v")) != EOF) 196 switch (c) { 197 case 'v': 198 verbose++; 199 break; 200 default: 201 usage(argv[0]); 202 break; 203 } 204 if (optind == argc) 205 usage(argv[0]); 206 for (cpp=argv+optind; *cpp; cpp++) { 207 if (verbose) 208 printf("Checking %s\n", *cpp); 209 frag_report(*cpp); 210 } 211 return 0; 212} 213#endif 214