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 #ifdef __FreeBSD__ 45 #include <libutil.h> 46 #endif 47 48 namespace config = utils::config; 49 namespace fs = utils::fs; 50 namespace passwd = utils::passwd; 51 namespace units = utils::units; 52 53 54 namespace { 55 56 57 /// Checks if all required configuration variables are present. 58 /// 59 /// \param required_configs Set of required variable names. 60 /// \param user_config Runtime user configuration. 61 /// \param test_suite_name Name of the test suite the test belongs to. 62 /// 63 /// \return Empty if all variables are present or an error message otherwise. 64 static std::string 65 check_required_configs(const model::strings_set& required_configs, 66 const config::tree& user_config, 67 const std::string& test_suite_name) 68 { 69 for (model::strings_set::const_iterator iter = required_configs.begin(); 70 iter != required_configs.end(); iter++) { 71 std::string property; 72 // TODO(jmmv): All this rewrite logic belongs in the ATF interface. 73 if ((*iter) == "unprivileged-user" || (*iter) == "unprivileged_user") 74 property = "unprivileged_user"; 75 else 76 property = F("test_suites.%s.%s") % test_suite_name % (*iter); 77 78 if (!user_config.is_set(property)) 79 return F("Required configuration property '%s' not defined") % 80 (*iter); 81 } 82 return ""; 83 } 84 85 86 /// Checks if the allowed architectures match the current architecture. 87 /// 88 /// \param allowed_architectures Set of allowed architectures. 89 /// \param user_config Runtime user configuration. 90 /// 91 /// \return Empty if the current architecture is in the list or an error 92 /// message otherwise. 93 static std::string 94 check_allowed_architectures(const model::strings_set& allowed_architectures, 95 const config::tree& user_config) 96 { 97 if (!allowed_architectures.empty()) { 98 const std::string architecture = 99 user_config.lookup< config::string_node >("architecture"); 100 if (allowed_architectures.find(architecture) == 101 allowed_architectures.end()) 102 return F("Current architecture '%s' not supported") % architecture; 103 } 104 return ""; 105 } 106 107 108 /// Checks if test's execenv matches the user configuration. 109 /// 110 /// \param execenv Execution environment name a test is designed for. 111 /// \param user_config Runtime user configuration. 112 /// 113 /// \return Empty if the execenv is in the list or an error message otherwise. 114 static std::string 115 check_execenv(const std::string& execenv, const config::tree& user_config) 116 { 117 std::string name = execenv; 118 if (name.empty()) 119 name = engine::execenv::default_execenv_name; // if test claims nothing 120 121 std::set< std::string > execenvs; 122 try { 123 execenvs = user_config.lookup< config::strings_set_node >("execenvs"); 124 } catch (const config::unknown_key_error&) { 125 // okay, user config does not define it, empty set then 126 } 127 128 if (execenvs.find(name) == execenvs.end()) 129 return F("'%s' execenv is not supported or not allowed by " 130 "the runtime user configuration") % name; 131 132 return ""; 133 } 134 135 136 /// Checks if the allowed platforms match the current architecture. 137 /// 138 /// \param allowed_platforms Set of allowed platforms. 139 /// \param user_config Runtime user configuration. 140 /// 141 /// \return Empty if the current platform is in the list or an error message 142 /// otherwise. 143 static std::string 144 check_allowed_platforms(const model::strings_set& allowed_platforms, 145 const config::tree& user_config) 146 { 147 if (!allowed_platforms.empty()) { 148 const std::string platform = 149 user_config.lookup< config::string_node >("platform"); 150 if (allowed_platforms.find(platform) == allowed_platforms.end()) 151 return F("Current platform '%s' not supported") % platform; 152 } 153 return ""; 154 } 155 156 157 /// Checks if the current user matches the required user. 158 /// 159 /// \param required_user Name of the required user category. 160 /// \param user_config Runtime user configuration. 161 /// 162 /// \return Empty if the current user fits the required user characteristics or 163 /// an error message otherwise. 164 static std::string 165 check_required_user(const std::string& required_user, 166 const config::tree& user_config) 167 { 168 if (!required_user.empty()) { 169 const passwd::user user = passwd::current_user(); 170 if (required_user == "root") { 171 if (!user.is_root()) 172 return "Requires root privileges"; 173 } else if (required_user == "unprivileged") { 174 if (user.is_root()) 175 if (!user_config.is_set("unprivileged_user")) 176 return "Requires an unprivileged user but the " 177 "unprivileged-user configuration variable is not " 178 "defined"; 179 } else 180 UNREACHABLE_MSG("Value of require.user not properly validated"); 181 } 182 return ""; 183 } 184 185 186 /// Checks if all required files exist. 187 /// 188 /// \param required_files Set of paths. 189 /// 190 /// \return Empty if the required files all exist or an error message otherwise. 191 static std::string 192 check_required_files(const model::paths_set& required_files) 193 { 194 for (model::paths_set::const_iterator iter = required_files.begin(); 195 iter != required_files.end(); iter++) { 196 INV((*iter).is_absolute()); 197 if (!fs::exists(*iter)) 198 return F("Required file '%s' not found") % *iter; 199 } 200 return ""; 201 } 202 203 204 /// Checks if all required programs exist. 205 /// 206 /// \param required_programs Set of paths. 207 /// 208 /// \return Empty if the required programs all exist or an error message 209 /// otherwise. 210 static std::string 211 check_required_programs(const model::paths_set& required_programs) 212 { 213 for (model::paths_set::const_iterator iter = required_programs.begin(); 214 iter != required_programs.end(); iter++) { 215 if ((*iter).is_absolute()) { 216 if (!fs::exists(*iter)) 217 return F("Required program '%s' not found") % *iter; 218 } else { 219 if (!fs::find_in_path((*iter).c_str())) 220 return F("Required program '%s' not found in PATH") % *iter; 221 } 222 } 223 return ""; 224 } 225 226 227 #ifdef __FreeBSD__ 228 /// Checks if all required kmods are loaded. 229 /// 230 /// \param required_programs Set of kmods. 231 /// 232 /// \return Empty if the required kmods are all loaded or an error 233 /// message otherwise. 234 static std::string 235 check_required_kmods(const model::strings_set& required_kmods) 236 { 237 for (model::strings_set::const_iterator iter = required_kmods.begin(); 238 iter != required_kmods.end(); iter++) { 239 if (!kld_isloaded((*iter).c_str())) 240 return F("Required kmod '%s' not loaded") % *iter; 241 } 242 return ""; 243 } 244 #endif 245 246 247 /// Checks if the current system has the specified amount of memory. 248 /// 249 /// \param required_memory Amount of required physical memory, or zero if not 250 /// applicable. 251 /// 252 /// \return Empty if the current system has the required amount of memory or an 253 /// error message otherwise. 254 static std::string 255 check_required_memory(const units::bytes& required_memory) 256 { 257 if (required_memory > 0) { 258 const units::bytes physical_memory = utils::physical_memory(); 259 if (physical_memory > 0 && physical_memory < required_memory) 260 return F("Requires %s bytes of physical memory but only %s " 261 "available") % 262 required_memory.format() % physical_memory.format(); 263 } 264 return ""; 265 } 266 267 268 /// Checks if the work directory's file system has enough free disk space. 269 /// 270 /// \param required_disk_space Amount of required free disk space, or zero if 271 /// not applicable. 272 /// \param work_directory Path to where the test case will be run. 273 /// 274 /// \return Empty if the file system where the work directory is hosted has 275 /// enough free disk space or an error message otherwise. 276 static std::string 277 check_required_disk_space(const units::bytes& required_disk_space, 278 const fs::path& work_directory) 279 { 280 if (required_disk_space > 0) { 281 const units::bytes free_disk_space = fs::free_disk_space( 282 work_directory); 283 if (free_disk_space < required_disk_space) 284 return F("Requires %s bytes of free disk space but only %s " 285 "available") % 286 required_disk_space.format() % free_disk_space.format(); 287 } 288 return ""; 289 } 290 291 292 } // anonymous namespace 293 294 295 /// Checks if all the requirements specified by the test case are met. 296 /// 297 /// \param md The test metadata. 298 /// \param cfg The engine configuration. 299 /// \param test_suite Name of the test suite the test belongs to. 300 /// \param work_directory Path to where the test case will be run. 301 /// 302 /// \return A string describing the reason for skipping the test, or empty if 303 /// the test should be executed. 304 std::string 305 engine::check_reqs(const model::metadata& md, const config::tree& cfg, 306 const std::string& test_suite, 307 const fs::path& work_directory) 308 { 309 std::string reason; 310 311 reason = check_required_configs(md.required_configs(), cfg, test_suite); 312 if (!reason.empty()) 313 return reason; 314 315 reason = check_allowed_architectures(md.allowed_architectures(), cfg); 316 if (!reason.empty()) 317 return reason; 318 319 reason = check_execenv(md.execenv(), cfg); 320 if (!reason.empty()) 321 return reason; 322 323 reason = check_allowed_platforms(md.allowed_platforms(), cfg); 324 if (!reason.empty()) 325 return reason; 326 327 reason = check_required_user(md.required_user(), cfg); 328 if (!reason.empty()) 329 return reason; 330 331 reason = check_required_files(md.required_files()); 332 if (!reason.empty()) 333 return reason; 334 335 reason = check_required_programs(md.required_programs()); 336 if (!reason.empty()) 337 return reason; 338 339 #ifdef __FreeBSD__ 340 reason = check_required_kmods(md.required_kmods()); 341 if (!reason.empty()) 342 return reason; 343 #endif 344 345 reason = check_required_memory(md.required_memory()); 346 if (!reason.empty()) 347 return reason; 348 349 reason = check_required_disk_space(md.required_disk_space(), 350 work_directory); 351 if (!reason.empty()) 352 return reason; 353 354 INV(reason.empty()); 355 return reason; 356 } 357