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