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