1//
2// Automated Testing Framework (atf)
3//
4// Copyright (c) 2007, 2008, 2009, 2010 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// TODO: We probably don't want to raise std::runtime_error for the errors
31// detected in this file.
32#include <stdexcept>
33
34#include "atf-c++/config.hpp"
35
36#include "atf-c++/detail/fs.hpp"
37#include "atf-c++/detail/env.hpp"
38#include "atf-c++/detail/sanity.hpp"
39#include "atf-c++/detail/text.hpp"
40
41#include "requirements.hpp"
42#include "user.hpp"
43
44namespace impl = atf::atf_run;
45
46namespace {
47
48static
49bool
50has_program(const atf::fs::path& program)
51{
52    bool found = false;
53
54    if (program.is_absolute()) {
55        found = atf::fs::is_executable(program);
56    } else {
57        if (program.str().find('/') != std::string::npos)
58            throw std::runtime_error("Relative paths are not allowed "
59                                     "when searching for a program (" +
60                                     program.str() + ")");
61
62        const std::vector< std::string > dirs = atf::text::split(
63            atf::env::get("PATH"), ":");
64        for (std::vector< std::string >::const_iterator iter = dirs.begin();
65             !found && iter != dirs.end(); iter++) {
66            const atf::fs::path& p = atf::fs::path(*iter) / program;
67            if (atf::fs::is_executable(p))
68                found = true;
69        }
70    }
71
72    return found;
73}
74
75static
76std::string
77check_arch(const std::string& arches)
78{
79    const std::vector< std::string > v = atf::text::split(arches, " ");
80
81    for (std::vector< std::string >::const_iterator iter = v.begin();
82         iter != v.end(); iter++) {
83        if ((*iter) == atf::config::get("atf_arch"))
84            return "";
85    }
86
87    if (v.size() == 1)
88        return "Requires the '" + arches + "' architecture";
89    else
90        return "Requires one of the '" + arches + "' architectures";
91}
92
93static
94std::string
95check_config(const std::string& variables, const atf::tests::vars_map& config)
96{
97    const std::vector< std::string > v = atf::text::split(variables, " ");
98    for (std::vector< std::string >::const_iterator iter = v.begin();
99         iter != v.end(); iter++) {
100        if (config.find((*iter)) == config.end())
101            return "Required configuration variable '" + (*iter) + "' not "
102                "defined";
103    }
104    return "";
105}
106
107static
108std::string
109check_machine(const std::string& machines)
110{
111    const std::vector< std::string > v = atf::text::split(machines, " ");
112
113    for (std::vector< std::string >::const_iterator iter = v.begin();
114         iter != v.end(); iter++) {
115        if ((*iter) == atf::config::get("atf_machine"))
116            return "";
117    }
118
119    if (v.size() == 1)
120        return "Requires the '" + machines + "' machine type";
121    else
122        return "Requires one of the '" + machines + "' machine types";
123}
124
125static
126std::string
127check_progs(const std::string& progs)
128{
129    const std::vector< std::string > v = atf::text::split(progs, " ");
130    for (std::vector< std::string >::const_iterator iter = v.begin();
131         iter != v.end(); iter++) {
132        if (!has_program(atf::fs::path(*iter)))
133            return "Required program '" + (*iter) + "' not found in the PATH";
134    }
135    return "";
136}
137
138static
139std::string
140check_user(const std::string& user, const atf::tests::vars_map& config)
141{
142    if (user == "root") {
143        if (!impl::is_root())
144            return "Requires root privileges";
145        else
146            return "";
147    } else if (user == "unprivileged") {
148        if (impl::is_root()) {
149            const atf::tests::vars_map::const_iterator iter = config.find(
150                "unprivileged-user");
151            if (iter == config.end())
152                return "Requires an unprivileged user and the "
153                    "'unprivileged-user' configuration variable is not set";
154            else {
155                const std::string& unprivileged_user = (*iter).second;
156                try {
157                    (void)impl::get_user_ids(unprivileged_user);
158                    return "";
159                } catch (const std::runtime_error& e) {
160                    return "Failed to get information for user " +
161                        unprivileged_user;
162                }
163            }
164        } else
165            return "";
166    } else
167        throw std::runtime_error("Invalid value '" + user + "' for property "
168                                 "require.user");
169}
170
171} // anonymous namespace
172
173std::string
174impl::check_requirements(const atf::tests::vars_map& metadata,
175                         const atf::tests::vars_map& config)
176{
177    std::string failure_reason = "";
178
179    for (atf::tests::vars_map::const_iterator iter = metadata.begin();
180         failure_reason.empty() && iter != metadata.end(); iter++) {
181        const std::string& name = (*iter).first;
182        const std::string& value = (*iter).second;
183        INV(!value.empty()); // Enforced by application/X-atf-tp parser.
184
185        if (name == "require.arch")
186            failure_reason = check_arch(value);
187        else if (name == "require.config")
188            failure_reason = check_config(value, config);
189        else if (name == "require.machine")
190            failure_reason = check_machine(value);
191        else if (name == "require.progs")
192            failure_reason = check_progs(value);
193        else if (name == "require.user")
194            failure_reason = check_user(value, config);
195        else {
196            // Unknown require.* properties are forbidden by the
197            // application/X-atf-tp parser.
198            INV(failure_reason.find("require.") != 0);
199        }
200    }
201
202    return failure_reason;
203}
204
205std::pair< int, int >
206impl::get_required_user(const atf::tests::vars_map& metadata,
207                        const atf::tests::vars_map& config)
208{
209    const atf::tests::vars_map::const_iterator user = metadata.find(
210        "require.user");
211    if (user == metadata.end())
212        return std::make_pair(-1, -1);
213
214    if ((*user).second == "unprivileged") {
215        if (impl::is_root()) {
216            const atf::tests::vars_map::const_iterator iter = config.find(
217                "unprivileged-user");
218            try {
219                return impl::get_user_ids((*iter).second);
220            } catch (const std::exception& e) {
221                UNREACHABLE;  // This has been validated by check_user.
222                throw e;
223            }
224        } else {
225            return std::make_pair(-1, -1);
226        }
227    } else
228        return std::make_pair(-1, -1);
229}
230