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