summaryrefslogtreecommitdiff
path: root/hscript
diff options
context:
space:
mode:
authorA. Wilcox <AWilcox@Wilcox-Tech.com>2019-10-25 04:35:58 -0500
committerA. Wilcox <AWilcox@Wilcox-Tech.com>2019-10-25 04:35:58 -0500
commitec32bbe2c9a77ae150de08e14c0a30e33ac5e321 (patch)
treebb920df38fc77fbf21246a05e3569d9917184089 /hscript
parent4755175ad26eaff1765407bb01f8820f394f847b (diff)
downloadhorizon-ec32bbe2c9a77ae150de08e14c0a30e33ac5e321.tar.gz
horizon-ec32bbe2c9a77ae150de08e14c0a30e33ac5e321.tar.bz2
horizon-ec32bbe2c9a77ae150de08e14c0a30e33ac5e321.tar.xz
horizon-ec32bbe2c9a77ae150de08e14c0a30e33ac5e321.zip
hscript: Implement Partition, add tests
Diffstat (limited to 'hscript')
-rw-r--r--hscript/disk.cc192
-rw-r--r--hscript/disk.hh46
-rw-r--r--hscript/script.cc27
3 files changed, 264 insertions, 1 deletions
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<DiskId> > diskids;
/*! Disklabel configuration keys */
std::vector< std::unique_ptr<DiskLabel> > disklabels;
+ /*! Partition creation keys */
+ std::vector< std::unique_ptr<Partition> > partitions;
/*! Target system's mountpoints. */
std::vector< std::unique_ptr<Mount> > mounts;
@@ -168,6 +170,10 @@ struct Script::ScriptPrivate {
std::unique_ptr<DiskLabel> l(dynamic_cast<DiskLabel *>(obj));
this->disklabels.push_back(std::move(l));
return true;
+ } else if(key_name == "partition") {
+ std::unique_ptr<Partition> p(dynamic_cast<Partition *>(obj));
+ this->partitions.push_back(std::move(p));
+ return true;
} else if(key_name == "mount") {
std::unique_ptr<Mount> mount(dynamic_cast<Mount *>(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<std::string> seen_diskids, seen_labels, seen_mounts;
+ std::set<std::string> seen_diskids, seen_labels, seen_parts, seen_mounts;
std::map<const std::string, int> 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)) {