xref: /freebsd/contrib/kyua/utils/fs/operations_test.cpp (revision 77a1348b3c1cfe8547be49a121b56299a1e18b69)
1 // Copyright 2010 The Kyua Authors.
2 // All rights reserved.
3 //
4 // Redistribution and use in source and binary forms, with or without
5 // modification, are permitted provided that the following conditions are
6 // met:
7 //
8 // * Redistributions of source code must retain the above copyright
9 //   notice, this list of conditions and the following disclaimer.
10 // * Redistributions in binary form must reproduce the above copyright
11 //   notice, this list of conditions and the following disclaimer in the
12 //   documentation and/or other materials provided with the distribution.
13 // * Neither the name of Google Inc. nor the names of its contributors
14 //   may be used to endorse or promote products derived from this software
15 //   without specific prior written permission.
16 //
17 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 
29 #include "utils/fs/operations.hpp"
30 
31 extern "C" {
32 #include <sys/types.h>
33 #include <sys/stat.h>
34 #include <sys/wait.h>
35 
36 #include <dirent.h>
37 #include <signal.h>
38 #include <unistd.h>
39 }
40 
41 #include <cerrno>
42 #include <cstdlib>
43 #include <cstring>
44 #include <iostream>
45 #include <stdexcept>
46 #include <string>
47 #include <vector>
48 
49 #include <atf-c++.hpp>
50 
51 #include "utils/env.hpp"
52 #include "utils/format/containers.ipp"
53 #include "utils/format/macros.hpp"
54 #include "utils/fs/directory.hpp"
55 #include "utils/fs/exceptions.hpp"
56 #include "utils/fs/path.hpp"
57 #include "utils/optional.ipp"
58 #include "utils/passwd.hpp"
59 #include "utils/stream.hpp"
60 #include "utils/units.hpp"
61 
62 namespace fs = utils::fs;
63 namespace passwd = utils::passwd;
64 namespace units = utils::units;
65 
66 using utils::optional;
67 
68 
69 namespace {
70 
71 
72 /// Checks if a directory entry exists and matches a specific type.
73 ///
74 /// \param dir The directory in which to look for the entry.
75 /// \param name The name of the entry to look up.
76 /// \param expected_type The expected type of the file as given by dir(5).
77 ///
78 /// \return True if the entry exists and matches the given type; false
79 /// otherwise.
80 static bool
81 lookup(const char* dir, const char* name, const unsigned int expected_type)
82 {
83     DIR* dirp = ::opendir(dir);
84     ATF_REQUIRE(dirp != NULL);
85 
86     bool found = false;
87     struct dirent* dp;
88     while (!found && (dp = readdir(dirp)) != NULL) {
89         if (std::strcmp(dp->d_name, name) == 0) {
90             struct ::stat s;
91             const fs::path lookup_path = fs::path(dir) / name;
92             ATF_REQUIRE(::stat(lookup_path.c_str(), &s) != -1);
93             if ((s.st_mode & S_IFMT) == expected_type) {
94                 found = true;
95             }
96         }
97     }
98     ::closedir(dirp);
99     return found;
100 }
101 
102 
103 }  // anonymous namespace
104 
105 
106 ATF_TEST_CASE_WITHOUT_HEAD(copy__ok);
107 ATF_TEST_CASE_BODY(copy__ok)
108 {
109     const fs::path source("f1.txt");
110     const fs::path target("f2.txt");
111 
112     atf::utils::create_file(source.str(), "This is the input");
113     fs::copy(source, target);
114     ATF_REQUIRE(atf::utils::compare_file(target.str(), "This is the input"));
115 }
116 
117 
118 ATF_TEST_CASE_WITHOUT_HEAD(copy__fail_open);
119 ATF_TEST_CASE_BODY(copy__fail_open)
120 {
121     const fs::path source("f1.txt");
122     const fs::path target("f2.txt");
123 
124     ATF_REQUIRE_THROW_RE(fs::error, "Cannot open copy source f1.txt",
125                          fs::copy(source, target));
126 }
127 
128 
129 ATF_TEST_CASE(copy__fail_create);
130 ATF_TEST_CASE_HEAD(copy__fail_create)
131 {
132     set_md_var("require.user", "unprivileged");
133 }
134 ATF_TEST_CASE_BODY(copy__fail_create)
135 {
136     const fs::path source("f1.txt");
137     const fs::path target("f2.txt");
138 
139     atf::utils::create_file(target.str(), "Do not override");
140     ATF_REQUIRE(::chmod(target.c_str(), 0444) != -1);
141 
142     atf::utils::create_file(source.str(), "This is the input");
143     ATF_REQUIRE_THROW_RE(fs::error, "Cannot create copy target f2.txt",
144                          fs::copy(source, target));
145 }
146 
147 
148 ATF_TEST_CASE_WITHOUT_HEAD(current_path__ok);
149 ATF_TEST_CASE_BODY(current_path__ok)
150 {
151     const fs::path previous = fs::current_path();
152     fs::mkdir(fs::path("root"), 0755);
153     ATF_REQUIRE(::chdir("root") != -1);
154     const fs::path cwd = fs::current_path();
155     ATF_REQUIRE_EQ(cwd.str().length() - 5, cwd.str().find("/root"));
156     ATF_REQUIRE_EQ(previous / "root", cwd);
157 }
158 
159 
160 ATF_TEST_CASE_WITHOUT_HEAD(current_path__enoent);
161 ATF_TEST_CASE_BODY(current_path__enoent)
162 {
163     const fs::path previous = fs::current_path();
164     fs::mkdir(fs::path("root"), 0755);
165     ATF_REQUIRE(::chdir("root") != -1);
166     ATF_REQUIRE(::rmdir("../root") != -1);
167     try {
168         (void)fs::current_path();
169         fail("system_errpr not raised");
170     } catch (const fs::system_error& e) {
171         ATF_REQUIRE_EQ(ENOENT, e.original_errno());
172     }
173 }
174 
175 
176 ATF_TEST_CASE_WITHOUT_HEAD(exists);
177 ATF_TEST_CASE_BODY(exists)
178 {
179     const fs::path dir("dir");
180     ATF_REQUIRE(!fs::exists(dir));
181     fs::mkdir(dir, 0755);
182     ATF_REQUIRE(fs::exists(dir));
183 }
184 
185 
186 ATF_TEST_CASE_WITHOUT_HEAD(find_in_path__no_path);
187 ATF_TEST_CASE_BODY(find_in_path__no_path)
188 {
189     utils::unsetenv("PATH");
190     ATF_REQUIRE(!fs::find_in_path("ls"));
191     atf::utils::create_file("ls", "");
192     ATF_REQUIRE(!fs::find_in_path("ls"));
193 }
194 
195 
196 ATF_TEST_CASE_WITHOUT_HEAD(find_in_path__empty_path);
197 ATF_TEST_CASE_BODY(find_in_path__empty_path)
198 {
199     utils::setenv("PATH", "");
200     ATF_REQUIRE(!fs::find_in_path("ls"));
201     atf::utils::create_file("ls", "");
202     ATF_REQUIRE(!fs::find_in_path("ls"));
203 }
204 
205 
206 ATF_TEST_CASE_WITHOUT_HEAD(find_in_path__one_component);
207 ATF_TEST_CASE_BODY(find_in_path__one_component)
208 {
209     const fs::path dir = fs::current_path() / "bin";
210     fs::mkdir(dir, 0755);
211     utils::setenv("PATH", dir.str());
212 
213     ATF_REQUIRE(!fs::find_in_path("ls"));
214     atf::utils::create_file((dir / "ls").str(), "");
215     ATF_REQUIRE_EQ(dir / "ls", fs::find_in_path("ls").get());
216 }
217 
218 
219 ATF_TEST_CASE_WITHOUT_HEAD(find_in_path__many_components);
220 ATF_TEST_CASE_BODY(find_in_path__many_components)
221 {
222     const fs::path dir1 = fs::current_path() / "dir1";
223     const fs::path dir2 = fs::current_path() / "dir2";
224     fs::mkdir(dir1, 0755);
225     fs::mkdir(dir2, 0755);
226     utils::setenv("PATH", dir1.str() + ":" + dir2.str());
227 
228     ATF_REQUIRE(!fs::find_in_path("ls"));
229     atf::utils::create_file((dir2 / "ls").str(), "");
230     ATF_REQUIRE_EQ(dir2 / "ls", fs::find_in_path("ls").get());
231     atf::utils::create_file((dir1 / "ls").str(), "");
232     ATF_REQUIRE_EQ(dir1 / "ls", fs::find_in_path("ls").get());
233 }
234 
235 
236 ATF_TEST_CASE_WITHOUT_HEAD(find_in_path__current_directory);
237 ATF_TEST_CASE_BODY(find_in_path__current_directory)
238 {
239     utils::setenv("PATH", "bin:");
240 
241     ATF_REQUIRE(!fs::find_in_path("foo-bar"));
242     atf::utils::create_file("foo-bar", "");
243     ATF_REQUIRE_EQ(fs::path("foo-bar").to_absolute(),
244                    fs::find_in_path("foo-bar").get());
245 }
246 
247 
248 ATF_TEST_CASE_WITHOUT_HEAD(find_in_path__always_absolute);
249 ATF_TEST_CASE_BODY(find_in_path__always_absolute)
250 {
251     fs::mkdir(fs::path("my-bin"), 0755);
252     utils::setenv("PATH", "my-bin");
253 
254     ATF_REQUIRE(!fs::find_in_path("abcd"));
255     atf::utils::create_file("my-bin/abcd", "");
256     ATF_REQUIRE_EQ(fs::path("my-bin/abcd").to_absolute(),
257                    fs::find_in_path("abcd").get());
258 }
259 
260 
261 ATF_TEST_CASE_WITHOUT_HEAD(free_disk_space__ok__smoke);
262 ATF_TEST_CASE_BODY(free_disk_space__ok__smoke)
263 {
264     const units::bytes space = fs::free_disk_space(fs::path("."));
265     ATF_REQUIRE(space > units::MB);  // Simple test that should always pass.
266 }
267 
268 
269 /// Unmounts a directory without raising errors.
270 ///
271 /// \param cookie Name of a file that exists while the mount point is still
272 ///     mounted.  Used to prevent a double-unmount, which would print a
273 ///     misleading error message.
274 /// \param mount_point Path to the mount point to unmount.
275 static void
276 cleanup_mount_point(const fs::path& cookie, const fs::path& mount_point)
277 {
278     try {
279         if (fs::exists(cookie)) {
280             fs::unmount(mount_point);
281         }
282     } catch (const std::runtime_error& e) {
283         std::cerr << "Failed trying to unmount " + mount_point.str() +
284             " during cleanup: " << e.what() << '\n';
285     }
286 }
287 
288 
289 ATF_TEST_CASE_WITH_CLEANUP(free_disk_space__ok__real);
290 ATF_TEST_CASE_HEAD(free_disk_space__ok__real)
291 {
292     set_md_var("require.user", "root");
293 }
294 ATF_TEST_CASE_BODY(free_disk_space__ok__real)
295 {
296     try {
297         const fs::path mount_point("mount_point");
298         fs::mkdir(mount_point, 0755);
299         fs::mount_tmpfs(mount_point, units::bytes(32 * units::MB));
300         atf::utils::create_file("mounted", "");
301         const units::bytes space = fs::free_disk_space(fs::path(mount_point));
302         fs::unmount(mount_point);
303         fs::unlink(fs::path("mounted"));
304         ATF_REQUIRE(space < 35 * units::MB);
305         ATF_REQUIRE(space > 28 * units::MB);
306     } catch (const fs::unsupported_operation_error& e) {
307         ATF_SKIP(e.what());
308     }
309 }
310 ATF_TEST_CASE_CLEANUP(free_disk_space__ok__real)
311 {
312     cleanup_mount_point(fs::path("mounted"), fs::path("mount_point"));
313 }
314 
315 
316 ATF_TEST_CASE_WITHOUT_HEAD(free_disk_space__fail);
317 ATF_TEST_CASE_BODY(free_disk_space__fail)
318 {
319     ATF_REQUIRE_THROW_RE(fs::error, "Failed to stat file system for missing",
320                          fs::free_disk_space(fs::path("missing")));
321 }
322 
323 
324 ATF_TEST_CASE_WITHOUT_HEAD(is_directory__ok);
325 ATF_TEST_CASE_BODY(is_directory__ok)
326 {
327     const fs::path file("file");
328     atf::utils::create_file(file.str(), "");
329     ATF_REQUIRE(!fs::is_directory(file));
330 
331     const fs::path dir("dir");
332     fs::mkdir(dir, 0755);
333     ATF_REQUIRE(fs::is_directory(dir));
334 }
335 
336 
337 ATF_TEST_CASE_WITH_CLEANUP(is_directory__fail);
338 ATF_TEST_CASE_HEAD(is_directory__fail)
339 {
340     set_md_var("require.user", "unprivileged");
341 }
342 ATF_TEST_CASE_BODY(is_directory__fail)
343 {
344     fs::mkdir(fs::path("dir"), 0000);
345     ATF_REQUIRE_THROW(fs::error, fs::is_directory(fs::path("dir/foo")));
346 }
347 ATF_TEST_CASE_CLEANUP(is_directory__fail)
348 {
349     if (::chmod("dir", 0755) == -1) {
350         // If we cannot restore the original permissions, we cannot do much
351         // more.  However, leaving an unwritable directory behind will cause the
352         // runtime engine to report us as broken.
353     }
354 }
355 
356 
357 ATF_TEST_CASE_WITHOUT_HEAD(mkdir__ok);
358 ATF_TEST_CASE_BODY(mkdir__ok)
359 {
360     fs::mkdir(fs::path("dir"), 0755);
361     ATF_REQUIRE(lookup(".", "dir", S_IFDIR));
362 }
363 
364 
365 ATF_TEST_CASE_WITHOUT_HEAD(mkdir__enoent);
366 ATF_TEST_CASE_BODY(mkdir__enoent)
367 {
368     try {
369         fs::mkdir(fs::path("dir1/dir2"), 0755);
370         fail("system_error not raised");
371     } catch (const fs::system_error& e) {
372         ATF_REQUIRE_EQ(ENOENT, e.original_errno());
373     }
374     ATF_REQUIRE(!lookup(".", "dir1", S_IFDIR));
375     ATF_REQUIRE(!lookup(".", "dir2", S_IFDIR));
376 }
377 
378 
379 ATF_TEST_CASE_WITHOUT_HEAD(mkdir_p__one_component);
380 ATF_TEST_CASE_BODY(mkdir_p__one_component)
381 {
382     ATF_REQUIRE(!lookup(".", "new-dir", S_IFDIR));
383     fs::mkdir_p(fs::path("new-dir"), 0755);
384     ATF_REQUIRE(lookup(".", "new-dir", S_IFDIR));
385 }
386 
387 
388 ATF_TEST_CASE_WITHOUT_HEAD(mkdir_p__many_components);
389 ATF_TEST_CASE_BODY(mkdir_p__many_components)
390 {
391     ATF_REQUIRE(!lookup(".", "a", S_IFDIR));
392     fs::mkdir_p(fs::path("a/b/c"), 0755);
393     ATF_REQUIRE(lookup(".", "a", S_IFDIR));
394     ATF_REQUIRE(lookup("a", "b", S_IFDIR));
395     ATF_REQUIRE(lookup("a/b", "c", S_IFDIR));
396 }
397 
398 
399 ATF_TEST_CASE_WITHOUT_HEAD(mkdir_p__already_exists);
400 ATF_TEST_CASE_BODY(mkdir_p__already_exists)
401 {
402     fs::mkdir(fs::path("a"), 0755);
403     fs::mkdir(fs::path("a/b"), 0755);
404     fs::mkdir_p(fs::path("a/b"), 0755);
405 }
406 
407 
408 ATF_TEST_CASE(mkdir_p__eacces)
409 ATF_TEST_CASE_HEAD(mkdir_p__eacces)
410 {
411     set_md_var("require.user", "unprivileged");
412 }
413 ATF_TEST_CASE_BODY(mkdir_p__eacces)
414 {
415     fs::mkdir(fs::path("a"), 0755);
416     fs::mkdir(fs::path("a/b"), 0755);
417     ATF_REQUIRE(::chmod("a/b", 0555) != -1);
418     try {
419         fs::mkdir_p(fs::path("a/b/c/d"), 0755);
420         fail("system_error not raised");
421     } catch (const fs::system_error& e) {
422         ATF_REQUIRE_EQ(EACCES, e.original_errno());
423     }
424     ATF_REQUIRE(lookup(".", "a", S_IFDIR));
425     ATF_REQUIRE(lookup("a", "b", S_IFDIR));
426     ATF_REQUIRE(!lookup(".", "c", S_IFDIR));
427     ATF_REQUIRE(!lookup("a", "c", S_IFDIR));
428     ATF_REQUIRE(!lookup("a/b", "c", S_IFDIR));
429 }
430 
431 
432 ATF_TEST_CASE_WITHOUT_HEAD(mkdtemp_public)
433 ATF_TEST_CASE_BODY(mkdtemp_public)
434 {
435     const fs::path tmpdir = fs::current_path() / "tmp";
436     utils::setenv("TMPDIR", tmpdir.str());
437     fs::mkdir(tmpdir, 0755);
438 
439     const std::string dir_template("tempdir.XXXXXX");
440     const fs::path tempdir = fs::mkdtemp_public(dir_template);
441     ATF_REQUIRE(!lookup("tmp", dir_template.c_str(), S_IFDIR));
442     ATF_REQUIRE(lookup("tmp", tempdir.leaf_name().c_str(), S_IFDIR));
443 }
444 
445 
446 ATF_TEST_CASE(mkdtemp_public__getcwd_as_non_root)
447 ATF_TEST_CASE_HEAD(mkdtemp_public__getcwd_as_non_root)
448 {
449     set_md_var("require.config", "unprivileged-user");
450     set_md_var("require.user", "root");
451 }
452 ATF_TEST_CASE_BODY(mkdtemp_public__getcwd_as_non_root)
453 {
454     const std::string dir_template("dir.XXXXXX");
455     const fs::path dir = fs::mkdtemp_public(dir_template);
456     const fs::path subdir = dir / "subdir";
457     fs::mkdir(subdir, 0755);
458 
459     const uid_t old_euid = ::geteuid();
460     const gid_t old_egid = ::getegid();
461 
462     const passwd::user unprivileged_user = passwd::find_user_by_name(
463         get_config_var("unprivileged-user"));
464     ATF_REQUIRE(::setegid(unprivileged_user.gid) != -1);
465     ATF_REQUIRE(::seteuid(unprivileged_user.uid) != -1);
466 
467     // The next code block runs as non-root.  We cannot use any ATF macros nor
468     // functions in it because a failure would cause the test to attempt to
469     // write to the ATF result file which may not be writable as non-root.
470     bool failed = false;
471     {
472         try {
473             if (::chdir(subdir.c_str()) == -1) {
474                 std::cerr << "Cannot enter directory\n";
475                 failed |= true;
476             } else {
477                 fs::current_path();
478             }
479         } catch (const fs::error& e) {
480             failed |= true;
481             std::cerr << "Failed to query current path in: " << subdir << '\n';
482         }
483 
484         if (::seteuid(old_euid) == -1) {
485             std::cerr << "Failed to restore euid; cannot continue\n";
486             std::abort();
487         }
488         if (::setegid(old_egid) == -1) {
489             std::cerr << "Failed to restore egid; cannot continue\n";
490             std::abort();
491         }
492     }
493 
494     if (failed)
495         fail("Test failed; see stdout for details");
496 }
497 
498 
499 ATF_TEST_CASE(mkdtemp_public__search_permissions_as_non_root)
500 ATF_TEST_CASE_HEAD(mkdtemp_public__search_permissions_as_non_root)
501 {
502     set_md_var("require.config", "unprivileged-user");
503     set_md_var("require.user", "root");
504 }
505 ATF_TEST_CASE_BODY(mkdtemp_public__search_permissions_as_non_root)
506 {
507     const std::string dir_template("dir.XXXXXX");
508     const fs::path dir = fs::mkdtemp_public(dir_template);
509     const fs::path cookie = dir / "not-secret";
510     atf::utils::create_file(cookie.str(), "this is readable");
511 
512     // We are running as root so there is no reason to assume that our current
513     // work directory is accessible by non-root.  Weaken the permissions so that
514     // our code below works.
515     ATF_REQUIRE(::chmod(".", 0755) != -1);
516 
517     const uid_t old_euid = ::geteuid();
518     const gid_t old_egid = ::getegid();
519 
520     const passwd::user unprivileged_user = passwd::find_user_by_name(
521         get_config_var("unprivileged-user"));
522     ATF_REQUIRE(::setegid(unprivileged_user.gid) != -1);
523     ATF_REQUIRE(::seteuid(unprivileged_user.uid) != -1);
524 
525     // The next code block runs as non-root.  We cannot use any ATF macros nor
526     // functions in it because a failure would cause the test to attempt to
527     // write to the ATF result file which may not be writable as non-root.
528     bool failed = false;
529     {
530         try {
531             const std::string contents = utils::read_file(cookie);
532             std::cerr << "Read contents: " << contents << '\n';
533             failed |= (contents != "this is readable");
534         } catch (const std::runtime_error& e) {
535             failed |= true;
536             std::cerr << "Failed to read " << cookie << '\n';
537         }
538 
539         if (::seteuid(old_euid) == -1) {
540             std::cerr << "Failed to restore euid; cannot continue\n";
541             std::abort();
542         }
543         if (::setegid(old_egid) == -1) {
544             std::cerr << "Failed to restore egid; cannot continue\n";
545             std::abort();
546         }
547     }
548 
549     if (failed)
550         fail("Test failed; see stdout for details");
551 }
552 
553 
554 ATF_TEST_CASE_WITHOUT_HEAD(mkstemp)
555 ATF_TEST_CASE_BODY(mkstemp)
556 {
557     const fs::path tmpdir = fs::current_path() / "tmp";
558     utils::setenv("TMPDIR", tmpdir.str());
559     fs::mkdir(tmpdir, 0755);
560 
561     const std::string file_template("tempfile.XXXXXX");
562     const fs::path tempfile = fs::mkstemp(file_template);
563     ATF_REQUIRE(!lookup("tmp", file_template.c_str(), S_IFREG));
564     ATF_REQUIRE(lookup("tmp", tempfile.leaf_name().c_str(), S_IFREG));
565 }
566 
567 
568 static void
569 test_mount_tmpfs_ok(const units::bytes& size)
570 {
571     const fs::path mount_point("mount_point");
572     fs::mkdir(mount_point, 0755);
573 
574     try {
575         atf::utils::create_file("outside", "");
576         fs::mount_tmpfs(mount_point, size);
577         atf::utils::create_file("mounted", "");
578         atf::utils::create_file((mount_point / "inside").str(), "");
579 
580         struct ::stat outside, inside;
581         ATF_REQUIRE(::stat("outside", &outside) != -1);
582         ATF_REQUIRE(::stat((mount_point / "inside").c_str(), &inside) != -1);
583         ATF_REQUIRE(outside.st_dev != inside.st_dev);
584         fs::unmount(mount_point);
585     } catch (const fs::unsupported_operation_error& e) {
586         ATF_SKIP(e.what());
587     }
588 }
589 
590 
591 ATF_TEST_CASE_WITH_CLEANUP(mount_tmpfs__ok__default_size)
592 ATF_TEST_CASE_HEAD(mount_tmpfs__ok__default_size)
593 {
594     set_md_var("require.user", "root");
595 }
596 ATF_TEST_CASE_BODY(mount_tmpfs__ok__default_size)
597 {
598     test_mount_tmpfs_ok(units::bytes());
599 }
600 ATF_TEST_CASE_CLEANUP(mount_tmpfs__ok__default_size)
601 {
602     cleanup_mount_point(fs::path("mounted"), fs::path("mount_point"));
603 }
604 
605 
606 ATF_TEST_CASE_WITH_CLEANUP(mount_tmpfs__ok__explicit_size)
607 ATF_TEST_CASE_HEAD(mount_tmpfs__ok__explicit_size)
608 {
609     set_md_var("require.user", "root");
610 }
611 ATF_TEST_CASE_BODY(mount_tmpfs__ok__explicit_size)
612 {
613     test_mount_tmpfs_ok(units::bytes(10 * units::MB));
614 }
615 ATF_TEST_CASE_CLEANUP(mount_tmpfs__ok__explicit_size)
616 {
617     cleanup_mount_point(fs::path("mounted"), fs::path("mount_point"));
618 }
619 
620 
621 ATF_TEST_CASE(mount_tmpfs__fail)
622 ATF_TEST_CASE_HEAD(mount_tmpfs__fail)
623 {
624     set_md_var("require.user", "root");
625 }
626 ATF_TEST_CASE_BODY(mount_tmpfs__fail)
627 {
628     try {
629         fs::mount_tmpfs(fs::path("non-existent"));
630     } catch (const fs::unsupported_operation_error& e) {
631         ATF_SKIP(e.what());
632     } catch (const fs::error& e) {
633         // Expected.
634     }
635 }
636 
637 
638 ATF_TEST_CASE_WITHOUT_HEAD(rm_r__empty);
639 ATF_TEST_CASE_BODY(rm_r__empty)
640 {
641     fs::mkdir(fs::path("root"), 0755);
642     ATF_REQUIRE(lookup(".", "root", S_IFDIR));
643     fs::rm_r(fs::path("root"));
644     ATF_REQUIRE(!lookup(".", "root", S_IFDIR));
645 }
646 
647 
648 ATF_TEST_CASE_WITHOUT_HEAD(rm_r__files_and_directories);
649 ATF_TEST_CASE_BODY(rm_r__files_and_directories)
650 {
651     fs::mkdir(fs::path("root"), 0755);
652     atf::utils::create_file("root/.hidden_file", "");
653     fs::mkdir(fs::path("root/.hidden_dir"), 0755);
654     atf::utils::create_file("root/.hidden_dir/a", "");
655     atf::utils::create_file("root/file", "");
656     atf::utils::create_file("root/with spaces", "");
657     fs::mkdir(fs::path("root/dir1"), 0755);
658     fs::mkdir(fs::path("root/dir1/dir2"), 0755);
659     atf::utils::create_file("root/dir1/dir2/file", "");
660     fs::mkdir(fs::path("root/dir1/dir3"), 0755);
661     ATF_REQUIRE(lookup(".", "root", S_IFDIR));
662     fs::rm_r(fs::path("root"));
663     ATF_REQUIRE(!lookup(".", "root", S_IFDIR));
664 }
665 
666 
667 ATF_TEST_CASE_WITHOUT_HEAD(rmdir__ok)
668 ATF_TEST_CASE_BODY(rmdir__ok)
669 {
670     ATF_REQUIRE(::mkdir("foo", 0755) != -1);
671     ATF_REQUIRE(::access("foo", X_OK) == 0);
672     fs::rmdir(fs::path("foo"));
673     ATF_REQUIRE(::access("foo", X_OK) == -1);
674 }
675 
676 
677 ATF_TEST_CASE_WITHOUT_HEAD(rmdir__fail)
678 ATF_TEST_CASE_BODY(rmdir__fail)
679 {
680     ATF_REQUIRE_THROW_RE(fs::system_error, "Removal of foo failed",
681                          fs::rmdir(fs::path("foo")));
682 }
683 
684 
685 ATF_TEST_CASE_WITHOUT_HEAD(scan_directory__ok)
686 ATF_TEST_CASE_BODY(scan_directory__ok)
687 {
688     fs::mkdir(fs::path("dir"), 0755);
689     atf::utils::create_file("dir/foo", "");
690     atf::utils::create_file("dir/.hidden", "");
691 
692     const std::set< fs::directory_entry > contents = fs::scan_directory(
693         fs::path("dir"));
694 
695     std::set< fs::directory_entry > exp_contents;
696     exp_contents.insert(fs::directory_entry("."));
697     exp_contents.insert(fs::directory_entry(".."));
698     exp_contents.insert(fs::directory_entry(".hidden"));
699     exp_contents.insert(fs::directory_entry("foo"));
700 
701     ATF_REQUIRE_EQ(exp_contents, contents);
702 }
703 
704 
705 ATF_TEST_CASE_WITHOUT_HEAD(scan_directory__fail)
706 ATF_TEST_CASE_BODY(scan_directory__fail)
707 {
708     ATF_REQUIRE_THROW_RE(fs::system_error, "opendir(.*missing.*) failed",
709                          fs::scan_directory(fs::path("missing")));
710 }
711 
712 
713 ATF_TEST_CASE_WITHOUT_HEAD(unlink__ok)
714 ATF_TEST_CASE_BODY(unlink__ok)
715 {
716     atf::utils::create_file("foo", "");
717     ATF_REQUIRE(::access("foo", R_OK) == 0);
718     fs::unlink(fs::path("foo"));
719     ATF_REQUIRE(::access("foo", R_OK) == -1);
720 }
721 
722 
723 ATF_TEST_CASE_WITHOUT_HEAD(unlink__fail)
724 ATF_TEST_CASE_BODY(unlink__fail)
725 {
726     ATF_REQUIRE_THROW_RE(fs::system_error, "Removal of foo failed",
727                          fs::unlink(fs::path("foo")));
728 }
729 
730 
731 ATF_TEST_CASE(unmount__ok)
732 ATF_TEST_CASE_HEAD(unmount__ok)
733 {
734     set_md_var("require.user", "root");
735 }
736 ATF_TEST_CASE_BODY(unmount__ok)
737 {
738     const fs::path mount_point("mount_point");
739     fs::mkdir(mount_point, 0755);
740 
741     atf::utils::create_file((mount_point / "test1").str(), "");
742     try {
743         fs::mount_tmpfs(mount_point);
744     } catch (const fs::unsupported_operation_error& e) {
745         ATF_SKIP(e.what());
746     }
747 
748     atf::utils::create_file((mount_point / "test2").str(), "");
749 
750     ATF_REQUIRE(!fs::exists(mount_point / "test1"));
751     ATF_REQUIRE( fs::exists(mount_point / "test2"));
752     fs::unmount(mount_point);
753     ATF_REQUIRE( fs::exists(mount_point / "test1"));
754     ATF_REQUIRE(!fs::exists(mount_point / "test2"));
755 }
756 
757 
758 ATF_TEST_CASE(unmount__fail)
759 ATF_TEST_CASE_HEAD(unmount__fail)
760 {
761     set_md_var("require.user", "root");
762 }
763 ATF_TEST_CASE_BODY(unmount__fail)
764 {
765     ATF_REQUIRE_THROW(fs::error, fs::unmount(fs::path("non-existent")));
766 }
767 
768 
769 ATF_INIT_TEST_CASES(tcs)
770 {
771     ATF_ADD_TEST_CASE(tcs, copy__ok);
772     ATF_ADD_TEST_CASE(tcs, copy__fail_open);
773     ATF_ADD_TEST_CASE(tcs, copy__fail_create);
774 
775     ATF_ADD_TEST_CASE(tcs, current_path__ok);
776     ATF_ADD_TEST_CASE(tcs, current_path__enoent);
777 
778     ATF_ADD_TEST_CASE(tcs, exists);
779 
780     ATF_ADD_TEST_CASE(tcs, find_in_path__no_path);
781     ATF_ADD_TEST_CASE(tcs, find_in_path__empty_path);
782     ATF_ADD_TEST_CASE(tcs, find_in_path__one_component);
783     ATF_ADD_TEST_CASE(tcs, find_in_path__many_components);
784     ATF_ADD_TEST_CASE(tcs, find_in_path__current_directory);
785     ATF_ADD_TEST_CASE(tcs, find_in_path__always_absolute);
786 
787     ATF_ADD_TEST_CASE(tcs, free_disk_space__ok__smoke);
788     ATF_ADD_TEST_CASE(tcs, free_disk_space__ok__real);
789     ATF_ADD_TEST_CASE(tcs, free_disk_space__fail);
790 
791     ATF_ADD_TEST_CASE(tcs, is_directory__ok);
792     ATF_ADD_TEST_CASE(tcs, is_directory__fail);
793 
794     ATF_ADD_TEST_CASE(tcs, mkdir__ok);
795     ATF_ADD_TEST_CASE(tcs, mkdir__enoent);
796 
797     ATF_ADD_TEST_CASE(tcs, mkdir_p__one_component);
798     ATF_ADD_TEST_CASE(tcs, mkdir_p__many_components);
799     ATF_ADD_TEST_CASE(tcs, mkdir_p__already_exists);
800     ATF_ADD_TEST_CASE(tcs, mkdir_p__eacces);
801 
802     ATF_ADD_TEST_CASE(tcs, mkdtemp_public);
803     ATF_ADD_TEST_CASE(tcs, mkdtemp_public__getcwd_as_non_root);
804     ATF_ADD_TEST_CASE(tcs, mkdtemp_public__search_permissions_as_non_root);
805 
806     ATF_ADD_TEST_CASE(tcs, mkstemp);
807 
808     ATF_ADD_TEST_CASE(tcs, mount_tmpfs__ok__default_size);
809     ATF_ADD_TEST_CASE(tcs, mount_tmpfs__ok__explicit_size);
810     ATF_ADD_TEST_CASE(tcs, mount_tmpfs__fail);
811 
812     ATF_ADD_TEST_CASE(tcs, rm_r__empty);
813     ATF_ADD_TEST_CASE(tcs, rm_r__files_and_directories);
814 
815     ATF_ADD_TEST_CASE(tcs, rmdir__ok);
816     ATF_ADD_TEST_CASE(tcs, rmdir__fail);
817 
818     ATF_ADD_TEST_CASE(tcs, scan_directory__ok);
819     ATF_ADD_TEST_CASE(tcs, scan_directory__fail);
820 
821     ATF_ADD_TEST_CASE(tcs, unlink__ok);
822     ATF_ADD_TEST_CASE(tcs, unlink__fail);
823 
824     ATF_ADD_TEST_CASE(tcs, unmount__ok);
825     ATF_ADD_TEST_CASE(tcs, unmount__fail);
826 }
827