@@ -471,8 +471,144 @@ static int change_csum_objectids(struct btrfs_fs_info *fs_info)
471471 return ret ;
472472}
473473
474+ static int rewrite_tree_block_csum (struct btrfs_fs_info * fs_info , u64 logical ,
475+ u16 new_csum_type )
476+ {
477+ struct extent_buffer * eb ;
478+ u8 result_old [BTRFS_CSUM_SIZE ];
479+ u8 result_new [BTRFS_CSUM_SIZE ];
480+ int ret ;
481+
482+ eb = alloc_dummy_extent_buffer (fs_info , logical , fs_info -> nodesize );
483+ if (!eb )
484+ return - ENOMEM ;
485+
486+ ret = btrfs_read_extent_buffer (eb , 0 , 0 , NULL );
487+ if (ret < 0 ) {
488+ errno = - ret ;
489+ error ("failed to read tree block at logical %llu: %m" , logical );
490+ goto out ;
491+ }
492+
493+ /* Verify the csum first. */
494+ btrfs_csum_data (fs_info , fs_info -> csum_type , (u8 * )eb -> data + BTRFS_CSUM_SIZE ,
495+ result_old , fs_info -> nodesize - BTRFS_CSUM_SIZE );
496+ btrfs_csum_data (fs_info , new_csum_type , (u8 * )eb -> data + BTRFS_CSUM_SIZE ,
497+ result_new , fs_info -> nodesize - BTRFS_CSUM_SIZE );
498+
499+ /* Matches old csum, rewrite. */
500+ if (memcmp_extent_buffer (eb , result_old , 0 , fs_info -> csum_size ) == 0 ) {
501+ write_extent_buffer (eb , result_new , 0 ,
502+ btrfs_csum_type_size (new_csum_type ));
503+ ret = write_data_to_disk (fs_info , eb -> data , eb -> start ,
504+ fs_info -> nodesize );
505+ if (ret < 0 ) {
506+ errno = - ret ;
507+ error ("failed to write tree block at logical %llu: %m" ,
508+ logical );
509+ }
510+ goto out ;
511+ }
512+
513+ /* Already new csum. */
514+ if (memcmp_extent_buffer (eb , result_new , 0 , fs_info -> csum_size ) == 0 )
515+ goto out ;
516+
517+ /* Csum doesn't match either old or new csum type, bad tree block. */
518+ ret = - EIO ;
519+ error ("tree block csum mismatch at logical %llu" , logical );
520+ out :
521+ free_extent_buffer (eb );
522+ return ret ;
523+ }
524+
525+ static int change_meta_csums (struct btrfs_fs_info * fs_info , u32 new_csum_type )
526+ {
527+ struct btrfs_root * extent_root = btrfs_extent_root (fs_info , 0 );
528+ struct btrfs_path path = { 0 };
529+ struct btrfs_key key ;
530+ int ret ;
531+
532+ /*
533+ * Disable metadata csum checks first, as we may hit tree blocks with
534+ * either old or new csums.
535+ * We will manually check the meta csums here.
536+ */
537+ fs_info -> skip_csum_check = true;
538+
539+ key .objectid = 0 ;
540+ key .type = 0 ;
541+ key .offset = 0 ;
542+
543+ ret = btrfs_search_slot (NULL , extent_root , & key , & path , 0 , 0 );
544+ if (ret < 0 ) {
545+ errno = - ret ;
546+ error ("failed to get the first tree block of extent tree: %m" );
547+ return ret ;
548+ }
549+ assert (ret > 0 );
550+ while (true) {
551+ btrfs_item_key_to_cpu (path .nodes [0 ], & key , path .slots [0 ]);
552+ if (key .type != BTRFS_EXTENT_ITEM_KEY &&
553+ key .type != BTRFS_METADATA_ITEM_KEY )
554+ goto next ;
555+
556+ if (key .type == BTRFS_EXTENT_ITEM_KEY ) {
557+ struct btrfs_extent_item * ei ;
558+ ei = btrfs_item_ptr (path .nodes [0 ], path .slots [0 ],
559+ struct btrfs_extent_item );
560+ if (btrfs_extent_flags (path .nodes [0 ], ei ) &
561+ BTRFS_EXTENT_FLAG_DATA )
562+ goto next ;
563+ }
564+ ret = rewrite_tree_block_csum (fs_info , key .objectid , new_csum_type );
565+ if (ret < 0 ) {
566+ errno = - ret ;
567+ error ("failed to rewrite csum for tree block %llu: %m" ,
568+ key .offset );
569+ goto out ;
570+ }
571+ next :
572+ ret = btrfs_next_extent_item (extent_root , & path , U64_MAX );
573+ if (ret < 0 ) {
574+ errno = - ret ;
575+ error ("failed to get next extent item: %m" );
576+ }
577+ if (ret > 0 ) {
578+ ret = 0 ;
579+ goto out ;
580+ }
581+ }
582+ out :
583+ btrfs_release_path (& path );
584+
585+ /*
586+ * Finish the change by clearing the csum change flag and update the superblock
587+ * csum type.
588+ */
589+ if (ret == 0 ) {
590+ u64 super_flags = btrfs_super_flags (fs_info -> super_copy );
591+
592+ btrfs_set_super_csum_type (fs_info -> super_copy , new_csum_type );
593+ super_flags &= ~(BTRFS_SUPER_FLAG_CHANGING_DATA_CSUM |
594+ BTRFS_SUPER_FLAG_CHANGING_META_CSUM );
595+ btrfs_set_super_flags (fs_info -> super_copy , super_flags );
596+
597+ fs_info -> csum_type = new_csum_type ;
598+ fs_info -> csum_size = btrfs_csum_type_size (new_csum_type );
599+
600+ ret = write_all_supers (fs_info );
601+ if (ret < 0 ) {
602+ errno = - ret ;
603+ error ("failed to write super blocks: %m" );
604+ }
605+ }
606+ return ret ;
607+ }
608+
474609int btrfs_change_csum_type (struct btrfs_fs_info * fs_info , u16 new_csum_type )
475610{
611+ u16 old_csum_type = fs_info -> csum_type ;
476612 int ret ;
477613
478614 /* Phase 0, check conflicting features. */
@@ -511,5 +647,10 @@ int btrfs_change_csum_type(struct btrfs_fs_info *fs_info, u16 new_csum_type)
511647 * like relocation in progs.
512648 * Thus we have to support reading a tree block with either csum.
513649 */
514- return - EOPNOTSUPP ;
650+ ret = change_meta_csums (fs_info , new_csum_type );
651+ if (ret == 0 )
652+ printf ("converted csum type from %s (%u) to %s (%u)\n" ,
653+ btrfs_super_csum_name (old_csum_type ), old_csum_type ,
654+ btrfs_super_csum_name (new_csum_type ), new_csum_type );
655+ return ret ;
515656}
0 commit comments