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