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