// Copyright (c) Vitaliy Filippov, 2019+ // License: VNPL-1.1 (see README.md for details) #include "disk_tool.h" #include "rw_blocking.h" #include "str_util.h" #include "json_util.h" int disk_tool_t::resize_data(std::string device) { if (options.find("move_journal") == options.end() && options.find("move_data") == options.end() && options.find("journal_size") == options.end() && options.find("data_size") == options.end()) { fprintf(stderr, "None of --move-journal, --move-data, --journal-size, --data-size options are specified - nothing to do!\n"); return 1; } if (stoull_full(device)) device = "/dev/vitastor/osd"+device+"-data"; json11::Json sb = read_osd_superblock(device, true, false); if (sb.is_null()) return 1; auto sb_params = json_to_string_map(sb["params"].object_items()); try { dsk.parse_config(sb_params); dsk.data_io = dsk.meta_io = dsk.journal_io = "cached"; dsk.open_data(); dsk.open_meta(); dsk.open_journal(); dsk.calc_lengths(); } catch (std::exception & e) { dsk.close_all(); fprintf(stderr, "%s\n", e.what()); return 1; } // Save FD numbers because calc_lengths() relies on them int old_journal_fd = dsk.journal_fd, old_meta_fd = dsk.meta_fd, old_data_fd = dsk.data_fd; dsk.close_all(); bool dry_run = options.find("dry_run") != options.end(); auto old_journal_device = dsk.journal_device; auto old_meta_device = dsk.meta_device; new_journal_len = dsk.journal_len; if (options.find("journal_size") != options.end()) { new_journal_len = parse_size(options["journal_size"]); if (options.find("move_journal") == options.end()) options["move_journal"] = dsk.journal_device == dsk.data_device ? "" : dsk.journal_device; } uint64_t new_data_dev_size = 0; if (options.find("data_size") != options.end()) { new_data_dev_size = parse_size(options["data_size"]); new_data_dev_size = options["data_size"] == "max" || new_data_dev_size > dsk.data_device_size ? dsk.data_device_size : new_data_dev_size; dsk.data_device_size = new_data_dev_size; dsk.cfg_data_size = 0; dsk.journal_fd = old_journal_fd; dsk.meta_fd = old_meta_fd; dsk.data_fd = old_data_fd; dsk.calc_lengths(); dsk.journal_fd = -1; dsk.meta_fd = -1; dsk.data_fd = -1; } std::map move_options; if (options.find("move_journal") != options.end()) { if (resize_parse_move_journal(move_options, dry_run) != 0) return 1; } if (options.find("move_meta") != options.end()) { if (resize_parse_move_meta(move_options, dry_run) != 0) return 1; } auto new_journal_device = move_options.find("new_journal_device") != move_options.end() ? move_options["new_journal_device"] : dsk.journal_device; auto new_meta_device = move_options.find("new_meta_device") != move_options.end() ? move_options["new_meta_device"] : dsk.meta_device; // Calculate new data & meta offsets if (!new_meta_len) new_meta_len = (dsk.meta_format == BLOCKSTORE_META_FORMAT_HEAP ? dsk.min_meta_len*2 : dsk.min_meta_len); new_data_offset = 4096 + (new_journal_device == dsk.data_device ? new_journal_len : 0) + (new_meta_device == dsk.data_device ? new_meta_len : 0); new_data_offset += ((dsk.data_offset-new_data_offset) % dsk.data_block_size); if (new_data_offset != dsk.data_offset) move_options["new_data_offset"] = std::to_string(new_data_offset); if (new_data_dev_size != 0) move_options["new_data_len"] = std::to_string(new_data_dev_size-new_data_offset); new_meta_offset = 4096 + (new_meta_device == new_journal_device ? new_journal_len : 0); if (new_meta_offset != dsk.meta_offset) move_options["new_meta_offset"] = std::to_string(new_meta_offset); // Run resize auto orig_options = std::move(options); options = sb_params; for (auto & kv: move_options) options[kv.first] = kv.second; if (!json) { std::string cmd; for (auto & kv: move_options) cmd += " "+kv.first+" = "+kv.second+"\n"; fprintf(stderr, "Running resize:\n%s", cmd.c_str()); } if (!dry_run && raw_resize() != 0) return 1; // Write new superblocks json11::Json::object new_sb_params = sb["params"].object_items(); if (move_options.find("new_journal_device") != move_options.end()) new_sb_params["journal_device"] = move_options["new_journal_device"]; if (move_options.find("new_meta_device") != move_options.end()) new_sb_params["meta_device"] = move_options["new_meta_device"]; new_sb_params["data_offset"] = new_data_offset; new_sb_params["meta_offset"] = new_meta_offset; if (move_options.find("new_data_len") != move_options.end()) new_sb_params["data_size"] = stoull_full(move_options["new_data_len"]); std::set clear_superblocks, write_superblocks; write_superblocks.insert(dsk.data_device); write_superblocks.insert(new_journal_device); write_superblocks.insert(new_meta_device); if (write_superblocks.find(old_journal_device) == write_superblocks.end()) clear_superblocks.insert(old_journal_device); if (write_superblocks.find(old_meta_device) == write_superblocks.end()) clear_superblocks.insert(old_meta_device); for (auto & dev: clear_superblocks) { if (!json) fprintf(stderr, "Clearing OSD superblock on %s\n", dev.c_str()); if (!dry_run && clear_osd_superblock(dev) != 0) return 1; } for (auto & dev: write_superblocks) { if (!json) fprintf(stderr, "Writing new OSD superblock to %s\n", dev.c_str()); if (!dry_run && !write_osd_superblock(dev, new_sb_params)) return 1; } if (json) { printf("%s\n", json11::Json(json11::Json::object { { "new_sb_params", new_sb_params }, }).dump().c_str()); } return 0; } int disk_tool_t::resize_parse_move_journal(std::map & move_options, bool dry_run) { if (options["move_journal"] == "") { // move back to the data device // but first check if not already there :) if (dsk.journal_device == dsk.data_device && new_journal_len == dsk.journal_len) { // already there fprintf(stderr, "journal is already on data device and has the same size\n"); return 0; } move_options["new_journal_device"] = dsk.data_device; move_options["new_journal_offset"] = "4096"; move_options["new_journal_len"] = std::to_string(new_journal_len); } else { std::string real_dev = realpath_str(options["move_journal"], false); if (real_dev == "") return 1; std::string parent_dev = get_parent_device(real_dev); if (parent_dev == "") return 1; if (parent_dev == real_dev) { // whole disk - create partition std::string old_real_dev = realpath_str(dsk.journal_device); if (old_real_dev == "") return 1; if (options.find("force") == options.end() && get_parent_device(old_real_dev) == parent_dev) { // already there fprintf(stderr, "journal is already on a partition of %s, add --force to create a new partition\n", options["move_journal"].c_str()); return 0; } new_journal_len = ((new_journal_len+1024*1024-1)/1024/1024)*1024*1024; if (!dry_run) { auto devinfos = collect_devices({ real_dev }); if (devinfos.size() == 0) return 1; std::vector sizes; sizes.push_back(std::to_string(new_journal_len/1024/1024)+"MiB"); auto new_parts = add_partitions(devinfos[0], sizes); if (!new_parts.array_items().size()) return 1; options["move_journal"] = "/dev/disk/by-partuuid/"+strtolower(new_parts[0]["uuid"].string_value()); } else options["move_journal"] = ""; } else { // already a partition - check that it's a GPT partition with correct type if ((options.find("force") == options.end() ? check_existing_partition(options["move_journal"]) : fix_partition_type(options["move_journal"])) != 0) { return 1; } new_journal_len = get_device_size(options["move_journal"], true); if (new_journal_len == UINT64_MAX) return 1; } new_journal_len -= 4096; move_options["new_journal_device"] = options["move_journal"]; move_options["new_journal_offset"] = "4096"; move_options["new_journal_len"] = std::to_string(new_journal_len); } return 0; } int disk_tool_t::resize_parse_move_meta(std::map & move_options, bool dry_run) { if (options["move_meta"] == "") { // move back to the data device // but first check if not already there :) if (dsk.meta_device == dsk.data_device) { // already there fprintf(stderr, "metadata is already on data device\n"); return 0; } auto new_journal_device = move_options.find("new_journal_device") != move_options.end() ? move_options["new_journal_device"] : dsk.journal_device; move_options["new_meta_device"] = dsk.data_device; move_options["new_meta_len"] = std::to_string(new_meta_len); } else { std::string real_dev = realpath_str(options["move_meta"], false); if (real_dev == "") return 1; std::string parent_dev = get_parent_device(real_dev); if (parent_dev == "") return 1; if (parent_dev == real_dev) { // whole disk - create partition std::string old_real_dev = realpath_str(dsk.meta_device); if (old_real_dev == "") return 1; if (options.find("force") == options.end() && get_parent_device(old_real_dev) == parent_dev) { // already there fprintf(stderr, "metadata is already on a partition of %s\n", options["move_meta"].c_str()); return 0; } new_meta_len = ((dsk.meta_area_size+1024*1024-1)/1024/1024)*1024*1024; if (!dry_run) { auto devinfos = collect_devices({ real_dev }); if (devinfos.size() == 0) return 1; std::vector sizes; sizes.push_back(std::to_string(new_meta_len/1024/1024)+"MiB"); auto new_parts = add_partitions(devinfos[0], sizes); if (!new_parts.array_items().size()) return 1; options["move_meta"] = "/dev/disk/by-partuuid/"+strtolower(new_parts[0]["uuid"].string_value()); } else options["move_meta"] = ""; } else { // already a partition - check that it's a GPT partition with correct type if ((options.find("force") == options.end() ? check_existing_partition(options["move_meta"]) : fix_partition_type(options["move_meta"])) != 0) { return 1; } new_meta_len = get_device_size(options["move_meta"], true); if (new_meta_len == UINT64_MAX) return 1; } new_meta_len -= 4096; move_options["new_meta_len"] = std::to_string(new_meta_len); move_options["new_meta_device"] = options["move_meta"]; move_options["new_meta_offset"] = "4096"; } return 0; }