Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  volumes.c   Sprache: C

 
// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (C) 2007 Oracle.  All rights reserved.
 */


#include <linux/sched.h>
#include <linux/sched/mm.h>
#include <linux/slab.h>
#include <linux/ratelimit.h>
#include <linux/kthread.h>
#include <linux/semaphore.h>
#include <linux/uuid.h>
#include <linux/list_sort.h>
#include <linux/namei.h>
#include "misc.h"
#include "disk-io.h"
#include "extent-tree.h"
#include "transaction.h"
#include "volumes.h"
#include "raid56.h"
#include "dev-replace.h"
#include "sysfs.h"
#include "tree-checker.h"
#include "space-info.h"
#include "block-group.h"
#include "discard.h"
#include "zoned.h"
#include "fs.h"
#include "accessors.h"
#include "uuid-tree.h"
#include "ioctl.h"
#include "relocation.h"
#include "scrub.h"
#include "super.h"
#include "raid-stripe-tree.h"

#define BTRFS_BLOCK_GROUP_STRIPE_MASK (BTRFS_BLOCK_GROUP_RAID0 | \
      BTRFS_BLOCK_GROUP_RAID10 | \
      BTRFS_BLOCK_GROUP_RAID56_MASK)

struct btrfs_io_geometry {
 u32 stripe_index;
 u32 stripe_nr;
 int mirror_num;
 int num_stripes;
 u64 stripe_offset;
 u64 raid56_full_stripe_start;
 int max_errors;
 enum btrfs_map_op op;
 bool use_rst;
};

const struct btrfs_raid_attr btrfs_raid_array[BTRFS_NR_RAID_TYPES] = {
 [BTRFS_RAID_RAID10] = {
  .sub_stripes = 2,
  .dev_stripes = 1,
  .devs_max = 0, /* 0 == as many as possible */
  .devs_min = 2,
  .tolerated_failures = 1,
  .devs_increment = 2,
  .ncopies = 2,
  .nparity        = 0,
  .raid_name = "raid10",
  .bg_flag = BTRFS_BLOCK_GROUP_RAID10,
  .mindev_error = BTRFS_ERROR_DEV_RAID10_MIN_NOT_MET,
 },
 [BTRFS_RAID_RAID1] = {
  .sub_stripes = 1,
  .dev_stripes = 1,
  .devs_max = 2,
  .devs_min = 2,
  .tolerated_failures = 1,
  .devs_increment = 2,
  .ncopies = 2,
  .nparity        = 0,
  .raid_name = "raid1",
  .bg_flag = BTRFS_BLOCK_GROUP_RAID1,
  .mindev_error = BTRFS_ERROR_DEV_RAID1_MIN_NOT_MET,
 },
 [BTRFS_RAID_RAID1C3] = {
  .sub_stripes = 1,
  .dev_stripes = 1,
  .devs_max = 3,
  .devs_min = 3,
  .tolerated_failures = 2,
  .devs_increment = 3,
  .ncopies = 3,
  .nparity        = 0,
  .raid_name = "raid1c3",
  .bg_flag = BTRFS_BLOCK_GROUP_RAID1C3,
  .mindev_error = BTRFS_ERROR_DEV_RAID1C3_MIN_NOT_MET,
 },
 [BTRFS_RAID_RAID1C4] = {
  .sub_stripes = 1,
  .dev_stripes = 1,
  .devs_max = 4,
  .devs_min = 4,
  .tolerated_failures = 3,
  .devs_increment = 4,
  .ncopies = 4,
  .nparity        = 0,
  .raid_name = "raid1c4",
  .bg_flag = BTRFS_BLOCK_GROUP_RAID1C4,
  .mindev_error = BTRFS_ERROR_DEV_RAID1C4_MIN_NOT_MET,
 },
 [BTRFS_RAID_DUP] = {
  .sub_stripes = 1,
  .dev_stripes = 2,
  .devs_max = 1,
  .devs_min = 1,
  .tolerated_failures = 0,
  .devs_increment = 1,
  .ncopies = 2,
  .nparity        = 0,
  .raid_name = "dup",
  .bg_flag = BTRFS_BLOCK_GROUP_DUP,
  .mindev_error = 0,
 },
 [BTRFS_RAID_RAID0] = {
  .sub_stripes = 1,
  .dev_stripes = 1,
  .devs_max = 0,
  .devs_min = 1,
  .tolerated_failures = 0,
  .devs_increment = 1,
  .ncopies = 1,
  .nparity        = 0,
  .raid_name = "raid0",
  .bg_flag = BTRFS_BLOCK_GROUP_RAID0,
  .mindev_error = 0,
 },
 [BTRFS_RAID_SINGLE] = {
  .sub_stripes = 1,
  .dev_stripes = 1,
  .devs_max = 1,
  .devs_min = 1,
  .tolerated_failures = 0,
  .devs_increment = 1,
  .ncopies = 1,
  .nparity        = 0,
  .raid_name = "single",
  .bg_flag = 0,
  .mindev_error = 0,
 },
 [BTRFS_RAID_RAID5] = {
  .sub_stripes = 1,
  .dev_stripes = 1,
  .devs_max = 0,
  .devs_min = 2,
  .tolerated_failures = 1,
  .devs_increment = 1,
  .ncopies = 1,
  .nparity        = 1,
  .raid_name = "raid5",
  .bg_flag = BTRFS_BLOCK_GROUP_RAID5,
  .mindev_error = BTRFS_ERROR_DEV_RAID5_MIN_NOT_MET,
 },
 [BTRFS_RAID_RAID6] = {
  .sub_stripes = 1,
  .dev_stripes = 1,
  .devs_max = 0,
  .devs_min = 3,
  .tolerated_failures = 2,
  .devs_increment = 1,
  .ncopies = 1,
  .nparity        = 2,
  .raid_name = "raid6",
  .bg_flag = BTRFS_BLOCK_GROUP_RAID6,
  .mindev_error = BTRFS_ERROR_DEV_RAID6_MIN_NOT_MET,
 },
};

/*
 * Convert block group flags (BTRFS_BLOCK_GROUP_*) to btrfs_raid_types, which
 * can be used as index to access btrfs_raid_array[].
 */

enum btrfs_raid_types __attribute_const__ btrfs_bg_flags_to_raid_index(u64 flags)
{
 const u64 profile = (flags & BTRFS_BLOCK_GROUP_PROFILE_MASK);

 if (!profile)
  return BTRFS_RAID_SINGLE;

 return BTRFS_BG_FLAG_TO_INDEX(profile);
}

const char *btrfs_bg_type_to_raid_name(u64 flags)
{
 const int index = btrfs_bg_flags_to_raid_index(flags);

 if (index >= BTRFS_NR_RAID_TYPES)
  return NULL;

 return btrfs_raid_array[index].raid_name;
}

int btrfs_nr_parity_stripes(u64 type)
{
 enum btrfs_raid_types index = btrfs_bg_flags_to_raid_index(type);

 return btrfs_raid_array[index].nparity;
}

/*
 * Fill @buf with textual description of @bg_flags, no more than @size_buf
 * bytes including terminating null byte.
 */

void btrfs_describe_block_groups(u64 bg_flags, char *buf, u32 size_buf)
{
 int i;
 int ret;
 char *bp = buf;
 u64 flags = bg_flags;
 u32 size_bp = size_buf;

 if (!flags)
  return;

#define DESCRIBE_FLAG(flag, desc)      \
 do {        \
  if (flags & (flag)) {     \
   ret = snprintf(bp, size_bp, "%s|", (desc)); \
   if (ret < 0 || ret >= size_bp)   \
    goto out_overflow;   \
   size_bp -= ret;     \
   bp += ret;     \
   flags &= ~(flag);    \
  }       \
 } while (0)

 DESCRIBE_FLAG(BTRFS_BLOCK_GROUP_DATA, "data");
 DESCRIBE_FLAG(BTRFS_BLOCK_GROUP_SYSTEM, "system");
 DESCRIBE_FLAG(BTRFS_BLOCK_GROUP_METADATA, "metadata");

 DESCRIBE_FLAG(BTRFS_AVAIL_ALLOC_BIT_SINGLE, "single");
 for (i = 0; i < BTRFS_NR_RAID_TYPES; i++)
  DESCRIBE_FLAG(btrfs_raid_array[i].bg_flag,
         btrfs_raid_array[i].raid_name);
#undef DESCRIBE_FLAG

 if (flags) {
  ret = snprintf(bp, size_bp, "0x%llx|", flags);
  size_bp -= ret;
 }

 if (size_bp < size_buf)
  buf[size_buf - size_bp - 1] = '\0'/* remove last | */

 /*
 * The text is trimmed, it's up to the caller to provide sufficiently
 * large buffer
 */

out_overflow:;
}

static int init_first_rw_device(struct btrfs_trans_handle *trans);
static int btrfs_relocate_sys_chunks(struct btrfs_fs_info *fs_info);
static void btrfs_dev_stat_print_on_load(struct btrfs_device *device);

/*
 * Device locking
 * ==============
 *
 * There are several mutexes that protect manipulation of devices and low-level
 * structures like chunks but not block groups, extents or files
 *
 * uuid_mutex (global lock)
 * ------------------------
 * protects the fs_uuids list that tracks all per-fs fs_devices, resulting from
 * the SCAN_DEV ioctl registration or from mount either implicitly (the first
 * device) or requested by the device= mount option
 *
 * the mutex can be very coarse and can cover long-running operations
 *
 * protects: updates to fs_devices counters like missing devices, rw devices,
 * seeding, structure cloning, opening/closing devices at mount/umount time
 *
 * global::fs_devs - add, remove, updates to the global list
 *
 * does not protect: manipulation of the fs_devices::devices list in general
 * but in mount context it could be used to exclude list modifications by eg.
 * scan ioctl
 *
 * btrfs_device::name - renames (write side), read is RCU
 *
 * fs_devices::device_list_mutex (per-fs, with RCU)
 * ------------------------------------------------
 * protects updates to fs_devices::devices, ie. adding and deleting
 *
 * simple list traversal with read-only actions can be done with RCU protection
 *
 * may be used to exclude some operations from running concurrently without any
 * modifications to the list (see write_all_supers)
 *
 * Is not required at mount and close times, because our device list is
 * protected by the uuid_mutex at that point.
 *
 * balance_mutex
 * -------------
 * protects balance structures (status, state) and context accessed from
 * several places (internally, ioctl)
 *
 * chunk_mutex
 * -----------
 * protects chunks, adding or removing during allocation, trim or when a new
 * device is added/removed. Additionally it also protects post_commit_list of
 * individual devices, since they can be added to the transaction's
 * post_commit_list only with chunk_mutex held.
 *
 * cleaner_mutex
 * -------------
 * a big lock that is held by the cleaner thread and prevents running subvolume
 * cleaning together with relocation or delayed iputs
 *
 *
 * Lock nesting
 * ============
 *
 * uuid_mutex
 *   device_list_mutex
 *     chunk_mutex
 *   balance_mutex
 *
 *
 * Exclusive operations
 * ====================
 *
 * Maintains the exclusivity of the following operations that apply to the
 * whole filesystem and cannot run in parallel.
 *
 * - Balance (*)
 * - Device add
 * - Device remove
 * - Device replace (*)
 * - Resize
 *
 * The device operations (as above) can be in one of the following states:
 *
 * - Running state
 * - Paused state
 * - Completed state
 *
 * Only device operations marked with (*) can go into the Paused state for the
 * following reasons:
 *
 * - ioctl (only Balance can be Paused through ioctl)
 * - filesystem remounted as read-only
 * - filesystem unmounted and mounted as read-only
 * - system power-cycle and filesystem mounted as read-only
 * - filesystem or device errors leading to forced read-only
 *
 * The status of exclusive operation is set and cleared atomically.
 * During the course of Paused state, fs_info::exclusive_operation remains set.
 * A device operation in Paused or Running state can be canceled or resumed
 * either by ioctl (Balance only) or when remounted as read-write.
 * The exclusive status is cleared when the device operation is canceled or
 * completed.
 */


DEFINE_MUTEX(uuid_mutex);
static LIST_HEAD(fs_uuids);
struct list_head * __attribute_const__ btrfs_get_fs_uuids(void)
{
 return &fs_uuids;
}

/*
 * Allocate new btrfs_fs_devices structure identified by a fsid.
 *
 * @fsid:    if not NULL, copy the UUID to fs_devices::fsid and to
 *           fs_devices::metadata_fsid
 *
 * Return a pointer to a new struct btrfs_fs_devices on success, or ERR_PTR().
 * The returned struct is not linked onto any lists and can be destroyed with
 * kfree() right away.
 */

static struct btrfs_fs_devices *alloc_fs_devices(const u8 *fsid)
{
 struct btrfs_fs_devices *fs_devs;

 fs_devs = kzalloc(sizeof(*fs_devs), GFP_KERNEL);
 if (!fs_devs)
  return ERR_PTR(-ENOMEM);

 mutex_init(&fs_devs->device_list_mutex);

 INIT_LIST_HEAD(&fs_devs->devices);
 INIT_LIST_HEAD(&fs_devs->alloc_list);
 INIT_LIST_HEAD(&fs_devs->fs_list);
 INIT_LIST_HEAD(&fs_devs->seed_list);

 if (fsid) {
  memcpy(fs_devs->fsid, fsid, BTRFS_FSID_SIZE);
  memcpy(fs_devs->metadata_uuid, fsid, BTRFS_FSID_SIZE);
 }

 return fs_devs;
}

static void btrfs_free_device(struct btrfs_device *device)
{
 WARN_ON(!list_empty(&device->post_commit_list));
 /*
 * No need to call kfree_rcu() nor do RCU lock/unlock, nothing is
 * reading the device name.
 */

 kfree(rcu_dereference_raw(device->name));
 btrfs_extent_io_tree_release(&device->alloc_state);
 btrfs_destroy_dev_zone_info(device);
 kfree(device);
}

static void free_fs_devices(struct btrfs_fs_devices *fs_devices)
{
 struct btrfs_device *device;

 WARN_ON(fs_devices->opened);
 WARN_ON(fs_devices->holding);
 while (!list_empty(&fs_devices->devices)) {
  device = list_first_entry(&fs_devices->devices,
       struct btrfs_device, dev_list);
  list_del(&device->dev_list);
  btrfs_free_device(device);
 }
 kfree(fs_devices);
}

void __exit btrfs_cleanup_fs_uuids(void)
{
 struct btrfs_fs_devices *fs_devices;

 while (!list_empty(&fs_uuids)) {
  fs_devices = list_first_entry(&fs_uuids, struct btrfs_fs_devices,
           fs_list);
  list_del(&fs_devices->fs_list);
  free_fs_devices(fs_devices);
 }
}

static bool match_fsid_fs_devices(const struct btrfs_fs_devices *fs_devices,
      const u8 *fsid, const u8 *metadata_fsid)
{
 if (memcmp(fsid, fs_devices->fsid, BTRFS_FSID_SIZE) != 0)
  return false;

 if (!metadata_fsid)
  return true;

 if (memcmp(metadata_fsid, fs_devices->metadata_uuid, BTRFS_FSID_SIZE) != 0)
  return false;

 return true;
}

static noinline struct btrfs_fs_devices *find_fsid(
  const u8 *fsid, const u8 *metadata_fsid)
{
 struct btrfs_fs_devices *fs_devices;

 ASSERT(fsid);

 /* Handle non-split brain cases */
 list_for_each_entry(fs_devices, &fs_uuids, fs_list) {
  if (match_fsid_fs_devices(fs_devices, fsid, metadata_fsid))
   return fs_devices;
 }
 return NULL;
}

static int
btrfs_get_bdev_and_sb(const char *device_path, blk_mode_t flags, void *holder,
        int flush, struct file **bdev_file,
        struct btrfs_super_block **disk_super)
{
 struct block_device *bdev;
 int ret;

 *bdev_file = bdev_file_open_by_path(device_path, flags, holder, &fs_holder_ops);

 if (IS_ERR(*bdev_file)) {
  ret = PTR_ERR(*bdev_file);
  btrfs_err(NULL, "failed to open device for path %s with flags 0x%x: %d",
     device_path, flags, ret);
  goto error;
 }
 bdev = file_bdev(*bdev_file);

 if (flush)
  sync_blockdev(bdev);
 if (holder) {
  ret = set_blocksize(*bdev_file, BTRFS_BDEV_BLOCKSIZE);
  if (ret) {
   bdev_fput(*bdev_file);
   goto error;
  }
 }
 invalidate_bdev(bdev);
 *disk_super = btrfs_read_disk_super(bdev, 0, false);
 if (IS_ERR(*disk_super)) {
  ret = PTR_ERR(*disk_super);
  bdev_fput(*bdev_file);
  goto error;
 }

 return 0;

error:
 *disk_super = NULL;
 *bdev_file = NULL;
 return ret;
}

/*
 *  Search and remove all stale devices (which are not mounted).  When both
 *  inputs are NULL, it will search and release all stale devices.
 *
 *  @devt:         Optional. When provided will it release all unmounted devices
 *                 matching this devt only.
 *  @skip_device:  Optional. Will skip this device when searching for the stale
 *                 devices.
 *
 *  Return: 0 for success or if @devt is 0.
 * -EBUSY if @devt is a mounted device.
 * -ENOENT if @devt does not match any device in the list.
 */

static int btrfs_free_stale_devices(dev_t devt, struct btrfs_device *skip_device)
{
 struct btrfs_fs_devices *fs_devices, *tmp_fs_devices;
 struct btrfs_device *device, *tmp_device;
 int ret;
 bool freed = false;

 lockdep_assert_held(&uuid_mutex);

 /* Return good status if there is no instance of devt. */
 ret = 0;
 list_for_each_entry_safe(fs_devices, tmp_fs_devices, &fs_uuids, fs_list) {

  mutex_lock(&fs_devices->device_list_mutex);
  list_for_each_entry_safe(device, tmp_device,
      &fs_devices->devices, dev_list) {
   if (skip_device && skip_device == device)
    continue;
   if (devt && devt != device->devt)
    continue;
   if (fs_devices->opened || fs_devices->holding) {
    if (devt)
     ret = -EBUSY;
    break;
   }

   /* delete the stale device */
   fs_devices->num_devices--;
   list_del(&device->dev_list);
   btrfs_free_device(device);

   freed = true;
  }
  mutex_unlock(&fs_devices->device_list_mutex);

  if (fs_devices->num_devices == 0) {
   btrfs_sysfs_remove_fsid(fs_devices);
   list_del(&fs_devices->fs_list);
   free_fs_devices(fs_devices);
  }
 }

 /* If there is at least one freed device return 0. */
 if (freed)
  return 0;

 return ret;
}

static struct btrfs_fs_devices *find_fsid_by_device(
     struct btrfs_super_block *disk_super,
     dev_t devt, bool *same_fsid_diff_dev)
{
 struct btrfs_fs_devices *fsid_fs_devices;
 struct btrfs_fs_devices *devt_fs_devices;
 const bool has_metadata_uuid = (btrfs_super_incompat_flags(disk_super) &
     BTRFS_FEATURE_INCOMPAT_METADATA_UUID);
 bool found_by_devt = false;

 /* Find the fs_device by the usual method, if found use it. */
 fsid_fs_devices = find_fsid(disk_super->fsid,
      has_metadata_uuid ? disk_super->metadata_uuid : NULL);

 /* The temp_fsid feature is supported only with single device filesystem. */
 if (btrfs_super_num_devices(disk_super) != 1)
  return fsid_fs_devices;

 /*
 * A seed device is an integral component of the sprout device, which
 * functions as a multi-device filesystem. So, temp-fsid feature is
 * not supported.
 */

 if (btrfs_super_flags(disk_super) & BTRFS_SUPER_FLAG_SEEDING)
  return fsid_fs_devices;

 /* Try to find a fs_devices by matching devt. */
 list_for_each_entry(devt_fs_devices, &fs_uuids, fs_list) {
  struct btrfs_device *device;

  list_for_each_entry(device, &devt_fs_devices->devices, dev_list) {
   if (device->devt == devt) {
    found_by_devt = true;
    break;
   }
  }
  if (found_by_devt)
   break;
 }

 if (found_by_devt) {
  /* Existing device. */
  if (fsid_fs_devices == NULL) {
   if (devt_fs_devices->opened == 0) {
    /* Stale device. */
    return NULL;
   } else {
    /* temp_fsid is mounting a subvol. */
    return devt_fs_devices;
   }
  } else {
   /* Regular or temp_fsid device mounting a subvol. */
   return devt_fs_devices;
  }
 } else {
  /* New device. */
  if (fsid_fs_devices == NULL) {
   return NULL;
  } else {
   /* sb::fsid is already used create a new temp_fsid. */
   *same_fsid_diff_dev = true;
   return NULL;
  }
 }

 /* Not reached. */
}

/*
 * This is only used on mount, and we are protected from competing things
 * messing with our fs_devices by the uuid_mutex, thus we do not need the
 * fs_devices->device_list_mutex here.
 */

static int btrfs_open_one_device(struct btrfs_fs_devices *fs_devices,
   struct btrfs_device *device, blk_mode_t flags,
   void *holder)
{
 struct file *bdev_file;
 struct btrfs_super_block *disk_super;
 u64 devid;
 int ret;

 if (device->bdev)
  return -EINVAL;
 if (!device->name)
  return -EINVAL;

 ret = btrfs_get_bdev_and_sb(rcu_dereference_raw(device->name), flags, holder, 1,
        &bdev_file, &disk_super);
 if (ret)
  return ret;

 devid = btrfs_stack_device_id(&disk_super->dev_item);
 if (devid != device->devid)
  goto error_free_page;

 if (memcmp(device->uuid, disk_super->dev_item.uuid, BTRFS_UUID_SIZE))
  goto error_free_page;

 device->generation = btrfs_super_generation(disk_super);

 if (btrfs_super_flags(disk_super) & BTRFS_SUPER_FLAG_SEEDING) {
  if (btrfs_super_incompat_flags(disk_super) &
      BTRFS_FEATURE_INCOMPAT_METADATA_UUID) {
   btrfs_err(NULL,
      "invalid seeding and uuid-changed device detected");
   goto error_free_page;
  }

  clear_bit(BTRFS_DEV_STATE_WRITEABLE, &device->dev_state);
  fs_devices->seeding = true;
 } else {
  if (bdev_read_only(file_bdev(bdev_file)))
   clear_bit(BTRFS_DEV_STATE_WRITEABLE, &device->dev_state);
  else
   set_bit(BTRFS_DEV_STATE_WRITEABLE, &device->dev_state);
 }

 if (!bdev_nonrot(file_bdev(bdev_file)))
  fs_devices->rotating = true;

 if (bdev_max_discard_sectors(file_bdev(bdev_file)))
  fs_devices->discardable = true;

 device->bdev_file = bdev_file;
 device->bdev = file_bdev(bdev_file);
 clear_bit(BTRFS_DEV_STATE_IN_FS_METADATA, &device->dev_state);

 if (device->devt != device->bdev->bd_dev) {
  btrfs_warn(NULL,
      "device %s maj:min changed from %d:%d to %d:%d",
      rcu_dereference_raw(device->name), MAJOR(device->devt),
      MINOR(device->devt), MAJOR(device->bdev->bd_dev),
      MINOR(device->bdev->bd_dev));

  device->devt = device->bdev->bd_dev;
 }

 fs_devices->open_devices++;
 if (test_bit(BTRFS_DEV_STATE_WRITEABLE, &device->dev_state) &&
     device->devid != BTRFS_DEV_REPLACE_DEVID) {
  fs_devices->rw_devices++;
  list_add_tail(&device->dev_alloc_list, &fs_devices->alloc_list);
 }
 btrfs_release_disk_super(disk_super);

 return 0;

error_free_page:
 btrfs_release_disk_super(disk_super);
 bdev_fput(bdev_file);

 return -EINVAL;
}

const u8 *btrfs_sb_fsid_ptr(const struct btrfs_super_block *sb)
{
 bool has_metadata_uuid = (btrfs_super_incompat_flags(sb) &
      BTRFS_FEATURE_INCOMPAT_METADATA_UUID);

 return has_metadata_uuid ? sb->metadata_uuid : sb->fsid;
}

static bool is_same_device(struct btrfs_device *device, const char *new_path)
{
 struct path old = { .mnt = NULL, .dentry = NULL };
 struct path new = { .mnt = NULL, .dentry = NULL };
 char *old_path = NULL;
 bool is_same = false;
 int ret;

 if (!device->name)
  goto out;

 old_path = kzalloc(PATH_MAX, GFP_NOFS);
 if (!old_path)
  goto out;

 rcu_read_lock();
 ret = strscpy(old_path, rcu_dereference(device->name), PATH_MAX);
 rcu_read_unlock();
 if (ret < 0)
  goto out;

 ret = kern_path(old_path, LOOKUP_FOLLOW, &old);
 if (ret)
  goto out;
 ret = kern_path(new_path, LOOKUP_FOLLOW, &new);
 if (ret)
  goto out;
 if (path_equal(&old, &new))
  is_same = true;
out:
 kfree(old_path);
 path_put(&old);
 path_put(&new);
 return is_same;
}

/*
 * Add new device to list of registered devices
 *
 * Returns:
 * device pointer which was just added or updated when successful
 * error pointer when failed
 */

static noinline struct btrfs_device *device_list_add(const char *path,
      struct btrfs_super_block *disk_super,
      bool *new_device_added)
{
 struct btrfs_device *device;
 struct btrfs_fs_devices *fs_devices = NULL;
 const char *name;
 u64 found_transid = btrfs_super_generation(disk_super);
 u64 devid = btrfs_stack_device_id(&disk_super->dev_item);
 dev_t path_devt;
 int ret;
 bool same_fsid_diff_dev = false;
 bool has_metadata_uuid = (btrfs_super_incompat_flags(disk_super) &
  BTRFS_FEATURE_INCOMPAT_METADATA_UUID);

 if (btrfs_super_flags(disk_super) & BTRFS_SUPER_FLAG_CHANGING_FSID_V2) {
  btrfs_err(NULL,
"device %s has incomplete metadata_uuid change, please use btrfstune to complete",
     path);
  return ERR_PTR(-EAGAIN);
 }

 ret = lookup_bdev(path, &path_devt);
 if (ret) {
  btrfs_err(NULL, "failed to lookup block device for path %s: %d",
     path, ret);
  return ERR_PTR(ret);
 }

 fs_devices = find_fsid_by_device(disk_super, path_devt, &same_fsid_diff_dev);

 if (!fs_devices) {
  fs_devices = alloc_fs_devices(disk_super->fsid);
  if (IS_ERR(fs_devices))
   return ERR_CAST(fs_devices);

  if (has_metadata_uuid)
   memcpy(fs_devices->metadata_uuid,
          disk_super->metadata_uuid, BTRFS_FSID_SIZE);

  if (same_fsid_diff_dev) {
   generate_random_uuid(fs_devices->fsid);
   fs_devices->temp_fsid = true;
   btrfs_info(NULL, "device %s (%d:%d) using temp-fsid %pU",
    path, MAJOR(path_devt), MINOR(path_devt),
    fs_devices->fsid);
  }

  mutex_lock(&fs_devices->device_list_mutex);
  list_add(&fs_devices->fs_list, &fs_uuids);

  device = NULL;
 } else {
  struct btrfs_dev_lookup_args args = {
   .devid = devid,
   .uuid = disk_super->dev_item.uuid,
  };

  mutex_lock(&fs_devices->device_list_mutex);
  device = btrfs_find_device(fs_devices, &args);

  if (found_transid > fs_devices->latest_generation) {
   memcpy(fs_devices->fsid, disk_super->fsid,
     BTRFS_FSID_SIZE);
   memcpy(fs_devices->metadata_uuid,
          btrfs_sb_fsid_ptr(disk_super), BTRFS_FSID_SIZE);
  }
 }

 if (!device) {
  unsigned int nofs_flag;

  if (fs_devices->opened) {
   btrfs_err(NULL,
"device %s (%d:%d) belongs to fsid %pU, and the fs is already mounted, scanned by %s (%d)",
      path, MAJOR(path_devt), MINOR(path_devt),
      fs_devices->fsid, current->comm,
      task_pid_nr(current));
   mutex_unlock(&fs_devices->device_list_mutex);
   return ERR_PTR(-EBUSY);
  }

  nofs_flag = memalloc_nofs_save();
  device = btrfs_alloc_device(NULL, &devid,
         disk_super->dev_item.uuid, path);
  memalloc_nofs_restore(nofs_flag);
  if (IS_ERR(device)) {
   mutex_unlock(&fs_devices->device_list_mutex);
   /* we can safely leave the fs_devices entry around */
   return device;
  }

  device->devt = path_devt;

  list_add_rcu(&device->dev_list, &fs_devices->devices);
  fs_devices->num_devices++;

  device->fs_devices = fs_devices;
  *new_device_added = true;

  if (disk_super->label[0])
   pr_info(
"BTRFS: device label %s devid %llu transid %llu %s (%d:%d) scanned by %s (%d)\n",
    disk_super->label, devid, found_transid, path,
    MAJOR(path_devt), MINOR(path_devt),
    current->comm, task_pid_nr(current));
  else
   pr_info(
"BTRFS: device fsid %pU devid %llu transid %llu %s (%d:%d) scanned by %s (%d)\n",
    disk_super->fsid, devid, found_transid, path,
    MAJOR(path_devt), MINOR(path_devt),
    current->comm, task_pid_nr(current));

 } else if (!device->name || !is_same_device(device, path)) {
  const char *old_name;

  /*
 * When FS is already mounted.
 * 1. If you are here and if the device->name is NULL that
 *    means this device was missing at time of FS mount.
 * 2. If you are here and if the device->name is different
 *    from 'path' that means either
 *      a. The same device disappeared and reappeared with
 *         different name. or
 *      b. The missing-disk-which-was-replaced, has
 *         reappeared now.
 *
 * We must allow 1 and 2a above. But 2b would be a spurious
 * and unintentional.
 *
 * Further in case of 1 and 2a above, the disk at 'path'
 * would have missed some transaction when it was away and
 * in case of 2a the stale bdev has to be updated as well.
 * 2b must not be allowed at all time.
 */


  /*
 * For now, we do allow update to btrfs_fs_device through the
 * btrfs dev scan cli after FS has been mounted.  We're still
 * tracking a problem where systems fail mount by subvolume id
 * when we reject replacement on a mounted FS.
 */

  if (!fs_devices->opened && found_transid < device->generation) {
   /*
 * That is if the FS is _not_ mounted and if you
 * are here, that means there is more than one
 * disk with same uuid and devid.We keep the one
 * with larger generation number or the last-in if
 * generation are equal.
 */

   mutex_unlock(&fs_devices->device_list_mutex);
   btrfs_err(NULL,
"device %s already registered with a higher generation, found %llu expect %llu",
      path, found_transid, device->generation);
   return ERR_PTR(-EEXIST);
  }

  /*
 * We are going to replace the device path for a given devid,
 * make sure it's the same device if the device is mounted
 *
 * NOTE: the device->fs_info may not be reliable here so pass
 * in a NULL to message helpers instead. This avoids a possible
 * use-after-free when the fs_info and fs_info->sb are already
 * torn down.
 */

  if (device->bdev) {
   if (device->devt != path_devt) {
    mutex_unlock(&fs_devices->device_list_mutex);
    btrfs_warn(NULL,
 "duplicate device %s devid %llu generation %llu scanned by %s (%d)",
        path, devid, found_transid,
        current->comm,
        task_pid_nr(current));
    return ERR_PTR(-EEXIST);
   }
   btrfs_info(NULL,
 "devid %llu device path %s changed to %s scanned by %s (%d)",
       devid, btrfs_dev_name(device),
       path, current->comm,
       task_pid_nr(current));
  }

  name = kstrdup(path, GFP_NOFS);
  if (!name) {
   mutex_unlock(&fs_devices->device_list_mutex);
   return ERR_PTR(-ENOMEM);
  }
  rcu_read_lock();
  old_name = rcu_dereference(device->name);
  rcu_read_unlock();
  rcu_assign_pointer(device->name, name);
  kfree_rcu_mightsleep(old_name);

  if (test_bit(BTRFS_DEV_STATE_MISSING, &device->dev_state)) {
   fs_devices->missing_devices--;
   clear_bit(BTRFS_DEV_STATE_MISSING, &device->dev_state);
  }
  device->devt = path_devt;
 }

 /*
 * Unmount does not free the btrfs_device struct but would zero
 * generation along with most of the other members. So just update
 * it back. We need it to pick the disk with largest generation
 * (as above).
 */

 if (!fs_devices->opened) {
  device->generation = found_transid;
  fs_devices->latest_generation = max_t(u64, found_transid,
      fs_devices->latest_generation);
 }

 fs_devices->total_devices = btrfs_super_num_devices(disk_super);

 mutex_unlock(&fs_devices->device_list_mutex);
 return device;
}

static struct btrfs_fs_devices *clone_fs_devices(struct btrfs_fs_devices *orig)
{
 struct btrfs_fs_devices *fs_devices;
 struct btrfs_device *device;
 struct btrfs_device *orig_dev;
 int ret = 0;

 lockdep_assert_held(&uuid_mutex);

 fs_devices = alloc_fs_devices(orig->fsid);
 if (IS_ERR(fs_devices))
  return fs_devices;

 fs_devices->total_devices = orig->total_devices;

 list_for_each_entry(orig_dev, &orig->devices, dev_list) {
  const char *dev_path = NULL;

  /*
 * This is ok to do without RCU read locked because we hold the
 * uuid mutex so nothing we touch in here is going to disappear.
 */

  if (orig_dev->name)
   dev_path = rcu_dereference_raw(orig_dev->name);

  device = btrfs_alloc_device(NULL, &orig_dev->devid,
         orig_dev->uuid, dev_path);
  if (IS_ERR(device)) {
   ret = PTR_ERR(device);
   goto error;
  }

  if (orig_dev->zone_info) {
   struct btrfs_zoned_device_info *zone_info;

   zone_info = btrfs_clone_dev_zone_info(orig_dev);
   if (!zone_info) {
    btrfs_free_device(device);
    ret = -ENOMEM;
    goto error;
   }
   device->zone_info = zone_info;
  }

  list_add(&device->dev_list, &fs_devices->devices);
  device->fs_devices = fs_devices;
  fs_devices->num_devices++;
 }
 return fs_devices;
error:
 free_fs_devices(fs_devices);
 return ERR_PTR(ret);
}

static void __btrfs_free_extra_devids(struct btrfs_fs_devices *fs_devices,
          struct btrfs_device **latest_dev)
{
 struct btrfs_device *device, *next;

 /* This is the initialized path, it is safe to release the devices. */
 list_for_each_entry_safe(device, next, &fs_devices->devices, dev_list) {
  if (test_bit(BTRFS_DEV_STATE_IN_FS_METADATA, &device->dev_state)) {
   if (!test_bit(BTRFS_DEV_STATE_REPLACE_TGT,
          &device->dev_state) &&
       !test_bit(BTRFS_DEV_STATE_MISSING,
          &device->dev_state) &&
       (!*latest_dev ||
        device->generation > (*latest_dev)->generation)) {
    *latest_dev = device;
   }
   continue;
  }

  /*
 * We have already validated the presence of BTRFS_DEV_REPLACE_DEVID,
 * in btrfs_init_dev_replace() so just continue.
 */

  if (device->devid == BTRFS_DEV_REPLACE_DEVID)
   continue;

  if (device->bdev_file) {
   bdev_fput(device->bdev_file);
   device->bdev = NULL;
   device->bdev_file = NULL;
   fs_devices->open_devices--;
  }
  if (test_bit(BTRFS_DEV_STATE_WRITEABLE, &device->dev_state)) {
   list_del_init(&device->dev_alloc_list);
   clear_bit(BTRFS_DEV_STATE_WRITEABLE, &device->dev_state);
   fs_devices->rw_devices--;
  }
  list_del_init(&device->dev_list);
  fs_devices->num_devices--;
  btrfs_free_device(device);
 }

}

/*
 * After we have read the system tree and know devids belonging to this
 * filesystem, remove the device which does not belong there.
 */

void btrfs_free_extra_devids(struct btrfs_fs_devices *fs_devices)
{
 struct btrfs_device *latest_dev = NULL;
 struct btrfs_fs_devices *seed_dev;

 mutex_lock(&uuid_mutex);
 __btrfs_free_extra_devids(fs_devices, &latest_dev);

 list_for_each_entry(seed_dev, &fs_devices->seed_list, seed_list)
  __btrfs_free_extra_devids(seed_dev, &latest_dev);

 fs_devices->latest_dev = latest_dev;

 mutex_unlock(&uuid_mutex);
}

static void btrfs_close_bdev(struct btrfs_device *device)
{
 if (!device->bdev)
  return;

 if (test_bit(BTRFS_DEV_STATE_WRITEABLE, &device->dev_state)) {
  sync_blockdev(device->bdev);
  invalidate_bdev(device->bdev);
 }

 bdev_fput(device->bdev_file);
}

static void btrfs_close_one_device(struct btrfs_device *device)
{
 struct btrfs_fs_devices *fs_devices = device->fs_devices;

 if (test_bit(BTRFS_DEV_STATE_WRITEABLE, &device->dev_state) &&
     device->devid != BTRFS_DEV_REPLACE_DEVID) {
  list_del_init(&device->dev_alloc_list);
  fs_devices->rw_devices--;
 }

 if (device->devid == BTRFS_DEV_REPLACE_DEVID)
  clear_bit(BTRFS_DEV_STATE_REPLACE_TGT, &device->dev_state);

 if (test_bit(BTRFS_DEV_STATE_MISSING, &device->dev_state)) {
  clear_bit(BTRFS_DEV_STATE_MISSING, &device->dev_state);
  fs_devices->missing_devices--;
 }

 btrfs_close_bdev(device);
 if (device->bdev) {
  fs_devices->open_devices--;
  device->bdev = NULL;
  device->bdev_file = NULL;
 }
 clear_bit(BTRFS_DEV_STATE_WRITEABLE, &device->dev_state);
 btrfs_destroy_dev_zone_info(device);

 device->fs_info = NULL;
 atomic_set(&device->dev_stats_ccnt, 0);
 btrfs_extent_io_tree_release(&device->alloc_state);

 /*
 * Reset the flush error record. We might have a transient flush error
 * in this mount, and if so we aborted the current transaction and set
 * the fs to an error state, guaranteeing no super blocks can be further
 * committed. However that error might be transient and if we unmount the
 * filesystem and mount it again, we should allow the mount to succeed
 * (btrfs_check_rw_degradable() should not fail) - if after mounting the
 * filesystem again we still get flush errors, then we will again abort
 * any transaction and set the error state, guaranteeing no commits of
 * unsafe super blocks.
 */

 device->last_flush_error = 0;

 /* Verify the device is back in a pristine state  */
 WARN_ON(test_bit(BTRFS_DEV_STATE_FLUSH_SENT, &device->dev_state));
 WARN_ON(test_bit(BTRFS_DEV_STATE_REPLACE_TGT, &device->dev_state));
 WARN_ON(!list_empty(&device->dev_alloc_list));
 WARN_ON(!list_empty(&device->post_commit_list));
}

static void close_fs_devices(struct btrfs_fs_devices *fs_devices)
{
 struct btrfs_device *device, *tmp;

 lockdep_assert_held(&uuid_mutex);

 if (--fs_devices->opened > 0)
  return;

 list_for_each_entry_safe(device, tmp, &fs_devices->devices, dev_list)
  btrfs_close_one_device(device);

 WARN_ON(fs_devices->open_devices);
 WARN_ON(fs_devices->rw_devices);
 fs_devices->opened = 0;
 fs_devices->seeding = false;
 fs_devices->fs_info = NULL;
}

void btrfs_close_devices(struct btrfs_fs_devices *fs_devices)
{
 LIST_HEAD(list);
 struct btrfs_fs_devices *tmp;

 mutex_lock(&uuid_mutex);
 close_fs_devices(fs_devices);
 if (!fs_devices->opened && !fs_devices->holding) {
  list_splice_init(&fs_devices->seed_list, &list);

  /*
 * If the struct btrfs_fs_devices is not assembled with any
 * other device, it can be re-initialized during the next mount
 * without the needing device-scan step. Therefore, it can be
 * fully freed.
 */

  if (fs_devices->num_devices == 1) {
   list_del(&fs_devices->fs_list);
   free_fs_devices(fs_devices);
  }
 }


 list_for_each_entry_safe(fs_devices, tmp, &list, seed_list) {
  close_fs_devices(fs_devices);
  list_del(&fs_devices->seed_list);
  free_fs_devices(fs_devices);
 }
 mutex_unlock(&uuid_mutex);
}

static int open_fs_devices(struct btrfs_fs_devices *fs_devices,
    blk_mode_t flags, void *holder)
{
 struct btrfs_device *device;
 struct btrfs_device *latest_dev = NULL;
 struct btrfs_device *tmp_device;
 s64 __maybe_unused value = 0;
 int ret = 0;

 list_for_each_entry_safe(device, tmp_device, &fs_devices->devices,
     dev_list) {
  int ret2;

  ret2 = btrfs_open_one_device(fs_devices, device, flags, holder);
  if (ret2 == 0 &&
      (!latest_dev || device->generation > latest_dev->generation)) {
   latest_dev = device;
  } else if (ret2 == -ENODATA) {
   fs_devices->num_devices--;
   list_del(&device->dev_list);
   btrfs_free_device(device);
  }
  if (ret == 0 && ret2 != 0)
   ret = ret2;
 }

 if (fs_devices->open_devices == 0) {
  if (ret)
   return ret;
  return -EINVAL;
 }

 fs_devices->opened = 1;
 fs_devices->latest_dev = latest_dev;
 fs_devices->total_rw_bytes = 0;
 fs_devices->chunk_alloc_policy = BTRFS_CHUNK_ALLOC_REGULAR;
#ifdef CONFIG_BTRFS_EXPERIMENTAL
 fs_devices->rr_min_contig_read = BTRFS_DEFAULT_RR_MIN_CONTIG_READ;
 fs_devices->read_devid = latest_dev->devid;
 fs_devices->read_policy = btrfs_read_policy_to_enum(btrfs_get_mod_read_policy(),
           &value);
 if (fs_devices->read_policy == BTRFS_READ_POLICY_RR)
  fs_devices->collect_fs_stats = true;

 if (value) {
  if (fs_devices->read_policy == BTRFS_READ_POLICY_RR)
   fs_devices->rr_min_contig_read = value;
  if (fs_devices->read_policy == BTRFS_READ_POLICY_DEVID)
   fs_devices->read_devid = value;
 }
#else
 fs_devices->read_policy = BTRFS_READ_POLICY_PID;
#endif

 return 0;
}

static int devid_cmp(void *priv, const struct list_head *a,
       const struct list_head *b)
{
 const struct btrfs_device *dev1, *dev2;

 dev1 = list_entry(a, struct btrfs_device, dev_list);
 dev2 = list_entry(b, struct btrfs_device, dev_list);

 if (dev1->devid < dev2->devid)
  return -1;
 else if (dev1->devid > dev2->devid)
  return 1;
 return 0;
}

int btrfs_open_devices(struct btrfs_fs_devices *fs_devices,
         blk_mode_t flags, void *holder)
{
 int ret;

 lockdep_assert_held(&uuid_mutex);
 /*
 * The device_list_mutex cannot be taken here in case opening the
 * underlying device takes further locks like open_mutex.
 *
 * We also don't need the lock here as this is called during mount and
 * exclusion is provided by uuid_mutex
 */


 if (fs_devices->opened) {
  fs_devices->opened++;
  ret = 0;
 } else {
  list_sort(NULL, &fs_devices->devices, devid_cmp);
  ret = open_fs_devices(fs_devices, flags, holder);
 }

 return ret;
}

void btrfs_release_disk_super(struct btrfs_super_block *super)
{
 struct page *page = virt_to_page(super);

 put_page(page);
}

struct btrfs_super_block *btrfs_read_disk_super(struct block_device *bdev,
      int copy_num, bool drop_cache)
{
 struct btrfs_super_block *super;
 struct page *page;
 u64 bytenr, bytenr_orig;
 struct address_space *mapping = bdev->bd_mapping;
 int ret;

 bytenr_orig = btrfs_sb_offset(copy_num);
 ret = btrfs_sb_log_location_bdev(bdev, copy_num, READ, &bytenr);
 if (ret < 0) {
  if (ret == -ENOENT)
   ret = -EINVAL;
  return ERR_PTR(ret);
 }

 if (bytenr + BTRFS_SUPER_INFO_SIZE >= bdev_nr_bytes(bdev))
  return ERR_PTR(-EINVAL);

 if (drop_cache) {
  /* This should only be called with the primary sb. */
  ASSERT(copy_num == 0);

  /*
 * Drop the page of the primary superblock, so later read will
 * always read from the device.
 */

  invalidate_inode_pages2_range(mapping, bytenr >> PAGE_SHIFT,
          (bytenr + BTRFS_SUPER_INFO_SIZE) >> PAGE_SHIFT);
 }

 page = read_cache_page_gfp(mapping, bytenr >> PAGE_SHIFT, GFP_NOFS);
 if (IS_ERR(page))
  return ERR_CAST(page);

 super = page_address(page);
 if (btrfs_super_magic(super) != BTRFS_MAGIC ||
     btrfs_super_bytenr(super) != bytenr_orig) {
  btrfs_release_disk_super(super);
  return ERR_PTR(-EINVAL);
 }

 /*
 * Make sure the last byte of label is properly NUL termiated.  We use
 * '%s' to print the label, if not properly NUL termiated we can access
 * beyond the label.
 */

 if (super->label[0] && super->label[BTRFS_LABEL_SIZE - 1])
  super->label[BTRFS_LABEL_SIZE - 1] = 0;

 return super;
}

int btrfs_forget_devices(dev_t devt)
{
 int ret;

 mutex_lock(&uuid_mutex);
 ret = btrfs_free_stale_devices(devt, NULL);
 mutex_unlock(&uuid_mutex);

 return ret;
}

static bool btrfs_skip_registration(struct btrfs_super_block *disk_super,
        const char *path, dev_t devt,
        bool mount_arg_dev)
{
 struct btrfs_fs_devices *fs_devices;

 /*
 * Do not skip device registration for mounted devices with matching
 * maj:min but different paths. Booting without initrd relies on
 * /dev/root initially, later replaced with the actual root device.
 * A successful scan ensures grub2-probe selects the correct device.
 */

 list_for_each_entry(fs_devices, &fs_uuids, fs_list) {
  struct btrfs_device *device;

  mutex_lock(&fs_devices->device_list_mutex);

  if (!fs_devices->opened) {
   mutex_unlock(&fs_devices->device_list_mutex);
   continue;
  }

  list_for_each_entry(device, &fs_devices->devices, dev_list) {
   if (device->bdev && (device->bdev->bd_dev == devt) &&
       strcmp(rcu_dereference_raw(device->name), path) != 0) {
    mutex_unlock(&fs_devices->device_list_mutex);

    /* Do not skip registration. */
    return false;
   }
  }
  mutex_unlock(&fs_devices->device_list_mutex);
 }

 if (!mount_arg_dev && btrfs_super_num_devices(disk_super) == 1 &&
     !(btrfs_super_flags(disk_super) & BTRFS_SUPER_FLAG_SEEDING))
  return true;

 return false;
}

/*
 * Look for a btrfs signature on a device. This may be called out of the mount path
 * and we are not allowed to call set_blocksize during the scan. The superblock
 * is read via pagecache.
 *
 * With @mount_arg_dev it's a scan during mount time that will always register
 * the device or return an error. Multi-device and seeding devices are registered
 * in both cases.
 */

struct btrfs_device *btrfs_scan_one_device(const char *path,
        bool mount_arg_dev)
{
 struct btrfs_super_block *disk_super;
 bool new_device_added = false;
 struct btrfs_device *device = NULL;
 struct file *bdev_file;
 dev_t devt;

 lockdep_assert_held(&uuid_mutex);

 /*
 * Avoid an exclusive open here, as the systemd-udev may initiate the
 * device scan which may race with the user's mount or mkfs command,
 * resulting in failure.
 * Since the device scan is solely for reading purposes, there is no
 * need for an exclusive open. Additionally, the devices are read again
 * during the mount process. It is ok to get some inconsistent
 * values temporarily, as the device paths of the fsid are the only
 * required information for assembling the volume.
 */

 bdev_file = bdev_file_open_by_path(path, BLK_OPEN_READ, NULL, NULL);
 if (IS_ERR(bdev_file))
  return ERR_CAST(bdev_file);

 disk_super = btrfs_read_disk_super(file_bdev(bdev_file), 0, false);
 if (IS_ERR(disk_super)) {
  device = ERR_CAST(disk_super);
  goto error_bdev_put;
 }

 devt = file_bdev(bdev_file)->bd_dev;
 if (btrfs_skip_registration(disk_super, path, devt, mount_arg_dev)) {
  btrfs_debug(NULL, "skip registering single non-seed device %s (%d:%d)",
     path, MAJOR(devt), MINOR(devt));

  btrfs_free_stale_devices(devt, NULL);

  device = NULL;
  goto free_disk_super;
 }

 device = device_list_add(path, disk_super, &new_device_added);
 if (!IS_ERR(device) && new_device_added)
  btrfs_free_stale_devices(device->devt, device);

free_disk_super:
 btrfs_release_disk_super(disk_super);

error_bdev_put:
 bdev_fput(bdev_file);

 return device;
}

/*
 * Try to find a chunk that intersects [start, start + len] range and when one
 * such is found, record the end of it in *start
 */

static bool contains_pending_extent(struct btrfs_device *device, u64 *start,
        u64 len)
{
 u64 physical_start, physical_end;

 lockdep_assert_held(&device->fs_info->chunk_mutex);

 if (btrfs_find_first_extent_bit(&device->alloc_state, *start,
     &physical_start, &physical_end,
     CHUNK_ALLOCATED, NULL)) {

  if (in_range(physical_start, *start, len) ||
      in_range(*start, physical_start,
        physical_end + 1 - physical_start)) {
   *start = physical_end + 1;
   return true;
  }
 }
 return false;
}

static u64 dev_extent_search_start(struct btrfs_device *device)
{
 switch (device->fs_devices->chunk_alloc_policy) {
 default:
  btrfs_warn_unknown_chunk_allocation(device->fs_devices->chunk_alloc_policy);
  fallthrough;
 case BTRFS_CHUNK_ALLOC_REGULAR:
  return BTRFS_DEVICE_RANGE_RESERVED;
 case BTRFS_CHUNK_ALLOC_ZONED:
  /*
 * We don't care about the starting region like regular
 * allocator, because we anyway use/reserve the first two zones
 * for superblock logging.
 */

  return 0;
 }
}

static bool dev_extent_hole_check_zoned(struct btrfs_device *device,
     u64 *hole_start, u64 *hole_size,
     u64 num_bytes)
{
 u64 zone_size = device->zone_info->zone_size;
 u64 pos;
 int ret;
 bool changed = false;

 ASSERT(IS_ALIGNED(*hole_start, zone_size),
        "hole_start=%llu zone_size=%llu", *hole_start, zone_size);

 while (*hole_size > 0) {
  pos = btrfs_find_allocatable_zones(device, *hole_start,
         *hole_start + *hole_size,
         num_bytes);
  if (pos != *hole_start) {
   *hole_size = *hole_start + *hole_size - pos;
   *hole_start = pos;
   changed = true;
   if (*hole_size < num_bytes)
    break;
  }

  ret = btrfs_ensure_empty_zones(device, pos, num_bytes);

  /* Range is ensured to be empty */
  if (!ret)
   return changed;

  /* Given hole range was invalid (outside of device) */
  if (ret == -ERANGE) {
   *hole_start += *hole_size;
   *hole_size = 0;
   return true;
  }

  *hole_start += zone_size;
  *hole_size -= zone_size;
  changed = true;
 }

 return changed;
}

/*
 * Check if specified hole is suitable for allocation.
 *
 * @device: the device which we have the hole
 * @hole_start: starting position of the hole
 * @hole_size: the size of the hole
 * @num_bytes: the size of the free space that we need
 *
 * This function may modify @hole_start and @hole_size to reflect the suitable
 * position for allocation. Returns 1 if hole position is updated, 0 otherwise.
 */

static bool dev_extent_hole_check(struct btrfs_device *device, u64 *hole_start,
      u64 *hole_size, u64 num_bytes)
{
 bool changed = false;
 u64 hole_end = *hole_start + *hole_size;

 for (;;) {
  /*
 * Check before we set max_hole_start, otherwise we could end up
 * sending back this offset anyway.
 */

  if (contains_pending_extent(device, hole_start, *hole_size)) {
   if (hole_end >= *hole_start)
    *hole_size = hole_end - *hole_start;
   else
    *hole_size = 0;
   changed = true;
  }

  switch (device->fs_devices->chunk_alloc_policy) {
  default:
   btrfs_warn_unknown_chunk_allocation(device->fs_devices->chunk_alloc_policy);
   fallthrough;
  case BTRFS_CHUNK_ALLOC_REGULAR:
   /* No extra check */
   break;
  case BTRFS_CHUNK_ALLOC_ZONED:
   if (dev_extent_hole_check_zoned(device, hole_start,
       hole_size, num_bytes)) {
    changed = true;
    /*
 * The changed hole can contain pending extent.
 * Loop again to check that.
 */

    continue;
   }
   break;
  }

  break;
 }

 return changed;
}

/*
 * Find free space in the specified device.
 *
 * @device:   the device which we search the free space in
 * @num_bytes:   the size of the free space that we need
 * @search_start: the position from which to begin the search
 * @start:   store the start of the free space.
 * @len:   the size of the free space. that we find, or the size
 *   of the max free space if we don't find suitable free space
 *
 * This does a pretty simple search, the expectation is that it is called very
 * infrequently and that a given device has a small number of extents.
 *
 * @start is used to store the start of the free space if we find. But if we
 * don't find suitable free space, it will be used to store the start position
 * of the max free space.
 *
 * @len is used to store the size of the free space that we find.
 * But if we don't find suitable free space, it is used to store the size of
 * the max free space.
 *
 * NOTE: This function will search *commit* root of device tree, and does extra
 * check to ensure dev extents are not double allocated.
 * This makes the function safe to allocate dev extents but may not report
 * correct usable device space, as device extent freed in current transaction
 * is not reported as available.
 */

static int find_free_dev_extent(struct btrfs_device *device, u64 num_bytes,
    u64 *start, u64 *len)
{
 struct btrfs_fs_info *fs_info = device->fs_info;
 struct btrfs_root *root = fs_info->dev_root;
 struct btrfs_key key;
 struct btrfs_dev_extent *dev_extent;
 struct btrfs_path *path;
 u64 search_start;
 u64 hole_size;
 u64 max_hole_start;
 u64 max_hole_size = 0;
 u64 extent_end;
 u64 search_end = device->total_bytes;
 int ret;
 int slot;
 struct extent_buffer *l;

 search_start = dev_extent_search_start(device);
 max_hole_start = search_start;

 WARN_ON(device->zone_info &&
  !IS_ALIGNED(num_bytes, device->zone_info->zone_size));

 path = btrfs_alloc_path();
 if (!path) {
  ret = -ENOMEM;
  goto out;
 }
again:
 if (search_start >= search_end ||
  test_bit(BTRFS_DEV_STATE_REPLACE_TGT, &device->dev_state)) {
  ret = -ENOSPC;
  goto out;
 }

 path->reada = READA_FORWARD;
 path->search_commit_root = 1;
 path->skip_locking = 1;

 key.objectid = device->devid;
 key.type = BTRFS_DEV_EXTENT_KEY;
 key.offset = search_start;

 ret = btrfs_search_backwards(root, &key, path);
 if (ret < 0)
  goto out;

 while (search_start < search_end) {
  l = path->nodes[0];
  slot = path->slots[0];
  if (slot >= btrfs_header_nritems(l)) {
   ret = btrfs_next_leaf(root, path);
   if (ret == 0)
    continue;
   if (ret < 0)
    goto out;

   break;
  }
  btrfs_item_key_to_cpu(l, &key, slot);

  if (key.objectid < device->devid)
   goto next;

  if (key.objectid > device->devid)
   break;

  if (key.type != BTRFS_DEV_EXTENT_KEY)
   goto next;

  if (key.offset > search_end)
   break;

  if (key.offset > search_start) {
   hole_size = key.offset - search_start;
   dev_extent_hole_check(device, &search_start, &hole_size,
           num_bytes);

   if (hole_size > max_hole_size) {
    max_hole_start = search_start;
    max_hole_size = hole_size;
   }

   /*
 * If this free space is greater than which we need,
 * it must be the max free space that we have found
 * until now, so max_hole_start must point to the start
 * of this free space and the length of this free space
 * is stored in max_hole_size. Thus, we return
 * max_hole_start and max_hole_size and go back to the
 * caller.
 */

   if (hole_size >= num_bytes) {
    ret = 0;
    goto out;
   }
  }

  dev_extent = btrfs_item_ptr(l, slot, struct btrfs_dev_extent);
  extent_end = key.offset + btrfs_dev_extent_length(l,
          dev_extent);
  if (extent_end > search_start)
   search_start = extent_end;
next:
  path->slots[0]++;
  cond_resched();
 }

 /*
 * At this point, search_start should be the end of
 * allocated dev extents, and when shrinking the device,
 * search_end may be smaller than search_start.
 */

 if (search_end > search_start) {
  hole_size = search_end - search_start;
  if (dev_extent_hole_check(device, &search_start, &hole_size,
       num_bytes)) {
   btrfs_release_path(path);
   goto again;
  }

  if (hole_size > max_hole_size) {
   max_hole_start = search_start;
   max_hole_size = hole_size;
  }
 }

 /* See above. */
 if (max_hole_size < num_bytes)
  ret = -ENOSPC;
 else
  ret = 0;

 ASSERT(max_hole_start + max_hole_size <= search_end,
        "max_hole_start=%llu max_hole_size=%llu search_end=%llu",
        max_hole_start, max_hole_size, search_end);
out:
 btrfs_free_path(path);
 *start = max_hole_start;
 if (len)
  *len = max_hole_size;
 return ret;
}

static int btrfs_free_dev_extent(struct btrfs_trans_handle *trans,
     struct btrfs_device *device,
     u64 start, u64 *dev_extent_len)
{
 struct btrfs_fs_info *fs_info = device->fs_info;
 struct btrfs_root *root = fs_info->dev_root;
 int ret;
 struct btrfs_path *path;
 struct btrfs_key key;
 struct btrfs_key found_key;
 struct extent_buffer *leaf = NULL;
 struct btrfs_dev_extent *extent = NULL;

 path = btrfs_alloc_path();
 if (!path)
  return -ENOMEM;

 key.objectid = device->devid;
 key.type = BTRFS_DEV_EXTENT_KEY;
 key.offset = start;
again:
 ret = btrfs_search_slot(trans, root, &key, path, -1, 1);
 if (ret > 0) {
  ret = btrfs_previous_item(root, path, key.objectid,
       BTRFS_DEV_EXTENT_KEY);
  if (ret)
   goto out;
  leaf = path->nodes[0];
  btrfs_item_key_to_cpu(leaf, &found_key, path->slots[0]);
  extent = btrfs_item_ptr(leaf, path->slots[0],
     struct btrfs_dev_extent);
  BUG_ON(found_key.offset > start || found_key.offset +
         btrfs_dev_extent_length(leaf, extent) < start);
  key = found_key;
  btrfs_release_path(path);
  goto again;
 } else if (ret == 0) {
  leaf = path->nodes[0];
  extent = btrfs_item_ptr(leaf, path->slots[0],
     struct btrfs_dev_extent);
 } else {
  goto out;
 }

 *dev_extent_len = btrfs_dev_extent_length(leaf, extent);

 ret = btrfs_del_item(trans, root, path);
 if (ret == 0)
  set_bit(BTRFS_TRANS_HAVE_FREE_BGS, &trans->transaction->flags);
out:
 btrfs_free_path(path);
 return ret;
}

static u64 find_next_chunk(struct btrfs_fs_info *fs_info)
{
 struct rb_node *n;
 u64 ret = 0;

 read_lock(&fs_info->mapping_tree_lock);
 n = rb_last(&fs_info->mapping_tree.rb_root);
 if (n) {
  struct btrfs_chunk_map *map;

  map = rb_entry(n, struct btrfs_chunk_map, rb_node);
  ret = map->start + map->chunk_len;
 }
 read_unlock(&fs_info->mapping_tree_lock);

 return ret;
}

static noinline int find_next_devid(struct btrfs_fs_info *fs_info,
        u64 *devid_ret)
{
 int ret;
 struct btrfs_key key;
 struct btrfs_key found_key;
 struct btrfs_path *path;

 path = btrfs_alloc_path();
 if (!path)
  return -ENOMEM;

 key.objectid = BTRFS_DEV_ITEMS_OBJECTID;
 key.type = BTRFS_DEV_ITEM_KEY;
 key.offset = (u64)-1;

 ret = btrfs_search_slot(NULL, fs_info->chunk_root, &key, path, 0, 0);
 if (ret < 0)
  goto error;

 if (ret == 0) {
  /* Corruption */
  btrfs_err(fs_info, "corrupted chunk tree devid -1 matched");
  ret = -EUCLEAN;
  goto error;
 }

 ret = btrfs_previous_item(fs_info->chunk_root, path,
      BTRFS_DEV_ITEMS_OBJECTID,
      BTRFS_DEV_ITEM_KEY);
 if (ret) {
  *devid_ret = 1;
 } else {
  btrfs_item_key_to_cpu(path->nodes[0], &found_key,
          path->slots[0]);
  *devid_ret = found_key.offset + 1;
 }
 ret = 0;
error:
 btrfs_free_path(path);
 return ret;
}

/*
 * the device information is stored in the chunk root
 * the btrfs_device struct should be fully filled in
 */

static int btrfs_add_dev_item(struct btrfs_trans_handle *trans,
       struct btrfs_device *device)
{
 int ret;
 struct btrfs_path *path;
 struct btrfs_dev_item *dev_item;
 struct extent_buffer *leaf;
 struct btrfs_key key;
 unsigned long ptr;

 path = btrfs_alloc_path();
 if (!path)
  return -ENOMEM;

 key.objectid = BTRFS_DEV_ITEMS_OBJECTID;
 key.type = BTRFS_DEV_ITEM_KEY;
 key.offset = device->devid;

 btrfs_reserve_chunk_metadata(trans, true);
 ret = btrfs_insert_empty_item(trans, trans->fs_info->chunk_root, path,
          &key, sizeof(*dev_item));
 btrfs_trans_release_chunk_metadata(trans);
 if (ret)
  goto out;

 leaf = path->nodes[0];
 dev_item = btrfs_item_ptr(leaf, path->slots[0], struct btrfs_dev_item);

 btrfs_set_device_id(leaf, dev_item, device->devid);
 btrfs_set_device_generation(leaf, dev_item, 0);
 btrfs_set_device_type(leaf, dev_item, device->type);
 btrfs_set_device_io_align(leaf, dev_item, device->io_align);
 btrfs_set_device_io_width(leaf, dev_item, device->io_width);
 btrfs_set_device_sector_size(leaf, dev_item, device->sector_size);
 btrfs_set_device_total_bytes(leaf, dev_item,
         btrfs_device_get_disk_total_bytes(device));
 btrfs_set_device_bytes_used(leaf, dev_item,
        btrfs_device_get_bytes_used(device));
 btrfs_set_device_group(leaf, dev_item, 0);
 btrfs_set_device_seek_speed(leaf, dev_item, 0);
 btrfs_set_device_bandwidth(leaf, dev_item, 0);
 btrfs_set_device_start_offset(leaf, dev_item, 0);

 ptr = btrfs_device_uuid(dev_item);
 write_extent_buffer(leaf, device->uuid, ptr, BTRFS_UUID_SIZE);
 ptr = btrfs_device_fsid(dev_item);
 write_extent_buffer(leaf, trans->fs_info->fs_devices->metadata_uuid,
       ptr, BTRFS_FSID_SIZE);

 ret = 0;
out:
 btrfs_free_path(path);
 return ret;
}

/*
 * Function to update ctime/mtime for a given device path.
 * Mainly used for ctime/mtime based probe like libblkid.
 *
 * We don't care about errors here, this is just to be kind to userspace.
 */

static void update_dev_time(const char *device_path)
{
 struct path path;
 int ret;

 ret = kern_path(device_path, LOOKUP_FOLLOW, &path);
 if (ret)
  return;

 inode_update_time(d_inode(path.dentry), S_MTIME | S_CTIME | S_VERSION);
 path_put(&path);
}

static int btrfs_rm_dev_item(struct btrfs_trans_handle *trans,
        struct btrfs_device *device)
{
 struct btrfs_root *root = device->fs_info->chunk_root;
 int ret;
 struct btrfs_path *path;
 struct btrfs_key key;

 path = btrfs_alloc_path();
 if (!path)
  return -ENOMEM;

 key.objectid = BTRFS_DEV_ITEMS_OBJECTID;
 key.type = BTRFS_DEV_ITEM_KEY;
 key.offset = device->devid;

 btrfs_reserve_chunk_metadata(trans, false);
 ret = btrfs_search_slot(trans, root, &key, path, -1, 1);
 btrfs_trans_release_chunk_metadata(trans);
 if (ret) {
  if (ret > 0)
   ret = -ENOENT;
  goto out;
 }

 ret = btrfs_del_item(trans, root, path);
out:
 btrfs_free_path(path);
 return ret;
}

/*
 * Verify that @num_devices satisfies the RAID profile constraints in the whole
 * filesystem. It's up to the caller to adjust that number regarding eg. device
 * replace.
 */

static int btrfs_check_raid_min_devices(struct btrfs_fs_info *fs_info,
  u64 num_devices)
{
 u64 all_avail;
 unsigned seq;
 int i;

 do {
  seq = read_seqbegin(&fs_info->profiles_lock);

  all_avail = fs_info->avail_data_alloc_bits |
       fs_info->avail_system_alloc_bits |
       fs_info->avail_metadata_alloc_bits;
 } while (read_seqretry(&fs_info->profiles_lock, seq));

 for (i = 0; i < BTRFS_NR_RAID_TYPES; i++) {
  if (!(all_avail & btrfs_raid_array[i].bg_flag))
   continue;

  if (num_devices < btrfs_raid_array[i].devs_min)
   return btrfs_raid_array[i].mindev_error;
 }

 return 0;
}

static struct btrfs_device * btrfs_find_next_active_device(
  struct btrfs_fs_devices *fs_devs, struct btrfs_device *device)
{
 struct btrfs_device *next_device;

 list_for_each_entry(next_device, &fs_devs->devices, dev_list) {
  if (next_device != device &&
      !test_bit(BTRFS_DEV_STATE_MISSING, &next_device->dev_state)
      && next_device->bdev)
   return next_device;
 }

 return NULL;
}

/*
 * Helper function to check if the given device is part of s_bdev / latest_dev
 * and replace it with the provided or the next active device, in the context
 * where this function called, there should be always be another device (or
 * this_dev) which is active.
 */

void __cold btrfs_assign_next_active_device(struct btrfs_device *device,
         struct btrfs_device *next_device)
{
 struct btrfs_fs_info *fs_info = device->fs_info;

 if (!next_device)
  next_device = btrfs_find_next_active_device(fs_info->fs_devices,
           device);
 ASSERT(next_device);

 if (fs_info->sb->s_bdev &&
   (fs_info->sb->s_bdev == device->bdev))
  fs_info->sb->s_bdev = next_device->bdev;

 if (fs_info->fs_devices->latest_dev->bdev == device->bdev)
  fs_info->fs_devices->latest_dev = next_device;
}

/*
 * Return btrfs_fs_devices::num_devices excluding the device that's being
 * currently replaced.
 */

static u64 btrfs_num_devices(struct btrfs_fs_info *fs_info)
{
 u64 num_devices = fs_info->fs_devices->num_devices;

 down_read(&fs_info->dev_replace.rwsem);
 if (btrfs_dev_replace_is_ongoing(&fs_info->dev_replace)) {
  ASSERT(num_devices > 1, "num_devices=%llu", num_devices);
  num_devices--;
 }
 up_read(&fs_info->dev_replace.rwsem);

 return num_devices;
}

static void btrfs_scratch_superblock(struct btrfs_fs_info *fs_info,
         struct block_device *bdev, int copy_num)
{
 struct btrfs_super_block *disk_super;
 const size_t len = sizeof(disk_super->magic);
 const u64 bytenr = btrfs_sb_offset(copy_num);
 int ret;

 disk_super = btrfs_read_disk_super(bdev, copy_num, false);
 if (IS_ERR(disk_super))
  return;

 memset(&disk_super->magic, 0, len);
 folio_mark_dirty(virt_to_folio(disk_super));
 btrfs_release_disk_super(disk_super);

 ret = sync_blockdev_range(bdev, bytenr, bytenr + len - 1);
 if (ret)
  btrfs_warn(fs_info, "error clearing superblock number %d (%d)",
   copy_num, ret);
}

void btrfs_scratch_superblocks(struct btrfs_fs_info *fs_info, struct btrfs_device *device)
{
 int copy_num;
 struct block_device *bdev = device->bdev;

 if (!bdev)
  return;

 for (copy_num = 0; copy_num < BTRFS_SUPER_MIRROR_MAX; copy_num++) {
  if (bdev_is_zoned(bdev))
   btrfs_reset_sb_log_zones(bdev, copy_num);
  else
   btrfs_scratch_superblock(fs_info, bdev, copy_num);
 }

 /* Notify udev that device has changed */
 btrfs_kobject_uevent(bdev, KOBJ_CHANGE);

 /* Update ctime/mtime for device path for libblkid */
 update_dev_time(rcu_dereference_raw(device->name));
}

int btrfs_rm_device(struct btrfs_fs_info *fs_info,
      struct btrfs_dev_lookup_args *args,
      struct file **bdev_file)
{
 struct btrfs_trans_handle *trans;
 struct btrfs_device *device;
 struct btrfs_fs_devices *cur_devices;
 struct btrfs_fs_devices *fs_devices = fs_info->fs_devices;
 u64 num_devices;
 int ret = 0;

 if (btrfs_fs_incompat(fs_info, EXTENT_TREE_V2)) {
  btrfs_err(fs_info, "device remove not supported on extent tree v2 yet");
  return -EINVAL;
 }

 /*
 * The device list in fs_devices is accessed without locks (neither
 * uuid_mutex nor device_list_mutex) as it won't change on a mounted
 * filesystem and another device rm cannot run.
 */

 num_devices = btrfs_num_devices(fs_info);

 ret = btrfs_check_raid_min_devices(fs_info, num_devices - 1);
 if (ret)
  return ret;

 device = btrfs_find_device(fs_info->fs_devices, args);
 if (!device) {
  if (args->missing)
   ret = BTRFS_ERROR_DEV_MISSING_NOT_FOUND;
  else
   ret = -ENOENT;
  return ret;
 }

 if (btrfs_pinned_by_swapfile(fs_info, device)) {
  btrfs_warn(fs_info,
    "cannot remove device %s (devid %llu) due to active swapfile",
      btrfs_dev_name(device), device->devid);
  return -ETXTBSY;
 }

 if (test_bit(BTRFS_DEV_STATE_REPLACE_TGT, &device->dev_state))
  return BTRFS_ERROR_DEV_TGT_REPLACE;

 if (test_bit(BTRFS_DEV_STATE_WRITEABLE, &device->dev_state) &&
     fs_info->fs_devices->rw_devices == 1)
  return BTRFS_ERROR_DEV_ONLY_WRITABLE;

 if (test_bit(BTRFS_DEV_STATE_WRITEABLE, &device->dev_state)) {
  mutex_lock(&fs_info->chunk_mutex);
  list_del_init(&device->dev_alloc_list);
  device->fs_devices->rw_devices--;
  mutex_unlock(&fs_info->chunk_mutex);
 }

 ret = btrfs_shrink_device(device, 0);
 if (ret)
  goto error_undo;

 trans = btrfs_start_transaction(fs_info->chunk_root, 0);
 if (IS_ERR(trans)) {
  ret = PTR_ERR(trans);
  goto error_undo;
 }

 ret = btrfs_rm_dev_item(trans, device);
 if (ret) {
  /* Any error in dev item removal is critical */
  btrfs_crit(fs_info,
      "failed to remove device item for devid %llu: %d",
      device->devid, ret);
  btrfs_abort_transaction(trans, ret);
  btrfs_end_transaction(trans);
  return ret;
 }

 clear_bit(BTRFS_DEV_STATE_IN_FS_METADATA, &device->dev_state);
 btrfs_scrub_cancel_dev(device);

 /*
 * the device list mutex makes sure that we don't change
 * the device list while someone else is writing out all
 * the device supers. Whoever is writing all supers, should
 * lock the device list mutex before getting the number of
 * devices in the super block (super_copy). Conversely,
 * whoever updates the number of devices in the super block
 * (super_copy) should hold the device list mutex.
 */


 /*
 * In normal cases the cur_devices == fs_devices. But in case
 * of deleting a seed device, the cur_devices should point to
 * its own fs_devices listed under the fs_devices->seed_list.
 */

 cur_devices = device->fs_devices;
 mutex_lock(&fs_devices->device_list_mutex);
 list_del_rcu(&device->dev_list);

 cur_devices->num_devices--;
 cur_devices->total_devices--;
 /* Update total_devices of the parent fs_devices if it's seed */
 if (cur_devices != fs_devices)
  fs_devices->total_devices--;

 if (test_bit(BTRFS_DEV_STATE_MISSING, &device->dev_state))
  cur_devices->missing_devices--;

 btrfs_assign_next_active_device(device, NULL);

 if (device->bdev_file) {
  cur_devices->open_devices--;
  /* remove sysfs entry */
  btrfs_sysfs_remove_device(device);
 }

 num_devices = btrfs_super_num_devices(fs_info->super_copy) - 1;
 btrfs_set_super_num_devices(fs_info->super_copy, num_devices);
 mutex_unlock(&fs_devices->device_list_mutex);

 /*
 * At this point, the device is zero sized and detached from the
 * devices list.  All that's left is to zero out the old supers and
 * free the device.
 *
 * We cannot call btrfs_close_bdev() here because we're holding the sb
 * write lock, and bdev_fput() on the block device will pull in the
 * ->open_mutex on the block device and it's dependencies.  Instead
 *  just flush the device and let the caller do the final bdev_release.
 */

 if (test_bit(BTRFS_DEV_STATE_WRITEABLE, &device->dev_state)) {
  btrfs_scratch_superblocks(fs_info, device);
  if (device->bdev) {
   sync_blockdev(device->bdev);
   invalidate_bdev(device->bdev);
  }
 }

 *bdev_file = device->bdev_file;
 synchronize_rcu();
 btrfs_free_device(device);

 /*
 * This can happen if cur_devices is the private seed devices list.  We
 * cannot call close_fs_devices() here because it expects the uuid_mutex
 * to be held, but in fact we don't need that for the private
 * seed_devices, we can simply decrement cur_devices->opened and then
 * remove it from our list and free the fs_devices.
 */

 if (cur_devices->num_devices == 0) {
  list_del_init(&cur_devices->seed_list);
  ASSERT(cur_devices->opened == 1, "opened=%d", cur_devices->opened);
  cur_devices->opened--;
  free_fs_devices(cur_devices);
 }

 ret = btrfs_commit_transaction(trans);

 return ret;

error_undo:
 if (test_bit(BTRFS_DEV_STATE_WRITEABLE, &device->dev_state)) {
  mutex_lock(&fs_info->chunk_mutex);
  list_add(&device->dev_alloc_list,
    &fs_devices->alloc_list);
  device->fs_devices->rw_devices++;
  mutex_unlock(&fs_info->chunk_mutex);
 }
 return ret;
}

void btrfs_rm_dev_replace_remove_srcdev(struct btrfs_device *srcdev)
{
 struct btrfs_fs_devices *fs_devices;

 lockdep_assert_held(&srcdev->fs_info->fs_devices->device_list_mutex);

 /*
 * in case of fs with no seed, srcdev->fs_devices will point
 * to fs_devices of fs_info. However when the dev being replaced is
 * a seed dev it will point to the seed's local fs_devices. In short
 * srcdev will have its correct fs_devices in both the cases.
 */

 fs_devices = srcdev->fs_devices;

 list_del_rcu(&srcdev->dev_list);
 list_del(&srcdev->dev_alloc_list);
 fs_devices->num_devices--;
 if (test_bit(BTRFS_DEV_STATE_MISSING, &srcdev->dev_state))
  fs_devices->missing_devices--;

 if (test_bit(BTRFS_DEV_STATE_WRITEABLE, &srcdev->dev_state))
  fs_devices->rw_devices--;

 if (srcdev->bdev)
  fs_devices->open_devices--;
}

void btrfs_rm_dev_replace_free_srcdev(struct btrfs_device *srcdev)
{
 struct btrfs_fs_devices *fs_devices = srcdev->fs_devices;

 mutex_lock(&uuid_mutex);

 btrfs_close_bdev(srcdev);
 synchronize_rcu();
 btrfs_free_device(srcdev);

 /* if this is no devs we rather delete the fs_devices */
 if (!fs_devices->num_devices) {
  /*
 * On a mounted FS, num_devices can't be zero unless it's a
 * seed. In case of a seed device being replaced, the replace
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=94 H=94 G=93

¤ Dauer der Verarbeitung: 0.11 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge