summaryrefslogtreecommitdiffstats
path: root/e2fsck/emptydir.c
blob: 7aea7b6f55592bb897eb712111b0b7499d62167e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
/*
 * emptydir.c --- clear empty directory blocks
 *
 * Copyright (C) 1998 Theodore Ts'o
 *
 * %Begin-Header%
 * This file may be redistributed under the terms of the GNU Public
 * License.
 * %End-Header%
 *
 * This file has the necessary routines to search for empty directory
 * blocks and get rid of them.
 */

#include "config.h"
#include "e2fsck.h"
#include "problem.h"

/*
 * For e2fsck.h
 */
struct empty_dir_info_struct {
	ext2_dblist empty_dblist;
	ext2fs_block_bitmap empty_dir_blocks;
	ext2fs_inode_bitmap dir_map;
	char *block_buf;
	ext2_ino_t ino;
	struct ext2_inode inode;
	blk64_t	logblk;
	blk64_t	freed_blocks;
};

typedef struct empty_dir_info_struct *empty_dir_info;

extern empty_dir_info init_empty_dir(e2fsck_t ctx);
extern void free_empty_dirblock(empty_dir_info edi);
extern void add_empty_dirblock(empty_dir_info edi,
			       struct ext2_db_entry2 *db);
extern void process_empty_dirblock(e2fsck_t ctx, empty_dir_info edi);


empty_dir_info init_empty_dir(e2fsck_t ctx)
{
	empty_dir_info	edi;
	errcode_t	retval;

	edi = e2fsck_allocate_memzero(ctx, sizeof(struct empty_dir_info_struct),
				      "empty dir info");
	if (retval)
		return NULL;

	retval = ext2fs_init_dblist(ctx->fs, &edi->empty_dblist);
	if (retval)
		goto errout;

	retval = ext2fs_allocate_block_bitmap(ctx->fs, _("empty dirblocks"),
					      &edi->empty_dir_blocks);
	if (retval)
		goto errout;

	retval = ext2fs_allocate_inode_bitmap(ctx->fs, _("empty dir map"),
					      &edi->dir_map);
	if (retval)
		goto errout;

	return (edi);

errout:
	free_empty_dirblock(edi);
	return NULL;
}

void free_empty_dirblock(empty_dir_info edi)
{
	if (!edi)
		return;
	if (edi->empty_dblist)
		ext2fs_free_dblist(edi->empty_dblist);
	if (edi->empty_dir_blocks)
		ext2fs_free_block_bitmap(edi->empty_dir_blocks);
	if (edi->dir_map)
		ext2fs_free_inode_bitmap(edi->dir_map);

	memset(edi, 0, sizeof(struct empty_dir_info_struct));
	ext2fs_free_mem(&edi);
}

void add_empty_dirblock(empty_dir_info edi,
			struct ext2_db_entry2 *db)
{
	if (!edi || !db)
		return;

	if (db->ino == 11)
		return;		/* Inode number 11 is usually lost+found */

	printf(_("Empty directory block %u (#%d) in inode %u\n"),
	       db->blk, db->blockcnt, db->ino);

	ext2fs_mark_block_bitmap2(edi->empty_dir_blocks, db->blk);
	if (ext2fs_test_inode_bitmap(edi->dir_map, db->ino))
		return;
	ext2fs_mark_inode_bitmap(edi->dir_map, db->ino);

	ext2fs_add_dir_block2(edi->empty_dblist, db->ino,
			      db->blk, db->blockcnt);
}

/*
 * Helper function used by fix_directory.
 *
 * XXX need to finish this.  General approach is to use bmap to
 * iterate over all of the logical blocks using the bmap function, and
 * copy the block reference as necessary.  Big question --- what do
 * about error recovery?
 *
 * Also question --- how to free the indirect blocks.
 */
int empty_pass1(ext2_filsys fs, blk64_t *block_nr, e2_blkcnt_t blockcnt,
		blk64_t ref_block, int ref_offset, void *priv_data)
{
	empty_dir_info edi = (empty_dir_info) priv_data;
	blk64_t	block, new_block;
	errcode_t	retval;

	if (blockcnt < 0)
		return 0;
	block = *block_nr;
	do {
		retval = ext2fs_bmap2(fs, edi->ino, &edi->inode,
				      edi->block_buf, 0, edi->logblk, 0,
				      &new_block);
		if (retval)
			return DIRENT_ABORT;   /* XXX what to do? */
		if (new_block == 0)
			break;
		edi->logblk++;
	} while (ext2fs_test_block_bitmap2(edi->empty_dir_blocks, new_block));

	if (new_block == block)
		return 0;
	if (new_block == 0)
		edi->freed_blocks++;
	*block_nr = new_block;
	return BLOCK_CHANGED;
}

static int fix_directory(ext2_filsys fs,
			 struct ext2_db_entry2 *db,
			 void *priv_data)
{
	errcode_t	retval;

	empty_dir_info edi = (empty_dir_info) priv_data;

	edi->logblk = 0;
	edi->freed_blocks = 0;
	edi->ino = db->ino;

	retval = ext2fs_read_inode(fs, db->ino, &edi->inode);
	if (retval)
		return 0;

	retval = ext2fs_block_iterate3(fs, db->ino, 0, edi->block_buf,
				       empty_pass1, edi);
	if (retval)
		return 0;

	if (edi->freed_blocks) {
		edi->inode.i_size -= edi->freed_blocks * fs->blocksize;
		ext2fs_iblk_add_blocks(fs, &edi->inode, edi->freed_blocks);
		retval = ext2fs_write_inode(fs, db->ino, &edi->inode);
		if (retval)
			return 0;
	}
	return 0;
}

void process_empty_dirblock(e2fsck_t ctx, empty_dir_info edi)
{
	if (!edi)
		return;

	retval = ext2f_get_mem(ctx, ctx->fs->blocksize * 3,
			       &edi->block_buf);

	if (edi->block_buf) {
		(void) ext2fs_dblist_iterate2(edi->empty_dblist,
					      fix_directory, &edi);
	}
	ext2fs_free_mem(&edi->block_buf);
	free_empty_dirblock(edi);
}