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
24 extern int optind;
25 extern 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 
34 struct dir_list {
35 	char		*name;
36 	ext2_ino_t	ino;
37 	struct dir_list	*next;
38 };
39 
40 struct 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 
int_log10(unsigned long long arg)56 static 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 
print_header(struct filefrag_struct * fs)68 static 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 
report_filefrag(struct filefrag_struct * fs)78 static 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 
filefrag_blocks_proc(ext2_filsys ext4_fs EXT2FS_ATTR ((unused)),blk64_t * blocknr,e2_blkcnt_t blockcnt,blk64_t ref_block EXT2FS_ATTR ((unused)),int ref_offset EXT2FS_ATTR ((unused)),void * private)101 static 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 
filefrag(ext2_ino_t ino,struct ext2_inode * inode,struct filefrag_struct * fs)128 static 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 
filefrag_dir_proc(ext2_ino_t dir EXT2FS_ATTR ((unused)),int entry,struct ext2_dir_entry * dirent,int offset EXT2FS_ATTR ((unused)),int blocksize EXT2FS_ATTR ((unused)),char * buf EXT2FS_ATTR ((unused)),void * private)167 static 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 	}
227 errout:
228 	free(cp);
229 	fs->name = 0;
230 	return 0;
231 }
232 
233 
dir_iterate(ext2_ino_t ino,struct filefrag_struct * fs)234 static 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 
do_filefrag(int argc,char * argv[])261 void 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