1//
2// Automated Testing Framework (atf)
3//
4// Copyright (c) 2007, 2008, 2009 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
49#include "fs.hpp"
50#include "user.hpp"
51
52namespace impl = atf::atf_run;
53#define IMPL_NAME "atf::atf_run"
54
55// ------------------------------------------------------------------------
56// Auxiliary functions.
57// ------------------------------------------------------------------------
58
59static void cleanup_aux(const atf::fs::path&, dev_t, bool);
60static void cleanup_aux_dir(const atf::fs::path&, const atf::fs::file_info&,
61                            bool);
62static void do_unmount(const atf::fs::path&);
63
64// The erase parameter in this routine is to control nested mount points.
65// We want to descend into a mount point to unmount anything that is
66// mounted under it, but we do not want to delete any files while doing
67// this traversal.  In other words, we erase files until we cross the
68// first mount point, and after that point we only scan and unmount.
69static
70void
71cleanup_aux(const atf::fs::path& p, dev_t parent_device, bool erase)
72{
73    atf::fs::file_info fi(p);
74
75    if (fi.get_type() == atf::fs::file_info::dir_type)
76        cleanup_aux_dir(p, fi, fi.get_device() == parent_device);
77
78    if (fi.get_device() != parent_device)
79        do_unmount(p);
80
81    if (erase) {
82        if (fi.get_type() == atf::fs::file_info::dir_type)
83            atf::fs::rmdir(p);
84        else
85            atf::fs::remove(p);
86    }
87}
88
89static
90void
91cleanup_aux_dir(const atf::fs::path& p, const atf::fs::file_info& fi,
92                bool erase)
93{
94    if (erase && ((fi.get_mode() & S_IRWXU) != S_IRWXU)) {
95        if (chmod(p.c_str(), fi.get_mode() | S_IRWXU) == -1)
96            throw atf::system_error(IMPL_NAME "::cleanup(" +
97                                    p.str() + ")", "chmod(2) failed", errno);
98    }
99
100    std::set< std::string > subdirs;
101    {
102        const atf::fs::directory d(p);
103        subdirs = d.names();
104    }
105
106    for (std::set< std::string >::const_iterator iter = subdirs.begin();
107         iter != subdirs.end(); iter++) {
108        const std::string& name = *iter;
109        if (name != "." && name != "..")
110            cleanup_aux(p / name, fi.get_device(), erase);
111    }
112}
113
114static
115void
116do_unmount(const atf::fs::path& in_path)
117{
118    // At least, FreeBSD's unmount(2) requires the path to be absolute.
119    // Let's make it absolute in all cases just to be safe that this does
120    // not affect other systems.
121    const atf::fs::path& abs_path = in_path.is_absolute() ?
122        in_path : in_path.to_absolute();
123
124#if defined(HAVE_UNMOUNT)
125    if (unmount(abs_path.c_str(), 0) == -1)
126        throw atf::system_error(IMPL_NAME "::cleanup(" + in_path.str() + ")",
127                                "unmount(2) failed", errno);
128#else
129    // We could use umount(2) instead if it was available... but
130    // trying to do so under, e.g. Linux, is a nightmare because we
131    // also have to update /etc/mtab to match what we did.  It is
132    // satf::fser to just leave the system-specific umount(8) tool deal
133    // with it, at least for now.
134
135    const atf::fs::path prog("umount");
136    atf::process::argv_array argv("umount", abs_path.c_str(), NULL);
137
138    atf::process::status s = atf::process::exec(prog, argv,
139        atf::process::stream_inherit(), atf::process::stream_inherit());
140    if (!s.exited() || s.exitstatus() != EXIT_SUCCESS)
141        throw std::runtime_error("Call to unmount failed");
142#endif
143}
144
145// ------------------------------------------------------------------------
146// The "temp_dir" class.
147// ------------------------------------------------------------------------
148
149impl::temp_dir::temp_dir(const atf::fs::path& p)
150{
151    atf::utils::auto_array< char > buf(new char[p.str().length() + 1]);
152    std::strcpy(buf.get(), p.c_str());
153    if (::mkdtemp(buf.get()) == NULL)
154        throw system_error(IMPL_NAME "::temp_dir::temp_dir(" +
155                           p.str() + ")", "mkdtemp(3) failed",
156                           errno);
157
158    m_path.reset(new atf::fs::path(buf.get()));
159}
160
161impl::temp_dir::~temp_dir(void)
162{
163    cleanup(*m_path);
164}
165
166const atf::fs::path&
167impl::temp_dir::get_path(void)
168    const
169{
170    return *m_path;
171}
172
173// ------------------------------------------------------------------------
174// Free functions.
175// ------------------------------------------------------------------------
176
177atf::fs::path
178impl::change_directory(const atf::fs::path& dir)
179{
180    atf::fs::path olddir = get_current_dir();
181
182    if (olddir != dir) {
183        if (::chdir(dir.c_str()) == -1)
184            throw system_error(IMPL_NAME "::chdir(" + dir.str() + ")",
185                               "chdir(2) failed", errno);
186    }
187
188    return olddir;
189}
190
191void
192impl::cleanup(const atf::fs::path& p)
193{
194    atf::fs::file_info fi(p);
195    cleanup_aux(p, fi.get_device(), true);
196}
197
198atf::fs::path
199impl::get_current_dir(void)
200{
201    std::auto_ptr< char > cwd;
202#if defined(HAVE_GETCWD_DYN)
203    cwd.reset(getcwd(NULL, 0));
204#else
205    cwd.reset(getcwd(NULL, MAXPATHLEN));
206#endif
207    if (cwd.get() == NULL)
208        throw atf::system_error(IMPL_NAME "::get_current_dir()",
209                                "getcwd() failed", errno);
210
211    return atf::fs::path(cwd.get());
212}
213