1b0d29bc4SBrooks Davis // Copyright 2012 The Kyua Authors. 2b0d29bc4SBrooks Davis // All rights reserved. 3b0d29bc4SBrooks Davis // 4b0d29bc4SBrooks Davis // Redistribution and use in source and binary forms, with or without 5b0d29bc4SBrooks Davis // modification, are permitted provided that the following conditions are 6b0d29bc4SBrooks Davis // met: 7b0d29bc4SBrooks Davis // 8b0d29bc4SBrooks Davis // * Redistributions of source code must retain the above copyright 9b0d29bc4SBrooks Davis // notice, this list of conditions and the following disclaimer. 10b0d29bc4SBrooks Davis // * Redistributions in binary form must reproduce the above copyright 11b0d29bc4SBrooks Davis // notice, this list of conditions and the following disclaimer in the 12b0d29bc4SBrooks Davis // documentation and/or other materials provided with the distribution. 13b0d29bc4SBrooks Davis // * Neither the name of Google Inc. nor the names of its contributors 14b0d29bc4SBrooks Davis // may be used to endorse or promote products derived from this software 15b0d29bc4SBrooks Davis // without specific prior written permission. 16b0d29bc4SBrooks Davis // 17b0d29bc4SBrooks Davis // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18b0d29bc4SBrooks Davis // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19b0d29bc4SBrooks Davis // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20b0d29bc4SBrooks Davis // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21b0d29bc4SBrooks Davis // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22b0d29bc4SBrooks Davis // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23b0d29bc4SBrooks Davis // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24b0d29bc4SBrooks Davis // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25b0d29bc4SBrooks Davis // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26b0d29bc4SBrooks Davis // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27b0d29bc4SBrooks Davis // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28b0d29bc4SBrooks Davis 29b0d29bc4SBrooks Davis #include "engine/requirements.hpp" 30b0d29bc4SBrooks Davis 31*257e70f1SIgor Ostapenko #include "engine/execenv/execenv.hpp" 32b0d29bc4SBrooks Davis #include "model/metadata.hpp" 33b0d29bc4SBrooks Davis #include "model/types.hpp" 34b0d29bc4SBrooks Davis #include "utils/config/nodes.ipp" 35b0d29bc4SBrooks Davis #include "utils/config/tree.ipp" 36b0d29bc4SBrooks Davis #include "utils/format/macros.hpp" 37b0d29bc4SBrooks Davis #include "utils/fs/operations.hpp" 38b0d29bc4SBrooks Davis #include "utils/fs/path.hpp" 39b0d29bc4SBrooks Davis #include "utils/memory.hpp" 40b0d29bc4SBrooks Davis #include "utils/passwd.hpp" 41b0d29bc4SBrooks Davis #include "utils/sanity.hpp" 42b0d29bc4SBrooks Davis #include "utils/units.hpp" 43b0d29bc4SBrooks Davis 44b0d29bc4SBrooks Davis namespace config = utils::config; 45b0d29bc4SBrooks Davis namespace fs = utils::fs; 46b0d29bc4SBrooks Davis namespace passwd = utils::passwd; 47b0d29bc4SBrooks Davis namespace units = utils::units; 48b0d29bc4SBrooks Davis 49b0d29bc4SBrooks Davis 50b0d29bc4SBrooks Davis namespace { 51b0d29bc4SBrooks Davis 52b0d29bc4SBrooks Davis 53b0d29bc4SBrooks Davis /// Checks if all required configuration variables are present. 54b0d29bc4SBrooks Davis /// 55b0d29bc4SBrooks Davis /// \param required_configs Set of required variable names. 56b0d29bc4SBrooks Davis /// \param user_config Runtime user configuration. 57b0d29bc4SBrooks Davis /// \param test_suite_name Name of the test suite the test belongs to. 58b0d29bc4SBrooks Davis /// 59b0d29bc4SBrooks Davis /// \return Empty if all variables are present or an error message otherwise. 60b0d29bc4SBrooks Davis static std::string 61b0d29bc4SBrooks Davis check_required_configs(const model::strings_set& required_configs, 62b0d29bc4SBrooks Davis const config::tree& user_config, 63b0d29bc4SBrooks Davis const std::string& test_suite_name) 64b0d29bc4SBrooks Davis { 65b0d29bc4SBrooks Davis for (model::strings_set::const_iterator iter = required_configs.begin(); 66b0d29bc4SBrooks Davis iter != required_configs.end(); iter++) { 67b0d29bc4SBrooks Davis std::string property; 68b0d29bc4SBrooks Davis // TODO(jmmv): All this rewrite logic belongs in the ATF interface. 69b0d29bc4SBrooks Davis if ((*iter) == "unprivileged-user" || (*iter) == "unprivileged_user") 70b0d29bc4SBrooks Davis property = "unprivileged_user"; 71b0d29bc4SBrooks Davis else 72b0d29bc4SBrooks Davis property = F("test_suites.%s.%s") % test_suite_name % (*iter); 73b0d29bc4SBrooks Davis 74b0d29bc4SBrooks Davis if (!user_config.is_set(property)) 75b0d29bc4SBrooks Davis return F("Required configuration property '%s' not defined") % 76b0d29bc4SBrooks Davis (*iter); 77b0d29bc4SBrooks Davis } 78b0d29bc4SBrooks Davis return ""; 79b0d29bc4SBrooks Davis } 80b0d29bc4SBrooks Davis 81b0d29bc4SBrooks Davis 82b0d29bc4SBrooks Davis /// Checks if the allowed architectures match the current architecture. 83b0d29bc4SBrooks Davis /// 84b0d29bc4SBrooks Davis /// \param allowed_architectures Set of allowed architectures. 85b0d29bc4SBrooks Davis /// \param user_config Runtime user configuration. 86b0d29bc4SBrooks Davis /// 87b0d29bc4SBrooks Davis /// \return Empty if the current architecture is in the list or an error 88b0d29bc4SBrooks Davis /// message otherwise. 89b0d29bc4SBrooks Davis static std::string 90b0d29bc4SBrooks Davis check_allowed_architectures(const model::strings_set& allowed_architectures, 91b0d29bc4SBrooks Davis const config::tree& user_config) 92b0d29bc4SBrooks Davis { 93b0d29bc4SBrooks Davis if (!allowed_architectures.empty()) { 94b0d29bc4SBrooks Davis const std::string architecture = 95b0d29bc4SBrooks Davis user_config.lookup< config::string_node >("architecture"); 96b0d29bc4SBrooks Davis if (allowed_architectures.find(architecture) == 97b0d29bc4SBrooks Davis allowed_architectures.end()) 98b0d29bc4SBrooks Davis return F("Current architecture '%s' not supported") % architecture; 99b0d29bc4SBrooks Davis } 100b0d29bc4SBrooks Davis return ""; 101b0d29bc4SBrooks Davis } 102b0d29bc4SBrooks Davis 103b0d29bc4SBrooks Davis 104*257e70f1SIgor Ostapenko /// Checks if test's execenv matches the user configuration. 105*257e70f1SIgor Ostapenko /// 106*257e70f1SIgor Ostapenko /// \param execenv Execution environment name a test is designed for. 107*257e70f1SIgor Ostapenko /// \param user_config Runtime user configuration. 108*257e70f1SIgor Ostapenko /// 109*257e70f1SIgor Ostapenko /// \return Empty if the execenv is in the list or an error message otherwise. 110*257e70f1SIgor Ostapenko static std::string 111*257e70f1SIgor Ostapenko check_execenv(const std::string& execenv, const config::tree& user_config) 112*257e70f1SIgor Ostapenko { 113*257e70f1SIgor Ostapenko std::string name = execenv; 114*257e70f1SIgor Ostapenko if (name.empty()) 115*257e70f1SIgor Ostapenko name = engine::execenv::default_execenv_name; // if test claims nothing 116*257e70f1SIgor Ostapenko 117*257e70f1SIgor Ostapenko std::set< std::string > execenvs; 118*257e70f1SIgor Ostapenko try { 119*257e70f1SIgor Ostapenko execenvs = user_config.lookup< config::strings_set_node >("execenvs"); 120*257e70f1SIgor Ostapenko } catch (const config::unknown_key_error&) { 121*257e70f1SIgor Ostapenko // okay, user config does not define it, empty set then 122*257e70f1SIgor Ostapenko } 123*257e70f1SIgor Ostapenko 124*257e70f1SIgor Ostapenko if (execenvs.find(name) == execenvs.end()) 125*257e70f1SIgor Ostapenko return F("'%s' execenv is not supported or not allowed by " 126*257e70f1SIgor Ostapenko "the runtime user configuration") % name; 127*257e70f1SIgor Ostapenko 128*257e70f1SIgor Ostapenko return ""; 129*257e70f1SIgor Ostapenko } 130*257e70f1SIgor Ostapenko 131*257e70f1SIgor Ostapenko 132b0d29bc4SBrooks Davis /// Checks if the allowed platforms match the current architecture. 133b0d29bc4SBrooks Davis /// 134b0d29bc4SBrooks Davis /// \param allowed_platforms Set of allowed platforms. 135b0d29bc4SBrooks Davis /// \param user_config Runtime user configuration. 136b0d29bc4SBrooks Davis /// 137b0d29bc4SBrooks Davis /// \return Empty if the current platform is in the list or an error message 138b0d29bc4SBrooks Davis /// otherwise. 139b0d29bc4SBrooks Davis static std::string 140b0d29bc4SBrooks Davis check_allowed_platforms(const model::strings_set& allowed_platforms, 141b0d29bc4SBrooks Davis const config::tree& user_config) 142b0d29bc4SBrooks Davis { 143b0d29bc4SBrooks Davis if (!allowed_platforms.empty()) { 144b0d29bc4SBrooks Davis const std::string platform = 145b0d29bc4SBrooks Davis user_config.lookup< config::string_node >("platform"); 146b0d29bc4SBrooks Davis if (allowed_platforms.find(platform) == allowed_platforms.end()) 147b0d29bc4SBrooks Davis return F("Current platform '%s' not supported") % platform; 148b0d29bc4SBrooks Davis } 149b0d29bc4SBrooks Davis return ""; 150b0d29bc4SBrooks Davis } 151b0d29bc4SBrooks Davis 152b0d29bc4SBrooks Davis 153b0d29bc4SBrooks Davis /// Checks if the current user matches the required user. 154b0d29bc4SBrooks Davis /// 155b0d29bc4SBrooks Davis /// \param required_user Name of the required user category. 156b0d29bc4SBrooks Davis /// \param user_config Runtime user configuration. 157b0d29bc4SBrooks Davis /// 158b0d29bc4SBrooks Davis /// \return Empty if the current user fits the required user characteristics or 159b0d29bc4SBrooks Davis /// an error message otherwise. 160b0d29bc4SBrooks Davis static std::string 161b0d29bc4SBrooks Davis check_required_user(const std::string& required_user, 162b0d29bc4SBrooks Davis const config::tree& user_config) 163b0d29bc4SBrooks Davis { 164b0d29bc4SBrooks Davis if (!required_user.empty()) { 165b0d29bc4SBrooks Davis const passwd::user user = passwd::current_user(); 166b0d29bc4SBrooks Davis if (required_user == "root") { 167b0d29bc4SBrooks Davis if (!user.is_root()) 168b0d29bc4SBrooks Davis return "Requires root privileges"; 169b0d29bc4SBrooks Davis } else if (required_user == "unprivileged") { 170b0d29bc4SBrooks Davis if (user.is_root()) 171b0d29bc4SBrooks Davis if (!user_config.is_set("unprivileged_user")) 172b0d29bc4SBrooks Davis return "Requires an unprivileged user but the " 173b0d29bc4SBrooks Davis "unprivileged-user configuration variable is not " 174b0d29bc4SBrooks Davis "defined"; 175b0d29bc4SBrooks Davis } else 176b0d29bc4SBrooks Davis UNREACHABLE_MSG("Value of require.user not properly validated"); 177b0d29bc4SBrooks Davis } 178b0d29bc4SBrooks Davis return ""; 179b0d29bc4SBrooks Davis } 180b0d29bc4SBrooks Davis 181b0d29bc4SBrooks Davis 182b0d29bc4SBrooks Davis /// Checks if all required files exist. 183b0d29bc4SBrooks Davis /// 184b0d29bc4SBrooks Davis /// \param required_files Set of paths. 185b0d29bc4SBrooks Davis /// 186b0d29bc4SBrooks Davis /// \return Empty if the required files all exist or an error message otherwise. 187b0d29bc4SBrooks Davis static std::string 188b0d29bc4SBrooks Davis check_required_files(const model::paths_set& required_files) 189b0d29bc4SBrooks Davis { 190b0d29bc4SBrooks Davis for (model::paths_set::const_iterator iter = required_files.begin(); 191b0d29bc4SBrooks Davis iter != required_files.end(); iter++) { 192b0d29bc4SBrooks Davis INV((*iter).is_absolute()); 193b0d29bc4SBrooks Davis if (!fs::exists(*iter)) 194b0d29bc4SBrooks Davis return F("Required file '%s' not found") % *iter; 195b0d29bc4SBrooks Davis } 196b0d29bc4SBrooks Davis return ""; 197b0d29bc4SBrooks Davis } 198b0d29bc4SBrooks Davis 199b0d29bc4SBrooks Davis 200b0d29bc4SBrooks Davis /// Checks if all required programs exist. 201b0d29bc4SBrooks Davis /// 202b0d29bc4SBrooks Davis /// \param required_programs Set of paths. 203b0d29bc4SBrooks Davis /// 204b0d29bc4SBrooks Davis /// \return Empty if the required programs all exist or an error message 205b0d29bc4SBrooks Davis /// otherwise. 206b0d29bc4SBrooks Davis static std::string 207b0d29bc4SBrooks Davis check_required_programs(const model::paths_set& required_programs) 208b0d29bc4SBrooks Davis { 209b0d29bc4SBrooks Davis for (model::paths_set::const_iterator iter = required_programs.begin(); 210b0d29bc4SBrooks Davis iter != required_programs.end(); iter++) { 211b0d29bc4SBrooks Davis if ((*iter).is_absolute()) { 212b0d29bc4SBrooks Davis if (!fs::exists(*iter)) 213b0d29bc4SBrooks Davis return F("Required program '%s' not found") % *iter; 214b0d29bc4SBrooks Davis } else { 215b0d29bc4SBrooks Davis if (!fs::find_in_path((*iter).c_str())) 216b0d29bc4SBrooks Davis return F("Required program '%s' not found in PATH") % *iter; 217b0d29bc4SBrooks Davis } 218b0d29bc4SBrooks Davis } 219b0d29bc4SBrooks Davis return ""; 220b0d29bc4SBrooks Davis } 221b0d29bc4SBrooks Davis 222b0d29bc4SBrooks Davis 223b0d29bc4SBrooks Davis /// Checks if the current system has the specified amount of memory. 224b0d29bc4SBrooks Davis /// 225b0d29bc4SBrooks Davis /// \param required_memory Amount of required physical memory, or zero if not 226b0d29bc4SBrooks Davis /// applicable. 227b0d29bc4SBrooks Davis /// 228b0d29bc4SBrooks Davis /// \return Empty if the current system has the required amount of memory or an 229b0d29bc4SBrooks Davis /// error message otherwise. 230b0d29bc4SBrooks Davis static std::string 231b0d29bc4SBrooks Davis check_required_memory(const units::bytes& required_memory) 232b0d29bc4SBrooks Davis { 233b0d29bc4SBrooks Davis if (required_memory > 0) { 234b0d29bc4SBrooks Davis const units::bytes physical_memory = utils::physical_memory(); 235b0d29bc4SBrooks Davis if (physical_memory > 0 && physical_memory < required_memory) 236b0d29bc4SBrooks Davis return F("Requires %s bytes of physical memory but only %s " 237b0d29bc4SBrooks Davis "available") % 238b0d29bc4SBrooks Davis required_memory.format() % physical_memory.format(); 239b0d29bc4SBrooks Davis } 240b0d29bc4SBrooks Davis return ""; 241b0d29bc4SBrooks Davis } 242b0d29bc4SBrooks Davis 243b0d29bc4SBrooks Davis 244b0d29bc4SBrooks Davis /// Checks if the work directory's file system has enough free disk space. 245b0d29bc4SBrooks Davis /// 246b0d29bc4SBrooks Davis /// \param required_disk_space Amount of required free disk space, or zero if 247b0d29bc4SBrooks Davis /// not applicable. 248b0d29bc4SBrooks Davis /// \param work_directory Path to where the test case will be run. 249b0d29bc4SBrooks Davis /// 250b0d29bc4SBrooks Davis /// \return Empty if the file system where the work directory is hosted has 251b0d29bc4SBrooks Davis /// enough free disk space or an error message otherwise. 252b0d29bc4SBrooks Davis static std::string 253b0d29bc4SBrooks Davis check_required_disk_space(const units::bytes& required_disk_space, 254b0d29bc4SBrooks Davis const fs::path& work_directory) 255b0d29bc4SBrooks Davis { 256b0d29bc4SBrooks Davis if (required_disk_space > 0) { 257b0d29bc4SBrooks Davis const units::bytes free_disk_space = fs::free_disk_space( 258b0d29bc4SBrooks Davis work_directory); 259b0d29bc4SBrooks Davis if (free_disk_space < required_disk_space) 260b0d29bc4SBrooks Davis return F("Requires %s bytes of free disk space but only %s " 261b0d29bc4SBrooks Davis "available") % 262b0d29bc4SBrooks Davis required_disk_space.format() % free_disk_space.format(); 263b0d29bc4SBrooks Davis } 264b0d29bc4SBrooks Davis return ""; 265b0d29bc4SBrooks Davis } 266b0d29bc4SBrooks Davis 267b0d29bc4SBrooks Davis 268b0d29bc4SBrooks Davis } // anonymous namespace 269b0d29bc4SBrooks Davis 270b0d29bc4SBrooks Davis 271b0d29bc4SBrooks Davis /// Checks if all the requirements specified by the test case are met. 272b0d29bc4SBrooks Davis /// 273b0d29bc4SBrooks Davis /// \param md The test metadata. 274b0d29bc4SBrooks Davis /// \param cfg The engine configuration. 275b0d29bc4SBrooks Davis /// \param test_suite Name of the test suite the test belongs to. 276b0d29bc4SBrooks Davis /// \param work_directory Path to where the test case will be run. 277b0d29bc4SBrooks Davis /// 278b0d29bc4SBrooks Davis /// \return A string describing the reason for skipping the test, or empty if 279b0d29bc4SBrooks Davis /// the test should be executed. 280b0d29bc4SBrooks Davis std::string 281b0d29bc4SBrooks Davis engine::check_reqs(const model::metadata& md, const config::tree& cfg, 282b0d29bc4SBrooks Davis const std::string& test_suite, 283b0d29bc4SBrooks Davis const fs::path& work_directory) 284b0d29bc4SBrooks Davis { 285b0d29bc4SBrooks Davis std::string reason; 286b0d29bc4SBrooks Davis 287b0d29bc4SBrooks Davis reason = check_required_configs(md.required_configs(), cfg, test_suite); 288b0d29bc4SBrooks Davis if (!reason.empty()) 289b0d29bc4SBrooks Davis return reason; 290b0d29bc4SBrooks Davis 291b0d29bc4SBrooks Davis reason = check_allowed_architectures(md.allowed_architectures(), cfg); 292b0d29bc4SBrooks Davis if (!reason.empty()) 293b0d29bc4SBrooks Davis return reason; 294b0d29bc4SBrooks Davis 295*257e70f1SIgor Ostapenko reason = check_execenv(md.execenv(), cfg); 296*257e70f1SIgor Ostapenko if (!reason.empty()) 297*257e70f1SIgor Ostapenko return reason; 298*257e70f1SIgor Ostapenko 299b0d29bc4SBrooks Davis reason = check_allowed_platforms(md.allowed_platforms(), cfg); 300b0d29bc4SBrooks Davis if (!reason.empty()) 301b0d29bc4SBrooks Davis return reason; 302b0d29bc4SBrooks Davis 303b0d29bc4SBrooks Davis reason = check_required_user(md.required_user(), cfg); 304b0d29bc4SBrooks Davis if (!reason.empty()) 305b0d29bc4SBrooks Davis return reason; 306b0d29bc4SBrooks Davis 307b0d29bc4SBrooks Davis reason = check_required_files(md.required_files()); 308b0d29bc4SBrooks Davis if (!reason.empty()) 309b0d29bc4SBrooks Davis return reason; 310b0d29bc4SBrooks Davis 311b0d29bc4SBrooks Davis reason = check_required_programs(md.required_programs()); 312b0d29bc4SBrooks Davis if (!reason.empty()) 313b0d29bc4SBrooks Davis return reason; 314b0d29bc4SBrooks Davis 315b0d29bc4SBrooks Davis reason = check_required_memory(md.required_memory()); 316b0d29bc4SBrooks Davis if (!reason.empty()) 317b0d29bc4SBrooks Davis return reason; 318b0d29bc4SBrooks Davis 319b0d29bc4SBrooks Davis reason = check_required_disk_space(md.required_disk_space(), 320b0d29bc4SBrooks Davis work_directory); 321b0d29bc4SBrooks Davis if (!reason.empty()) 322b0d29bc4SBrooks Davis return reason; 323b0d29bc4SBrooks Davis 324b0d29bc4SBrooks Davis INV(reason.empty()); 325b0d29bc4SBrooks Davis return reason; 326b0d29bc4SBrooks Davis } 327