File: pass4.c

package info (click to toggle)
ocfs2-tools 1.8.6-1
  • links: PTS, VCS
  • area: main
  • in suites: bullseye, sid
  • size: 6,232 kB
  • sloc: ansic: 86,865; sh: 5,781; python: 2,380; makefile: 1,305
file content (371 lines) | stat: -rw-r--r-- 10,261 bytes parent folder | download
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
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
/* -*- mode: c; c-basic-offset: 8; -*-
 * vim: noexpandtab sw=8 ts=8 sts=0:
 *
 * Copyright (C) 1993-2004 by Theodore Ts'o.
 * Copyright (C) 2004 Oracle.  All rights reserved.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License, version 2,  as published by the Free Software Foundation.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public
 * License along with this program; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
 * Boston, MA  02110-1301 USA.
 *
 * --
 *
 * Pass 4 walks all the active inodes and makes sure that they are reachable
 * via directory entries, just like pass 3 did for directories.  It also 
 * makes sure each inode's link_count reflects the number of entries that
 * refer to it.  Inodes that aren't referred to by any entries are moved
 * to lost+found.
 */
#include <inttypes.h>
#include <limits.h>

#include "ocfs2/ocfs2.h"

#include "fsck.h"
#include "icount.h"
#include "pass3.h"
#include "pass4.h"
#include "problem.h"
#include "util.h"

struct orphan_dir_ctxt {
	o2fsck_state *ost;
	uint64_t orphan_dir;
};

static const char *whoami = "pass4";

static void check_link_counts(o2fsck_state *ost,
			      struct ocfs2_dinode *di,
			      uint64_t blkno)
{
	uint16_t refs, in_inode;
	errcode_t ret;

	refs = o2fsck_icount_get(ost->ost_icount_refs, blkno);
	in_inode = o2fsck_icount_get(ost->ost_icount_in_inodes, blkno);

	verbosef("ino %"PRIu64", refs %u in %u\n", blkno, refs, in_inode);

	/* XXX offer to remove files/dirs with no data? */
	if (refs == 0 &&
	    prompt(ost, PY, PR_INODE_NOT_CONNECTED,
		   "Inode %"PRIu64" isn't referenced by any "
		   "directory entries.  Move it to lost+found?", blkno)) {
		o2fsck_reconnect_file(ost, blkno);
		refs = o2fsck_icount_get(ost->ost_icount_refs, blkno);
	}

	if (refs == in_inode)
		goto out;

	ret = ocfs2_read_inode(ost->ost_fs, blkno, (char *)di);
	if (ret) {
		com_err(whoami, ret, "reading inode %"PRIu64" to update its "
			"i_links_count.  Could this be because a directory "
			"entry referenced an invalid inode but wasn't fixed?",
			blkno);
		goto out;
	}

	if (in_inode != di->i_links_count)
		com_err(whoami, OCFS2_ET_INTERNAL_FAILURE, "fsck's thinks "
			"inode %"PRIu64" has a link count of %"PRIu16" but on "
			"disk it is %"PRIu16, (uint64_t)di->i_blkno, in_inode,
			di->i_links_count);

	if (prompt(ost, PY, PR_INODE_COUNT,
		   "Inode %"PRIu64" has a link count of %"PRIu16" on "
		   "disk but directory entry references come to %"PRIu16". "
		   "Update the count on disk to match?",
		   (uint64_t)di->i_blkno, in_inode,
		   refs)) {
		di->i_links_count = refs;
		o2fsck_icount_set(ost->ost_icount_in_inodes, di->i_blkno,
				  refs);
		o2fsck_write_inode(ost, di->i_blkno, di);
	}

out:
	return;
}

#define OCFS2_DIO_ORPHAN_PREFIX "dio-"
#define OCFS2_DIO_ORPHAN_PREFIX_LEN 4

static int replay_orphan_iterate(struct ocfs2_dir_entry *dirent,
				 uint64_t blocknr,
				 int	offset,
				 int	blocksize,
				 char	*buf,
				 void	*priv_data)
{
	struct orphan_dir_ctxt *ctxt = priv_data;
	o2fsck_state *ost = ctxt->ost;
	int ret_flags = 0;
	errcode_t ret = 0;

	if (!(ost->ost_fs->fs_flags & OCFS2_FLAG_RW)) {
		printf("** Skipping orphan dir replay because -n was "
		       "given.\n");
		ret_flags |= OCFS2_DIRENT_ABORT;
		goto out;
	}

	ost->ost_orphan_count++;

	/* Only ask for confirmation in force check. */
	if (ost->ost_force) {
		if (!prompt(ost, PY, PR_INODE_ORPHANED,
			   "Inode %"PRIu64" was found in the orphan directory. "
			   "Delete its contents and unlink it?",
			   (uint64_t)dirent->inode))
			goto out;
	}

	ret = ocfs2_truncate(ost->ost_fs, dirent->inode, 0);
	if (ret) {
		com_err(whoami, ret, "while truncating orphan inode %"PRIu64,
			(uint64_t)dirent->inode);
		ret_flags |= OCFS2_DIRENT_ABORT;
		goto out;
	}

	/* do not delete inode in case of dio orphan entry */
	if (!strncmp(dirent->name, OCFS2_DIO_ORPHAN_PREFIX,
			OCFS2_DIO_ORPHAN_PREFIX_LEN))
		goto out_check;

	ret = ocfs2_delete_inode(ost->ost_fs, dirent->inode);
	if (ret) {
		com_err(whoami, ret, "while deleting orphan inode %"PRIu64
			"after truncating it", (uint64_t)dirent->inode);
		ret_flags |= OCFS2_DIRENT_ABORT;
		goto out;
	}

	ost->ost_orphan_deleted_count++;

out_check:
	/* Only calculate icount in force check. */
	if (ost->ost_force) {
		/*
		 * this matches a special case in o2fsck_verify_inode_fields()
		 * where orphan dir members are recorded as having 1 link count,
		 * even though they have 0 on disk
		 */
		o2fsck_icount_delta(ost->ost_icount_in_inodes,
				    dirent->inode, -1);

		/*
		 * dirs have this dirent ref and their '.' dirent and we also
		 * need to handle '..' dirent for their parents.
		 */
		if (dirent->file_type == OCFS2_FT_DIR) {
			o2fsck_icount_delta(ost->ost_icount_refs,
					    dirent->inode, -2);
			o2fsck_icount_delta(ost->ost_icount_refs,
					    ctxt->orphan_dir, -1);
		} else
			o2fsck_icount_delta(ost->ost_icount_refs,
					    dirent->inode, -1);
	}

	dirent->inode = 0;
	ret_flags |= OCFS2_DIRENT_CHANGED;

out:
	ost->ost_err = ret;
	return ret_flags;
}

static errcode_t create_orphan_dir(o2fsck_state *ost, char *fname)
{
	errcode_t ret;
	uint64_t blkno;
	ocfs2_filesys *fs = ost->ost_fs;

	/* create inode for system file */
	ret = ocfs2_new_system_inode(fs, &blkno,
			ocfs2_system_inodes[ORPHAN_DIR_SYSTEM_INODE].si_mode,
			ocfs2_system_inodes[ORPHAN_DIR_SYSTEM_INODE].si_iflags);
	if (ret)
		goto bail;

	ret = ocfs2_init_dir(fs, blkno, fs->fs_sysdir_blkno);
	if (ret)
		goto bail;

	/* Add the inode to the system dir */
	ret = ocfs2_link(fs, fs->fs_sysdir_blkno, fname, blkno,
			 OCFS2_FT_DIR);
	if (ret)
		goto bail;

	/* we have created an orphan dir under system dir and updated the disk,
	 * so we have to update the refs in ost accordingly.
	 */
	o2fsck_icount_delta(ost->ost_icount_refs, fs->fs_sysdir_blkno, 1);
	o2fsck_icount_delta(ost->ost_icount_in_inodes, fs->fs_sysdir_blkno, 1);
bail:
	return ret;
}

/*
 * replay_orphan_dir could happen in 2 places and we handle it diffrently.
 * 1. In slot recovery, we will return any error which lead to a force check.
 * 2. in o2fsck_pass4, all other errors should be fixed in pass0,1,2 and 3, so
 *    we try to fix some errors by ourselves.
 */
errcode_t replay_orphan_dir(o2fsck_state *ost, int slot_recovery)
{
	errcode_t ret = OCFS2_ET_CORRUPT_SUPERBLOCK;
	char name[PATH_MAX];
	uint64_t ino;
	int bytes;
	int i;
	int num_slots = OCFS2_RAW_SB(ost->ost_fs->fs_super)->s_max_slots;
	struct orphan_dir_ctxt ctxt;

	ctxt.ost = ost;
	for (i = 0; i < num_slots; ++i) {
		bytes = ocfs2_sprintf_system_inode_name(name, PATH_MAX,
				ORPHAN_DIR_SYSTEM_INODE, i);
		if (bytes < 1) {
			ret = OCFS2_ET_INTERNAL_FAILURE;
			goto out;
		}

		ret = ocfs2_lookup(ost->ost_fs, ost->ost_fs->fs_sysdir_blkno,
				   name, bytes, NULL, &ino);
		if (ret) {
			if (slot_recovery)
				goto out;

			if (ret != OCFS2_ET_FILE_NOT_FOUND)
				goto out;

			/* orphan dir is missing, it may be caused by an
			 * unsuccessful removing slots in tunefs.ocfs2.
			 * so create it.
			 */
	   		if (prompt(ost, PY, PR_ORPHAN_DIR_MISSING,
				   "%s is missing in system directory. "
				   "Create it?", name)) {
				ret = create_orphan_dir(ost, name);
				if (ret) {
					com_err(whoami, ret, "while creating"
						"orphan directory %s", name);
					continue;
				}
			}
		}

		ctxt.orphan_dir = ino;
		ost->ost_err = 0;
		ret = ocfs2_dir_iterate(ost->ost_fs, ino,
					OCFS2_DIRENT_FLAG_EXCLUDE_DOTS, NULL,
					replay_orphan_iterate, &ctxt);
		if (!ret)
			ret = ost->ost_err;
		if (ret && slot_recovery)
			break;
	}

out:
	return ret;
}

/* return the next inode that has either directory entries pointing to it or
 * that was valid and had a non-zero i_links_count.  OCFS2_ET_BIT_NOT_FOUND
 * will be bubbled up from the next_blkno() calls when there is no such next
 * inode.  It is expected that sometimes these won't match.  If a directory
 * has been lost there can be inodes with i_links_count and no directory
 * entries at all.  If an inode was lost but the user chose not to erase
 * the directory entries then there may be references to inodes that
 * we never saw the i_links_count for */
static errcode_t next_inode_any_ref(o2fsck_state *ost, uint64_t start,
				    uint64_t *blkno_ret)
{
	errcode_t tmp, ret = OCFS2_ET_BIT_NOT_FOUND;
	uint64_t blkno;

	tmp = o2fsck_icount_next_blkno(ost->ost_icount_refs, start, &blkno);
	if (tmp == 0) {
		*blkno_ret = blkno;
		ret = 0;
	}

	tmp = o2fsck_icount_next_blkno(ost->ost_icount_in_inodes, start,
				       &blkno);
	/* use this if we didn't have one yet or this one's lesser */
	if (tmp == 0 && (ret != 0 || (blkno < *blkno_ret))) {
		ret = 0;
		*blkno_ret = blkno;
	}

	return ret;
}

errcode_t o2fsck_pass4(o2fsck_state *ost)
{
	struct ocfs2_dinode *di;
	char *buf = NULL;
	errcode_t ret;
	uint64_t blkno = 0, start;
	ocfs2_filesys *fs = ost->ost_fs;
	struct o2fsck_resource_track rt;

	printf("Pass 4a: Checking for orphaned inodes\n");

	o2fsck_init_resource_track(&rt, fs->fs_io);

	ret = replay_orphan_dir(ost, 0);
	if (ret) {
		com_err(whoami, ret, "while trying to replay the orphan "
			"directory");
		goto out;
	}

	o2fsck_compute_resource_track(&rt, fs->fs_io);
	o2fsck_print_resource_track("Pass 4a", ost, &rt, fs->fs_io);
	o2fsck_add_resource_track(&ost->ost_rt, &rt);

	printf("Pass 4b: Checking inodes link counts\n");

	o2fsck_init_resource_track(&rt, fs->fs_io);

	ret = ocfs2_malloc_block(ost->ost_fs->fs_io, &buf);
	if (ret) {
		com_err(whoami, ret, "while allocating space to read inodes");
		goto out;
	}

	di = (struct ocfs2_dinode *)buf;
	start = 0;

	while (next_inode_any_ref(ost, start, &blkno) == 0) {
		check_link_counts(ost, di, blkno);
		start = blkno + 1;
	}

	o2fsck_compute_resource_track(&rt, fs->fs_io);
	o2fsck_print_resource_track("Pass 4b", ost, &rt, fs->fs_io);
	o2fsck_add_resource_track(&ost->ost_rt, &rt);

out:
	if (buf)
		ocfs2_free(&buf);

	return ret;
}