16
16
#include "xfs_quota.h"
17
17
#include "xfs_qm.h"
18
18
#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"
19
23
#include "scrub/scrub.h"
20
24
#include "scrub/common.h"
21
25
#include "scrub/trace.h"
22
26
#include "scrub/readdir.h"
27
+ #include "scrub/repair.h"
23
28
24
29
/*
25
30
* Metadata Directory Tree Paths
@@ -38,15 +43,28 @@ struct xchk_metapath {
38
43
/* Name for lookup */
39
44
struct xfs_name xname ;
40
45
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 */
42
50
const char * path ;
43
- const char * parent_path ;
44
51
45
52
/* Directory parent of the metadata file. */
46
53
struct xfs_inode * dp ;
47
54
48
55
/* Locks held on dp */
49
56
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 ;
50
68
};
51
69
52
70
/* Release resources tracked in the buffer. */
@@ -172,3 +190,332 @@ xchk_metapath(
172
190
xchk_trans_cancel (sc );
173
191
return error ;
174
192
}
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