1515#include "xfs_icache.h"
1616#include "xfs_dir2.h"
1717#include "xfs_dir2_priv.h"
18+ #include "xfs_attr.h"
19+ #include "xfs_parent.h"
1820#include "scrub/scrub.h"
1921#include "scrub/common.h"
2022#include "scrub/readdir.h"
2123#include "scrub/tempfile.h"
2224#include "scrub/repair.h"
25+ #include "scrub/listxattr.h"
26+ #include "scrub/trace.h"
2327
2428/* Set us up to scrub parents. */
2529int
@@ -197,6 +201,370 @@ xchk_parent_validate(
197201 return error ;
198202}
199203
204+ /*
205+ * Checking of Parent Pointers
206+ * ===========================
207+ *
208+ * On filesystems with directory parent pointers, we check the referential
209+ * integrity by visiting each parent pointer of a child file and checking that
210+ * the directory referenced by the pointer actually has a dirent pointing
211+ * forward to the child file.
212+ */
213+
214+ struct xchk_pptrs {
215+ struct xfs_scrub * sc ;
216+
217+ /* How many parent pointers did we find at the end? */
218+ unsigned long long pptrs_found ;
219+
220+ /* Parent of this directory. */
221+ xfs_ino_t parent_ino ;
222+ };
223+
224+ /* Does this parent pointer match the dotdot entry? */
225+ STATIC int
226+ xchk_parent_scan_dotdot (
227+ struct xfs_scrub * sc ,
228+ struct xfs_inode * ip ,
229+ unsigned int attr_flags ,
230+ const unsigned char * name ,
231+ unsigned int namelen ,
232+ const void * value ,
233+ unsigned int valuelen ,
234+ void * priv )
235+ {
236+ struct xchk_pptrs * pp = priv ;
237+ xfs_ino_t parent_ino ;
238+ int error ;
239+
240+ if (!(attr_flags & XFS_ATTR_PARENT ))
241+ return 0 ;
242+
243+ error = xfs_parent_from_attr (sc -> mp , attr_flags , name , namelen , value ,
244+ valuelen , & parent_ino , NULL );
245+ if (error )
246+ return error ;
247+
248+ if (pp -> parent_ino == parent_ino )
249+ return - ECANCELED ;
250+
251+ return 0 ;
252+ }
253+
254+ /* Look up the dotdot entry so that we can check it as we walk the pptrs. */
255+ STATIC int
256+ xchk_parent_pptr_and_dotdot (
257+ struct xchk_pptrs * pp )
258+ {
259+ struct xfs_scrub * sc = pp -> sc ;
260+ int error ;
261+
262+ /* Look up '..' */
263+ error = xchk_dir_lookup (sc , sc -> ip , & xfs_name_dotdot , & pp -> parent_ino );
264+ if (!xchk_fblock_process_error (sc , XFS_DATA_FORK , 0 , & error ))
265+ return error ;
266+ if (!xfs_verify_dir_ino (sc -> mp , pp -> parent_ino )) {
267+ xchk_fblock_set_corrupt (sc , XFS_DATA_FORK , 0 );
268+ return 0 ;
269+ }
270+
271+ /* Is this the root dir? Then '..' must point to itself. */
272+ if (sc -> ip == sc -> mp -> m_rootip ) {
273+ if (sc -> ip -> i_ino != pp -> parent_ino )
274+ xchk_fblock_set_corrupt (sc , XFS_DATA_FORK , 0 );
275+ return 0 ;
276+ }
277+
278+ /*
279+ * If this is now an unlinked directory, the dotdot value is
280+ * meaningless as long as it points to a valid inode.
281+ */
282+ if (VFS_I (sc -> ip )-> i_nlink == 0 )
283+ return 0 ;
284+
285+ if (pp -> sc -> sm -> sm_flags & XFS_SCRUB_OFLAG_CORRUPT )
286+ return 0 ;
287+
288+ /* Otherwise, walk the pptrs again, and check. */
289+ error = xchk_xattr_walk (sc , sc -> ip , xchk_parent_scan_dotdot , pp );
290+ if (error == - ECANCELED ) {
291+ /* Found a parent pointer that matches dotdot. */
292+ return 0 ;
293+ }
294+ if (!error || error == - EFSCORRUPTED ) {
295+ /* Found a broken parent pointer or no match. */
296+ xchk_fblock_set_corrupt (sc , XFS_ATTR_FORK , 0 );
297+ return 0 ;
298+ }
299+ return error ;
300+ }
301+
302+ /*
303+ * Try to lock a parent directory for checking dirents. Returns the inode
304+ * flags for the locks we now hold, or zero if we failed.
305+ */
306+ STATIC unsigned int
307+ xchk_parent_lock_dir (
308+ struct xfs_scrub * sc ,
309+ struct xfs_inode * dp )
310+ {
311+ if (!xfs_ilock_nowait (dp , XFS_IOLOCK_SHARED ))
312+ return 0 ;
313+
314+ if (!xfs_ilock_nowait (dp , XFS_ILOCK_SHARED )) {
315+ xfs_iunlock (dp , XFS_IOLOCK_SHARED );
316+ return 0 ;
317+ }
318+
319+ if (!xfs_need_iread_extents (& dp -> i_df ))
320+ return XFS_IOLOCK_SHARED | XFS_ILOCK_SHARED ;
321+
322+ xfs_iunlock (dp , XFS_ILOCK_SHARED );
323+
324+ if (!xfs_ilock_nowait (dp , XFS_ILOCK_EXCL )) {
325+ xfs_iunlock (dp , XFS_IOLOCK_SHARED );
326+ return 0 ;
327+ }
328+
329+ return XFS_IOLOCK_SHARED | XFS_ILOCK_EXCL ;
330+ }
331+
332+ /* Check the forward link (dirent) associated with this parent pointer. */
333+ STATIC int
334+ xchk_parent_dirent (
335+ struct xchk_pptrs * pp ,
336+ const struct xfs_name * xname ,
337+ struct xfs_inode * dp )
338+ {
339+ struct xfs_scrub * sc = pp -> sc ;
340+ xfs_ino_t child_ino ;
341+ int error ;
342+
343+ /*
344+ * Use the name attached to this parent pointer to look up the
345+ * directory entry in the alleged parent.
346+ */
347+ error = xchk_dir_lookup (sc , dp , xname , & child_ino );
348+ if (error == - ENOENT ) {
349+ xchk_fblock_xref_set_corrupt (sc , XFS_ATTR_FORK , 0 );
350+ return 0 ;
351+ }
352+ if (!xchk_fblock_xref_process_error (sc , XFS_ATTR_FORK , 0 , & error ))
353+ return error ;
354+
355+ /* Does the inode number match? */
356+ if (child_ino != sc -> ip -> i_ino ) {
357+ xchk_fblock_xref_set_corrupt (sc , XFS_ATTR_FORK , 0 );
358+ return 0 ;
359+ }
360+
361+ return 0 ;
362+ }
363+
364+ /* Try to grab a parent directory. */
365+ STATIC int
366+ xchk_parent_iget (
367+ struct xchk_pptrs * pp ,
368+ const struct xfs_parent_rec * pptr ,
369+ struct xfs_inode * * dpp )
370+ {
371+ struct xfs_scrub * sc = pp -> sc ;
372+ struct xfs_inode * ip ;
373+ xfs_ino_t parent_ino = be64_to_cpu (pptr -> p_ino );
374+ int error ;
375+
376+ /* Validate inode number. */
377+ error = xfs_dir_ino_validate (sc -> mp , parent_ino );
378+ if (error ) {
379+ xchk_fblock_set_corrupt (sc , XFS_ATTR_FORK , 0 );
380+ return - ECANCELED ;
381+ }
382+
383+ error = xchk_iget (sc , parent_ino , & ip );
384+ if (error == - EINVAL || error == - ENOENT ) {
385+ xchk_fblock_set_corrupt (sc , XFS_ATTR_FORK , 0 );
386+ return - ECANCELED ;
387+ }
388+ if (!xchk_fblock_xref_process_error (sc , XFS_ATTR_FORK , 0 , & error ))
389+ return error ;
390+
391+ /* The parent must be a directory. */
392+ if (!S_ISDIR (VFS_I (ip )-> i_mode )) {
393+ xchk_fblock_xref_set_corrupt (sc , XFS_ATTR_FORK , 0 );
394+ goto out_rele ;
395+ }
396+
397+ /* Validate generation number. */
398+ if (VFS_I (ip )-> i_generation != be32_to_cpu (pptr -> p_gen )) {
399+ xchk_fblock_xref_set_corrupt (sc , XFS_ATTR_FORK , 0 );
400+ goto out_rele ;
401+ }
402+
403+ * dpp = ip ;
404+ return 0 ;
405+ out_rele :
406+ xchk_irele (sc , ip );
407+ return 0 ;
408+ }
409+
410+ /*
411+ * Walk an xattr of a file. If this xattr is a parent pointer, follow it up
412+ * to a parent directory and check that the parent has a dirent pointing back
413+ * to us.
414+ */
415+ STATIC int
416+ xchk_parent_scan_attr (
417+ struct xfs_scrub * sc ,
418+ struct xfs_inode * ip ,
419+ unsigned int attr_flags ,
420+ const unsigned char * name ,
421+ unsigned int namelen ,
422+ const void * value ,
423+ unsigned int valuelen ,
424+ void * priv )
425+ {
426+ struct xfs_name xname = {
427+ .name = name ,
428+ .len = namelen ,
429+ };
430+ struct xchk_pptrs * pp = priv ;
431+ struct xfs_inode * dp = NULL ;
432+ const struct xfs_parent_rec * pptr_rec = value ;
433+ xfs_ino_t parent_ino ;
434+ unsigned int lockmode ;
435+ int error ;
436+
437+ if (!(attr_flags & XFS_ATTR_PARENT ))
438+ return 0 ;
439+
440+ error = xfs_parent_from_attr (sc -> mp , attr_flags , name , namelen , value ,
441+ valuelen , & parent_ino , NULL );
442+ if (error ) {
443+ xchk_fblock_set_corrupt (sc , XFS_ATTR_FORK , 0 );
444+ return error ;
445+ }
446+
447+ /* No self-referential parent pointers. */
448+ if (parent_ino == sc -> ip -> i_ino ) {
449+ xchk_fblock_set_corrupt (sc , XFS_ATTR_FORK , 0 );
450+ return - ECANCELED ;
451+ }
452+
453+ pp -> pptrs_found ++ ;
454+
455+ error = xchk_parent_iget (pp , pptr_rec , & dp );
456+ if (error )
457+ return error ;
458+ if (!dp )
459+ return 0 ;
460+
461+ /* Try to lock the inode. */
462+ lockmode = xchk_parent_lock_dir (sc , dp );
463+ if (!lockmode ) {
464+ xchk_set_incomplete (sc );
465+ error = - ECANCELED ;
466+ goto out_rele ;
467+ }
468+
469+ error = xchk_parent_dirent (pp , & xname , dp );
470+ if (error )
471+ goto out_unlock ;
472+
473+ out_unlock :
474+ xfs_iunlock (dp , lockmode );
475+ out_rele :
476+ xchk_irele (sc , dp );
477+ return error ;
478+ }
479+
480+ /*
481+ * Compare the number of parent pointers to the link count. For
482+ * non-directories these should be the same. For unlinked directories the
483+ * count should be zero; for linked directories, it should be nonzero.
484+ */
485+ STATIC int
486+ xchk_parent_count_pptrs (
487+ struct xchk_pptrs * pp )
488+ {
489+ struct xfs_scrub * sc = pp -> sc ;
490+
491+ if (S_ISDIR (VFS_I (sc -> ip )-> i_mode )) {
492+ if (sc -> ip == sc -> mp -> m_rootip )
493+ pp -> pptrs_found ++ ;
494+
495+ if (VFS_I (sc -> ip )-> i_nlink == 0 && pp -> pptrs_found > 0 )
496+ xchk_ino_set_corrupt (sc , sc -> ip -> i_ino );
497+ else if (VFS_I (sc -> ip )-> i_nlink > 0 &&
498+ pp -> pptrs_found == 0 )
499+ xchk_ino_set_corrupt (sc , sc -> ip -> i_ino );
500+ } else {
501+ if (VFS_I (sc -> ip )-> i_nlink != pp -> pptrs_found )
502+ xchk_ino_set_corrupt (sc , sc -> ip -> i_ino );
503+ }
504+
505+ return 0 ;
506+ }
507+
508+ /* Check parent pointers of a file. */
509+ STATIC int
510+ xchk_parent_pptr (
511+ struct xfs_scrub * sc )
512+ {
513+ struct xchk_pptrs * pp ;
514+ int error ;
515+
516+ pp = kvzalloc (sizeof (struct xchk_pptrs ), XCHK_GFP_FLAGS );
517+ if (!pp )
518+ return - ENOMEM ;
519+ pp -> sc = sc ;
520+
521+ error = xchk_xattr_walk (sc , sc -> ip , xchk_parent_scan_attr , pp );
522+ if (error == - ECANCELED ) {
523+ error = 0 ;
524+ goto out_pp ;
525+ }
526+ if (error )
527+ goto out_pp ;
528+
529+ if (pp -> sc -> sm -> sm_flags & XFS_SCRUB_OFLAG_CORRUPT )
530+ goto out_pp ;
531+
532+ /*
533+ * For subdirectories, make sure the dotdot entry references the same
534+ * inode as the parent pointers.
535+ *
536+ * If we're scanning a /consistent/ directory, there should only be
537+ * one parent pointer, and it should point to the same directory as
538+ * the dotdot entry.
539+ *
540+ * However, a corrupt directory tree might feature a subdirectory with
541+ * multiple parents. The directory loop scanner is responsible for
542+ * correcting that kind of problem, so for now we only validate that
543+ * the dotdot entry matches /one/ of the parents.
544+ */
545+ if (S_ISDIR (VFS_I (sc -> ip )-> i_mode )) {
546+ error = xchk_parent_pptr_and_dotdot (pp );
547+ if (error )
548+ goto out_pp ;
549+ }
550+
551+ if (pp -> sc -> sm -> sm_flags & XFS_SCRUB_OFLAG_CORRUPT )
552+ goto out_pp ;
553+
554+ /*
555+ * Complain if the number of parent pointers doesn't match the link
556+ * count. This could be a sign of missing parent pointers (or an
557+ * incorrect link count).
558+ */
559+ error = xchk_parent_count_pptrs (pp );
560+ if (error )
561+ goto out_pp ;
562+
563+ out_pp :
564+ kvfree (pp );
565+ return error ;
566+ }
567+
200568/* Scrub a parent pointer. */
201569int
202570xchk_parent (
@@ -206,6 +574,9 @@ xchk_parent(
206574 xfs_ino_t parent_ino ;
207575 int error = 0 ;
208576
577+ if (xfs_has_parent (mp ))
578+ return xchk_parent_pptr (sc );
579+
209580 /*
210581 * If we're a directory, check that the '..' link points up to
211582 * a directory that has one entry pointing to us.
0 commit comments