filefrag.c revision 18a1444b4f1e6a0948fd38fa0de382d86cfe04de
1/*
2 * filefrag.c --- display the fragmentation information for a file
3 *
4 * Copyright (C) 2011 Theodore Ts'o.  This file may be redistributed
5 * under the terms of the GNU Public License.
6 */
7
8#include <stdio.h>
9#include <unistd.h>
10#include <stdlib.h>
11#include <ctype.h>
12#include <string.h>
13#include <time.h>
14#ifdef HAVE_ERRNO_H
15#include <errno.h>
16#endif
17#include <sys/types.h>
18#include <sys/stat.h>
19#include <fcntl.h>
20#include <utime.h>
21#ifdef HAVE_GETOPT_H
22#include <getopt.h>
23#else
24extern int optind;
25extern char *optarg;
26#endif
27
28#include "debugfs.h"
29
30#define VERBOSE_OPT	0x0001
31#define DIR_OPT		0x0002
32#define RECURSIVE_OPT	0x0004
33
34struct dir_list {
35	char		*name;
36	ext2_ino_t	ino;
37	struct dir_list	*next;
38};
39
40struct filefrag_struct {
41	FILE		*f;
42	const char	*name;
43	const char	*dir_name;
44	int		options;
45	int		logical_width;
46	int		physical_width;
47	int		ext;
48	int		cont_ext;
49	e2_blkcnt_t	num;
50	e2_blkcnt_t	logical_start;
51	blk64_t		physical_start;
52	blk64_t		expected;
53	struct dir_list *dir_list, *dir_last;
54};
55
56static int int_log10(unsigned long long arg)
57{
58	int     l = 0;
59
60	arg = arg / 10;
61	while (arg) {
62		l++;
63		arg = arg / 10;
64	}
65	return l;
66}
67
68static void print_header(struct filefrag_struct *fs)
69{
70	if (fs->options & VERBOSE_OPT) {
71		fprintf(fs->f, "%4s %*s %*s %*s %*s\n", "ext",
72			fs->logical_width, "logical", fs->physical_width,
73			"physical", fs->physical_width, "expected",
74			fs->logical_width, "length");
75	}
76}
77
78static void report_filefrag(struct filefrag_struct *fs)
79{
80	if (fs->num == 0)
81		return;
82	if (fs->options & VERBOSE_OPT) {
83		if (fs->expected)
84			fprintf(fs->f, "%4d %*lu %*llu %*llu %*lu\n", fs->ext,
85				fs->logical_width,
86				(unsigned long) fs->logical_start,
87				fs->physical_width, fs->physical_start,
88				fs->physical_width, fs->expected,
89				fs->logical_width, (unsigned long) fs->num);
90		else
91			fprintf(fs->f, "%4d %*lu %*llu %*s %*lu\n", fs->ext,
92				fs->logical_width,
93				(unsigned long) fs->logical_start,
94				fs->physical_width, fs->physical_start,
95				fs->physical_width, "",
96				fs->logical_width, (unsigned long) fs->num);
97	}
98	fs->ext++;
99}
100
101static int filefrag_blocks_proc(ext2_filsys ext4_fs EXT2FS_ATTR((unused)),
102				blk64_t *blocknr, e2_blkcnt_t blockcnt,
103				blk64_t ref_block EXT2FS_ATTR((unused)),
104				int ref_offset EXT2FS_ATTR((unused)),
105				void *private)
106{
107	struct filefrag_struct *fs = private;
108
109	if (blockcnt < 0 || *blocknr == 0)
110		return 0;
111
112	if ((fs->num == 0) || (blockcnt != fs->logical_start + fs->num) ||
113	    (*blocknr != fs->physical_start + fs->num)) {
114		report_filefrag(fs);
115		if (blockcnt == fs->logical_start + fs->num)
116			fs->expected = fs->physical_start + fs->num;
117		else
118			fs->expected = 0;
119		fs->logical_start = blockcnt;
120		fs->physical_start = *blocknr;
121		fs->num = 1;
122		fs->cont_ext++;
123	} else
124		fs->num++;
125	return 0;
126}
127
128static void filefrag(ext2_ino_t ino, struct ext2_inode *inode,
129		     struct filefrag_struct *fs)
130{
131	errcode_t	retval;
132	int		blocksize = current_fs->blocksize;
133
134	fs->logical_width = int_log10((EXT2_I_SIZE(inode) + blocksize - 1) /
135				      blocksize) + 1;
136	if (fs->logical_width < 7)
137		fs->logical_width = 7;
138	fs->ext = 0;
139	fs->cont_ext = 0;
140	fs->logical_start = 0;
141	fs->physical_start = 0;
142	fs->num = 0;
143
144	if (fs->options & VERBOSE_OPT) {
145		blk64_t num_blocks = ext2fs_inode_i_blocks(current_fs, inode);
146
147		if (!(current_fs->super->s_feature_ro_compat &
148		     EXT4_FEATURE_RO_COMPAT_HUGE_FILE) ||
149		    !(inode->i_flags & EXT4_HUGE_FILE_FL))
150			num_blocks /= current_fs->blocksize / 512;
151
152		fprintf(fs->f, "\n%s has %llu block(s), i_size is %llu\n",
153			fs->name, num_blocks, EXT2_I_SIZE(inode));
154	}
155	print_header(fs);
156	retval = ext2fs_block_iterate3(current_fs, ino,
157				       BLOCK_FLAG_READ_ONLY, NULL,
158				       filefrag_blocks_proc, fs);
159	if (retval)
160		com_err("ext2fs_block_iterate3", retval, 0);
161
162	report_filefrag(fs);
163	fprintf(fs->f, "%s: %d contiguous extents%s\n", fs->name, fs->ext,
164		LINUX_S_ISDIR(inode->i_mode) ? " (dir)" : "");
165}
166
167static int filefrag_dir_proc(ext2_ino_t dir EXT2FS_ATTR((unused)),
168			     int	entry,
169			     struct ext2_dir_entry *dirent,
170			     int	offset EXT2FS_ATTR((unused)),
171			     int	blocksize EXT2FS_ATTR((unused)),
172			     char	*buf EXT2FS_ATTR((unused)),
173			     void	*private)
174{
175	struct filefrag_struct *fs = private;
176	struct ext2_inode	inode;
177	ext2_ino_t		ino;
178	char			name[EXT2_NAME_LEN + 1];
179	char			*cp;
180	int			thislen;
181
182	if (entry == DIRENT_DELETED_FILE)
183		return 0;
184
185	thislen = dirent->name_len & 0xFF;
186	strncpy(name, dirent->name, thislen);
187	name[thislen] = '\0';
188	ino = dirent->inode;
189
190	if (!strcmp(name, ".") || !strcmp(name, ".."))
191		return 0;
192
193	cp = malloc(strlen(fs->dir_name) + strlen(name) + 2);
194	if (!cp) {
195		fprintf(stderr, "Couldn't allocate memory for %s/%s\n",
196			fs->dir_name, name);
197		return 0;
198	}
199
200	sprintf(cp, "%s/%s", fs->dir_name, name);
201	fs->name = cp;
202
203	if (debugfs_read_inode(ino, &inode, fs->name))
204		goto errout;
205
206	filefrag(ino, &inode, fs);
207
208	if ((fs->options & RECURSIVE_OPT) && LINUX_S_ISDIR(inode.i_mode)) {
209		struct dir_list *p;
210
211		p = malloc(sizeof(struct dir_list));
212		if (!p) {
213			fprintf(stderr, "Couldn't allocate dir_list for %s\n",
214				fs->name);
215			goto errout;
216		}
217		memset(p, 0, sizeof(struct dir_list));
218		p->name = cp;
219		p->ino = ino;
220		if (fs->dir_last)
221			fs->dir_last->next = p;
222		else
223			fs->dir_list = p;
224		fs->dir_last = p;
225		return 0;
226	}
227errout:
228	free(cp);
229	fs->name = 0;
230	return 0;
231}
232
233
234static void dir_iterate(ext2_ino_t ino, struct filefrag_struct *fs)
235{
236	errcode_t	retval;
237	struct dir_list	*p = NULL;
238
239	fs->dir_name = fs->name;
240
241	while (1) {
242		retval = ext2fs_dir_iterate2(current_fs, ino, 0,
243					     0, filefrag_dir_proc, fs);
244		if (retval)
245			com_err("ext2fs_dir_iterate2", retval, 0);
246		if (p) {
247			free(p->name);
248			fs->dir_list = p->next;
249			if (!fs->dir_list)
250				fs->dir_last = 0;
251			free(p);
252		}
253		p = fs->dir_list;
254		if (!p)
255			break;
256		ino = p->ino;
257		fs->dir_name = p->name;
258	}
259}
260
261void do_filefrag(int argc, char *argv[])
262{
263	struct filefrag_struct fs;
264	struct ext2_inode inode;
265	ext2_ino_t	ino;
266	int		c;
267
268	memset(&fs, 0, sizeof(fs));
269	if (check_fs_open(argv[0]))
270		return;
271
272	reset_getopt();
273	while ((c = getopt(argc, argv, "dvr")) != EOF) {
274		switch (c) {
275		case 'd':
276			fs.options |= DIR_OPT;
277			break;
278		case 'v':
279			fs.options |= VERBOSE_OPT;
280			break;
281		case 'r':
282			fs.options |= RECURSIVE_OPT;
283			break;
284		default:
285			goto print_usage;
286		}
287	}
288
289	if (argc > optind+1) {
290	print_usage:
291		com_err(0, 0, "Usage: filefrag [-dvr] file");
292		return;
293	}
294
295	if (argc == optind) {
296		ino = cwd;
297		fs.name = ".";
298	} else {
299		ino = string_to_inode(argv[optind]);
300		fs.name = argv[optind];
301	}
302	if (!ino)
303		return;
304
305	if (debugfs_read_inode(ino, &inode, argv[0]))
306		return;
307
308	fs.f = open_pager();
309	fs.physical_width = int_log10(ext2fs_blocks_count(current_fs->super));
310	fs.physical_width++;
311	if (fs.physical_width < 8)
312		fs.physical_width = 8;
313
314	if (!LINUX_S_ISDIR(inode.i_mode) || (fs.options & DIR_OPT))
315		filefrag(ino, &inode, &fs);
316	else
317		dir_iterate(ino, &fs);
318
319	fprintf(fs.f, "\n");
320	close_pager(fs.f);
321
322	return;
323}
324