2 // Automated Testing Framework (atf)
4 // Copyright (c) 2007 The NetBSD Foundation, Inc.
5 // All rights reserved.
7 // Redistribution and use in source and binary forms, with or without
8 // modification, are permitted provided that the following conditions
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.
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.
31 #include <sys/param.h>
32 #include <sys/types.h>
33 #include <sys/mount.h>
47 #include "auto_array.hpp"
49 #include "exceptions.hpp"
54 namespace impl
= tools::fs
;
55 #define IMPL_NAME "tools::fs"
57 // ------------------------------------------------------------------------
58 // Auxiliary functions.
59 // ------------------------------------------------------------------------
61 static void cleanup_aux(const impl::path
&, dev_t
, bool);
62 static void cleanup_aux_dir(const impl::path
&, const impl::file_info
&,
64 static void do_unmount(const impl::path
&);
65 static bool safe_access(const impl::path
&, int, int);
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;
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.
79 eaccess(const tools::fs::path
& p
, int mode
)
81 assert(mode
& access_f
|| mode
& access_r
||
82 mode
& access_w
|| mode
& access_x
);
85 if (lstat(p
.c_str(), &st
) == -1)
86 throw tools::system_error(IMPL_NAME
"::eaccess",
87 "Cannot get information from file " +
90 /* Early return if we are only checking for existence and the file
91 * exists (stat call returned). */
96 if (tools::user::is_root()) {
97 if (!ok
&& !(mode
& access_x
)) {
98 /* Allow root to read/write any file. */
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
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
));
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
));
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
));
127 throw tools::system_error(IMPL_NAME
"::eaccess", "Access check failed",
132 //! \brief A controlled version of access(2).
134 //! This function reimplements the standard access(2) system call to
135 //! safely control its exit status and raise an exception in case of
140 safe_access(const impl::path
& p
, int mode
, int experr
)
145 } catch (const tools::system_error
& e
) {
146 if (e
.code() == experr
)
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.
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
163 static const int max_retries
= 5;
164 static const int retry_delay_in_seconds
= 1;
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.
173 cleanup_aux(const impl::path
& p
, dev_t parent_device
, bool erase
)
176 impl::file_info
fi(p
);
178 if (fi
.get_type() == impl::file_info::dir_type
)
179 cleanup_aux_dir(p
, fi
, fi
.get_device() == parent_device
);
181 if (fi
.get_device() != parent_device
)
185 if (fi
.get_type() == impl::file_info::dir_type
)
190 } catch (const tools::system_error
& e
) {
191 if (e
.code() != ENOENT
&& e
.code() != ENOTDIR
)
198 cleanup_aux_dir(const impl::path
& p
, const impl::file_info
& fi
,
201 if (erase
&& ((fi
.get_mode() & S_IRWXU
) != S_IRWXU
)) {
202 int retries
= max_retries
;
204 if (chmod(p
.c_str(), fi
.get_mode() | S_IRWXU
) == -1) {
207 ::sleep(retry_delay_in_seconds
);
210 throw tools::system_error(IMPL_NAME
"::cleanup(" +
211 p
.str() + ")", "chmod(2) failed",
217 std::set
< std::string
> subdirs
;
220 int retries
= max_retries
;
224 const impl::directory
d(p
);
227 } catch (const tools::system_error
& e
) {
231 ::sleep(retry_delay_in_seconds
);
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
);
247 do_unmount(const impl::path
& in_path
)
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();
255 int retries
= max_retries
;
258 if (minix_umount(abs_path
.c_str(), 0) == -1) {
260 if (unmount(abs_path
.c_str(), 0) == -1) {
261 #endif /* defined(__minix) */
262 if (errno
== EBUSY
&& retries
> 0) {
264 ::sleep(retry_delay_in_seconds
);
267 throw tools::system_error(IMPL_NAME
"::cleanup(" + in_path
.str() +
268 ")", "unmount(2) failed", errno
);
275 normalize(const std::string
& in
)
281 std::string::size_type pos
= 0;
283 const std::string::size_type next_pos
= in
.find('/', pos
);
285 const std::string component
= in
.substr(pos
, next_pos
- pos
);
286 if (!component
.empty()) {
289 else if (component
!= ".")
290 out
+= "/" + component
;
293 if (next_pos
== std::string::npos
)
297 } while (pos
!= std::string::npos
);
299 return out
.empty() ? "/" : out
;
302 // ------------------------------------------------------------------------
304 // ------------------------------------------------------------------------
306 impl::path::path(const std::string
& s
) :
311 impl::path::~path(void)
316 impl::path::c_str(void)
319 return m_data
.c_str();
323 impl::path::str(void)
330 impl::path::is_absolute(void)
333 return !m_data
.empty() && m_data
[0] == '/';
337 impl::path::is_root(void)
340 return m_data
== "/";
344 impl::path::branch_path(void)
347 const std::string::size_type endpos
= m_data
.rfind('/');
348 if (endpos
== std::string::npos
)
350 else if (endpos
== 0)
353 return path(m_data
.substr(0, endpos
));
357 impl::path::leaf_name(void)
360 std::string::size_type begpos
= m_data
.rfind('/');
361 if (begpos
== std::string::npos
)
366 return m_data
.substr(begpos
);
370 impl::path::to_absolute(void)
373 assert(!is_absolute());
374 return get_current_dir() / m_data
;
378 impl::path::operator==(const path
& p
)
381 return m_data
== p
.m_data
;
385 impl::path::operator!=(const path
& p
)
388 return m_data
!= p
.m_data
;
392 impl::path::operator/(const std::string
& p
)
395 return path(m_data
+ "/" + normalize(p
));
399 impl::path::operator/(const path
& p
)
402 return path(m_data
) / p
.m_data
;
406 impl::path::operator<(const path
& p
)
409 return std::strcmp(m_data
.c_str(), p
.m_data
.c_str()) < 0;
412 // ------------------------------------------------------------------------
413 // The "file_info" class.
414 // ------------------------------------------------------------------------
416 const int impl::file_info::blk_type
= 1;
417 const int impl::file_info::chr_type
= 2;
418 const int impl::file_info::dir_type
= 3;
419 const int impl::file_info::fifo_type
= 4;
420 const int impl::file_info::lnk_type
= 5;
421 const int impl::file_info::reg_type
= 6;
422 const int impl::file_info::sock_type
= 7;
423 const int impl::file_info::wht_type
= 8;
425 impl::file_info::file_info(const path
& p
)
427 if (lstat(p
.c_str(), &m_sb
) == -1)
428 throw system_error(IMPL_NAME
"::file_info",
429 "Cannot get information of " + p
.str() + "; " +
430 "lstat(2) failed", errno
);
432 int type
= m_sb
.st_mode
& S_IFMT
;
434 case S_IFBLK
: m_type
= blk_type
; break;
435 case S_IFCHR
: m_type
= chr_type
; break;
436 case S_IFDIR
: m_type
= dir_type
; break;
437 case S_IFIFO
: m_type
= fifo_type
; break;
438 case S_IFLNK
: m_type
= lnk_type
; break;
439 case S_IFREG
: m_type
= reg_type
; break;
440 case S_IFSOCK
: m_type
= sock_type
; break;
441 case S_IFWHT
: m_type
= wht_type
; break;
443 throw system_error(IMPL_NAME
"::file_info", "Unknown file type "
448 impl::file_info::~file_info(void)
453 impl::file_info::get_device(void)
460 impl::file_info::get_inode(void)
467 impl::file_info::get_mode(void)
470 return m_sb
.st_mode
& ~S_IFMT
;
474 impl::file_info::get_size(void)
481 impl::file_info::get_type(void)
488 impl::file_info::is_owner_readable(void)
491 return m_sb
.st_mode
& S_IRUSR
;
495 impl::file_info::is_owner_writable(void)
498 return m_sb
.st_mode
& S_IWUSR
;
502 impl::file_info::is_owner_executable(void)
505 return m_sb
.st_mode
& S_IXUSR
;
509 impl::file_info::is_group_readable(void)
512 return m_sb
.st_mode
& S_IRGRP
;
516 impl::file_info::is_group_writable(void)
519 return m_sb
.st_mode
& S_IWGRP
;
523 impl::file_info::is_group_executable(void)
526 return m_sb
.st_mode
& S_IXGRP
;
530 impl::file_info::is_other_readable(void)
533 return m_sb
.st_mode
& S_IROTH
;
537 impl::file_info::is_other_writable(void)
540 return m_sb
.st_mode
& S_IWOTH
;
544 impl::file_info::is_other_executable(void)
547 return m_sb
.st_mode
& S_IXOTH
;
550 // ------------------------------------------------------------------------
551 // The "directory" class.
552 // ------------------------------------------------------------------------
554 impl::directory::directory(const path
& p
)
556 DIR* dp
= ::opendir(p
.c_str());
558 throw system_error(IMPL_NAME
"::directory::directory(" +
559 p
.str() + ")", "opendir(3) failed", errno
);
562 while ((dep
= ::readdir(dp
)) != NULL
) {
563 path entryp
= p
/ dep
->d_name
;
564 insert(value_type(dep
->d_name
, file_info(entryp
)));
567 if (::closedir(dp
) == -1)
568 throw system_error(IMPL_NAME
"::directory::directory(" +
569 p
.str() + ")", "closedir(3) failed", errno
);
572 std::set
< std::string
>
573 impl::directory::names(void)
576 std::set
< std::string
> ns
;
578 for (const_iterator iter
= begin(); iter
!= end(); iter
++)
579 ns
.insert((*iter
).first
);
584 // ------------------------------------------------------------------------
585 // The "temp_dir" class.
586 // ------------------------------------------------------------------------
588 impl::temp_dir::temp_dir(const path
& p
)
590 tools::auto_array
< char > buf(new char[p
.str().length() + 1]);
591 std::strcpy(buf
.get(), p
.c_str());
592 if (::mkdtemp(buf
.get()) == NULL
)
593 throw tools::system_error(IMPL_NAME
"::temp_dir::temp_dir(" +
594 p
.str() + ")", "mkdtemp(3) failed",
597 m_path
.reset(new path(buf
.get()));
600 impl::temp_dir::~temp_dir(void)
606 impl::temp_dir::get_path(void)
612 // ------------------------------------------------------------------------
614 // ------------------------------------------------------------------------
617 impl::exists(const path
& p
)
620 eaccess(p
, access_f
);
622 } catch (const system_error
& e
) {
623 if (e
.code() == ENOENT
)
631 impl::have_prog_in_path(const std::string
& prog
)
633 assert(prog
.find('/') == std::string::npos
);
635 // Do not bother to provide a default value for PATH. If it is not
636 // there something is broken in the user's environment.
637 if (!tools::env::has("PATH"))
638 throw std::runtime_error("PATH not defined in the environment");
639 std::vector
< std::string
> dirs
=
640 tools::text::split(tools::env::get("PATH"), ":");
643 for (std::vector
< std::string
>::const_iterator iter
= dirs
.begin();
644 !found
&& iter
!= dirs
.end(); iter
++) {
645 const path
& dir
= path(*iter
);
647 if (is_executable(dir
/ prog
))
654 impl::is_executable(const path
& p
)
658 return safe_access(p
, access_x
, EACCES
);
662 impl::remove(const path
& p
)
664 if (file_info(p
).get_type() == file_info::dir_type
)
665 throw tools::system_error(IMPL_NAME
"::remove(" + p
.str() + ")",
668 if (::unlink(p
.c_str()) == -1)
669 throw tools::system_error(IMPL_NAME
"::remove(" + p
.str() + ")",
670 "unlink(" + p
.str() + ") failed",
675 impl::rmdir(const path
& p
)
677 if (::rmdir(p
.c_str())) {
678 if (errno
== EEXIST
) {
679 /* Some operating systems (e.g. OpenSolaris 200906) return
680 * EEXIST instead of ENOTEMPTY for non-empty directories.
681 * Homogenize the return value so that callers don't need
682 * to bother about differences in operating systems. */
685 throw system_error(IMPL_NAME
"::rmdir", "Cannot remove directory",
691 impl::change_directory(const path
& dir
)
693 path olddir
= get_current_dir();
696 if (::chdir(dir
.c_str()) == -1)
697 throw tools::system_error(IMPL_NAME
"::chdir(" + dir
.str() + ")",
698 "chdir(2) failed", errno
);
705 impl::cleanup(const path
& p
)
707 impl::file_info
fi(p
);
708 cleanup_aux(p
, fi
.get_device(), true);
712 impl::get_current_dir(void)
714 std::auto_ptr
< char > cwd
;
715 cwd
.reset(getcwd(NULL
, 0));
716 if (cwd
.get() == NULL
)
717 throw tools::system_error(IMPL_NAME
"::get_current_dir()",
718 "getcwd() failed", errno
);
720 return path(cwd
.get());