From ec32bbe2c9a77ae150de08e14c0a30e33ac5e321 Mon Sep 17 00:00:00 2001 From: "A. Wilcox" Date: Fri, 25 Oct 2019 04:35:58 -0500 Subject: hscript: Implement Partition, add tests --- hscript/disk.cc | 192 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ hscript/disk.hh | 46 +++++++++++++ hscript/script.cc | 27 +++++++- 3 files changed, 264 insertions(+), 1 deletion(-) (limited to 'hscript') diff --git a/hscript/disk.cc b/hscript/disk.cc index 875d84f..c41e6d0 100644 --- a/hscript/disk.cc +++ b/hscript/disk.cc @@ -189,6 +189,198 @@ bool DiskLabel::execute(ScriptOptions) const { } +/*! Parse a size string into a size and type. + * @param in_size (in) The string to parse. + * @param out_size (out) Where to which to write the size in bytes or %. + * @param type (out) The type of size determined. + * @returns true if the string was parseable, false otherwise. + */ +bool parse_size_string(const std::string &in_size, uint64_t *out_size, SizeType *type) { + std::string size(in_size), numbers, suffix; + std::string::size_type suffix_pos; + uint64_t multiplicand = 0; + + /* Validate parameters */ + if(out_size == nullptr || type == nullptr) { + return false; + } + + /* Simpler since the string isn't case-sensitive. */ + std::transform(size.cbegin(), size.cend(), size.begin(), ::tolower); + + if(size == "fill") { + /* That was easy™ */ + *type = SizeType::Fill; + *out_size = 0; + return true; + } + + if(size.size() <= 1) { + /* at least two characters are required: + * - a 9 byte partition is invalid + */ + return false; + } + + if(size.size() > 21) { + output_error("partition", "Value too large"); + return false; + } + + suffix_pos = size.find_first_not_of("12345667890"); + /* this is always correct unless suffix is %, which is handled below */ + *type = SizeType::Bytes; + try { + *out_size = std::stoul(size.substr(0, suffix_pos)); + } catch(const std::exception &) { + /* something is wrong; throw the same error as a non-numeric value */ + suffix_pos = 0; + } + + if(suffix_pos == std::string::npos) { + output_warning("partition", "size has no suffix; assuming bytes"); + return true; + } + + if(suffix_pos == 0) { + output_error("partition", "size must be a whole number, " + "followed by optional suffix [K|M|G|T|%]"); + return false; + } + + suffix = size.substr(suffix_pos); + +#define OVERFLOW_ON(MAX_VAL) \ + if(*out_size > MAX_VAL) {\ + output_error("partition", "Value too large");\ + return false;\ + } + + switch(suffix[0]) { + case 'k': + multiplicand = 1024; + OVERFLOW_ON(0x3FFFFFFFFFFFFF) + break; + case 'm': + multiplicand = 1048576; + OVERFLOW_ON(0xFFFFFFFFFFF) + break; + case 'g': + multiplicand = 1073741824; + OVERFLOW_ON(0x3FFFFFFFF) + break; + case 't': + multiplicand = 1099511627776; + OVERFLOW_ON(0xFFFFFF) + break; + case '%': + *type = SizeType::Percent; + multiplicand = 1; + OVERFLOW_ON(100) + break; + } + +#undef OVERFLOW_ON + + /* if multiplicand is 0, it's an invalid suffix. */ + if(suffix.size() != 1 || multiplicand == 0) { + output_error("partition", "size suffix must be K, M, G, T, or %"); + return false; + } + + *out_size *= multiplicand; + return true; +} + + +Key *Partition::parseFromData(const std::string &data, int lineno, int *errors, + int *) { + std::string block, pno, size_str, typecode; + std::string::size_type next_pos, last_pos; + int part_no; + SizeType size_type; + uint64_t size; + PartitionType type = None; + + long spaces = std::count(data.cbegin(), data.cend(), ' '); + if(spaces < 2 || spaces > 3) { + if(errors) *errors += 1; + output_error("installfile:" + std::to_string(lineno), + "partition: expected either 3 or 4 elements, got: " + + std::to_string(spaces), + "syntax is: partition [block] [#] [size] ([type])"); + return nullptr; + } + + last_pos = next_pos = data.find_first_of(' '); + block = data.substr(0, next_pos); + + if(block.compare(0, 4, "/dev")) { + if(errors) *errors += 1; + output_error("installfile:" + std::to_string(lineno), + "partition: expected path to block device", + "'" + block + "' is not a valid block device path"); + return nullptr; + } + + next_pos = data.find_first_of(' ', last_pos + 1); + pno = data.substr(last_pos + 1, next_pos - last_pos); + try { + part_no = std::stoi(pno); + } catch(const std::exception &) { + if(errors) *errors += 1; + output_error("installfile:" + std::to_string(lineno), + "partition: expected partition number, got", pno); + return nullptr; + } + last_pos = next_pos; + next_pos = data.find_first_of(' ', last_pos + 1); + if(next_pos == std::string::npos) { + size_str = data.substr(last_pos + 1); + } else { + size_str = data.substr(last_pos + 1, next_pos - last_pos - 1); + typecode = data.substr(next_pos + 1); + } + if(!parse_size_string(size_str, &size, &size_type)) { + if(errors) *errors += 1; + output_error("installfile:" + std::to_string(lineno), + "partition: invalid size", size_str); + return nullptr; + } + + if(!typecode.empty()) { + std::transform(typecode.cbegin(), typecode.cend(), typecode.begin(), + ::tolower); + if(typecode == "boot") { + type = Boot; + } else if(typecode == "esp") { + type = ESP; + } else { + if(errors) *errors += 1; + output_error("installfile:" + std::to_string(lineno), + "partition: expected type code, got: " + typecode, + "valid type codes are 'boot' and 'esp'"); + return nullptr; + } + } + + return new Partition(lineno, block, part_no, size_type, size, type); +} + +bool Partition::validate(ScriptOptions opts) const { +#ifdef HAS_INSTALL_ENV + if(opts.test(InstallEnvironment)) { + return is_block_device("partition", this->lineno(), this->device()); + } +#endif /* HAS_INSTALL_ENV */ + return true; +} + +bool Partition::execute(ScriptOptions) const { + return false; +} + + Key *Mount::parseFromData(const std::string &data, int lineno, int *errors, int *warnings) { std::string dev, where, opt; diff --git a/hscript/disk.hh b/hscript/disk.hh index f8a9574..90deca4 100644 --- a/hscript/disk.hh +++ b/hscript/disk.hh @@ -64,7 +64,53 @@ public: bool execute(ScriptOptions) const override; }; +/*! The type of size a disk key has */ +enum SizeType { + /*! Sized in bytes */ + Bytes, + /*! Sized as a percentage of the whole disk */ + Percent, + /*! Fill the rest of the disk */ + Fill +}; + class Partition : public Key { +public: + /*! Valid partition type codes */ + enum PartitionType { + /*! None (default) */ + None = 0, + /*! Bootable */ + Boot, + /*! EFI System Partition (GPT only) */ + ESP + }; +private: + const std::string _block; + const int _partno; + const SizeType _size_type; + const uint64_t _size; + const PartitionType _type; + + Partition(int _line, const std::string &_b, const int _p, + const SizeType _st, const uint64_t _s, const PartitionType _pt) : + Key(_line), _block(_b), _partno(_p), _size_type(_st), _size(_s), + _type(_pt) {} +public: + /*! Retrieve the block device that this key identifies. */ + const std::string device() const { return this->_block; } + /*! Retrieve the partition number that this key identifies. */ + int partno() const { return this->_partno; } + /*! Retrieve the type of size that this partition uses. */ + SizeType size_type() const { return this->_size_type; } + /*! Retrieve the size of this partition. */ + uint64_t size() const { return this->_size; } + /*! Retrieve the Type Code of this partition, if any. */ + PartitionType type() const { return this->_type; } + + static Key *parseFromData(const std::string &, int, int*, int*); + bool validate(ScriptOptions) const override; + bool execute(ScriptOptions) const override; }; class Encrypt : public Key { diff --git a/hscript/script.cc b/hscript/script.cc index 63ea3de..afb1870 100644 --- a/hscript/script.cc +++ b/hscript/script.cc @@ -108,6 +108,8 @@ struct Script::ScriptPrivate { std::vector< std::unique_ptr > diskids; /*! Disklabel configuration keys */ std::vector< std::unique_ptr > disklabels; + /*! Partition creation keys */ + std::vector< std::unique_ptr > partitions; /*! Target system's mountpoints. */ std::vector< std::unique_ptr > mounts; @@ -168,6 +170,10 @@ struct Script::ScriptPrivate { std::unique_ptr l(dynamic_cast(obj)); this->disklabels.push_back(std::move(l)); return true; + } else if(key_name == "partition") { + std::unique_ptr p(dynamic_cast(obj)); + this->partitions.push_back(std::move(p)); + return true; } else if(key_name == "mount") { std::unique_ptr mount(dynamic_cast(obj)); this->mounts.push_back(std::move(mount)); @@ -520,7 +526,7 @@ const Script *Script::load(std::istream &sstream, bool Script::validate() const { int failures = 0; - std::set seen_diskids, seen_labels, seen_mounts; + std::set seen_diskids, seen_labels, seen_parts, seen_mounts; std::map seen_iface; /* REQ: Runner.Validate.network */ @@ -754,6 +760,25 @@ bool Script::validate() const { seen_labels.insert(label->device()); } + /* REQ: Runner.Validate.partition */ + for(auto &part : this->internal->partitions) { + if(!part->validate(this->opts)) { + failures++; + continue; + } + + /* REQ: Runner.Validate.partition.Unique */ + std::string name = part->device() + std::to_string(part->partno()); + if(seen_parts.find(name) != seen_parts.end()) { + failures++; + output_error("installfile:" + std::to_string(part->lineno()), + "partition: partition #" + + std::to_string(part->partno()) + + " already exists on device " + part->device()); + } + seen_parts.insert(name); + } + /* REQ: Runner.Validate.mount */ for(auto &mount : this->internal->mounts) { if(!mount->validate(this->opts)) { -- cgit v1.2.3-70-g09d2