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#if defined(HAVE_CONFIG_H)
31#include "bconfig.h"
32#endif
33
34extern "C" {
35#include <sys/types.h>
36#include <sys/param.h>
37#include <sys/mount.h>
38#include <sys/stat.h>
39
40#include <unistd.h>
41}
42
43#include <cerrno>
44#include <cstdlib>
45#include <cstring>
46
47#include "atf-c++/detail/process.hpp"
48#include "atf-c++/detail/sanity.hpp"
49
50#include "fs.hpp"
51#include "user.hpp"
52
53namespace impl = atf::atf_run;
54#define IMPL_NAME "atf::atf_run"
55
56// ------------------------------------------------------------------------
57// Auxiliary functions.
58// ------------------------------------------------------------------------
59
60static void cleanup_aux(const atf::fs::path&, dev_t, bool);
61static void cleanup_aux_dir(const atf::fs::path&, const atf::fs::file_info&,
62                            bool);
63static void do_unmount(const atf::fs::path&);
64
65// The cleanup routines below are tricky: they are executed immediately after
66// a test case's death, and after we have forcibly killed any stale processes.
67// However, even if the processes are dead, this does not mean that the file
68// system we are scanning is stable.  In particular, if the test case has
69// mounted file systems through fuse/puffs, the fact that the processes died
70// does not mean that the file system is truly unmounted.
71//
72// The code below attempts to cope with this by catching errors and either
73// ignoring them or retrying the actions on the same file/directory a few times
74// before giving up.
75static const int max_retries = 5;
76static const int retry_delay_in_seconds = 1;
77
78// The erase parameter in this routine is to control nested mount points.
79// We want to descend into a mount point to unmount anything that is
80// mounted under it, but we do not want to delete any files while doing
81// this traversal.  In other words, we erase files until we cross the
82// first mount point, and after that point we only scan and unmount.
83static
84void
85cleanup_aux(const atf::fs::path& p, dev_t parent_device, bool erase)
86{
87    try {
88        atf::fs::file_info fi(p);
89
90        if (fi.get_type() == atf::fs::file_info::dir_type)
91            cleanup_aux_dir(p, fi, fi.get_device() == parent_device);
92
93        if (fi.get_device() != parent_device)
94            do_unmount(p);
95
96        if (erase) {
97            if (fi.get_type() == atf::fs::file_info::dir_type)
98                atf::fs::rmdir(p);
99            else
100                atf::fs::remove(p);
101        }
102    } catch (const atf::system_error& e) {
103        if (e.code() != ENOENT && e.code() != ENOTDIR)
104            throw e;
105    }
106}
107
108static
109void
110cleanup_aux_dir(const atf::fs::path& p, const atf::fs::file_info& fi,
111                bool erase)
112{
113    if (erase && ((fi.get_mode() & S_IRWXU) != S_IRWXU)) {
114        int retries = max_retries;
115retry_chmod:
116        if (chmod(p.c_str(), fi.get_mode() | S_IRWXU) == -1) {
117            if (retries > 0) {
118                retries--;
119                ::sleep(retry_delay_in_seconds);
120                goto retry_chmod;
121            } else {
122                throw atf::system_error(IMPL_NAME "::cleanup(" +
123                                        p.str() + ")", "chmod(2) failed",
124                                        errno);
125            }
126        }
127    }
128
129    std::set< std::string > subdirs;
130    {
131        bool ok = false;
132        int retries = max_retries;
133        while (!ok) {
134            INV(retries > 0);
135            try {
136                const atf::fs::directory d(p);
137                subdirs = d.names();
138                ok = true;
139            } catch (const atf::system_error& e) {
140                retries--;
141                if (retries == 0)
142                    throw e;
143                ::sleep(retry_delay_in_seconds);
144            }
145        }
146        INV(ok);
147    }
148
149    for (std::set< std::string >::const_iterator iter = subdirs.begin();
150         iter != subdirs.end(); iter++) {
151        const std::string& name = *iter;
152        if (name != "." && name != "..")
153            cleanup_aux(p / name, fi.get_device(), erase);
154    }
155}
156
157static
158void
159do_unmount(const atf::fs::path& in_path)
160{
161    // At least, FreeBSD's unmount(2) requires the path to be absolute.
162    // Let's make it absolute in all cases just to be safe that this does
163    // not affect other systems.
164    const atf::fs::path& abs_path = in_path.is_absolute() ?
165        in_path : in_path.to_absolute();
166
167#if defined(HAVE_UNMOUNT)
168    int retries = max_retries;
169retry_unmount:
170    if (unmount(abs_path.c_str(), 0) == -1) {
171        if (errno == EBUSY && retries > 0) {
172            retries--;
173            ::sleep(retry_delay_in_seconds);
174            goto retry_unmount;
175        } else {
176            throw atf::system_error(IMPL_NAME "::cleanup(" + in_path.str() +
177                                    ")", "unmount(2) failed", errno);
178        }
179    }
180#else
181    // We could use umount(2) instead if it was available... but
182    // trying to do so under, e.g. Linux, is a nightmare because we
183    // also have to update /etc/mtab to match what we did.  It is
184    // satf::fser to just leave the system-specific umount(8) tool deal
185    // with it, at least for now.
186
187    const atf::fs::path prog("umount");
188    atf::process::argv_array argv("umount", abs_path.c_str(), NULL);
189
190    atf::process::status s = atf::process::exec(prog, argv,
191        atf::process::stream_inherit(), atf::process::stream_inherit());
192    if (!s.exited() || s.exitstatus() != EXIT_SUCCESS)
193        throw std::runtime_error("Call to unmount failed");
194#endif
195}
196
197// ------------------------------------------------------------------------
198// The "temp_dir" class.
199// ------------------------------------------------------------------------
200
201impl::temp_dir::temp_dir(const atf::fs::path& p)
202{
203    atf::utils::auto_array< char > buf(new char[p.str().length() + 1]);
204    std::strcpy(buf.get(), p.c_str());
205    if (::mkdtemp(buf.get()) == NULL)
206        throw system_error(IMPL_NAME "::temp_dir::temp_dir(" +
207                           p.str() + ")", "mkdtemp(3) failed",
208                           errno);
209
210    m_path.reset(new atf::fs::path(buf.get()));
211}
212
213impl::temp_dir::~temp_dir(void)
214{
215    cleanup(*m_path);
216}
217
218const atf::fs::path&
219impl::temp_dir::get_path(void)
220    const
221{
222    return *m_path;
223}
224
225// ------------------------------------------------------------------------
226// Free functions.
227// ------------------------------------------------------------------------
228
229atf::fs::path
230impl::change_directory(const atf::fs::path& dir)
231{
232    atf::fs::path olddir = get_current_dir();
233
234    if (olddir != dir) {
235        if (::chdir(dir.c_str()) == -1)
236            throw system_error(IMPL_NAME "::chdir(" + dir.str() + ")",
237                               "chdir(2) failed", errno);
238    }
239
240    return olddir;
241}
242
243void
244impl::cleanup(const atf::fs::path& p)
245{
246    atf::fs::file_info fi(p);
247    cleanup_aux(p, fi.get_device(), true);
248}
249
250atf::fs::path
251impl::get_current_dir(void)
252{
253    std::auto_ptr< char > cwd;
254#if defined(HAVE_GETCWD_DYN)
255    cwd.reset(getcwd(NULL, 0));
256#else
257    cwd.reset(getcwd(NULL, MAXPATHLEN));
258#endif
259    if (cwd.get() == NULL)
260        throw atf::system_error(IMPL_NAME "::get_current_dir()",
261                                "getcwd() failed", errno);
262
263    return atf::fs::path(cwd.get());
264}
265