summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorA. Wilcox <AWilcox@Wilcox-Tech.com>2019-10-24 02:20:33 -0500
committerA. Wilcox <AWilcox@Wilcox-Tech.com>2019-10-24 02:20:33 -0500
commit4755175ad26eaff1765407bb01f8820f394f847b (patch)
tree9951bb91734a04e71f82d44284c74e56e8711f09
parent30b63f19fedde7b60bf99f3a68c0437a0c93b64e (diff)
downloadhorizon-4755175ad26eaff1765407bb01f8820f394f847b.tar.gz
horizon-4755175ad26eaff1765407bb01f8820f394f847b.tar.bz2
horizon-4755175ad26eaff1765407bb01f8820f394f847b.tar.xz
horizon-4755175ad26eaff1765407bb01f8820f394f847b.zip
hscript: Fully implement Timezone, add tests
-rw-r--r--hscript/meta.cc92
-rw-r--r--hscript/meta.hh7
-rw-r--r--hscript/script.cc42
-rw-r--r--tests/fixtures/0131-timezone-basic.installfile6
-rw-r--r--tests/fixtures/0132-timezone-invalid.installfile7
-rw-r--r--tests/fixtures/0133-timezone-malicious.installfile7
-rw-r--r--tests/fixtures/0134-timezone-subtz.installfile6
-rw-r--r--tests/spec/validator_spec.rb78
8 files changed, 210 insertions, 35 deletions
diff --git a/hscript/meta.cc b/hscript/meta.cc
index 79881f4..9f0da76 100644
--- a/hscript/meta.cc
+++ b/hscript/meta.cc
@@ -19,8 +19,8 @@
# include <cstring> /* strerror */
# include <errno.h> /* errno */
# include <sys/stat.h> /* chmod */
-# include <unistd.h>
#endif /* HAS_INSTALL_ENV */
+#include <unistd.h> /* access - used by tz code even in RT env */
#include "meta.hh"
#include "util/output.hh"
@@ -183,6 +183,20 @@ Key *PkgInstall::parseFromData(const std::string &data, int lineno, int *errors,
}
+/* LCOV_EXCL_START */
+bool PkgInstall::validate(ScriptOptions) const {
+ /* Any validation errors would have occurred above. */
+ return true;
+}
+
+
+bool PkgInstall::execute(ScriptOptions) const {
+ /* Package installation is handled in Script::execute. */
+ return true;
+}
+/* LCOV_EXCL_STOP */
+
+
/* All ISO 639-1 language codes.
* Source: https://www.loc.gov/standards/iso639-2/ISO-639-2_utf-8.txt
* Python to construct table:
@@ -318,18 +332,78 @@ bool Firmware::execute(ScriptOptions) const {
}
-/* LCOV_EXCL_START */
-bool PkgInstall::validate(ScriptOptions) const {
- /* Any validation errors would have occurred above. */
- return true;
+Key *Timezone::parseFromData(const std::string &data, int lineno, int *errors,
+ int *warnings) {
+ if(data.find_first_of(" .\\") != std::string::npos || data[0] == '/') {
+ if(errors) *errors += 1;
+ output_error("installfile:" + std::to_string(lineno),
+ "timezone: invalid timezone name");
+ return nullptr;
+ }
+
+ if(access("/usr/share/zoneinfo", X_OK) != 0) {
+ if(warnings) *warnings += 1;
+ output_warning("installfile:" + std::to_string(lineno),
+ "timezone: can't determine validity of timezone",
+ "zoneinfo data is missing or inaccessible");
+ } else {
+ std::string zi_path = "/usr/share/zoneinfo/" + data;
+ if(access(zi_path.c_str(), F_OK) != 0) {
+ if(errors) *errors += 1;
+ output_error("installfile:" + std::to_string(lineno),
+ "timezone: unknown timezone '" + data + "'");
+ return nullptr;
+ }
+ }
+
+ return new Timezone(lineno, data);
}
+bool Timezone::execute(ScriptOptions opts) const {
+ if(opts.test(Simulate)) {
+ /* If the target doesn't have tzdata installed, copy the zoneinfo from
+ * the Horizon environment. */
+ std::cout << "([ -f /target/usr/share/zoneinfo/" << this->value()
+ << " ] && ln -s /usr/share/zoneinfo/" << this->value()
+ << " /target/etc/localtime) || cp /usr/share/zoneinfo/"
+ << this->value() << " /target/etc/localtime";
+ return true;
+ }
-bool PkgInstall::execute(ScriptOptions) const {
- /* Package installation is handled in Script::execute. */
- return true;
+#ifdef HAS_INSTALL_ENV
+ std::string zi_path = "/usr/share/zoneinfo/" + this->value();
+ std::string target_zi = "/target" + zi_path;
+ if(access(target_zi.c_str(), F_OK) == 0) {
+ if(symlink(zi_path.c_str(), "/target/etc/localtime") != 0) {
+ output_error("installfile:" + std::to_string(this->lineno()),
+ "timezone: failed to create symbolic link",
+ strerror(errno));
+ return false;
+ }
+ return true;
+ } else {
+ /* The target doesn't have tzdata installed. We copy the zoneinfo
+ * file from the Horizon environment to the target. */
+ std::ifstream zoneinfo(zi_path, std::ios::binary);
+ if(!zoneinfo) {
+ output_error("installfile:" + std::to_string(this->lineno()),
+ "timezone: failed to open zoneinfo file");
+ return false;
+ }
+ std::ofstream output("/target/etc/localtime");
+ if(!output) {
+ output_error("installfile:" + std::to_string(this->lineno()),
+ "timezone: failed to prepare target environment");
+ return false;
+ }
+
+ output << zoneinfo.rdbuf();
+ return true;
+ }
+#else
+ return false;
+#endif
}
-/* LCOV_EXCL_STOP */
Key *Repository::parseFromData(const std::string &data, int lineno, int *errors,
diff --git a/hscript/meta.hh b/hscript/meta.hh
index 7dc3f68..b2e1963 100644
--- a/hscript/meta.hh
+++ b/hscript/meta.hh
@@ -66,6 +66,13 @@ public:
};
class Timezone : public StringKey {
+private:
+ Timezone(int _line, const std::string &my_zone) :
+ StringKey(_line, my_zone) {}
+public:
+ static Key *parseFromData(const std::string &data, int lineno, int *errors,
+ int *warnings);
+ bool execute(ScriptOptions) const override;
};
class Repository : public StringKey {
diff --git a/hscript/script.cc b/hscript/script.cc
index 0e99059..63ea3de 100644
--- a/hscript/script.cc
+++ b/hscript/script.cc
@@ -90,6 +90,9 @@ struct Script::ScriptPrivate {
std::unique_ptr<RootPassphrase> rootpw;
/*! The system language. */
std::unique_ptr<Language> lang;
+ /* keymap */
+ /*! The system timezone. */
+ std::unique_ptr<Timezone> tzone;
/*! Network addressing configuration */
std::vector< std::unique_ptr<NetAddress> > addresses;
@@ -141,6 +144,8 @@ struct Script::ScriptPrivate {
return store_lang(obj, lineno, errors, warnings, opts);
} else if(key_name == "firmware") {
return store_firmware(obj, lineno, errors, warnings, opts);
+ } else if(key_name == "timezone") {
+ return store_timezone(obj, lineno, errors, warnings, opts);
} else if(key_name == "repository") {
std::unique_ptr<Repository> repo(dynamic_cast<Repository *>(obj));
this->repos.push_back(std::move(repo));
@@ -263,6 +268,18 @@ struct Script::ScriptPrivate {
return true;
}
+ bool store_timezone(Keys::Key *obj, int lineno, int *errors, int *,
+ ScriptOptions) {
+ if(this->tzone) {
+ DUPLICATE_ERROR(this->tzone, std::string("timezone"),
+ this->tzone->value())
+ return false;
+ }
+ std::unique_ptr<Timezone> t(dynamic_cast<Timezone *>(obj));
+ this->tzone = std::move(t);
+ return true;
+ }
+
bool store_username(Keys::Key *obj, int lineno, int *errors, int *,
ScriptOptions) {
if(accounts.size() >= 255) {
@@ -560,7 +577,22 @@ bool Script::validate() const {
if(!this->internal->firmware->validate(this->opts)) failures++;
#endif
- /* timezone */
+ /* REQ: Runner.Execute.timezone */
+ if(!internal->tzone) {
+ Keys::Timezone *utc = dynamic_cast<Keys::Timezone *>(
+ Horizon::Keys::Timezone::parseFromData("UTC", 0,
+ &failures, nullptr)
+ );
+ if(!utc) {
+ output_error("internal", "failed to create default timezone");
+ return false;
+ }
+ std::unique_ptr<Keys::Timezone> zone(utc);
+ this->internal->tzone = std::move(zone);
+ }
+
+ /* REQ: Runner.Validate.timezone */
+ if(!this->internal->tzone->validate(this->opts)) failures++;
/* REQ: Script.repository */
if(this->internal->repos.size() == 0) {
@@ -900,6 +932,14 @@ bool Script::execute() const {
return false;
}
+ /* keymap */
+ /* UserAccounts */
+
+ if(!this->internal->tzone->execute(opts)) {
+ EXECUTE_FAILURE("post-metadata");
+ return false;
+ }
+
output_step_end("post-metadata");
return true;
}
diff --git a/tests/fixtures/0131-timezone-basic.installfile b/tests/fixtures/0131-timezone-basic.installfile
new file mode 100644
index 0000000..ca8306a
--- /dev/null
+++ b/tests/fixtures/0131-timezone-basic.installfile
@@ -0,0 +1,6 @@
+network false
+hostname test.machine
+pkginstall adelie-base
+rootpw $6$gumtLGmHwOVIRpQR$2M9PUO24hy5mofzWWf9a.YLbzOgOlUby1g0hDj.wG67E2wrrvys59fq02PPdxBdbgkLZFtjfEx6MHZwMBamwu/
+mount /dev/sda1 /
+timezone UTC
diff --git a/tests/fixtures/0132-timezone-invalid.installfile b/tests/fixtures/0132-timezone-invalid.installfile
new file mode 100644
index 0000000..53e8446
--- /dev/null
+++ b/tests/fixtures/0132-timezone-invalid.installfile
@@ -0,0 +1,7 @@
+network false
+hostname test.machine
+pkginstall adelie-base
+rootpw $6$gumtLGmHwOVIRpQR$2M9PUO24hy5mofzWWf9a.YLbzOgOlUby1g0hDj.wG67E2wrrvys59fq02PPdxBdbgkLZFtjfEx6MHZwMBamwu/
+mount /dev/sda1 /
+# This is in Africa, not America.
+timezone America/Nairobi
diff --git a/tests/fixtures/0133-timezone-malicious.installfile b/tests/fixtures/0133-timezone-malicious.installfile
new file mode 100644
index 0000000..519d3ec
--- /dev/null
+++ b/tests/fixtures/0133-timezone-malicious.installfile
@@ -0,0 +1,7 @@
+network false
+hostname test.machine
+pkginstall adelie-base
+rootpw $6$gumtLGmHwOVIRpQR$2M9PUO24hy5mofzWWf9a.YLbzOgOlUby1g0hDj.wG67E2wrrvys59fq02PPdxBdbgkLZFtjfEx6MHZwMBamwu/
+mount /dev/sda1 /
+# Try to get the Runner to copy /etc/passwd to /target/etc/localtime
+timezone ../../../etc/passwd
diff --git a/tests/fixtures/0134-timezone-subtz.installfile b/tests/fixtures/0134-timezone-subtz.installfile
new file mode 100644
index 0000000..72af414
--- /dev/null
+++ b/tests/fixtures/0134-timezone-subtz.installfile
@@ -0,0 +1,6 @@
+network false
+hostname test.machine
+pkginstall adelie-base
+rootpw $6$gumtLGmHwOVIRpQR$2M9PUO24hy5mofzWWf9a.YLbzOgOlUby1g0hDj.wG67E2wrrvys59fq02PPdxBdbgkLZFtjfEx6MHZwMBamwu/
+mount /dev/sda1 /
+timezone Pacific/Galapagos
diff --git a/tests/spec/validator_spec.rb b/tests/spec/validator_spec.rb
index b07e383..457aa93 100644
--- a/tests/spec/validator_spec.rb
+++ b/tests/spec/validator_spec.rb
@@ -160,6 +160,44 @@ RSpec.describe 'HorizonScript validation', :type => :aruba do
run_validate
expect(last_command_started).to have_output(/error: .*rootpw.*/)
end
+ context "for 'language' key" do
+ # Runner.Validate.language
+ # Runner.Validate.language.Format
+ it "supports a simple language" do
+ use_fixture '0115-language-simple.installfile'
+ run_validate
+ expect(last_command_started).to have_output(PARSER_SUCCESS)
+ expect(last_command_started).to have_output(VALIDATOR_SUCCESS)
+ end
+ it "supports a language_territory value" do
+ use_fixture '0116-language-country.installfile'
+ run_validate
+ expect(last_command_started).to have_output(PARSER_SUCCESS)
+ expect(last_command_started).to have_output(VALIDATOR_SUCCESS)
+ end
+ it "supports the UTF-8 codeset" do
+ use_fixture '0117-language-codeset.installfile'
+ run_validate
+ expect(last_command_started).to have_output(PARSER_SUCCESS)
+ expect(last_command_started).to have_output(VALIDATOR_SUCCESS)
+ end
+ it "supports the special case C.UTF-8" do
+ use_fixture '0118-language-c-exception.installfile'
+ run_validate
+ expect(last_command_started).to have_output(PARSER_SUCCESS)
+ expect(last_command_started).to have_output(VALIDATOR_SUCCESS)
+ end
+ it "requires a valid language" do
+ use_fixture '0119-language-invalid.installfile'
+ run_validate
+ expect(last_command_started).to have_output(/error: .*language.*invalid/)
+ end
+ it "requires the UTF-8 codeset" do
+ use_fixture '0120-language-iso.installfile'
+ run_validate
+ expect(last_command_started).to have_output(/error: .*language.*codeset/)
+ end
+ end
context "for 'firmware' key" do
it "always supports 'false' value" do
use_fixture '0112-firmware-false.installfile'
@@ -186,43 +224,33 @@ RSpec.describe 'HorizonScript validation', :type => :aruba do
expect(last_command_started).to have_output(/error: .*firmware.*value/)
end
end
- context "for 'language' key" do
- # Runner.Validate.language
- # Runner.Validate.language.Format
- it "supports a simple language" do
- use_fixture '0115-language-simple.installfile'
+ context "for 'timezone' key" do
+ # Runner.Validate.timezone.
+ it "fails with an invalid value" do
+ use_fixture '0132-timezone-invalid.installfile'
run_validate
- expect(last_command_started).to have_output(PARSER_SUCCESS)
- expect(last_command_started).to have_output(VALIDATOR_SUCCESS)
+ expect(last_command_started).to have_output(/error: .*timezone.*invalid/)
end
- it "supports a language_territory value" do
- use_fixture '0116-language-country.installfile'
+ # Runner.Validate.timezone.
+ it "fails with a maliciously invalid value" do
+ use_fixture '0133-timezone-malicious.installfile'
run_validate
- expect(last_command_started).to have_output(PARSER_SUCCESS)
- expect(last_command_started).to have_output(VALIDATOR_SUCCESS)
+ expect(last_command_started).to have_output(/error: .*timezone.*invalid/)
end
- it "supports the UTF-8 codeset" do
- use_fixture '0117-language-codeset.installfile'
+ # Runner.Validate.timezone.zoneinfo.
+ it "succeeds with simple value" do
+ use_fixture '0131-timezone-basic.installfile'
run_validate
expect(last_command_started).to have_output(PARSER_SUCCESS)
expect(last_command_started).to have_output(VALIDATOR_SUCCESS)
end
- it "supports the special case C.UTF-8" do
- use_fixture '0118-language-c-exception.installfile'
+ # Runner.Validate.timezone.zoneinfo.
+ it "succeeds with Locality/Zone value" do
+ use_fixture '0134-timezone-subtz.installfile'
run_validate
expect(last_command_started).to have_output(PARSER_SUCCESS)
expect(last_command_started).to have_output(VALIDATOR_SUCCESS)
end
- it "requires a valid language" do
- use_fixture '0119-language-invalid.installfile'
- run_validate
- expect(last_command_started).to have_output(/error: .*language.*invalid/)
- end
- it "requires the UTF-8 codeset" do
- use_fixture '0120-language-iso.installfile'
- run_validate
- expect(last_command_started).to have_output(/error: .*language.*codeset/)
- end
end
context "for 'mount' key" do
# Runner.Validate.mount.