1 //
2 // Automated Testing Framework (atf)
3 //
4 // Copyright (c) 2007 The NetBSD Foundation, Inc.
5 // All rights reserved.
6 //
7 // Redistribution and use in source and binary forms, with or without
8 // modification, are permitted provided that the following conditions
9 // are met:
10 // 1. Redistributions of source code must retain the above copyright
11 //    notice, this list of conditions and the following disclaimer.
12 // 2. Redistributions in binary form must reproduce the above copyright
13 //    notice, this list of conditions and the following disclaimer in the
14 //    documentation and/or other materials provided with the distribution.
15 //
16 // THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
17 // CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
18 // INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
19 // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 // IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
21 // DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
23 // GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24 // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
25 // IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
26 // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
27 // IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 //
29 
30 extern "C" {
31 #include <sys/param.h>
32 #include <sys/sysctl.h>
33 }
34 
35 #include <cassert>
36 #include <cerrno>
37 #include <cstdlib>
38 #include <cstring>
39 #include <stdexcept>
40 
41 #include "config.hpp"
42 #include "env.hpp"
43 #include "fs.hpp"
44 #include "requirements.hpp"
45 #include "text.hpp"
46 #include "user.hpp"
47 
48 namespace impl = tools;
49 
50 namespace {
51 
52 typedef std::map< std::string, std::string > vars_map;
53 
54 static
55 bool
has_program(const tools::fs::path & program)56 has_program(const tools::fs::path& program)
57 {
58     bool found = false;
59 
60     if (program.is_absolute()) {
61         found = tools::fs::is_executable(program);
62     } else {
63         if (program.str().find('/') != std::string::npos)
64             throw std::runtime_error("Relative paths are not allowed "
65                                      "when searching for a program (" +
66                                      program.str() + ")");
67 
68         const std::vector< std::string > dirs = tools::text::split(
69             tools::env::get("PATH"), ":");
70         for (std::vector< std::string >::const_iterator iter = dirs.begin();
71              !found && iter != dirs.end(); iter++) {
72             const tools::fs::path& p = tools::fs::path(*iter) / program;
73             if (tools::fs::is_executable(p))
74                 found = true;
75         }
76     }
77 
78     return found;
79 }
80 
81 static
82 std::string
check_arch(const std::string & arches)83 check_arch(const std::string& arches)
84 {
85     const std::vector< std::string > v = tools::text::split(arches, " ");
86 
87     for (std::vector< std::string >::const_iterator iter = v.begin();
88          iter != v.end(); iter++) {
89         if ((*iter) == tools::config::get("atf_arch"))
90             return "";
91     }
92 
93     if (v.size() == 1)
94         return "Requires the '" + arches + "' architecture";
95     else
96         return "Requires one of the '" + arches + "' architectures";
97 }
98 
99 static
100 std::string
check_config(const std::string & variables,const vars_map & config)101 check_config(const std::string& variables, const vars_map& config)
102 {
103     const std::vector< std::string > v = tools::text::split(variables, " ");
104     for (std::vector< std::string >::const_iterator iter = v.begin();
105          iter != v.end(); iter++) {
106         if (config.find((*iter)) == config.end())
107             return "Required configuration variable '" + (*iter) + "' not "
108                 "defined";
109     }
110     return "";
111 }
112 
113 static
114 std::string
check_files(const std::string & progs)115 check_files(const std::string& progs)
116 {
117     const std::vector< std::string > v = tools::text::split(progs, " ");
118     for (std::vector< std::string >::const_iterator iter = v.begin();
119          iter != v.end(); iter++) {
120         const tools::fs::path file(*iter);
121         if (!file.is_absolute())
122             throw std::runtime_error("Relative paths are not allowed when "
123                 "checking for a required file (" + file.str() + ")");
124         if (!tools::fs::exists(file))
125             return "Required file '" + file.str() + "' not found";
126     }
127     return "";
128 }
129 
130 static
131 std::string
check_machine(const std::string & machines)132 check_machine(const std::string& machines)
133 {
134     const std::vector< std::string > v = tools::text::split(machines, " ");
135 
136     for (std::vector< std::string >::const_iterator iter = v.begin();
137          iter != v.end(); iter++) {
138         if ((*iter) == tools::config::get("atf_machine"))
139             return "";
140     }
141 
142     if (v.size() == 1)
143         return "Requires the '" + machines + "' machine type";
144     else
145         return "Requires one of the '" + machines + "' machine types";
146 }
147 
148 static
149 std::string
check_memory(const std::string & raw_memory)150 check_memory(const std::string& raw_memory)
151 {
152 #if defined(__minix)
153     const char* e = std::strerror(errno);
154     return "Failed to get sysctl(hw.usermem64) value: " + std::string(e);
155 #else
156     const int64_t needed = tools::text::to_bytes(raw_memory);
157 
158     int64_t available;
159     std::size_t available_length = sizeof(available);
160     if (::sysctlbyname("hw.usermem64", &available, &available_length,
161                        NULL, 0) == -1) {
162         const char* e = std::strerror(errno);
163         return "Failed to get sysctl(hw.usermem64) value: " + std::string(e);
164     }
165 
166     if (available < needed) {
167         return "Not enough memory; needed " + tools::text::to_string(needed) +
168             ", available " + tools::text::to_string(available);
169     } else
170         return "";
171 #endif /* !defined(__minix) */
172 }
173 
174 static
175 std::string
check_progs(const std::string & progs)176 check_progs(const std::string& progs)
177 {
178     const std::vector< std::string > v = tools::text::split(progs, " ");
179     for (std::vector< std::string >::const_iterator iter = v.begin();
180          iter != v.end(); iter++) {
181         if (!has_program(tools::fs::path(*iter)))
182             return "Required program '" + (*iter) + "' not found in the PATH";
183     }
184     return "";
185 }
186 
187 static
188 std::string
check_user(const std::string & user,const vars_map & config)189 check_user(const std::string& user, const vars_map& config)
190 {
191     if (user == "root") {
192         if (!tools::user::is_root())
193             return "Requires root privileges";
194         else
195             return "";
196     } else if (user == "unprivileged") {
197         if (tools::user::is_root()) {
198             const vars_map::const_iterator iter = config.find(
199                 "unprivileged-user");
200             if (iter == config.end())
201                 return "Requires an unprivileged user and the "
202                     "'unprivileged-user' configuration variable is not set";
203             else {
204                 const std::string& unprivileged_user = (*iter).second;
205                 try {
206                     (void)tools::user::get_user_ids(unprivileged_user);
207                     return "";
208                 } catch (const std::runtime_error& e) {
209                     return "Failed to get information for user " +
210                         unprivileged_user;
211                 }
212             }
213         } else
214             return "";
215     } else
216         throw std::runtime_error("Invalid value '" + user + "' for property "
217                                  "require.user");
218 }
219 
220 } // anonymous namespace
221 
222 std::string
check_requirements(const vars_map & metadata,const vars_map & config)223 impl::check_requirements(const vars_map& metadata,
224                          const vars_map& config)
225 {
226     std::string failure_reason = "";
227 
228     for (vars_map::const_iterator iter = metadata.begin();
229          failure_reason.empty() && iter != metadata.end(); iter++) {
230         const std::string& name = (*iter).first;
231         const std::string& value = (*iter).second;
232         assert(!value.empty()); // Enforced by application/X-atf-tp parser.
233 
234         if (name == "require.arch")
235             failure_reason = check_arch(value);
236         else if (name == "require.config")
237             failure_reason = check_config(value, config);
238         else if (name == "require.files")
239             failure_reason = check_files(value);
240         else if (name == "require.machine")
241             failure_reason = check_machine(value);
242         else if (name == "require.memory")
243             failure_reason = check_memory(value);
244         else if (name == "require.progs")
245             failure_reason = check_progs(value);
246         else if (name == "require.user")
247             failure_reason = check_user(value, config);
248         else {
249             // Unknown require.* properties are forbidden by the
250             // application/X-atf-tp parser.
251             assert(failure_reason.find("require.") != 0);
252         }
253     }
254 
255     return failure_reason;
256 }
257 
258 std::pair< int, int >
get_required_user(const vars_map & metadata,const vars_map & config)259 impl::get_required_user(const vars_map& metadata,
260                         const vars_map& config)
261 {
262     const vars_map::const_iterator user = metadata.find(
263         "require.user");
264     if (user == metadata.end())
265         return std::make_pair(-1, -1);
266 
267     if ((*user).second == "unprivileged") {
268         if (tools::user::is_root()) {
269             const vars_map::const_iterator iter = config.find(
270                 "unprivileged-user");
271             try {
272                 return tools::user::get_user_ids((*iter).second);
273             } catch (const std::exception& e) {
274                 std::abort();  // This has been validated by check_user.
275             }
276         } else {
277             return std::make_pair(-1, -1);
278         }
279     } else
280         return std::make_pair(-1, -1);
281 }
282