Skip to content

Commit 0d2c636

Browse files
author
Darrick J. Wong
committed
xfs: repair metadata directory file path connectivity
Fix disconnected or incorrect metadata directory paths. Signed-off-by: Darrick J. Wong <[email protected]> Reviewed-by: Christoph Hellwig <[email protected]>
1 parent 87b7c20 commit 0d2c636

File tree

4 files changed

+358
-3
lines changed

4 files changed

+358
-3
lines changed

fs/xfs/scrub/metapath.c

Lines changed: 349 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,15 @@
1616
#include "xfs_quota.h"
1717
#include "xfs_qm.h"
1818
#include "xfs_dir2.h"
19+
#include "xfs_parent.h"
20+
#include "xfs_bmap_btree.h"
21+
#include "xfs_trans_space.h"
22+
#include "xfs_attr.h"
1923
#include "scrub/scrub.h"
2024
#include "scrub/common.h"
2125
#include "scrub/trace.h"
2226
#include "scrub/readdir.h"
27+
#include "scrub/repair.h"
2328

2429
/*
2530
* Metadata Directory Tree Paths
@@ -38,15 +43,28 @@ struct xchk_metapath {
3843
/* Name for lookup */
3944
struct xfs_name xname;
4045

41-
/* Path for this metadata file and the parent directory */
46+
/* Directory update for repairs */
47+
struct xfs_dir_update du;
48+
49+
/* Path down to this metadata file from the parent directory */
4250
const char *path;
43-
const char *parent_path;
4451

4552
/* Directory parent of the metadata file. */
4653
struct xfs_inode *dp;
4754

4855
/* Locks held on dp */
4956
unsigned int dp_ilock_flags;
57+
58+
/* Transaction block reservations */
59+
unsigned int link_resblks;
60+
unsigned int unlink_resblks;
61+
62+
/* Parent pointer updates */
63+
struct xfs_parent_args link_ppargs;
64+
struct xfs_parent_args unlink_ppargs;
65+
66+
/* Scratchpads for removing links */
67+
struct xfs_da_args pptr_args;
5068
};
5169

5270
/* Release resources tracked in the buffer. */
@@ -172,3 +190,332 @@ xchk_metapath(
172190
xchk_trans_cancel(sc);
173191
return error;
174192
}
193+
194+
#ifdef CONFIG_XFS_ONLINE_REPAIR
195+
/* Create the dirent represented by the final component of the path. */
196+
STATIC int
197+
xrep_metapath_link(
198+
struct xchk_metapath *mpath)
199+
{
200+
struct xfs_scrub *sc = mpath->sc;
201+
202+
mpath->du.dp = mpath->dp;
203+
mpath->du.name = &mpath->xname;
204+
mpath->du.ip = sc->ip;
205+
206+
if (xfs_has_parent(sc->mp))
207+
mpath->du.ppargs = &mpath->link_ppargs;
208+
else
209+
mpath->du.ppargs = NULL;
210+
211+
trace_xrep_metapath_link(sc, mpath->path, mpath->dp, sc->ip->i_ino);
212+
213+
return xfs_dir_add_child(sc->tp, mpath->link_resblks, &mpath->du);
214+
}
215+
216+
/* Remove the dirent at the final component of the path. */
217+
STATIC int
218+
xrep_metapath_unlink(
219+
struct xchk_metapath *mpath,
220+
xfs_ino_t ino,
221+
struct xfs_inode *ip)
222+
{
223+
struct xfs_parent_rec rec;
224+
struct xfs_scrub *sc = mpath->sc;
225+
struct xfs_mount *mp = sc->mp;
226+
int error;
227+
228+
trace_xrep_metapath_unlink(sc, mpath->path, mpath->dp, ino);
229+
230+
if (!ip) {
231+
/* The child inode isn't allocated. Junk the dirent. */
232+
xfs_trans_log_inode(sc->tp, mpath->dp, XFS_ILOG_CORE);
233+
return xfs_dir_removename(sc->tp, mpath->dp, &mpath->xname,
234+
ino, mpath->unlink_resblks);
235+
}
236+
237+
mpath->du.dp = mpath->dp;
238+
mpath->du.name = &mpath->xname;
239+
mpath->du.ip = ip;
240+
mpath->du.ppargs = NULL;
241+
242+
/* Figure out if we're removing a parent pointer too. */
243+
if (xfs_has_parent(mp)) {
244+
xfs_inode_to_parent_rec(&rec, ip);
245+
error = xfs_parent_lookup(sc->tp, ip, &mpath->xname, &rec,
246+
&mpath->pptr_args);
247+
switch (error) {
248+
case -ENOATTR:
249+
break;
250+
case 0:
251+
mpath->du.ppargs = &mpath->unlink_ppargs;
252+
break;
253+
default:
254+
return error;
255+
}
256+
}
257+
258+
return xfs_dir_remove_child(sc->tp, mpath->unlink_resblks, &mpath->du);
259+
}
260+
261+
/*
262+
* Try to create a dirent in @mpath->dp with the name @mpath->xname that points
263+
* to @sc->ip. Returns:
264+
*
265+
* -EEXIST and an @alleged_child if the dirent that points to the wrong inode;
266+
* 0 if there is now a dirent pointing to @sc->ip; or
267+
* A negative errno on error.
268+
*/
269+
STATIC int
270+
xrep_metapath_try_link(
271+
struct xchk_metapath *mpath,
272+
xfs_ino_t *alleged_child)
273+
{
274+
struct xfs_scrub *sc = mpath->sc;
275+
xfs_ino_t ino;
276+
int error;
277+
278+
/* Allocate transaction, lock inodes, join to transaction. */
279+
error = xchk_trans_alloc(sc, mpath->link_resblks);
280+
if (error)
281+
return error;
282+
283+
error = xchk_metapath_ilock_both(mpath);
284+
if (error) {
285+
xchk_trans_cancel(sc);
286+
return error;
287+
}
288+
xfs_trans_ijoin(sc->tp, mpath->dp, 0);
289+
xfs_trans_ijoin(sc->tp, sc->ip, 0);
290+
291+
error = xchk_dir_lookup(sc, mpath->dp, &mpath->xname, &ino);
292+
trace_xrep_metapath_lookup(sc, mpath->path, mpath->dp, ino);
293+
if (error == -ENOENT) {
294+
/*
295+
* There is no dirent in the directory. Create an entry
296+
* pointing to @sc->ip.
297+
*/
298+
error = xrep_metapath_link(mpath);
299+
if (error)
300+
goto out_cancel;
301+
302+
error = xrep_trans_commit(sc);
303+
xchk_metapath_iunlock(mpath);
304+
return error;
305+
}
306+
if (error)
307+
goto out_cancel;
308+
309+
if (ino == sc->ip->i_ino) {
310+
/* The dirent already points to @sc->ip; we're done. */
311+
error = 0;
312+
goto out_cancel;
313+
}
314+
315+
/*
316+
* The dirent points elsewhere; pass that back so that the caller
317+
* can try to remove the dirent.
318+
*/
319+
*alleged_child = ino;
320+
error = -EEXIST;
321+
322+
out_cancel:
323+
xchk_trans_cancel(sc);
324+
xchk_metapath_iunlock(mpath);
325+
return error;
326+
}
327+
328+
/*
329+
* Take the ILOCK on the metadata directory parent and a bad child, if one is
330+
* supplied. We do not know that the metadata directory is not corrupt, so we
331+
* lock the parent and try to lock the child. Returns 0 if successful, or
332+
* -EINTR to abort the repair. The lock state of @dp is not recorded in @mpath.
333+
*/
334+
STATIC int
335+
xchk_metapath_ilock_parent_and_child(
336+
struct xchk_metapath *mpath,
337+
struct xfs_inode *ip)
338+
{
339+
struct xfs_scrub *sc = mpath->sc;
340+
int error = 0;
341+
342+
while (true) {
343+
xfs_ilock(mpath->dp, XFS_ILOCK_EXCL);
344+
if (!ip || xfs_ilock_nowait(ip, XFS_ILOCK_EXCL))
345+
return 0;
346+
xfs_iunlock(mpath->dp, XFS_ILOCK_EXCL);
347+
348+
if (xchk_should_terminate(sc, &error))
349+
return error;
350+
351+
delay(1);
352+
}
353+
354+
ASSERT(0);
355+
return -EINTR;
356+
}
357+
358+
/*
359+
* Try to remove a dirent in @mpath->dp with the name @mpath->xname that points
360+
* to @alleged_child. Returns:
361+
*
362+
* 0 if there is no longer a dirent;
363+
* -EEXIST if the dirent points to @sc->ip;
364+
* -EAGAIN and an updated @alleged_child if the dirent points elsewhere; or
365+
* A negative errno for any other error.
366+
*/
367+
STATIC int
368+
xrep_metapath_try_unlink(
369+
struct xchk_metapath *mpath,
370+
xfs_ino_t *alleged_child)
371+
{
372+
struct xfs_scrub *sc = mpath->sc;
373+
struct xfs_inode *ip = NULL;
374+
xfs_ino_t ino;
375+
int error;
376+
377+
ASSERT(*alleged_child != sc->ip->i_ino);
378+
379+
trace_xrep_metapath_try_unlink(sc, mpath->path, mpath->dp,
380+
*alleged_child);
381+
382+
/*
383+
* Allocate transaction, grab the alleged child inode, lock inodes,
384+
* join to transaction.
385+
*/
386+
error = xchk_trans_alloc(sc, mpath->unlink_resblks);
387+
if (error)
388+
return error;
389+
390+
error = xchk_iget(sc, *alleged_child, &ip);
391+
if (error == -EINVAL || error == -ENOENT) {
392+
/* inode number is bogus, junk the dirent */
393+
error = 0;
394+
}
395+
if (error) {
396+
xchk_trans_cancel(sc);
397+
return error;
398+
}
399+
400+
error = xchk_metapath_ilock_parent_and_child(mpath, ip);
401+
if (error) {
402+
xchk_trans_cancel(sc);
403+
return error;
404+
}
405+
xfs_trans_ijoin(sc->tp, mpath->dp, 0);
406+
if (ip)
407+
xfs_trans_ijoin(sc->tp, ip, 0);
408+
409+
error = xchk_dir_lookup(sc, mpath->dp, &mpath->xname, &ino);
410+
trace_xrep_metapath_lookup(sc, mpath->path, mpath->dp, ino);
411+
if (error == -ENOENT) {
412+
/*
413+
* There is no dirent in the directory anymore. We're ready to
414+
* try the link operation again.
415+
*/
416+
error = 0;
417+
goto out_cancel;
418+
}
419+
if (error)
420+
goto out_cancel;
421+
422+
if (ino == sc->ip->i_ino) {
423+
/* The dirent already points to @sc->ip; we're done. */
424+
error = -EEXIST;
425+
goto out_cancel;
426+
}
427+
428+
/*
429+
* The dirent does not point to the alleged child. Update the caller
430+
* and signal that we want to be called again.
431+
*/
432+
if (ino != *alleged_child) {
433+
*alleged_child = ino;
434+
error = -EAGAIN;
435+
goto out_cancel;
436+
}
437+
438+
/* Remove the link to the child. */
439+
error = xrep_metapath_unlink(mpath, ino, ip);
440+
if (error)
441+
goto out_cancel;
442+
443+
error = xrep_trans_commit(sc);
444+
goto out_unlock;
445+
446+
out_cancel:
447+
xchk_trans_cancel(sc);
448+
out_unlock:
449+
xfs_iunlock(mpath->dp, XFS_ILOCK_EXCL);
450+
if (ip) {
451+
xfs_iunlock(ip, XFS_ILOCK_EXCL);
452+
xchk_irele(sc, ip);
453+
}
454+
return error;
455+
}
456+
457+
/*
458+
* Make sure the metadata directory path points to the child being examined.
459+
*
460+
* Repair needs to be able to create a directory structure, create its own
461+
* transactions, and take ILOCKs. This function /must/ be called after all
462+
* other repairs have completed.
463+
*/
464+
int
465+
xrep_metapath(
466+
struct xfs_scrub *sc)
467+
{
468+
struct xchk_metapath *mpath = sc->buf;
469+
struct xfs_mount *mp = sc->mp;
470+
int error = 0;
471+
472+
/* Just probing, nothing to repair. */
473+
if (sc->sm->sm_ino == XFS_SCRUB_METAPATH_PROBE)
474+
return 0;
475+
476+
/* Parent required to do anything else. */
477+
if (mpath->dp == NULL)
478+
return -EFSCORRUPTED;
479+
480+
/*
481+
* Make sure the child file actually has an attr fork to receive a new
482+
* parent pointer if the fs has parent pointers.
483+
*/
484+
if (xfs_has_parent(mp)) {
485+
error = xfs_attr_add_fork(sc->ip,
486+
sizeof(struct xfs_attr_sf_hdr), 1);
487+
if (error)
488+
return error;
489+
}
490+
491+
/* Compute block reservation required to unlink and link a file. */
492+
mpath->unlink_resblks = xfs_remove_space_res(mp, MAXNAMELEN);
493+
mpath->link_resblks = xfs_link_space_res(mp, MAXNAMELEN);
494+
495+
do {
496+
xfs_ino_t alleged_child;
497+
498+
/* Re-establish the link, or tell us which inode to remove. */
499+
error = xrep_metapath_try_link(mpath, &alleged_child);
500+
if (!error)
501+
return 0;
502+
if (error != -EEXIST)
503+
return error;
504+
505+
/*
506+
* Remove an incorrect link to an alleged child, or tell us
507+
* which inode to remove.
508+
*/
509+
do {
510+
error = xrep_metapath_try_unlink(mpath, &alleged_child);
511+
} while (error == -EAGAIN);
512+
if (error == -EEXIST) {
513+
/* Link established; we're done. */
514+
error = 0;
515+
break;
516+
}
517+
} while (!error);
518+
519+
return error;
520+
}
521+
#endif /* CONFIG_XFS_ONLINE_REPAIR */

0 commit comments

Comments
 (0)