X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=utils.c;h=715bab0ebfb56e91b92338385bd5838add274235;hb=f5dd778f5299088f89d380ce922a84d77a98b9d6;hp=c139eb2ee59d64d11bcaadb5157a0997c0677e7c;hpb=18e2663db3e18d4506b6fe583ad93fc83235fca9;p=platform%2Fupstream%2Fbtrfs-progs.git diff --git a/utils.c b/utils.c index c139eb2..715bab0 100644 --- a/utils.c +++ b/utils.c @@ -17,10 +17,6 @@ * Boston, MA 021110-1307, USA. */ -#define _XOPEN_SOURCE 700 -#define __USE_XOPEN2K8 -#define __XOPEN2K8 /* due to an error in dirent.h, to get dirfd() */ -#define _GNU_SOURCE /* O_NOATIME */ #include #include #include @@ -28,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -38,6 +35,13 @@ #include #include #include +#include +#include +#include +#include + +#include + #include "kerncompat.h" #include "radix-tree.h" #include "ctree.h" @@ -47,11 +51,20 @@ #include "utils.h" #include "volumes.h" #include "ioctl.h" +#include "commands.h" +#include "mkfs/common.h" #ifndef BLKDISCARD #define BLKDISCARD _IO(0x12,119) #endif +static int btrfs_scan_done = 0; + +static int rand_seed_initlized = 0; +static unsigned short rand_seed[3]; + +struct btrfs_config bconf; + /* * Discard the given range in one go */ @@ -71,7 +84,7 @@ static int discard_blocks(int fd, u64 start, u64 len) { while (len > 0) { /* 1G granularity */ - u64 chunk_size = min_t(u64, len, 1*1024*1024*1024); + u64 chunk_size = min_t(u64, len, SZ_1G); int ret; ret = discard_range(fd, start, chunk_size); @@ -84,15 +97,6 @@ static int discard_blocks(int fd, u64 start, u64 len) return 0; } -static u64 reference_root_table[] = { - [1] = BTRFS_ROOT_TREE_OBJECTID, - [2] = BTRFS_EXTENT_TREE_OBJECTID, - [3] = BTRFS_CHUNK_TREE_OBJECTID, - [4] = BTRFS_DEV_TREE_OBJECTID, - [5] = BTRFS_FS_TREE_OBJECTID, - [6] = BTRFS_CSUM_TREE_OBJECTID, -}; - int test_uuid_unique(char *fs_uuid) { int unique = 1; @@ -100,7 +104,7 @@ int test_uuid_unique(char *fs_uuid) blkid_dev dev = NULL; blkid_cache cache = NULL; - if (blkid_get_cache(&cache, 0) < 0) { + if (blkid_get_cache(&cache, NULL) < 0) { printf("ERROR: lblkid cache get failed\n"); return 1; } @@ -122,392 +126,6 @@ int test_uuid_unique(char *fs_uuid) return unique; } -int make_btrfs(int fd, const char *device, const char *label, char *fs_uuid, - u64 blocks[7], u64 num_bytes, u32 nodesize, - u32 leafsize, u32 sectorsize, u32 stripesize, u64 features) -{ - struct btrfs_super_block super; - struct extent_buffer *buf = NULL; - struct btrfs_root_item root_item; - struct btrfs_disk_key disk_key; - struct btrfs_extent_item *extent_item; - struct btrfs_inode_item *inode_item; - struct btrfs_chunk *chunk; - struct btrfs_dev_item *dev_item; - struct btrfs_dev_extent *dev_extent; - u8 chunk_tree_uuid[BTRFS_UUID_SIZE]; - u8 *ptr; - int i; - int ret; - u32 itemoff; - u32 nritems = 0; - u64 first_free; - u64 ref_root; - u32 array_size; - u32 item_size; - int skinny_metadata = !!(features & - BTRFS_FEATURE_INCOMPAT_SKINNY_METADATA); - - first_free = BTRFS_SUPER_INFO_OFFSET + sectorsize * 2 - 1; - first_free &= ~((u64)sectorsize - 1); - - memset(&super, 0, sizeof(super)); - - num_bytes = (num_bytes / sectorsize) * sectorsize; - if (fs_uuid) { - if (uuid_parse(fs_uuid, super.fsid) != 0) { - fprintf(stderr, "could not parse UUID: %s\n", fs_uuid); - ret = -EINVAL; - goto out; - } - if (!test_uuid_unique(fs_uuid)) { - fprintf(stderr, "non-unique UUID: %s\n", fs_uuid); - ret = -EBUSY; - goto out; - } - } else { - uuid_generate(super.fsid); - } - uuid_generate(super.dev_item.uuid); - uuid_generate(chunk_tree_uuid); - - btrfs_set_super_bytenr(&super, blocks[0]); - btrfs_set_super_num_devices(&super, 1); - btrfs_set_super_magic(&super, BTRFS_MAGIC); - btrfs_set_super_generation(&super, 1); - btrfs_set_super_root(&super, blocks[1]); - btrfs_set_super_chunk_root(&super, blocks[3]); - btrfs_set_super_total_bytes(&super, num_bytes); - btrfs_set_super_bytes_used(&super, 6 * leafsize); - btrfs_set_super_sectorsize(&super, sectorsize); - btrfs_set_super_leafsize(&super, leafsize); - btrfs_set_super_nodesize(&super, nodesize); - btrfs_set_super_stripesize(&super, stripesize); - btrfs_set_super_csum_type(&super, BTRFS_CSUM_TYPE_CRC32); - btrfs_set_super_chunk_root_generation(&super, 1); - btrfs_set_super_cache_generation(&super, -1); - btrfs_set_super_incompat_flags(&super, features); - if (label) - strncpy(super.label, label, BTRFS_LABEL_SIZE - 1); - - buf = malloc(sizeof(*buf) + max(sectorsize, leafsize)); - - /* create the tree of root objects */ - memset(buf->data, 0, leafsize); - buf->len = leafsize; - btrfs_set_header_bytenr(buf, blocks[1]); - btrfs_set_header_nritems(buf, 4); - btrfs_set_header_generation(buf, 1); - btrfs_set_header_backref_rev(buf, BTRFS_MIXED_BACKREF_REV); - btrfs_set_header_owner(buf, BTRFS_ROOT_TREE_OBJECTID); - write_extent_buffer(buf, super.fsid, btrfs_header_fsid(), - BTRFS_FSID_SIZE); - - write_extent_buffer(buf, chunk_tree_uuid, - btrfs_header_chunk_tree_uuid(buf), - BTRFS_UUID_SIZE); - - /* create the items for the root tree */ - memset(&root_item, 0, sizeof(root_item)); - inode_item = &root_item.inode; - btrfs_set_stack_inode_generation(inode_item, 1); - btrfs_set_stack_inode_size(inode_item, 3); - btrfs_set_stack_inode_nlink(inode_item, 1); - btrfs_set_stack_inode_nbytes(inode_item, leafsize); - btrfs_set_stack_inode_mode(inode_item, S_IFDIR | 0755); - btrfs_set_root_refs(&root_item, 1); - btrfs_set_root_used(&root_item, leafsize); - btrfs_set_root_generation(&root_item, 1); - - memset(&disk_key, 0, sizeof(disk_key)); - btrfs_set_disk_key_type(&disk_key, BTRFS_ROOT_ITEM_KEY); - btrfs_set_disk_key_offset(&disk_key, 0); - nritems = 0; - - itemoff = __BTRFS_LEAF_DATA_SIZE(leafsize) - sizeof(root_item); - btrfs_set_root_bytenr(&root_item, blocks[2]); - btrfs_set_disk_key_objectid(&disk_key, BTRFS_EXTENT_TREE_OBJECTID); - btrfs_set_item_key(buf, &disk_key, nritems); - btrfs_set_item_offset(buf, btrfs_item_nr(nritems), itemoff); - btrfs_set_item_size(buf, btrfs_item_nr(nritems), - sizeof(root_item)); - write_extent_buffer(buf, &root_item, btrfs_item_ptr_offset(buf, - nritems), sizeof(root_item)); - nritems++; - - itemoff = itemoff - sizeof(root_item); - btrfs_set_root_bytenr(&root_item, blocks[4]); - btrfs_set_disk_key_objectid(&disk_key, BTRFS_DEV_TREE_OBJECTID); - btrfs_set_item_key(buf, &disk_key, nritems); - btrfs_set_item_offset(buf, btrfs_item_nr(nritems), itemoff); - btrfs_set_item_size(buf, btrfs_item_nr(nritems), - sizeof(root_item)); - write_extent_buffer(buf, &root_item, - btrfs_item_ptr_offset(buf, nritems), - sizeof(root_item)); - nritems++; - - itemoff = itemoff - sizeof(root_item); - btrfs_set_root_bytenr(&root_item, blocks[5]); - btrfs_set_disk_key_objectid(&disk_key, BTRFS_FS_TREE_OBJECTID); - btrfs_set_item_key(buf, &disk_key, nritems); - btrfs_set_item_offset(buf, btrfs_item_nr(nritems), itemoff); - btrfs_set_item_size(buf, btrfs_item_nr(nritems), - sizeof(root_item)); - write_extent_buffer(buf, &root_item, - btrfs_item_ptr_offset(buf, nritems), - sizeof(root_item)); - nritems++; - - itemoff = itemoff - sizeof(root_item); - btrfs_set_root_bytenr(&root_item, blocks[6]); - btrfs_set_disk_key_objectid(&disk_key, BTRFS_CSUM_TREE_OBJECTID); - btrfs_set_item_key(buf, &disk_key, nritems); - btrfs_set_item_offset(buf, btrfs_item_nr(nritems), itemoff); - btrfs_set_item_size(buf, btrfs_item_nr(nritems), - sizeof(root_item)); - write_extent_buffer(buf, &root_item, - btrfs_item_ptr_offset(buf, nritems), - sizeof(root_item)); - nritems++; - - - csum_tree_block_size(buf, BTRFS_CRC32_SIZE, 0); - ret = pwrite(fd, buf->data, leafsize, blocks[1]); - if (ret != leafsize) { - ret = (ret < 0 ? -errno : -EIO); - goto out; - } - - /* create the items for the extent tree */ - memset(buf->data+sizeof(struct btrfs_header), 0, - leafsize-sizeof(struct btrfs_header)); - nritems = 0; - itemoff = __BTRFS_LEAF_DATA_SIZE(leafsize); - for (i = 1; i < 7; i++) { - item_size = sizeof(struct btrfs_extent_item); - if (!skinny_metadata) - item_size += sizeof(struct btrfs_tree_block_info); - - BUG_ON(blocks[i] < first_free); - BUG_ON(blocks[i] < blocks[i - 1]); - - /* create extent item */ - itemoff -= item_size; - btrfs_set_disk_key_objectid(&disk_key, blocks[i]); - if (skinny_metadata) { - btrfs_set_disk_key_type(&disk_key, - BTRFS_METADATA_ITEM_KEY); - btrfs_set_disk_key_offset(&disk_key, 0); - } else { - btrfs_set_disk_key_type(&disk_key, - BTRFS_EXTENT_ITEM_KEY); - btrfs_set_disk_key_offset(&disk_key, leafsize); - } - btrfs_set_item_key(buf, &disk_key, nritems); - btrfs_set_item_offset(buf, btrfs_item_nr(nritems), - itemoff); - btrfs_set_item_size(buf, btrfs_item_nr(nritems), - item_size); - extent_item = btrfs_item_ptr(buf, nritems, - struct btrfs_extent_item); - btrfs_set_extent_refs(buf, extent_item, 1); - btrfs_set_extent_generation(buf, extent_item, 1); - btrfs_set_extent_flags(buf, extent_item, - BTRFS_EXTENT_FLAG_TREE_BLOCK); - nritems++; - - /* create extent ref */ - ref_root = reference_root_table[i]; - btrfs_set_disk_key_objectid(&disk_key, blocks[i]); - btrfs_set_disk_key_offset(&disk_key, ref_root); - btrfs_set_disk_key_type(&disk_key, BTRFS_TREE_BLOCK_REF_KEY); - btrfs_set_item_key(buf, &disk_key, nritems); - btrfs_set_item_offset(buf, btrfs_item_nr(nritems), - itemoff); - btrfs_set_item_size(buf, btrfs_item_nr(nritems), 0); - nritems++; - } - btrfs_set_header_bytenr(buf, blocks[2]); - btrfs_set_header_owner(buf, BTRFS_EXTENT_TREE_OBJECTID); - btrfs_set_header_nritems(buf, nritems); - csum_tree_block_size(buf, BTRFS_CRC32_SIZE, 0); - ret = pwrite(fd, buf->data, leafsize, blocks[2]); - if (ret != leafsize) { - ret = (ret < 0 ? -errno : -EIO); - goto out; - } - - /* create the chunk tree */ - memset(buf->data+sizeof(struct btrfs_header), 0, - leafsize-sizeof(struct btrfs_header)); - nritems = 0; - item_size = sizeof(*dev_item); - itemoff = __BTRFS_LEAF_DATA_SIZE(leafsize) - item_size; - - /* first device 1 (there is no device 0) */ - btrfs_set_disk_key_objectid(&disk_key, BTRFS_DEV_ITEMS_OBJECTID); - btrfs_set_disk_key_offset(&disk_key, 1); - btrfs_set_disk_key_type(&disk_key, BTRFS_DEV_ITEM_KEY); - btrfs_set_item_key(buf, &disk_key, nritems); - btrfs_set_item_offset(buf, btrfs_item_nr(nritems), itemoff); - btrfs_set_item_size(buf, btrfs_item_nr(nritems), item_size); - - dev_item = btrfs_item_ptr(buf, nritems, struct btrfs_dev_item); - btrfs_set_device_id(buf, dev_item, 1); - btrfs_set_device_generation(buf, dev_item, 0); - btrfs_set_device_total_bytes(buf, dev_item, num_bytes); - btrfs_set_device_bytes_used(buf, dev_item, - BTRFS_MKFS_SYSTEM_GROUP_SIZE); - btrfs_set_device_io_align(buf, dev_item, sectorsize); - btrfs_set_device_io_width(buf, dev_item, sectorsize); - btrfs_set_device_sector_size(buf, dev_item, sectorsize); - btrfs_set_device_type(buf, dev_item, 0); - - write_extent_buffer(buf, super.dev_item.uuid, - (unsigned long)btrfs_device_uuid(dev_item), - BTRFS_UUID_SIZE); - write_extent_buffer(buf, super.fsid, - (unsigned long)btrfs_device_fsid(dev_item), - BTRFS_UUID_SIZE); - read_extent_buffer(buf, &super.dev_item, (unsigned long)dev_item, - sizeof(*dev_item)); - - nritems++; - item_size = btrfs_chunk_item_size(1); - itemoff = itemoff - item_size; - - /* then we have chunk 0 */ - btrfs_set_disk_key_objectid(&disk_key, BTRFS_FIRST_CHUNK_TREE_OBJECTID); - btrfs_set_disk_key_offset(&disk_key, 0); - btrfs_set_disk_key_type(&disk_key, BTRFS_CHUNK_ITEM_KEY); - btrfs_set_item_key(buf, &disk_key, nritems); - btrfs_set_item_offset(buf, btrfs_item_nr(nritems), itemoff); - btrfs_set_item_size(buf, btrfs_item_nr(nritems), item_size); - - chunk = btrfs_item_ptr(buf, nritems, struct btrfs_chunk); - btrfs_set_chunk_length(buf, chunk, BTRFS_MKFS_SYSTEM_GROUP_SIZE); - btrfs_set_chunk_owner(buf, chunk, BTRFS_EXTENT_TREE_OBJECTID); - btrfs_set_chunk_stripe_len(buf, chunk, 64 * 1024); - btrfs_set_chunk_type(buf, chunk, BTRFS_BLOCK_GROUP_SYSTEM); - btrfs_set_chunk_io_align(buf, chunk, sectorsize); - btrfs_set_chunk_io_width(buf, chunk, sectorsize); - btrfs_set_chunk_sector_size(buf, chunk, sectorsize); - btrfs_set_chunk_num_stripes(buf, chunk, 1); - btrfs_set_stripe_devid_nr(buf, chunk, 0, 1); - btrfs_set_stripe_offset_nr(buf, chunk, 0, 0); - nritems++; - - write_extent_buffer(buf, super.dev_item.uuid, - (unsigned long)btrfs_stripe_dev_uuid(&chunk->stripe), - BTRFS_UUID_SIZE); - - /* copy the key for the chunk to the system array */ - ptr = super.sys_chunk_array; - array_size = sizeof(disk_key); - - memcpy(ptr, &disk_key, sizeof(disk_key)); - ptr += sizeof(disk_key); - - /* copy the chunk to the system array */ - read_extent_buffer(buf, ptr, (unsigned long)chunk, item_size); - array_size += item_size; - ptr += item_size; - btrfs_set_super_sys_array_size(&super, array_size); - - btrfs_set_header_bytenr(buf, blocks[3]); - btrfs_set_header_owner(buf, BTRFS_CHUNK_TREE_OBJECTID); - btrfs_set_header_nritems(buf, nritems); - csum_tree_block_size(buf, BTRFS_CRC32_SIZE, 0); - ret = pwrite(fd, buf->data, leafsize, blocks[3]); - if (ret != leafsize) { - ret = (ret < 0 ? -errno : -EIO); - goto out; - } - - /* create the device tree */ - memset(buf->data+sizeof(struct btrfs_header), 0, - leafsize-sizeof(struct btrfs_header)); - nritems = 0; - itemoff = __BTRFS_LEAF_DATA_SIZE(leafsize) - - sizeof(struct btrfs_dev_extent); - - btrfs_set_disk_key_objectid(&disk_key, 1); - btrfs_set_disk_key_offset(&disk_key, 0); - btrfs_set_disk_key_type(&disk_key, BTRFS_DEV_EXTENT_KEY); - btrfs_set_item_key(buf, &disk_key, nritems); - btrfs_set_item_offset(buf, btrfs_item_nr(nritems), itemoff); - btrfs_set_item_size(buf, btrfs_item_nr(nritems), - sizeof(struct btrfs_dev_extent)); - dev_extent = btrfs_item_ptr(buf, nritems, struct btrfs_dev_extent); - btrfs_set_dev_extent_chunk_tree(buf, dev_extent, - BTRFS_CHUNK_TREE_OBJECTID); - btrfs_set_dev_extent_chunk_objectid(buf, dev_extent, - BTRFS_FIRST_CHUNK_TREE_OBJECTID); - btrfs_set_dev_extent_chunk_offset(buf, dev_extent, 0); - - write_extent_buffer(buf, chunk_tree_uuid, - (unsigned long)btrfs_dev_extent_chunk_tree_uuid(dev_extent), - BTRFS_UUID_SIZE); - - btrfs_set_dev_extent_length(buf, dev_extent, - BTRFS_MKFS_SYSTEM_GROUP_SIZE); - nritems++; - - btrfs_set_header_bytenr(buf, blocks[4]); - btrfs_set_header_owner(buf, BTRFS_DEV_TREE_OBJECTID); - btrfs_set_header_nritems(buf, nritems); - csum_tree_block_size(buf, BTRFS_CRC32_SIZE, 0); - ret = pwrite(fd, buf->data, leafsize, blocks[4]); - if (ret != leafsize) { - ret = (ret < 0 ? -errno : -EIO); - goto out; - } - - /* create the FS root */ - memset(buf->data+sizeof(struct btrfs_header), 0, - leafsize-sizeof(struct btrfs_header)); - btrfs_set_header_bytenr(buf, blocks[5]); - btrfs_set_header_owner(buf, BTRFS_FS_TREE_OBJECTID); - btrfs_set_header_nritems(buf, 0); - csum_tree_block_size(buf, BTRFS_CRC32_SIZE, 0); - ret = pwrite(fd, buf->data, leafsize, blocks[5]); - if (ret != leafsize) { - ret = (ret < 0 ? -errno : -EIO); - goto out; - } - /* finally create the csum root */ - memset(buf->data+sizeof(struct btrfs_header), 0, - leafsize-sizeof(struct btrfs_header)); - btrfs_set_header_bytenr(buf, blocks[6]); - btrfs_set_header_owner(buf, BTRFS_CSUM_TREE_OBJECTID); - btrfs_set_header_nritems(buf, 0); - csum_tree_block_size(buf, BTRFS_CRC32_SIZE, 0); - ret = pwrite(fd, buf->data, leafsize, blocks[6]); - if (ret != leafsize) { - ret = (ret < 0 ? -errno : -EIO); - goto out; - } - - /* and write out the super block */ - BUG_ON(sizeof(super) > sectorsize); - memset(buf->data, 0, sectorsize); - memcpy(buf->data, &super, sizeof(super)); - buf->len = sectorsize; - csum_tree_block_size(buf, BTRFS_CRC32_SIZE, 0); - ret = pwrite(fd, buf->data, sectorsize, blocks[0]); - if (ret != sectorsize) { - ret = (ret < 0 ? -errno : -EIO); - goto out; - } - - ret = 0; - -out: - free(buf); - return ret; -} - u64 btrfs_device_size(int fd, struct stat *st) { u64 size; @@ -539,51 +157,52 @@ static int zero_blocks(int fd, off_t start, size_t len) return ret; } -static int zero_dev_start(int fd) +#define ZERO_DEV_BYTES SZ_2M + +/* don't write outside the device by clamping the region to the device size */ +static int zero_dev_clamped(int fd, off_t start, ssize_t len, u64 dev_size) { - off_t start = 0; - size_t len = 2 * 1024 * 1024; + off_t end = max(start, start + len); #ifdef __sparc__ - /* don't overwrite the disk labels on sparc */ - start = 1024; - len -= 1024; + /* and don't overwrite the disk labels on sparc */ + start = max(start, 1024); + end = max(end, 1024); #endif - return zero_blocks(fd, start, len); -} -static int zero_dev_end(int fd, u64 dev_size) -{ - size_t len = 2 * 1024 * 1024; - off_t start = dev_size - len; + start = min_t(u64, start, dev_size); + end = min_t(u64, end, dev_size); - return zero_blocks(fd, start, len); + return zero_blocks(fd, start, end - start); } int btrfs_add_to_fsid(struct btrfs_trans_handle *trans, - struct btrfs_root *root, int fd, char *path, - u64 block_count, u32 io_width, u32 io_align, + struct btrfs_root *root, int fd, const char *path, + u64 device_total_bytes, u32 io_width, u32 io_align, u32 sectorsize) { struct btrfs_super_block *disk_super; - struct btrfs_super_block *super = root->fs_info->super_copy; + struct btrfs_fs_info *fs_info = root->fs_info; + struct btrfs_super_block *super = fs_info->super_copy; struct btrfs_device *device; struct btrfs_dev_item *dev_item; - char *buf; - u64 total_bytes; + char *buf = NULL; + u64 fs_total_bytes; u64 num_devs; int ret; - device = kzalloc(sizeof(*device), GFP_NOFS); - if (!device) - return -ENOMEM; - buf = kmalloc(sectorsize, GFP_NOFS); + device_total_bytes = (device_total_bytes / sectorsize) * sectorsize; + + device = calloc(1, sizeof(*device)); + if (!device) { + ret = -ENOMEM; + goto out; + } + buf = calloc(1, sectorsize); if (!buf) { - kfree(device); - return -ENOMEM; + ret = -ENOMEM; + goto out; } - BUG_ON(sizeof(*disk_super) > sectorsize); - memset(buf, 0, sectorsize); disk_super = (struct btrfs_super_block *)buf; dev_item = &disk_super->dev_item; @@ -596,25 +215,29 @@ int btrfs_add_to_fsid(struct btrfs_trans_handle *trans, device->sector_size = sectorsize; device->fd = fd; device->writeable = 1; - device->total_bytes = block_count; + device->total_bytes = device_total_bytes; device->bytes_used = 0; device->total_ios = 0; - device->dev_root = root->fs_info->dev_root; + device->dev_root = fs_info->dev_root; + device->name = strdup(path); + if (!device->name) { + ret = -ENOMEM; + goto out; + } - ret = btrfs_add_device(trans, root, device); - BUG_ON(ret); + INIT_LIST_HEAD(&device->dev_list); + ret = btrfs_add_device(trans, fs_info, device); + if (ret) + goto out; - total_bytes = btrfs_super_total_bytes(super) + block_count; - btrfs_set_super_total_bytes(super, total_bytes); + fs_total_bytes = btrfs_super_total_bytes(super) + device_total_bytes; + btrfs_set_super_total_bytes(super, fs_total_bytes); num_devs = btrfs_super_num_devices(super) + 1; btrfs_set_super_num_devices(super, num_devs); memcpy(disk_super, super, sizeof(*disk_super)); - printf("adding device %s id %llu\n", path, - (unsigned long long)device->devid); - btrfs_set_super_bytenr(disk_super, BTRFS_SUPER_INFO_OFFSET); btrfs_set_stack_device_id(dev_item, device->devid); btrfs_set_stack_device_type(dev_item, device->type); @@ -628,82 +251,123 @@ int btrfs_add_to_fsid(struct btrfs_trans_handle *trans, ret = pwrite(fd, buf, sectorsize, BTRFS_SUPER_INFO_OFFSET); BUG_ON(ret != sectorsize); - kfree(buf); - list_add(&device->dev_list, &root->fs_info->fs_devices->devices); - device->fs_devices = root->fs_info->fs_devices; + free(buf); + list_add(&device->dev_list, &fs_info->fs_devices->devices); + device->fs_devices = fs_info->fs_devices; return 0; + +out: + free(device); + free(buf); + return ret; +} + +static int btrfs_wipe_existing_sb(int fd) +{ + const char *off = NULL; + size_t len = 0; + loff_t offset; + char buf[BUFSIZ]; + int ret = 0; + blkid_probe pr = NULL; + + pr = blkid_new_probe(); + if (!pr) + return -1; + + if (blkid_probe_set_device(pr, fd, 0, 0)) { + ret = -1; + goto out; + } + + ret = blkid_probe_lookup_value(pr, "SBMAGIC_OFFSET", &off, NULL); + if (!ret) + ret = blkid_probe_lookup_value(pr, "SBMAGIC", NULL, &len); + + if (ret || len == 0 || off == NULL) { + /* + * If lookup fails, the probe did not find any values, eg. for + * a file image or a loop device. Soft error. + */ + ret = 1; + goto out; + } + + offset = strtoll(off, NULL, 10); + if (len > sizeof(buf)) + len = sizeof(buf); + + memset(buf, 0, len); + ret = pwrite(fd, buf, len, offset); + if (ret < 0) { + error("cannot wipe existing superblock: %m"); + ret = -1; + } else if (ret != len) { + error("cannot wipe existing superblock: wrote %d of %zd", ret, len); + ret = -1; + } + fsync(fd); + +out: + blkid_free_probe(pr); + return ret; } -int btrfs_prepare_device(int fd, char *file, int zero_end, u64 *block_count_ret, - u64 max_block_count, int *mixed, int discard) +int btrfs_prepare_device(int fd, const char *file, u64 *block_count_ret, + u64 max_block_count, unsigned opflags) { u64 block_count; - u64 bytenr; struct stat st; int i, ret; ret = fstat(fd, &st); if (ret < 0) { - fprintf(stderr, "unable to stat %s\n", file); + error("unable to stat %s: %m", file); return 1; } block_count = btrfs_device_size(fd, &st); if (block_count == 0) { - fprintf(stderr, "unable to find %s size\n", file); + error("unable to determine size of %s", file); return 1; } if (max_block_count) block_count = min(block_count, max_block_count); - if (block_count < 1024 * 1024 * 1024 && !(*mixed)) { - printf("SMALL VOLUME: forcing mixed metadata/data groups\n"); - *mixed = 1; - } - - if (discard) { + if (opflags & PREP_DEVICE_DISCARD) { /* * We intentionally ignore errors from the discard ioctl. It * is not necessary for the mkfs functionality but just an * optimization. */ if (discard_range(fd, 0, 0) == 0) { - fprintf(stderr, "Performing full device TRIM (%s) ...\n", - pretty_size(block_count)); + if (opflags & PREP_DEVICE_VERBOSE) + printf("Performing full device TRIM %s (%s) ...\n", + file, pretty_size(block_count)); discard_blocks(fd, 0, block_count); } } - ret = zero_dev_start(fd); - if (ret) - goto zero_dev_error; - - for (i = 0 ; i < BTRFS_SUPER_MIRROR_MAX; i++) { - bytenr = btrfs_sb_offset(i); - if (bytenr >= block_count) - break; - ret = zero_blocks(fd, bytenr, BTRFS_SUPER_INFO_SIZE); - if (ret) - goto zero_dev_error; - } + ret = zero_dev_clamped(fd, 0, ZERO_DEV_BYTES, block_count); + for (i = 0 ; !ret && i < BTRFS_SUPER_MIRROR_MAX; i++) + ret = zero_dev_clamped(fd, btrfs_sb_offset(i), + BTRFS_SUPER_INFO_SIZE, block_count); + if (!ret && (opflags & PREP_DEVICE_ZERO_END)) + ret = zero_dev_clamped(fd, block_count - ZERO_DEV_BYTES, + ZERO_DEV_BYTES, block_count); - if (zero_end) { - ret = zero_dev_end(fd, block_count); - if (ret) - goto zero_dev_error; + if (ret < 0) { + error("failed to zero device '%s': %s", file, strerror(-ret)); + return 1; } - *block_count_ret = block_count; -zero_dev_error: + ret = btrfs_wipe_existing_sb(fd); if (ret < 0) { - fprintf(stderr, "ERROR: failed to zero device '%s' - %s\n", - file, strerror(-ret)); - return 1; - } else if (ret > 0) { - fprintf(stderr, "ERROR: failed to zero device '%s' - %d\n", - file, ret); + error("cannot wipe superblocks on %s", file); return 1; } + + *block_count_ret = block_count; return 0; } @@ -718,7 +382,7 @@ int btrfs_make_root_dir(struct btrfs_trans_handle *trans, btrfs_set_stack_inode_generation(&inode_item, trans->transid); btrfs_set_stack_inode_size(&inode_item, 0); btrfs_set_stack_inode_nlink(&inode_item, 1); - btrfs_set_stack_inode_nbytes(&inode_item, root->leafsize); + btrfs_set_stack_inode_nbytes(&inode_item, root->fs_info->nodesize); btrfs_set_stack_inode_mode(&inode_item, S_IFDIR | 0755); btrfs_set_stack_timespec_sec(&inode_item.atime, now); btrfs_set_stack_timespec_nsec(&inode_item.atime, 0); @@ -726,7 +390,7 @@ int btrfs_make_root_dir(struct btrfs_trans_handle *trans, btrfs_set_stack_timespec_nsec(&inode_item.ctime, 0); btrfs_set_stack_timespec_sec(&inode_item.mtime, now); btrfs_set_stack_timespec_nsec(&inode_item.mtime, 0); - btrfs_set_stack_timespec_sec(&inode_item.otime, 0); + btrfs_set_stack_timespec_sec(&inode_item.otime, now); btrfs_set_stack_timespec_nsec(&inode_item.otime, 0); if (root->fs_info->tree_root == root) @@ -758,7 +422,7 @@ int is_block_device(const char *path) if (stat(path, &statbuf) < 0) return -errno; - return S_ISBLK(statbuf.st_mode); + return !!S_ISBLK(statbuf.st_mode); } /* @@ -785,6 +449,68 @@ int is_mount_point(const char *path) return ret; } +int is_reg_file(const char *path) +{ + struct stat statbuf; + + if (stat(path, &statbuf) < 0) + return -errno; + return S_ISREG(statbuf.st_mode); +} + +int is_path_exist(const char *path) +{ + struct stat statbuf; + int ret; + + ret = stat(path, &statbuf); + if (ret < 0) { + if (errno == ENOENT) + return 0; + else + return -errno; + } + return 1; +} + +/* + * This function checks if the given input parameter is + * an uuid or a path + * return <0 : some error in the given input + * return BTRFS_ARG_UNKNOWN: unknown input + * return BTRFS_ARG_UUID: given input is uuid + * return BTRFS_ARG_MNTPOINT: given input is path + * return BTRFS_ARG_REG: given input is regular file + * return BTRFS_ARG_BLKDEV: given input is block device + */ +int check_arg_type(const char *input) +{ + uuid_t uuid; + char path[PATH_MAX]; + + if (!input) + return -EINVAL; + + if (realpath(input, path)) { + if (is_block_device(path) == 1) + return BTRFS_ARG_BLKDEV; + + if (is_mount_point(path) == 1) + return BTRFS_ARG_MNTPOINT; + + if (is_reg_file(path)) + return BTRFS_ARG_REG; + + return BTRFS_ARG_UNKNOWN; + } + + if (strlen(input) == (BTRFS_UUID_UNPARSED_SIZE - 1) && + !uuid_parse(input, uuid)) + return BTRFS_ARG_UUID; + + return BTRFS_ARG_UNKNOWN; +} + /* * Find the mount point for a mounted device. * On success, returns 0 with mountpoint in *mp. @@ -799,11 +525,10 @@ int get_btrfs_mount(const char *dev, char *mp, size_t mp_size) ret = is_block_device(dev); if (ret <= 0) { if (!ret) { - fprintf(stderr, "%s is not a block device\n", dev); + error("not a block device: %s", dev); ret = -EINVAL; } else { - fprintf(stderr, "Could not check %s: %s\n", - dev, strerror(-ret)); + error("cannot check %s: %s", dev, strerror(-ret)); } goto out; } @@ -811,7 +536,7 @@ int get_btrfs_mount(const char *dev, char *mp, size_t mp_size) fd = open(dev, O_RDONLY); if (fd < 0) { ret = -errno; - fprintf(stderr, "Could not open %s: %s\n", dev, strerror(errno)); + error("cannot open %s: %m", dev); goto out; } @@ -834,26 +559,77 @@ out: * * On error, return -1, errno should be set. */ -int open_path_or_dev_mnt(const char *path, DIR **dirstream) +int open_path_or_dev_mnt(const char *path, DIR **dirstream, int verbose) { - char mp[BTRFS_PATH_NAME_MAX + 1]; - int fdmnt; + char mp[PATH_MAX]; + int ret; if (is_block_device(path)) { - int ret; - ret = get_btrfs_mount(path, mp, sizeof(mp)); if (ret < 0) { /* not a mounted btrfs dev */ + error_on(verbose, "'%s' is not a mounted btrfs device", + path); errno = EINVAL; return -1; } - fdmnt = open_file_or_dir(mp, dirstream); + ret = open_file_or_dir(mp, dirstream); + error_on(verbose && ret < 0, "can't access '%s': %m", + path); } else { - fdmnt = open_file_or_dir(path, dirstream); + ret = btrfs_open_dir(path, dirstream, 1); + } + + return ret; +} + +/* + * Do the following checks before calling open_file_or_dir(): + * 1: path is in a btrfs filesystem + * 2: path is a directory if dir_only is 1 + */ +int btrfs_open(const char *path, DIR **dirstream, int verbose, int dir_only) +{ + struct statfs stfs; + struct stat st; + int ret; + + if (statfs(path, &stfs) != 0) { + error_on(verbose, "cannot access '%s': %m", path); + return -1; + } + + if (stfs.f_type != BTRFS_SUPER_MAGIC) { + error_on(verbose, "not a btrfs filesystem: %s", path); + return -2; + } + + if (stat(path, &st) != 0) { + error_on(verbose, "cannot access '%s': %m", path); + return -1; + } + + if (dir_only && !S_ISDIR(st.st_mode)) { + error_on(verbose, "not a directory: %s", path); + return -3; + } + + ret = open_file_or_dir(path, dirstream); + if (ret < 0) { + error_on(verbose, "cannot access '%s': %m", path); } - return fdmnt; + return ret; +} + +int btrfs_open_dir(const char *path, DIR **dirstream, int verbose) +{ + return btrfs_open(path, dirstream, verbose, 1); +} + +int btrfs_open_file_or_dir(const char *path, DIR **dirstream, int verbose) +{ + return btrfs_open(path, dirstream, verbose, 0); } /* checks if a device is a loop device */ @@ -867,6 +643,34 @@ static int is_loop_device (const char* device) { MAJOR(statbuf.st_rdev) == LOOP_MAJOR); } +/* + * Takes a loop device path (e.g. /dev/loop0) and returns + * the associated file (e.g. /images/my_btrfs.img) using + * loopdev API + */ +static int resolve_loop_device_with_loopdev(const char* loop_dev, char* loop_file) +{ + int fd; + int ret; + struct loop_info64 lo64; + + fd = open(loop_dev, O_RDONLY | O_NONBLOCK); + if (fd < 0) + return -errno; + ret = ioctl(fd, LOOP_GET_STATUS64, &lo64); + if (ret < 0) { + ret = -errno; + goto out; + } + + memcpy(loop_file, lo64.lo_file_name, sizeof(lo64.lo_file_name)); + loop_file[sizeof(lo64.lo_file_name)] = 0; + +out: + close(fd); + + return ret; +} /* Takes a loop device path (e.g. /dev/loop0) and returns * the associated file (e.g. /images/my_btrfs.img) */ @@ -882,8 +686,15 @@ static int resolve_loop_device(const char* loop_dev, char* loop_file, if (!realpath(loop_dev, real_loop_dev)) return -errno; snprintf(p, PATH_MAX, "/sys/block/%s/loop/backing_file", strrchr(real_loop_dev, '/')); - if (!(f = fopen(p, "r"))) + if (!(f = fopen(p, "r"))) { + if (errno == ENOENT) + /* + * It's possibly a partitioned loop device, which is + * resolvable with loopdev API. + */ + return resolve_loop_device_with_loopdev(loop_dev, loop_file); return -errno; + } snprintf(fmt, 20, "%%%i[^\n]", max_len-1); ret = fscanf(f, fmt, loop_file); @@ -894,7 +705,8 @@ static int resolve_loop_device(const char* loop_dev, char* loop_file, return 0; } -/* Checks whether a and b are identical or device +/* + * Checks whether a and b are identical or device * files associated with the same block device */ static int is_same_blk_file(const char* a, const char* b) @@ -903,36 +715,31 @@ static int is_same_blk_file(const char* a, const char* b) char real_a[PATH_MAX]; char real_b[PATH_MAX]; - if(!realpath(a, real_a)) - strcpy(real_a, a); + if (!realpath(a, real_a)) + strncpy_null(real_a, a); if (!realpath(b, real_b)) - strcpy(real_b, b); + strncpy_null(real_b, b); /* Identical path? */ - if(strcmp(real_a, real_b) == 0) + if (strcmp(real_a, real_b) == 0) return 1; - if(stat(a, &st_buf_a) < 0 || - stat(b, &st_buf_b) < 0) - { + if (stat(a, &st_buf_a) < 0 || stat(b, &st_buf_b) < 0) { if (errno == ENOENT) return 0; return -errno; } /* Same blockdevice? */ - if(S_ISBLK(st_buf_a.st_mode) && - S_ISBLK(st_buf_b.st_mode) && - st_buf_a.st_rdev == st_buf_b.st_rdev) - { + if (S_ISBLK(st_buf_a.st_mode) && S_ISBLK(st_buf_b.st_mode) && + st_buf_a.st_rdev == st_buf_b.st_rdev) { return 1; } /* Hardlink? */ if (st_buf_a.st_dev == st_buf_b.st_dev && - st_buf_a.st_ino == st_buf_b.st_ino) - { + st_buf_a.st_ino == st_buf_b.st_ino) { return 1; } @@ -1011,14 +818,9 @@ static int blk_file_in_dev_list(struct btrfs_fs_devices* fs_devices, const char* file) { int ret; - struct list_head *head; - struct list_head *cur; struct btrfs_device *device; - head = &fs_devices->devices; - list_for_each(cur, head) { - device = list_entry(cur, struct btrfs_device, dev_list); - + list_for_each_entry(device, &fs_devices->devices, dev_list) { if((ret = is_same_loop_file(device->name, file))) return ret; } @@ -1095,7 +897,7 @@ int check_mounted(const char* file) fd = open(file, O_RDONLY); if (fd < 0) { - fprintf (stderr, "check_mounted(): Could not open %s\n", file); + error("mount check: cannot open %s: %m", file); return -errno; } @@ -1117,16 +919,17 @@ int check_mounted_where(int fd, const char *file, char *where, int size, /* scan the initial device */ ret = btrfs_scan_one_device(fd, file, &fs_devices_mnt, - &total_devs, BTRFS_SUPER_INFO_OFFSET); + &total_devs, BTRFS_SUPER_INFO_OFFSET, SBREAD_DEFAULT); is_btrfs = (ret >= 0); /* scan other devices */ if (is_btrfs && total_devs > 1) { - if ((ret = btrfs_scan_for_fsid(!BTRFS_UPDATE_KERNEL))) + ret = btrfs_scan_devices(); + if (ret) return ret; } - /* iterate over the list of currently mountes filesystems */ + /* iterate over the list of currently mounted filesystems */ if ((f = setmntent ("/proc/self/mounts", "r")) == NULL) return -errno; @@ -1174,149 +977,53 @@ struct pending_dir { char name[PATH_MAX]; }; -void btrfs_register_one_device(char *fname) +int btrfs_register_one_device(const char *fname) { struct btrfs_ioctl_vol_args args; int fd; int ret; - int e; - fd = open("/dev/btrfs-control", O_RDONLY); + fd = open("/dev/btrfs-control", O_RDWR); if (fd < 0) { - fprintf(stderr, "failed to open /dev/btrfs-control " - "skipping device registration: %s\n", - strerror(errno)); - return; + warning( + "failed to open /dev/btrfs-control, skipping device registration: %m"); + return -errno; } - strncpy(args.name, fname, BTRFS_PATH_NAME_MAX); - args.name[BTRFS_PATH_NAME_MAX-1] = 0; + memset(&args, 0, sizeof(args)); + strncpy_null(args.name, fname); ret = ioctl(fd, BTRFS_IOC_SCAN_DEV, &args); - e = errno; - if(ret<0){ - fprintf(stderr, "ERROR: device scan failed '%s' - %s\n", - fname, strerror(e)); + if (ret < 0) { + error("device scan failed on '%s': %m", fname); + ret = -errno; } close(fd); + return ret; } -int btrfs_scan_one_dir(char *dirname, int run_ioctl) +/* + * Register all devices in the fs_uuid list created in the user + * space. Ensure btrfs_scan_devices() is called before this func. + */ +int btrfs_register_all_devices(void) { - DIR *dirp = NULL; - struct dirent *dirent; - struct pending_dir *pending; - struct stat st; - int ret; - int fd; - int dirname_len; - char *fullpath; - struct list_head pending_list; - struct btrfs_fs_devices *tmp_devices; - u64 num_devices; - - INIT_LIST_HEAD(&pending_list); + int err = 0; + int ret = 0; + struct btrfs_fs_devices *fs_devices; + struct btrfs_device *device; + struct list_head *all_uuids; - pending = malloc(sizeof(*pending)); - if (!pending) - return -ENOMEM; - strcpy(pending->name, dirname); + all_uuids = btrfs_scanned_uuids(); -again: - dirname_len = strlen(pending->name); - fullpath = malloc(PATH_MAX); - dirname = pending->name; + list_for_each_entry(fs_devices, all_uuids, list) { + list_for_each_entry(device, &fs_devices->devices, dev_list) { + if (*device->name) + err = btrfs_register_one_device(device->name); - if (!fullpath) { - ret = -ENOMEM; - goto fail; - } - dirp = opendir(dirname); - if (!dirp) { - fprintf(stderr, "Unable to open %s for scanning\n", dirname); - ret = -errno; - goto fail; - } - while(1) { - dirent = readdir(dirp); - if (!dirent) - break; - if (dirent->d_name[0] == '.') - continue; - if (dirname_len + strlen(dirent->d_name) + 2 > PATH_MAX) { - ret = -EFAULT; - goto fail; - } - snprintf(fullpath, PATH_MAX, "%s/%s", dirname, dirent->d_name); - ret = lstat(fullpath, &st); - if (ret < 0) { - fprintf(stderr, "failed to stat %s\n", fullpath); - continue; + if (err) + ret++; } - if (S_ISLNK(st.st_mode)) - continue; - if (S_ISDIR(st.st_mode)) { - struct pending_dir *next = malloc(sizeof(*next)); - if (!next) { - ret = -ENOMEM; - goto fail; - } - strcpy(next->name, fullpath); - list_add_tail(&next->list, &pending_list); - } - if (!S_ISBLK(st.st_mode)) { - continue; - } - fd = open(fullpath, O_RDONLY); - if (fd < 0) { - /* ignore the following errors: - ENXIO (device don't exists) - ENOMEDIUM (No medium found -> - like a cd tray empty) - */ - if(errno != ENXIO && errno != ENOMEDIUM) - fprintf(stderr, "failed to read %s: %s\n", - fullpath, strerror(errno)); - continue; - } - ret = btrfs_scan_one_device(fd, fullpath, &tmp_devices, - &num_devices, - BTRFS_SUPER_INFO_OFFSET); - if (ret == 0 && run_ioctl > 0) { - btrfs_register_one_device(fullpath); - } - close(fd); - } - if (!list_empty(&pending_list)) { - free(pending); - pending = list_entry(pending_list.next, struct pending_dir, - list); - free(fullpath); - list_del(&pending->list); - closedir(dirp); - dirp = NULL; - goto again; } - ret = 0; -fail: - free(pending); - free(fullpath); - while (!list_empty(&pending_list)) { - pending = list_entry(pending_list.next, struct pending_dir, - list); - list_del(&pending->list); - free(pending); - } - if (dirp) - closedir(dirp); - return ret; -} - -int btrfs_scan_for_fsid(int run_ioctls) -{ - int ret; - ret = scan_for_btrfs(BTRFS_SCAN_PROC, run_ioctls); - if (ret) - ret = scan_for_btrfs(BTRFS_SCAN_DEV, run_ioctls); return ret; } @@ -1338,7 +1045,12 @@ int btrfs_device_already_in_root(struct btrfs_root *root, int fd, ret = 0; disk_super = (struct btrfs_super_block *)buf; - if (btrfs_super_magic(disk_super) != BTRFS_MAGIC) + /* + * Accept devices from the same filesystem, allow partially created + * structures. + */ + if (btrfs_super_magic(disk_super) != BTRFS_MAGIC && + btrfs_super_magic(disk_super) != BTRFS_MAGIC_PARTIAL) goto brelse; if (!memcmp(disk_super->fsid, root->fs_info->super_copy->fsid, @@ -1350,39 +1062,129 @@ out: return ret; } -static char *size_strs[] = { "", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"}; -int pretty_size_snprintf(u64 size, char *str, size_t str_bytes) +/* + * Note: this function uses a static per-thread buffer. Do not call this + * function more than 10 times within one argument list! + */ +const char *pretty_size_mode(u64 size, unsigned mode) +{ + static __thread int ps_index = 0; + static __thread char ps_array[10][32]; + char *ret; + + ret = ps_array[ps_index]; + ps_index++; + ps_index %= 10; + (void)pretty_size_snprintf(size, ret, 32, mode); + + return ret; +} + +static const char* unit_suffix_binary[] = + { "B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"}; +static const char* unit_suffix_decimal[] = + { "B", "kB", "MB", "GB", "TB", "PB", "EB"}; + +int pretty_size_snprintf(u64 size, char *str, size_t str_size, unsigned unit_mode) { - int num_divs = 0; + int num_divs; float fraction; + u64 base = 0; + int mult = 0; + const char** suffix = NULL; + u64 last_size; + int negative; - if (str_bytes == 0) + if (str_size == 0) return 0; - if( size < 1024 ){ - fraction = size; - num_divs = 0; - } else { - u64 last_size = size; - num_divs = 0; - while(size >= 1024){ - last_size = size; - size /= 1024; - num_divs ++; - } + negative = !!(unit_mode & UNITS_NEGATIVE); + unit_mode &= ~UNITS_NEGATIVE; - if (num_divs >= ARRAY_SIZE(size_strs)) { - str[0] = '\0'; - return -1; + if ((unit_mode & ~UNITS_MODE_MASK) == UNITS_RAW) { + if (negative) + snprintf(str, str_size, "%lld", size); + else + snprintf(str, str_size, "%llu", size); + return 0; + } + + if ((unit_mode & ~UNITS_MODE_MASK) == UNITS_BINARY) { + base = 1024; + mult = 1024; + suffix = unit_suffix_binary; + } else if ((unit_mode & ~UNITS_MODE_MASK) == UNITS_DECIMAL) { + base = 1000; + mult = 1000; + suffix = unit_suffix_decimal; + } + + /* Unknown mode */ + if (!base) { + fprintf(stderr, "INTERNAL ERROR: unknown unit base, mode %d\n", + unit_mode); + assert(0); + return -1; + } + + num_divs = 0; + last_size = size; + switch (unit_mode & UNITS_MODE_MASK) { + case UNITS_TBYTES: base *= mult; num_divs++; + case UNITS_GBYTES: base *= mult; num_divs++; + case UNITS_MBYTES: base *= mult; num_divs++; + case UNITS_KBYTES: num_divs++; + break; + case UNITS_BYTES: + base = 1; + num_divs = 0; + break; + default: + if (negative) { + s64 ssize = (s64)size; + s64 last_ssize = ssize; + + while ((ssize < 0 ? -ssize : ssize) >= mult) { + last_ssize = ssize; + ssize /= mult; + num_divs++; + } + last_size = (u64)last_ssize; + } else { + while (size >= mult) { + last_size = size; + size /= mult; + num_divs++; + } } - fraction = (float)last_size / 1024; + /* + * If the value is smaller than base, we didn't do any + * division, in that case, base should be 1, not original + * base, or the unit will be wrong + */ + if (num_divs == 0) + base = 1; } - return snprintf(str, str_bytes, "%.2f%s", fraction, - size_strs[num_divs]); + + if (num_divs >= ARRAY_SIZE(unit_suffix_binary)) { + str[0] = '\0'; + printf("INTERNAL ERROR: unsupported unit suffix, index %d\n", + num_divs); + assert(0); + return -1; + } + + if (negative) { + fraction = (float)(s64)last_size / base; + } else { + fraction = (float)last_size / base; + } + + return snprintf(str, str_size, "%.2f%s", fraction, suffix[num_divs]); } /* - * __strncpy__null - strncpy with null termination + * __strncpy_null - strncpy with null termination * @dest: the target array * @src: the source string * @n: maximum bytes to copy (size of *dest) @@ -1393,7 +1195,7 @@ int pretty_size_snprintf(u64 size, char *str, size_t str_bytes) * byte ('\0'), to the buffer pointed to by dest, up to a maximum * of n bytes. Then ensure that dest is null-terminated. */ -char *__strncpy__null(char *dest, const char *src, size_t n) +char *__strncpy_null(char *dest, const char *src, size_t n) { strncpy(dest, src, n); if (n > 0) @@ -1412,8 +1214,8 @@ static int check_label(const char *input) int len = strlen(input); if (len > BTRFS_LABEL_SIZE - 1) { - fprintf(stderr, "ERROR: Label %s is too long (max %d)\n", - input, BTRFS_LABEL_SIZE - 1); + error("label %s is too long (max %d)", input, + BTRFS_LABEL_SIZE - 1); return -1; } @@ -1428,12 +1230,11 @@ static int set_label_unmounted(const char *dev, const char *label) ret = check_mounted(dev); if (ret < 0) { - fprintf(stderr, "FATAL: error checking %s mount status\n", dev); + error("checking mount status of %s failed: %d", dev, ret); return -1; } if (ret > 0) { - fprintf(stderr, "ERROR: dev %s is mounted, use mount point\n", - dev); + error("device %s is mounted, use mount point", dev); return -1; } @@ -1445,8 +1246,9 @@ static int set_label_unmounted(const char *dev, const char *label) return -1; trans = btrfs_start_transaction(root, 1); - snprintf(root->fs_info->super_copy->label, BTRFS_LABEL_SIZE, "%s", - label); + BUG_ON(IS_ERR(trans)); + __strncpy_null(root->fs_info->super_copy->label, label, BTRFS_LABEL_SIZE - 1); + btrfs_commit_transaction(trans, root); /* Now we close it since we are done. */ @@ -1454,19 +1256,21 @@ static int set_label_unmounted(const char *dev, const char *label) return 0; } -static int set_label_mounted(const char *mount_path, const char *label) +static int set_label_mounted(const char *mount_path, const char *labelp) { int fd; + char label[BTRFS_LABEL_SIZE]; fd = open(mount_path, O_RDONLY | O_NOATIME); if (fd < 0) { - fprintf(stderr, "ERROR: unable to access '%s'\n", mount_path); + error("unable to access %s: %m", mount_path); return -1; } + memset(label, 0, sizeof(label)); + __strncpy_null(label, labelp, BTRFS_LABEL_SIZE - 1); if (ioctl(fd, BTRFS_IOC_SET_FSLABEL, label) < 0) { - fprintf(stderr, "ERROR: unable to set label %s\n", - strerror(errno)); + error("unable to set label of %s: %m", mount_path); close(fd); return -1; } @@ -1475,21 +1279,16 @@ static int set_label_mounted(const char *mount_path, const char *label) return 0; } -static int get_label_unmounted(const char *dev, char *label) +int get_label_unmounted(const char *dev, char *label) { struct btrfs_root *root; int ret; ret = check_mounted(dev); if (ret < 0) { - fprintf(stderr, "FATAL: error checking %s mount status\n", dev); + error("checking mount status of %s failed: %d", dev, ret); return -1; } - if (ret > 0) { - fprintf(stderr, "ERROR: dev %s is mounted, use mount point\n", - dev); - return -1; - } /* Open the super_block at the default location * and as read-only. @@ -1498,7 +1297,8 @@ static int get_label_unmounted(const char *dev, char *label) if(!root) return -1; - memcpy(label, root->fs_info->super_copy->label, BTRFS_LABEL_SIZE); + __strncpy_null(label, root->fs_info->super_copy->label, + BTRFS_LABEL_SIZE - 1); /* Now we close it since we are done. */ close_ctree(root); @@ -1514,21 +1314,25 @@ int get_label_mounted(const char *mount_path, char *labelp) { char label[BTRFS_LABEL_SIZE]; int fd; + int ret; fd = open(mount_path, O_RDONLY | O_NOATIME); if (fd < 0) { - fprintf(stderr, "ERROR: unable to access '%s'\n", mount_path); + error("unable to access %s: %m", mount_path); return -1; } memset(label, '\0', sizeof(label)); - if (ioctl(fd, BTRFS_IOC_GET_FSLABEL, label) < 0) { - fprintf(stderr, "ERROR: unable get label %s\n", strerror(errno)); + ret = ioctl(fd, BTRFS_IOC_GET_FSLABEL, label); + if (ret < 0) { + if (errno != ENOTTY) + error("unable to get label of %s: %m", mount_path); + ret = -errno; close(fd); - return -1; + return ret; } - strncpy(labelp, label, sizeof(label)); + __strncpy_null(labelp, label, BTRFS_LABEL_SIZE - 1); close(fd); return 0; } @@ -1537,105 +1341,29 @@ int get_label(const char *btrfs_dev, char *label) { int ret; - if (is_existing_blk_or_reg_file(btrfs_dev)) - ret = get_label_unmounted(btrfs_dev, label); - else + ret = is_existing_blk_or_reg_file(btrfs_dev); + if (!ret) ret = get_label_mounted(btrfs_dev, label); + else if (ret > 0) + ret = get_label_unmounted(btrfs_dev, label); return ret; } int set_label(const char *btrfs_dev, const char *label) { - if (check_label(label)) - return -1; - - return is_existing_blk_or_reg_file(btrfs_dev) ? - set_label_unmounted(btrfs_dev, label) : - set_label_mounted(btrfs_dev, label); -} - -int btrfs_scan_block_devices(int run_ioctl) -{ - - struct stat st; int ret; - int fd; - struct btrfs_fs_devices *tmp_devices; - u64 num_devices; - FILE *proc_partitions; - int i; - char buf[1024]; - char fullpath[110]; - int scans = 0; - int special; - -scan_again: - proc_partitions = fopen("/proc/partitions","r"); - if (!proc_partitions) { - fprintf(stderr, "Unable to open '/proc/partitions' for scanning\n"); - return -ENOENT; - } - /* skip the header */ - for (i = 0; i < 2; i++) - if (!fgets(buf, 1023, proc_partitions)) { - fprintf(stderr, - "Unable to read '/proc/partitions' for scanning\n"); - fclose(proc_partitions); - return -ENOENT; - } - - strcpy(fullpath,"/dev/"); - while(fgets(buf, 1023, proc_partitions)) { - i = sscanf(buf," %*d %*d %*d %99s", fullpath+5); - - /* - * multipath and MD devices may register as a btrfs filesystem - * both through the original block device and through - * the special (/dev/mapper or /dev/mdX) entry. - * This scans the special entries last - */ - special = strncmp(fullpath, "/dev/dm-", strlen("/dev/dm-")) == 0; - if (!special) - special = strncmp(fullpath, "/dev/md", strlen("/dev/md")) == 0; - - if (scans == 0 && special) - continue; - if (scans > 0 && !special) - continue; - - ret = lstat(fullpath, &st); - if (ret < 0) { - fprintf(stderr, "failed to stat %s\n", fullpath); - continue; - } - if (!S_ISBLK(st.st_mode)) { - continue; - } - fd = open(fullpath, O_RDONLY); - if (fd < 0) { - if (errno != ENOMEDIUM) - fprintf(stderr, "failed to open %s: %s\n", - fullpath, strerror(errno)); - continue; - } - ret = btrfs_scan_one_device(fd, fullpath, &tmp_devices, - &num_devices, - BTRFS_SUPER_INFO_OFFSET); - if (ret == 0 && run_ioctl > 0) { - btrfs_register_one_device(fullpath); - } - close(fd); - } + if (check_label(label)) + return -1; - fclose(proc_partitions); + ret = is_existing_blk_or_reg_file(btrfs_dev); + if (!ret) + ret = set_label_mounted(btrfs_dev, label); + else if (ret > 0) + ret = set_label_unmounted(btrfs_dev, label); - if (scans == 0) { - scans++; - goto scan_again; - } - return 0; + return ret; } /* @@ -1647,7 +1375,7 @@ static int fls64(u64 x) int i; for (i = 0; i <64; i++) - if (x << i & (1UL << 63)) + if (x << i & (1ULL << 63)) return 64 - i; return 64 - i; } @@ -1660,21 +1388,20 @@ u64 parse_size(char *s) u64 ret; if (!s) { - fprintf(stderr, "ERROR: Size value is empty\n"); + error("size value is empty"); exit(1); } if (s[0] == '-') { - fprintf(stderr, - "ERROR: Size value '%s' is less equal than 0\n", s); + error("size value '%s' is less equal than 0", s); exit(1); } ret = strtoull(s, &endptr, 10); if (endptr == s) { - fprintf(stderr, "ERROR: Size value '%s' is invalid\n", s); + error("size value '%s' is invalid", s); exit(1); } if (endptr[0] && endptr[1]) { - fprintf(stderr, "ERROR: Illegal suffix contains character '%c' in wrong position\n", + error("illegal suffix contains character '%c' in wrong position", endptr[1]); exit(1); } @@ -1683,8 +1410,7 @@ u64 parse_size(char *s) * need to call strtoull to get the real size */ if (errno == ERANGE && ret == ULLONG_MAX) { - fprintf(stderr, - "ERROR: Size value '%s' is too large for u64\n", s); + error("size value '%s' is too large for u64", s); exit(1); } if (endptr[0]) { @@ -1711,21 +1437,71 @@ u64 parse_size(char *s) case 'b': break; default: - fprintf(stderr, "ERROR: Unknown size descriptor '%c'\n", - c); + error("unknown size descriptor '%c'", c); exit(1); } } /* Check whether ret * mult overflow */ if (fls64(ret) + fls64(mult) - 1 > 64) { - fprintf(stderr, - "ERROR: Size value '%s' is too large for u64\n", s); + error("size value '%s' is too large for u64", s); exit(1); } ret *= mult; return ret; } +u64 parse_qgroupid(const char *p) +{ + char *s = strchr(p, '/'); + const char *ptr_src_end = p + strlen(p); + char *ptr_parse_end = NULL; + enum btrfs_util_error err; + u64 level; + u64 id; + int fd; + int ret = 0; + + if (p[0] == '/') + goto path; + + /* Numeric format like '0/257' is the primary case */ + if (!s) { + id = strtoull(p, &ptr_parse_end, 10); + if (ptr_parse_end != ptr_src_end) + goto path; + return id; + } + level = strtoull(p, &ptr_parse_end, 10); + if (ptr_parse_end != s) + goto path; + + id = strtoull(s + 1, &ptr_parse_end, 10); + if (ptr_parse_end != ptr_src_end) + goto path; + + return (level << BTRFS_QGROUP_LEVEL_SHIFT) | id; + +path: + /* Path format like subv at 'my_subvol' is the fallback case */ + err = btrfs_util_is_subvolume(p); + if (err) + goto err; + fd = open(p, O_RDONLY); + if (fd < 0) + goto err; + ret = lookup_path_rootid(fd, &id); + if (ret) + error("failed to lookup root id: %s", strerror(-ret)); + close(fd); + if (ret < 0) + goto err; + return id; + +err: + error("invalid qgroupid or subvolume path: %s", p); + exit(-1); +} + int open_file_or_dir3(const char *fname, DIR **dirstream, int open_flags) { int ret; @@ -1753,8 +1529,10 @@ int open_file_or_dir3(const char *fname, DIR **dirstream, int open_flags) } if (fd < 0) { fd = -1; - if (*dirstream) + if (*dirstream) { closedir(*dirstream); + *dirstream = NULL; + } } return fd; } @@ -1766,10 +1544,16 @@ int open_file_or_dir(const char *fname, DIR **dirstream) void close_file_or_dir(int fd, DIR *dirstream) { - if (dirstream) + int old_errno; + + old_errno = errno; + if (dirstream) { closedir(dirstream); - else if (fd >= 0) + } else if (fd >= 0) { close(fd); + } + + errno = old_errno; } int get_device_info(int fd, u64 devid, @@ -1781,7 +1565,76 @@ int get_device_info(int fd, u64 devid, memset(&di_args->uuid, '\0', sizeof(di_args->uuid)); ret = ioctl(fd, BTRFS_IOC_DEV_INFO, di_args); - return ret ? -errno : 0; + return ret < 0 ? -errno : 0; +} + +static u64 find_max_device_id(struct btrfs_ioctl_search_args *search_args, + int nr_items) +{ + struct btrfs_dev_item *dev_item; + char *buf = search_args->buf; + + buf += (nr_items - 1) * (sizeof(struct btrfs_ioctl_search_header) + + sizeof(struct btrfs_dev_item)); + buf += sizeof(struct btrfs_ioctl_search_header); + + dev_item = (struct btrfs_dev_item *)buf; + + return btrfs_stack_device_id(dev_item); +} + +static int search_chunk_tree_for_fs_info(int fd, + struct btrfs_ioctl_fs_info_args *fi_args) +{ + int ret; + int max_items; + u64 start_devid = 1; + struct btrfs_ioctl_search_args search_args; + struct btrfs_ioctl_search_key *search_key = &search_args.key; + + fi_args->num_devices = 0; + + max_items = BTRFS_SEARCH_ARGS_BUFSIZE + / (sizeof(struct btrfs_ioctl_search_header) + + sizeof(struct btrfs_dev_item)); + + search_key->tree_id = BTRFS_CHUNK_TREE_OBJECTID; + search_key->min_objectid = BTRFS_DEV_ITEMS_OBJECTID; + search_key->max_objectid = BTRFS_DEV_ITEMS_OBJECTID; + search_key->min_type = BTRFS_DEV_ITEM_KEY; + search_key->max_type = BTRFS_DEV_ITEM_KEY; + search_key->min_transid = 0; + search_key->max_transid = (u64)-1; + search_key->nr_items = max_items; + search_key->max_offset = (u64)-1; + +again: + search_key->min_offset = start_devid; + + ret = ioctl(fd, BTRFS_IOC_TREE_SEARCH, &search_args); + if (ret < 0) + return -errno; + + fi_args->num_devices += (u64)search_key->nr_items; + + if (search_key->nr_items == max_items) { + start_devid = find_max_device_id(&search_args, + search_key->nr_items) + 1; + goto again; + } + + /* get the lastest max_id to stay consistent with the num_devices */ + if (search_key->nr_items == 0) + /* + * last tree_search returns an empty buf, use the devid of + * the last dev_item of the previous tree_search + */ + fi_args->max_id = start_devid - 1; + else + fi_args->max_id = find_max_device_id(&search_args, + search_key->nr_items); + + return 0; } /* @@ -1794,31 +1647,31 @@ int get_device_info(int fd, u64 devid, * * Returns 0 on success, or a negative errno. */ -int get_fs_info(char *path, struct btrfs_ioctl_fs_info_args *fi_args, +int get_fs_info(const char *path, struct btrfs_ioctl_fs_info_args *fi_args, struct btrfs_ioctl_dev_info_args **di_ret) { int fd = -1; int ret = 0; int ndevs = 0; - int i = 0; + u64 last_devid = 0; + int replacing = 0; struct btrfs_fs_devices *fs_devices_mnt = NULL; struct btrfs_ioctl_dev_info_args *di_args; - char mp[BTRFS_PATH_NAME_MAX + 1]; + struct btrfs_ioctl_dev_info_args tmp; + char mp[PATH_MAX]; DIR *dirstream = NULL; memset(fi_args, 0, sizeof(*fi_args)); - if (is_block_device(path)) { + if (is_block_device(path) == 1) { struct btrfs_super_block *disk_super; char buf[BTRFS_SUPER_INFO_SIZE]; - u64 devid; /* Ensure it's mounted, then set path to the mountpoint */ fd = open(path, O_RDONLY); if (fd < 0) { ret = -errno; - fprintf(stderr, "Couldn't open %s: %s\n", - path, strerror(errno)); + error("cannot open %s: %m", path); goto out; } ret = check_mounted_where(fd, path, mp, sizeof(mp), @@ -1834,15 +1687,14 @@ int get_fs_info(char *path, struct btrfs_ioctl_fs_info_args *fi_args, fi_args->num_devices = 1; disk_super = (struct btrfs_super_block *)buf; - ret = btrfs_read_dev_super(fd, disk_super, BTRFS_SUPER_INFO_OFFSET); + ret = btrfs_read_dev_super(fd, disk_super, + BTRFS_SUPER_INFO_OFFSET, 0); if (ret < 0) { ret = -EIO; goto out; } - devid = btrfs_stack_device_id(&disk_super->dev_item); - - fi_args->max_id = devid; - i = devid; + last_devid = btrfs_stack_device_id(&disk_super->dev_item); + fi_args->max_id = last_devid; memcpy(fi_args->fsid, fs_devices_mnt->fsid, BTRFS_FSID_SIZE); close(fd); @@ -1862,20 +1714,41 @@ int get_fs_info(char *path, struct btrfs_ioctl_fs_info_args *fi_args, ret = -errno; goto out; } + + /* + * The fs_args->num_devices does not include seed devices + */ + ret = search_chunk_tree_for_fs_info(fd, fi_args); + if (ret) + goto out; + + /* + * search_chunk_tree_for_fs_info() will lacks the devid 0 + * so manual probe for it here. + */ + ret = get_device_info(fd, 0, &tmp); + if (!ret) { + fi_args->num_devices++; + ndevs++; + replacing = 1; + if (last_devid == 0) + last_devid++; + } } if (!fi_args->num_devices) goto out; - di_args = *di_ret = malloc(fi_args->num_devices * sizeof(*di_args)); + di_args = *di_ret = malloc((fi_args->num_devices) * sizeof(*di_args)); if (!di_args) { ret = -errno; goto out; } - for (; i <= fi_args->max_id; ++i) { - BUG_ON(ndevs >= fi_args->num_devices); - ret = get_device_info(fd, i, &di_args[ndevs]); + if (replacing) + memcpy(di_args, &tmp, sizeof(tmp)); + for (; last_devid <= fi_args->max_id; last_devid++) { + ret = get_device_info(fd, last_devid, &di_args[ndevs]); if (ret == -ENODEV) continue; if (ret) @@ -1897,166 +1770,133 @@ out: return ret; } -#define isoctal(c) (((c) & ~7) == '0') - -static inline void translate(char *f, char *t) -{ - while (*f != '\0') { - if (*f == '\\' && - isoctal(f[1]) && isoctal(f[2]) && isoctal(f[3])) { - *t++ = 64*(f[1] & 7) + 8*(f[2] & 7) + (f[3] & 7); - f += 4; - } else - *t++ = *f++; - } - *t = '\0'; - return; -} - -/* - * Checks if the swap device. - * Returns 1 if swap device, < 0 on error or 0 if not swap device. - */ -static int is_swap_device(const char *file) +int get_fsid(const char *path, u8 *fsid, int silent) { - FILE *f; - struct stat st_buf; - dev_t dev; - ino_t ino = 0; - char tmp[PATH_MAX]; - char buf[PATH_MAX]; - char *cp; - int ret = 0; - - if (stat(file, &st_buf) < 0) - return -errno; - if (S_ISBLK(st_buf.st_mode)) - dev = st_buf.st_rdev; - else if (S_ISREG(st_buf.st_mode)) { - dev = st_buf.st_dev; - ino = st_buf.st_ino; - } else - return 0; - - if ((f = fopen("/proc/swaps", "r")) == NULL) - return 0; + int ret; + int fd; + struct btrfs_ioctl_fs_info_args args; - /* skip the first line */ - if (fgets(tmp, sizeof(tmp), f) == NULL) + fd = open(path, O_RDONLY); + if (fd < 0) { + ret = -errno; + if (!silent) + error("failed to open %s: %s", path, + strerror(-ret)); goto out; + } - while (fgets(tmp, sizeof(tmp), f) != NULL) { - if ((cp = strchr(tmp, ' ')) != NULL) - *cp = '\0'; - if ((cp = strchr(tmp, '\t')) != NULL) - *cp = '\0'; - translate(tmp, buf); - if (stat(buf, &st_buf) != 0) - continue; - if (S_ISBLK(st_buf.st_mode)) { - if (dev == st_buf.st_rdev) { - ret = 1; - break; - } - } else if (S_ISREG(st_buf.st_mode)) { - if (dev == st_buf.st_dev && ino == st_buf.st_ino) { - ret = 1; - break; - } - } + ret = ioctl(fd, BTRFS_IOC_FS_INFO, &args); + if (ret < 0) { + ret = -errno; + goto out; } -out: - fclose(f); + memcpy(fsid, args.fsid, BTRFS_FSID_SIZE); + ret = 0; +out: + if (fd != -1) + close(fd); return ret; } -/* - * Check for existing filesystem or partition table on device. - * Returns: - * 1 for existing fs or partition - * 0 for nothing found - * -1 for internal error - */ -static int -check_overwrite( - char *device) +int is_seen_fsid(u8 *fsid, struct seen_fsid *seen_fsid_hash[]) { - const char *type; - blkid_probe pr = NULL; - int ret; - blkid_loff_t size; + u8 hash = fsid[0]; + int slot = hash % SEEN_FSID_HASH_SIZE; + struct seen_fsid *seen = seen_fsid_hash[slot]; - if (!device || !*device) - return 0; + while (seen) { + if (memcmp(seen->fsid, fsid, BTRFS_FSID_SIZE) == 0) + return 1; - ret = -1; /* will reset on success of all setup calls */ + seen = seen->next; + } - pr = blkid_new_probe_from_filename(device); - if (!pr) - goto out; + return 0; +} - size = blkid_probe_get_size(pr); - if (size < 0) - goto out; +int add_seen_fsid(u8 *fsid, struct seen_fsid *seen_fsid_hash[], + int fd, DIR *dirstream) +{ + u8 hash = fsid[0]; + int slot = hash % SEEN_FSID_HASH_SIZE; + struct seen_fsid *seen = seen_fsid_hash[slot]; + struct seen_fsid *alloc; - /* nothing to overwrite on a 0-length device */ - if (size == 0) { - ret = 0; - goto out; - } + if (!seen) + goto insert; - ret = blkid_probe_enable_partitions(pr, 1); - if (ret < 0) - goto out; + while (1) { + if (memcmp(seen->fsid, fsid, BTRFS_FSID_SIZE) == 0) + return -EEXIST; - ret = blkid_do_fullprobe(pr); - if (ret < 0) - goto out; + if (!seen->next) + break; - /* - * Blkid returns 1 for nothing found and 0 when it finds a signature, - * but we want the exact opposite, so reverse the return value here. - * - * In addition print some useful diagnostics about what actually is - * on the device. - */ - if (ret) { - ret = 0; - goto out; + seen = seen->next; } - if (!blkid_probe_lookup_value(pr, "TYPE", &type, NULL)) { - fprintf(stderr, - "%s appears to contain an existing " - "filesystem (%s).\n", device, type); - } else if (!blkid_probe_lookup_value(pr, "PTTYPE", &type, NULL)) { - fprintf(stderr, - "%s appears to contain a partition " - "table (%s).\n", device, type); - } else { - fprintf(stderr, - "%s appears to contain something weird " - "according to blkid\n", device); +insert: + alloc = malloc(sizeof(*alloc)); + if (!alloc) + return -ENOMEM; + + alloc->next = NULL; + memcpy(alloc->fsid, fsid, BTRFS_FSID_SIZE); + alloc->fd = fd; + alloc->dirstream = dirstream; + + if (seen) + seen->next = alloc; + else + seen_fsid_hash[slot] = alloc; + + return 0; +} + +void free_seen_fsid(struct seen_fsid *seen_fsid_hash[]) +{ + int slot; + struct seen_fsid *seen; + struct seen_fsid *next; + + for (slot = 0; slot < SEEN_FSID_HASH_SIZE; slot++) { + seen = seen_fsid_hash[slot]; + while (seen) { + next = seen->next; + close_file_or_dir(seen->fd, seen->dirstream); + free(seen); + seen = next; + } + seen_fsid_hash[slot] = NULL; } - ret = 1; +} -out: - if (pr) - blkid_free_probe(pr); - if (ret == -1) - fprintf(stderr, - "probe of %s failed, cannot detect " - "existing filesystem.\n", device); - return ret; +static int group_profile_devs_min(u64 flag) +{ + switch (flag & BTRFS_BLOCK_GROUP_PROFILE_MASK) { + case 0: /* single */ + case BTRFS_BLOCK_GROUP_DUP: + return 1; + case BTRFS_BLOCK_GROUP_RAID0: + case BTRFS_BLOCK_GROUP_RAID1: + case BTRFS_BLOCK_GROUP_RAID5: + return 2; + case BTRFS_BLOCK_GROUP_RAID6: + return 3; + case BTRFS_BLOCK_GROUP_RAID10: + return 4; + default: + return -1; + } } int test_num_disk_vs_raid(u64 metadata_profile, u64 data_profile, - u64 dev_cnt, int mixed, char *estr) + u64 dev_cnt, int mixed, int ssd) { - size_t sz = 100; u64 allowed = 0; + u64 profile = metadata_profile | data_profile; switch (dev_cnt) { default: @@ -2067,92 +1907,61 @@ int test_num_disk_vs_raid(u64 metadata_profile, u64 data_profile, case 2: allowed |= BTRFS_BLOCK_GROUP_RAID0 | BTRFS_BLOCK_GROUP_RAID1 | BTRFS_BLOCK_GROUP_RAID5; - break; case 1: allowed |= BTRFS_BLOCK_GROUP_DUP; } + if (dev_cnt > 1 && profile & BTRFS_BLOCK_GROUP_DUP) { + warning("DUP is not recommended on filesystem with multiple devices"); + } if (metadata_profile & ~allowed) { - snprintf(estr, sz, "unable to create FS with metadata " - "profile %llu (have %llu devices)\n", - metadata_profile, dev_cnt); + fprintf(stderr, + "ERROR: unable to create FS with metadata profile %s " + "(have %llu devices but %d devices are required)\n", + btrfs_group_profile_str(metadata_profile), dev_cnt, + group_profile_devs_min(metadata_profile)); return 1; } if (data_profile & ~allowed) { - snprintf(estr, sz, "unable to create FS with data " - "profile %llu (have %llu devices)\n", - metadata_profile, dev_cnt); + fprintf(stderr, + "ERROR: unable to create FS with data profile %s " + "(have %llu devices but %d devices are required)\n", + btrfs_group_profile_str(data_profile), dev_cnt, + group_profile_devs_min(data_profile)); return 1; } - if (!mixed && (data_profile & BTRFS_BLOCK_GROUP_DUP)) { - snprintf(estr, sz, - "dup for data is allowed only in mixed mode"); - return 1; + if (dev_cnt == 3 && profile & BTRFS_BLOCK_GROUP_RAID6) { + warning("RAID6 is not recommended on filesystem with 3 devices only"); + } + if (dev_cnt == 2 && profile & BTRFS_BLOCK_GROUP_RAID5) { + warning("RAID5 is not recommended on filesystem with 2 devices only"); } + warning_on(!mixed && (data_profile & BTRFS_BLOCK_GROUP_DUP) && ssd, + "DUP may not actually lead to 2 copies on the device, see manual page"); + return 0; } -/* Check if disk is suitable for btrfs - * returns: - * 1: something is wrong, estr provides the error - * 0: all is fine - */ -int test_dev_for_mkfs(char *file, int force_overwrite, char *estr) +int group_profile_max_safe_loss(u64 flags) { - int ret, fd; - size_t sz = 100; - struct stat st; - - ret = is_swap_device(file); - if (ret < 0) { - snprintf(estr, sz, "error checking %s status: %s\n", file, - strerror(-ret)); - return 1; - } - if (ret == 1) { - snprintf(estr, sz, "%s is a swap device\n", file); - return 1; - } - if (!force_overwrite) { - if (check_overwrite(file)) { - snprintf(estr, sz, "Use the -f option to force overwrite.\n"); - return 1; - } - } - ret = check_mounted(file); - if (ret < 0) { - snprintf(estr, sz, "error checking %s mount status\n", - file); - return 1; - } - if (ret == 1) { - snprintf(estr, sz, "%s is mounted\n", file); - return 1; - } - /* check if the device is busy */ - fd = open(file, O_RDWR|O_EXCL); - if (fd < 0) { - snprintf(estr, sz, "unable to open %s: %s\n", file, - strerror(errno)); - return 1; - } - if (fstat(fd, &st)) { - snprintf(estr, sz, "unable to stat %s: %s\n", file, - strerror(errno)); - close(fd); - return 1; - } - if (!S_ISBLK(st.st_mode)) { - fprintf(stderr, "'%s' is not a block device\n", file); - close(fd); + switch (flags & BTRFS_BLOCK_GROUP_PROFILE_MASK) { + case 0: /* single */ + case BTRFS_BLOCK_GROUP_DUP: + case BTRFS_BLOCK_GROUP_RAID0: + return 0; + case BTRFS_BLOCK_GROUP_RAID1: + case BTRFS_BLOCK_GROUP_RAID5: + case BTRFS_BLOCK_GROUP_RAID10: return 1; + case BTRFS_BLOCK_GROUP_RAID6: + return 2; + default: + return -1; } - close(fd); - return 0; } -int btrfs_scan_lblkid(int update_kernel) +int btrfs_scan_devices(void) { int fd = -1; int ret; @@ -2163,8 +1972,11 @@ int btrfs_scan_lblkid(int update_kernel) blkid_cache cache = NULL; char path[PATH_MAX]; - if (blkid_get_cache(&cache, 0) < 0) { - printf("ERROR: lblkid cache get failed\n"); + if (btrfs_scan_done) + return 0; + + if (blkid_get_cache(&cache, NULL) < 0) { + error("blkid cache get failed"); return 1; } blkid_probe_all(cache); @@ -2175,86 +1987,38 @@ int btrfs_scan_lblkid(int update_kernel) if (!dev) continue; /* if we are here its definitely a btrfs disk*/ - strncpy(path, blkid_dev_devname(dev), PATH_MAX); + strncpy_null(path, blkid_dev_devname(dev)); fd = open(path, O_RDONLY); if (fd < 0) { - printf("ERROR: could not open %s\n", path); + error("cannot open %s: %m", path); continue; } ret = btrfs_scan_one_device(fd, path, &tmp_devices, - &num_devices, BTRFS_SUPER_INFO_OFFSET); + &num_devices, BTRFS_SUPER_INFO_OFFSET, + SBREAD_DEFAULT); if (ret) { - printf("ERROR: could not scan %s\n", path); + error("cannot scan %s: %s", path, strerror(-ret)); close (fd); continue; } close(fd); - if (update_kernel) - btrfs_register_one_device(path); } blkid_dev_iterate_end(iter); blkid_put_cache(cache); + + btrfs_scan_done = 1; + return 0; } /* - * scans devs for the btrfs -*/ -int scan_for_btrfs(int where, int update_kernel) -{ - int ret = 0; - - switch (where) { - case BTRFS_SCAN_PROC: - ret = btrfs_scan_block_devices(update_kernel); - break; - case BTRFS_SCAN_DEV: - ret = btrfs_scan_one_dir("/dev", update_kernel); - break; - case BTRFS_SCAN_LBLKID: - ret = btrfs_scan_lblkid(update_kernel); - break; - } - return ret; -} - -int is_vol_small(char *file) -{ - int fd = -1; - int e; - struct stat st; - u64 size; - - fd = open(file, O_RDONLY); - if (fd < 0) - return -errno; - if (fstat(fd, &st) < 0) { - e = -errno; - close(fd); - return e; - } - size = btrfs_device_size(fd, &st); - if (size == 0) { - close(fd); - return -1; - } - if (size < 1024 * 1024 * 1024) { - close(fd); - return 1; - } else { - close(fd); - return 0; - } -} - -/* - * This reads a line from the stdin and only returns non-zero if the - * first whitespace delimited token is a case insensitive match with yes - * or y. - */ -int ask_user(char *question) + * This reads a line from the stdin and only returns non-zero if the + * first whitespace delimited token is a case insensitive match with yes + * or y. + */ +int ask_user(const char *question) { char buf[30] = {0,}; char *saveptr = NULL; @@ -2268,35 +2032,10 @@ int ask_user(char *question) } /* - * For a given: - * - file or directory return the containing tree root id - * - subvolume return it's own tree id - * - BTRFS_EMPTY_SUBVOL_DIR_OBJECTID (directory with ino == 2) the result is - * undefined and function returns -1 + * return 0 if a btrfs mount point is found + * return 1 if a mount point is found but not btrfs + * return <0 if something goes wrong */ -int lookup_ino_rootid(int fd, u64 *rootid) -{ - struct btrfs_ioctl_ino_lookup_args args; - int ret; - int e; - - memset(&args, 0, sizeof(args)); - args.treeid = 0; - args.objectid = BTRFS_FIRST_FREE_OBJECTID; - - ret = ioctl(fd, BTRFS_IOC_INO_LOOKUP, &args); - e = errno; - if (ret) { - fprintf(stderr, "ERROR: Failed to lookup root id - %s\n", - strerror(e)); - return ret; - } - - *rootid = args.treeid; - - return 0; -} - int find_mount_root(const char *path, char **mount_root) { FILE *mnttab; @@ -2304,6 +2043,7 @@ int find_mount_root(const char *path, char **mount_root) struct mntent *ent; int len; int ret; + int not_btrfs = 1; int longest_matchlen = 0; char *longest_match = NULL; @@ -2319,21 +2059,22 @@ int find_mount_root(const char *path, char **mount_root) while ((ent = getmntent(mnttab))) { len = strlen(ent->mnt_dir); if (strncmp(ent->mnt_dir, path, len) == 0) { - /* match found */ - if (longest_matchlen < len) { + /* match found and use the latest match */ + if (longest_matchlen <= len) { free(longest_match); longest_matchlen = len; longest_match = strdup(ent->mnt_dir); + not_btrfs = strcmp(ent->mnt_type, "btrfs"); } } } endmntent(mnttab); - if (!longest_match) { - fprintf(stderr, - "ERROR: Failed to find mount root for path %s.\n", - path); + if (!longest_match) return -ENOENT; + if (not_btrfs) { + free(longest_match); + return 1; } ret = 0; @@ -2345,22 +2086,507 @@ int find_mount_root(const char *path, char **mount_root) return ret; } -int test_minimum_size(const char *file, u32 leafsize) +/* + * Test if path is a directory + * Returns: + * 0 - path exists but it is not a directory + * 1 - path exists and it is a directory + * < 0 - error + */ +int test_isdir(const char *path) { - int fd; - struct stat statbuf; + struct stat st; + int ret; - fd = open(file, O_RDONLY); - if (fd < 0) - return -errno; - if (stat(file, &statbuf) < 0) { - close(fd); + ret = stat(path, &st); + if (ret < 0) return -errno; + + return !!S_ISDIR(st.st_mode); +} + +void units_set_mode(unsigned *units, unsigned mode) +{ + unsigned base = *units & UNITS_MODE_MASK; + + *units = base | mode; +} + +void units_set_base(unsigned *units, unsigned base) +{ + unsigned mode = *units & ~UNITS_MODE_MASK; + + *units = base | mode; +} + +int find_next_key(struct btrfs_path *path, struct btrfs_key *key) +{ + int level; + + for (level = 0; level < BTRFS_MAX_LEVEL; level++) { + if (!path->nodes[level]) + break; + if (path->slots[level] + 1 >= + btrfs_header_nritems(path->nodes[level])) + continue; + if (level == 0) + btrfs_item_key_to_cpu(path->nodes[level], key, + path->slots[level] + 1); + else + btrfs_node_key_to_cpu(path->nodes[level], key, + path->slots[level] + 1); + return 0; + } + return 1; +} + +const char* btrfs_group_type_str(u64 flag) +{ + u64 mask = BTRFS_BLOCK_GROUP_TYPE_MASK | + BTRFS_SPACE_INFO_GLOBAL_RSV; + + switch (flag & mask) { + case BTRFS_BLOCK_GROUP_DATA: + return "Data"; + case BTRFS_BLOCK_GROUP_SYSTEM: + return "System"; + case BTRFS_BLOCK_GROUP_METADATA: + return "Metadata"; + case BTRFS_BLOCK_GROUP_DATA|BTRFS_BLOCK_GROUP_METADATA: + return "Data+Metadata"; + case BTRFS_SPACE_INFO_GLOBAL_RSV: + return "GlobalReserve"; + default: + return "unknown"; + } +} + +const char* btrfs_group_profile_str(u64 flag) +{ + switch (flag & BTRFS_BLOCK_GROUP_PROFILE_MASK) { + case 0: + return "single"; + case BTRFS_BLOCK_GROUP_RAID0: + return "RAID0"; + case BTRFS_BLOCK_GROUP_RAID1: + return "RAID1"; + case BTRFS_BLOCK_GROUP_RAID5: + return "RAID5"; + case BTRFS_BLOCK_GROUP_RAID6: + return "RAID6"; + case BTRFS_BLOCK_GROUP_DUP: + return "DUP"; + case BTRFS_BLOCK_GROUP_RAID10: + return "RAID10"; + default: + return "unknown"; } - if (btrfs_device_size(fd, &statbuf) < btrfs_min_dev_size(leafsize)) { +} + +u64 disk_size(const char *path) +{ + struct statfs sfs; + + if (statfs(path, &sfs) < 0) + return 0; + else + return sfs.f_bsize * sfs.f_blocks; +} + +u64 get_partition_size(const char *dev) +{ + u64 result; + int fd = open(dev, O_RDONLY); + + if (fd < 0) + return 0; + if (ioctl(fd, BLKGETSIZE64, &result) < 0) { close(fd); - return 1; + return 0; } close(fd); + + return result; +} + +/* + * Check if the BTRFS_IOC_TREE_SEARCH_V2 ioctl is supported on a given + * filesystem, opened at fd + */ +int btrfs_tree_search2_ioctl_supported(int fd) +{ + struct btrfs_ioctl_search_args_v2 *args2; + struct btrfs_ioctl_search_key *sk; + int args2_size = 1024; + char args2_buf[args2_size]; + int ret; + + args2 = (struct btrfs_ioctl_search_args_v2 *)args2_buf; + sk = &(args2->key); + + /* + * Search for the extent tree item in the root tree. + */ + sk->tree_id = BTRFS_ROOT_TREE_OBJECTID; + sk->min_objectid = BTRFS_EXTENT_TREE_OBJECTID; + sk->max_objectid = BTRFS_EXTENT_TREE_OBJECTID; + sk->min_type = BTRFS_ROOT_ITEM_KEY; + sk->max_type = BTRFS_ROOT_ITEM_KEY; + sk->min_offset = 0; + sk->max_offset = (u64)-1; + sk->min_transid = 0; + sk->max_transid = (u64)-1; + sk->nr_items = 1; + args2->buf_size = args2_size - sizeof(struct btrfs_ioctl_search_args_v2); + ret = ioctl(fd, BTRFS_IOC_TREE_SEARCH_V2, args2); + if (ret == -EOPNOTSUPP) + return 0; + else if (ret == 0) + return 1; + return ret; +} + +int btrfs_check_nodesize(u32 nodesize, u32 sectorsize, u64 features) +{ + if (nodesize < sectorsize) { + error("illegal nodesize %u (smaller than %u)", + nodesize, sectorsize); + return -1; + } else if (nodesize > BTRFS_MAX_METADATA_BLOCKSIZE) { + error("illegal nodesize %u (larger than %u)", + nodesize, BTRFS_MAX_METADATA_BLOCKSIZE); + return -1; + } else if (nodesize & (sectorsize - 1)) { + error("illegal nodesize %u (not aligned to %u)", + nodesize, sectorsize); + return -1; + } else if (features & BTRFS_FEATURE_INCOMPAT_MIXED_GROUPS && + nodesize != sectorsize) { + error("illegal nodesize %u (not equal to %u for mixed block group)", + nodesize, sectorsize); + return -1; + } + return 0; +} + +/* + * Copy a path argument from SRC to DEST and check the SRC length if it's at + * most PATH_MAX and fits into DEST. DESTLEN is supposed to be exact size of + * the buffer. + * The destination buffer is zero terminated. + * Return < 0 for error, 0 otherwise. + */ +int arg_copy_path(char *dest, const char *src, int destlen) +{ + size_t len = strlen(src); + + if (len >= PATH_MAX || len >= destlen) + return -ENAMETOOLONG; + + __strncpy_null(dest, src, destlen); + return 0; } + +unsigned int get_unit_mode_from_arg(int *argc, char *argv[], int df_mode) +{ + unsigned int unit_mode = UNITS_DEFAULT; + int arg_i; + int arg_end; + + for (arg_i = 0; arg_i < *argc; arg_i++) { + if (!strcmp(argv[arg_i], "--")) + break; + + if (!strcmp(argv[arg_i], "--raw")) { + unit_mode = UNITS_RAW; + argv[arg_i] = NULL; + continue; + } + if (!strcmp(argv[arg_i], "--human-readable")) { + unit_mode = UNITS_HUMAN_BINARY; + argv[arg_i] = NULL; + continue; + } + + if (!strcmp(argv[arg_i], "--iec")) { + units_set_mode(&unit_mode, UNITS_BINARY); + argv[arg_i] = NULL; + continue; + } + if (!strcmp(argv[arg_i], "--si")) { + units_set_mode(&unit_mode, UNITS_DECIMAL); + argv[arg_i] = NULL; + continue; + } + + if (!strcmp(argv[arg_i], "--kbytes")) { + units_set_base(&unit_mode, UNITS_KBYTES); + argv[arg_i] = NULL; + continue; + } + if (!strcmp(argv[arg_i], "--mbytes")) { + units_set_base(&unit_mode, UNITS_MBYTES); + argv[arg_i] = NULL; + continue; + } + if (!strcmp(argv[arg_i], "--gbytes")) { + units_set_base(&unit_mode, UNITS_GBYTES); + argv[arg_i] = NULL; + continue; + } + if (!strcmp(argv[arg_i], "--tbytes")) { + units_set_base(&unit_mode, UNITS_TBYTES); + argv[arg_i] = NULL; + continue; + } + + if (!df_mode) + continue; + + if (!strcmp(argv[arg_i], "-b")) { + unit_mode = UNITS_RAW; + argv[arg_i] = NULL; + continue; + } + if (!strcmp(argv[arg_i], "-h")) { + unit_mode = UNITS_HUMAN_BINARY; + argv[arg_i] = NULL; + continue; + } + if (!strcmp(argv[arg_i], "-H")) { + unit_mode = UNITS_HUMAN_DECIMAL; + argv[arg_i] = NULL; + continue; + } + if (!strcmp(argv[arg_i], "-k")) { + units_set_base(&unit_mode, UNITS_KBYTES); + argv[arg_i] = NULL; + continue; + } + if (!strcmp(argv[arg_i], "-m")) { + units_set_base(&unit_mode, UNITS_MBYTES); + argv[arg_i] = NULL; + continue; + } + if (!strcmp(argv[arg_i], "-g")) { + units_set_base(&unit_mode, UNITS_GBYTES); + argv[arg_i] = NULL; + continue; + } + if (!strcmp(argv[arg_i], "-t")) { + units_set_base(&unit_mode, UNITS_TBYTES); + argv[arg_i] = NULL; + continue; + } + } + + for (arg_i = 0, arg_end = 0; arg_i < *argc; arg_i++) { + if (!argv[arg_i]) + continue; + argv[arg_end] = argv[arg_i]; + arg_end++; + } + + *argc = arg_end; + + return unit_mode; +} + +u64 div_factor(u64 num, int factor) +{ + if (factor == 10) + return num; + num *= factor; + num /= 10; + return num; +} +/* + * Get the length of the string converted from a u64 number. + * + * Result is equal to log10(num) + 1, but without the use of math library. + */ +int count_digits(u64 num) +{ + int ret = 0; + + if (num == 0) + return 1; + while (num > 0) { + ret++; + num /= 10; + } + return ret; +} + +int string_is_numerical(const char *str) +{ + if (!str) + return 0; + if (!(*str >= '0' && *str <= '9')) + return 0; + while (*str >= '0' && *str <= '9') + str++; + if (*str != '\0') + return 0; + return 1; +} + +int prefixcmp(const char *str, const char *prefix) +{ + for (; ; str++, prefix++) + if (!*prefix) + return 0; + else if (*str != *prefix) + return (unsigned char)*prefix - (unsigned char)*str; +} + +/* Subvolume helper functions */ +/* + * test if name is a correct subvolume name + * this function return + * 0-> name is not a correct subvolume name + * 1-> name is a correct subvolume name + */ +int test_issubvolname(const char *name) +{ + return name[0] != '\0' && !strchr(name, '/') && + strcmp(name, ".") && strcmp(name, ".."); +} + +const char *subvol_strip_mountpoint(const char *mnt, const char *full_path) +{ + int len = strlen(mnt); + if (!len) + return full_path; + + if ((strncmp(mnt, full_path, len) != 0) || (full_path[len] != '/')) { + error("not on mount point: %s", mnt); + exit(1); + } + + if (mnt[len - 1] != '/') + len += 1; + + return full_path + len; +} + +/* Set the seed manually */ +void init_rand_seed(u64 seed) +{ + int i; + + /* only use the last 48 bits */ + for (i = 0; i < 3; i++) { + rand_seed[i] = (unsigned short)(seed ^ (unsigned short)(-1)); + seed >>= 16; + } + rand_seed_initlized = 1; +} + +static void __init_seed(void) +{ + struct timeval tv; + int ret; + int fd; + + if(rand_seed_initlized) + return; + /* Use urandom as primary seed source. */ + fd = open("/dev/urandom", O_RDONLY); + if (fd >= 0) { + ret = read(fd, rand_seed, sizeof(rand_seed)); + close(fd); + if (ret < sizeof(rand_seed)) + goto fallback; + } else { +fallback: + /* Use time and pid as fallback seed */ + warning("failed to read /dev/urandom, use time and pid as random seed"); + gettimeofday(&tv, 0); + rand_seed[0] = getpid() ^ (tv.tv_sec & 0xFFFF); + rand_seed[1] = getppid() ^ (tv.tv_usec & 0xFFFF); + rand_seed[2] = (tv.tv_sec ^ tv.tv_usec) >> 16; + } + rand_seed_initlized = 1; +} + +u32 rand_u32(void) +{ + __init_seed(); + /* + * Don't use nrand48, its range is [0,2^31) The highest bit will alwasy + * be 0. Use jrand48 to include the highest bit. + */ + return (u32)jrand48(rand_seed); +} + +/* Return random number in range [0, upper) */ +unsigned int rand_range(unsigned int upper) +{ + __init_seed(); + /* + * Use the full 48bits to mod, which would be more uniformly + * distributed + */ + return (unsigned int)(jrand48(rand_seed) % upper); +} + +int rand_int(void) +{ + return (int)(rand_u32()); +} + +u64 rand_u64(void) +{ + u64 ret = 0; + + ret += rand_u32(); + ret <<= 32; + ret += rand_u32(); + return ret; +} + +u16 rand_u16(void) +{ + return (u16)(rand_u32()); +} + +u8 rand_u8(void) +{ + return (u8)(rand_u32()); +} + +void btrfs_config_init(void) +{ +} + +/* Returns total size of main memory in bytes, -1UL if error. */ +unsigned long total_memory(void) +{ + struct sysinfo si; + + if (sysinfo(&si) < 0) { + error("can't determine memory size"); + return -1UL; + } + return si.totalram * si.mem_unit; /* bytes */ +} + +void print_device_info(struct btrfs_device *device, char *prefix) +{ + if (prefix) + printf("%s", prefix); + printf("Device: id = %llu, name = %s\n", + device->devid, device->name); +} + +void print_all_devices(struct list_head *devices) +{ + struct btrfs_device *dev; + + printf("All Devices:\n"); + list_for_each_entry(dev, devices, dev_list) + print_device_info(dev, "\t"); + printf("\n"); +}