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/types.h>
33 #include <sys/mount.h>
34 #include <sys/stat.h>
35 #include <sys/wait.h>
36
37 #include <dirent.h>
38 #include <libgen.h>
39 #include <unistd.h>
40 }
41
42 #include <cassert>
43 #include <cerrno>
44 #include <cstdlib>
45 #include <cstring>
46
47 #include "auto_array.hpp"
48 #include "env.hpp"
49 #include "exceptions.hpp"
50 #include "fs.hpp"
51 #include "text.hpp"
52 #include "user.hpp"
53
54 namespace impl = tools::fs;
55 #define IMPL_NAME "tools::fs"
56
57 // ------------------------------------------------------------------------
58 // Auxiliary functions.
59 // ------------------------------------------------------------------------
60
61 static void cleanup_aux(const impl::path&, dev_t, bool);
62 static void cleanup_aux_dir(const impl::path&, const impl::file_info&,
63 bool);
64 static void do_unmount(const impl::path&);
65 static bool safe_access(const impl::path&, int, int);
66
67 static const int access_f = 1 << 0;
68 static const int access_r = 1 << 1;
69 static const int access_w = 1 << 2;
70 static const int access_x = 1 << 3;
71
72 //!
73 //! An implementation of access(2) but using the effective user value
74 //! instead of the real one. Also avoids false positives for root when
75 //! asking for execute permissions, which appear in SunOS.
76 //!
77 static
78 void
eaccess(const tools::fs::path & p,int mode)79 eaccess(const tools::fs::path& p, int mode)
80 {
81 assert(mode & access_f || mode & access_r ||
82 mode & access_w || mode & access_x);
83
84 struct stat st;
85 if (lstat(p.c_str(), &st) == -1)
86 throw tools::system_error(IMPL_NAME "::eaccess",
87 "Cannot get information from file " +
88 p.str(), errno);
89
90 /* Early return if we are only checking for existence and the file
91 * exists (stat call returned). */
92 if (mode & access_f)
93 return;
94
95 bool ok = false;
96 if (tools::user::is_root()) {
97 if (!ok && !(mode & access_x)) {
98 /* Allow root to read/write any file. */
99 ok = true;
100 }
101
102 if (!ok && (st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) {
103 /* Allow root to execute the file if any of its execution bits
104 * are set. */
105 ok = true;
106 }
107 } else {
108 if (!ok && (tools::user::euid() == st.st_uid)) {
109 ok = ((mode & access_r) && (st.st_mode & S_IRUSR)) ||
110 ((mode & access_w) && (st.st_mode & S_IWUSR)) ||
111 ((mode & access_x) && (st.st_mode & S_IXUSR));
112 }
113 if (!ok && tools::user::is_member_of_group(st.st_gid)) {
114 ok = ((mode & access_r) && (st.st_mode & S_IRGRP)) ||
115 ((mode & access_w) && (st.st_mode & S_IWGRP)) ||
116 ((mode & access_x) && (st.st_mode & S_IXGRP));
117 }
118 if (!ok && ((tools::user::euid() != st.st_uid) &&
119 !tools::user::is_member_of_group(st.st_gid))) {
120 ok = ((mode & access_r) && (st.st_mode & S_IROTH)) ||
121 ((mode & access_w) && (st.st_mode & S_IWOTH)) ||
122 ((mode & access_x) && (st.st_mode & S_IXOTH));
123 }
124 }
125
126 if (!ok)
127 throw tools::system_error(IMPL_NAME "::eaccess", "Access check failed",
128 EACCES);
129 }
130
131 //!
132 //! \brief A controlled version of access(2).
133 //!
134 //! This function reimplements the standard access(2) system call to
135 //! safely control its exit status and raise an exception in case of
136 //! failure.
137 //!
138 static
139 bool
safe_access(const impl::path & p,int mode,int experr)140 safe_access(const impl::path& p, int mode, int experr)
141 {
142 try {
143 eaccess(p, mode);
144 return true;
145 } catch (const tools::system_error& e) {
146 if (e.code() == experr)
147 return false;
148 else
149 throw e;
150 }
151 }
152
153 // The cleanup routines below are tricky: they are executed immediately after
154 // a test case's death, and after we have forcibly killed any stale processes.
155 // However, even if the processes are dead, this does not mean that the file
156 // system we are scanning is stable. In particular, if the test case has
157 // mounted file systems through fuse/puffs, the fact that the processes died
158 // does not mean that the file system is truly unmounted.
159 //
160 // The code below attempts to cope with this by catching errors and either
161 // ignoring them or retrying the actions on the same file/directory a few times
162 // before giving up.
163 static const int max_retries = 5;
164 static const int retry_delay_in_seconds = 1;
165
166 // The erase parameter in this routine is to control nested mount points.
167 // We want to descend into a mount point to unmount anything that is
168 // mounted under it, but we do not want to delete any files while doing
169 // this traversal. In other words, we erase files until we cross the
170 // first mount point, and after that point we only scan and unmount.
171 static
172 void
cleanup_aux(const impl::path & p,dev_t parent_device,bool erase)173 cleanup_aux(const impl::path& p, dev_t parent_device, bool erase)
174 {
175 try {
176 impl::file_info fi(p);
177
178 if (fi.get_type() == impl::file_info::dir_type)
179 cleanup_aux_dir(p, fi, fi.get_device() == parent_device);
180
181 if (fi.get_device() != parent_device)
182 do_unmount(p);
183
184 if (erase) {
185 if (fi.get_type() == impl::file_info::dir_type)
186 impl::rmdir(p);
187 else
188 impl::remove(p);
189 }
190 } catch (const tools::system_error& e) {
191 if (e.code() != ENOENT && e.code() != ENOTDIR)
192 throw e;
193 }
194 }
195
196 static
197 void
cleanup_aux_dir(const impl::path & p,const impl::file_info & fi,bool erase)198 cleanup_aux_dir(const impl::path& p, const impl::file_info& fi,
199 bool erase)
200 {
201 if (erase && ((fi.get_mode() & S_IRWXU) != S_IRWXU)) {
202 int retries = max_retries;
203 retry_chmod:
204 if (chmod(p.c_str(), fi.get_mode() | S_IRWXU) == -1) {
205 if (retries > 0) {
206 retries--;
207 ::sleep(retry_delay_in_seconds);
208 goto retry_chmod;
209 } else {
210 throw tools::system_error(IMPL_NAME "::cleanup(" +
211 p.str() + ")", "chmod(2) failed",
212 errno);
213 }
214 }
215 }
216
217 std::set< std::string > subdirs;
218 {
219 bool ok = false;
220 int retries = max_retries;
221 while (!ok) {
222 assert(retries > 0);
223 try {
224 const impl::directory d(p);
225 subdirs = d.names();
226 ok = true;
227 } catch (const tools::system_error& e) {
228 retries--;
229 if (retries == 0)
230 throw e;
231 ::sleep(retry_delay_in_seconds);
232 }
233 }
234 assert(ok);
235 }
236
237 for (std::set< std::string >::const_iterator iter = subdirs.begin();
238 iter != subdirs.end(); iter++) {
239 const std::string& name = *iter;
240 if (name != "." && name != "..")
241 cleanup_aux(p / name, fi.get_device(), erase);
242 }
243 }
244
245 static
246 void
do_unmount(const impl::path & in_path)247 do_unmount(const impl::path& in_path)
248 {
249 // At least, FreeBSD's unmount(2) requires the path to be absolute.
250 // Let's make it absolute in all cases just to be safe that this does
251 // not affect other systems.
252 const impl::path& abs_path = in_path.is_absolute() ?
253 in_path : in_path.to_absolute();
254
255 int retries = max_retries;
256 retry_unmount:
257 if (unmount(abs_path.c_str(), 0) == -1) {
258 if (errno == EBUSY && retries > 0) {
259 retries--;
260 ::sleep(retry_delay_in_seconds);
261 goto retry_unmount;
262 } else {
263 throw tools::system_error(IMPL_NAME "::cleanup(" + in_path.str() +
264 ")", "unmount(2) failed", errno);
265 }
266 }
267 }
268
269 static
270 std::string
normalize(const std::string & in)271 normalize(const std::string& in)
272 {
273 assert(!in.empty());
274
275 std::string out;
276
277 std::string::size_type pos = 0;
278 do {
279 const std::string::size_type next_pos = in.find('/', pos);
280
281 const std::string component = in.substr(pos, next_pos - pos);
282 if (!component.empty()) {
283 if (pos == 0)
284 out += component;
285 else if (component != ".")
286 out += "/" + component;
287 }
288
289 if (next_pos == std::string::npos)
290 pos = next_pos;
291 else
292 pos = next_pos + 1;
293 } while (pos != std::string::npos);
294
295 return out.empty() ? "/" : out;
296 }
297
298 // ------------------------------------------------------------------------
299 // The "path" class.
300 // ------------------------------------------------------------------------
301
path(const std::string & s)302 impl::path::path(const std::string& s) :
303 m_data(normalize(s))
304 {
305 }
306
~path(void)307 impl::path::~path(void)
308 {
309 }
310
311 const char*
c_str(void) const312 impl::path::c_str(void)
313 const
314 {
315 return m_data.c_str();
316 }
317
318 std::string
str(void) const319 impl::path::str(void)
320 const
321 {
322 return m_data;
323 }
324
325 bool
is_absolute(void) const326 impl::path::is_absolute(void)
327 const
328 {
329 return !m_data.empty() && m_data[0] == '/';
330 }
331
332 bool
is_root(void) const333 impl::path::is_root(void)
334 const
335 {
336 return m_data == "/";
337 }
338
339 impl::path
branch_path(void) const340 impl::path::branch_path(void)
341 const
342 {
343 const std::string::size_type endpos = m_data.rfind('/');
344 if (endpos == std::string::npos)
345 return path(".");
346 else if (endpos == 0)
347 return path("/");
348 else
349 return path(m_data.substr(0, endpos));
350 }
351
352 std::string
leaf_name(void) const353 impl::path::leaf_name(void)
354 const
355 {
356 std::string::size_type begpos = m_data.rfind('/');
357 if (begpos == std::string::npos)
358 begpos = 0;
359 else
360 begpos++;
361
362 return m_data.substr(begpos);
363 }
364
365 impl::path
to_absolute(void) const366 impl::path::to_absolute(void)
367 const
368 {
369 assert(!is_absolute());
370 return get_current_dir() / m_data;
371 }
372
373 bool
operator ==(const path & p) const374 impl::path::operator==(const path& p)
375 const
376 {
377 return m_data == p.m_data;
378 }
379
380 bool
operator !=(const path & p) const381 impl::path::operator!=(const path& p)
382 const
383 {
384 return m_data != p.m_data;
385 }
386
387 impl::path
operator /(const std::string & p) const388 impl::path::operator/(const std::string& p)
389 const
390 {
391 return path(m_data + "/" + normalize(p));
392 }
393
394 impl::path
operator /(const path & p) const395 impl::path::operator/(const path& p)
396 const
397 {
398 return path(m_data) / p.m_data;
399 }
400
401 bool
operator <(const path & p) const402 impl::path::operator<(const path& p)
403 const
404 {
405 return std::strcmp(m_data.c_str(), p.m_data.c_str()) < 0;
406 }
407
408 // ------------------------------------------------------------------------
409 // The "file_info" class.
410 // ------------------------------------------------------------------------
411
412 const int impl::file_info::blk_type = 1;
413 const int impl::file_info::chr_type = 2;
414 const int impl::file_info::dir_type = 3;
415 const int impl::file_info::fifo_type = 4;
416 const int impl::file_info::lnk_type = 5;
417 const int impl::file_info::reg_type = 6;
418 const int impl::file_info::sock_type = 7;
419 const int impl::file_info::wht_type = 8;
420
file_info(const path & p)421 impl::file_info::file_info(const path& p)
422 {
423 if (lstat(p.c_str(), &m_sb) == -1)
424 throw system_error(IMPL_NAME "::file_info",
425 "Cannot get information of " + p.str() + "; " +
426 "lstat(2) failed", errno);
427
428 int type = m_sb.st_mode & S_IFMT;
429 switch (type) {
430 case S_IFBLK: m_type = blk_type; break;
431 case S_IFCHR: m_type = chr_type; break;
432 case S_IFDIR: m_type = dir_type; break;
433 case S_IFIFO: m_type = fifo_type; break;
434 case S_IFLNK: m_type = lnk_type; break;
435 case S_IFREG: m_type = reg_type; break;
436 case S_IFSOCK: m_type = sock_type; break;
437 case S_IFWHT: m_type = wht_type; break;
438 default:
439 throw system_error(IMPL_NAME "::file_info", "Unknown file type "
440 "error", EINVAL);
441 }
442 }
443
~file_info(void)444 impl::file_info::~file_info(void)
445 {
446 }
447
448 dev_t
get_device(void) const449 impl::file_info::get_device(void)
450 const
451 {
452 return m_sb.st_dev;
453 }
454
455 ino_t
get_inode(void) const456 impl::file_info::get_inode(void)
457 const
458 {
459 return m_sb.st_ino;
460 }
461
462 mode_t
get_mode(void) const463 impl::file_info::get_mode(void)
464 const
465 {
466 return m_sb.st_mode & ~S_IFMT;
467 }
468
469 off_t
get_size(void) const470 impl::file_info::get_size(void)
471 const
472 {
473 return m_sb.st_size;
474 }
475
476 int
get_type(void) const477 impl::file_info::get_type(void)
478 const
479 {
480 return m_type;
481 }
482
483 bool
is_owner_readable(void) const484 impl::file_info::is_owner_readable(void)
485 const
486 {
487 return m_sb.st_mode & S_IRUSR;
488 }
489
490 bool
is_owner_writable(void) const491 impl::file_info::is_owner_writable(void)
492 const
493 {
494 return m_sb.st_mode & S_IWUSR;
495 }
496
497 bool
is_owner_executable(void) const498 impl::file_info::is_owner_executable(void)
499 const
500 {
501 return m_sb.st_mode & S_IXUSR;
502 }
503
504 bool
is_group_readable(void) const505 impl::file_info::is_group_readable(void)
506 const
507 {
508 return m_sb.st_mode & S_IRGRP;
509 }
510
511 bool
is_group_writable(void) const512 impl::file_info::is_group_writable(void)
513 const
514 {
515 return m_sb.st_mode & S_IWGRP;
516 }
517
518 bool
is_group_executable(void) const519 impl::file_info::is_group_executable(void)
520 const
521 {
522 return m_sb.st_mode & S_IXGRP;
523 }
524
525 bool
is_other_readable(void) const526 impl::file_info::is_other_readable(void)
527 const
528 {
529 return m_sb.st_mode & S_IROTH;
530 }
531
532 bool
is_other_writable(void) const533 impl::file_info::is_other_writable(void)
534 const
535 {
536 return m_sb.st_mode & S_IWOTH;
537 }
538
539 bool
is_other_executable(void) const540 impl::file_info::is_other_executable(void)
541 const
542 {
543 return m_sb.st_mode & S_IXOTH;
544 }
545
546 // ------------------------------------------------------------------------
547 // The "directory" class.
548 // ------------------------------------------------------------------------
549
directory(const path & p)550 impl::directory::directory(const path& p)
551 {
552 DIR* dp = ::opendir(p.c_str());
553 if (dp == NULL)
554 throw system_error(IMPL_NAME "::directory::directory(" +
555 p.str() + ")", "opendir(3) failed", errno);
556
557 struct dirent* dep;
558 while ((dep = ::readdir(dp)) != NULL) {
559 path entryp = p / dep->d_name;
560 insert(value_type(dep->d_name, file_info(entryp)));
561 }
562
563 if (::closedir(dp) == -1)
564 throw system_error(IMPL_NAME "::directory::directory(" +
565 p.str() + ")", "closedir(3) failed", errno);
566 }
567
568 std::set< std::string >
names(void) const569 impl::directory::names(void)
570 const
571 {
572 std::set< std::string > ns;
573
574 for (const_iterator iter = begin(); iter != end(); iter++)
575 ns.insert((*iter).first);
576
577 return ns;
578 }
579
580 // ------------------------------------------------------------------------
581 // The "temp_dir" class.
582 // ------------------------------------------------------------------------
583
temp_dir(const path & p)584 impl::temp_dir::temp_dir(const path& p)
585 {
586 tools::auto_array< char > buf(new char[p.str().length() + 1]);
587 std::strcpy(buf.get(), p.c_str());
588 if (::mkdtemp(buf.get()) == NULL)
589 throw tools::system_error(IMPL_NAME "::temp_dir::temp_dir(" +
590 p.str() + ")", "mkdtemp(3) failed",
591 errno);
592
593 m_path.reset(new path(buf.get()));
594 }
595
~temp_dir(void)596 impl::temp_dir::~temp_dir(void)
597 {
598 cleanup(*m_path);
599 }
600
601 const impl::path&
get_path(void) const602 impl::temp_dir::get_path(void)
603 const
604 {
605 return *m_path;
606 }
607
608 // ------------------------------------------------------------------------
609 // Free functions.
610 // ------------------------------------------------------------------------
611
612 bool
exists(const path & p)613 impl::exists(const path& p)
614 {
615 try {
616 eaccess(p, access_f);
617 return true;
618 } catch (const system_error& e) {
619 if (e.code() == ENOENT)
620 return false;
621 else
622 throw;
623 }
624 }
625
626 bool
have_prog_in_path(const std::string & prog)627 impl::have_prog_in_path(const std::string& prog)
628 {
629 assert(prog.find('/') == std::string::npos);
630
631 // Do not bother to provide a default value for PATH. If it is not
632 // there something is broken in the user's environment.
633 if (!tools::env::has("PATH"))
634 throw std::runtime_error("PATH not defined in the environment");
635 std::vector< std::string > dirs =
636 tools::text::split(tools::env::get("PATH"), ":");
637
638 bool found = false;
639 for (std::vector< std::string >::const_iterator iter = dirs.begin();
640 !found && iter != dirs.end(); iter++) {
641 const path& dir = path(*iter);
642
643 if (is_executable(dir / prog))
644 found = true;
645 }
646 return found;
647 }
648
649 bool
is_executable(const path & p)650 impl::is_executable(const path& p)
651 {
652 if (!exists(p))
653 return false;
654 return safe_access(p, access_x, EACCES);
655 }
656
657 void
remove(const path & p)658 impl::remove(const path& p)
659 {
660 if (file_info(p).get_type() == file_info::dir_type)
661 throw tools::system_error(IMPL_NAME "::remove(" + p.str() + ")",
662 "Is a directory",
663 EPERM);
664 if (::unlink(p.c_str()) == -1)
665 throw tools::system_error(IMPL_NAME "::remove(" + p.str() + ")",
666 "unlink(" + p.str() + ") failed",
667 errno);
668 }
669
670 void
rmdir(const path & p)671 impl::rmdir(const path& p)
672 {
673 if (::rmdir(p.c_str())) {
674 if (errno == EEXIST) {
675 /* Some operating systems (e.g. OpenSolaris 200906) return
676 * EEXIST instead of ENOTEMPTY for non-empty directories.
677 * Homogenize the return value so that callers don't need
678 * to bother about differences in operating systems. */
679 errno = ENOTEMPTY;
680 }
681 throw system_error(IMPL_NAME "::rmdir", "Cannot remove directory",
682 errno);
683 }
684 }
685
686 void
change_ownership(const path & p,const std::pair<int,int> & user)687 impl::change_ownership(const path& p, const std::pair < int, int >& user)
688 {
689 if (::chown(p.c_str(), user.first, user.second) == -1) {
690 std::stringstream ss;
691 ss << IMPL_NAME "::chown(" << p.str() << ", " << user.first << ", "
692 << user.second << ")";
693 throw tools::system_error(ss.str(), "chown(2) failed", errno);
694 }
695 }
696
697 impl::path
change_directory(const path & dir)698 impl::change_directory(const path& dir)
699 {
700 path olddir = get_current_dir();
701
702 if (olddir != dir) {
703 if (::chdir(dir.c_str()) == -1)
704 throw tools::system_error(IMPL_NAME "::chdir(" + dir.str() + ")",
705 "chdir(2) failed", errno);
706 }
707
708 return olddir;
709 }
710
711 void
cleanup(const path & p)712 impl::cleanup(const path& p)
713 {
714 impl::file_info fi(p);
715 cleanup_aux(p, fi.get_device(), true);
716 }
717
718 impl::path
get_current_dir(void)719 impl::get_current_dir(void)
720 {
721 char *cwd = getcwd(NULL, 0);
722 if (cwd == NULL)
723 throw tools::system_error(IMPL_NAME "::get_current_dir()",
724 "getcwd() failed", errno);
725
726 try {
727 impl::path p(cwd);
728 free(cwd);
729 return p;
730 } catch(...) {
731 free(cwd);
732 throw;
733 }
734 }
735