1// Copyright 2012 Google Inc. 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 "fs.h" 30 31#include <sys/stat.h> 32#include <sys/wait.h> 33 34#include <assert.h> 35#include <dirent.h> 36#include <err.h> 37#include <errno.h> 38#include <signal.h> 39#include <stdbool.h> 40#include <stdio.h> 41#include <stdlib.h> 42#include <string.h> 43#include <unistd.h> 44 45#include <atf-c.h> 46 47#include "error.h" 48 49 50static void run_mount_tmpfs(const char*) KYUA_DEFS_NORETURN; 51 52 53/// Operating systems recognized by the code below. 54enum os_type { 55 os_unsupported = 0, 56 os_freebsd, 57 os_linux, 58 os_netbsd, 59 os_sunos, 60}; 61 62 63/// The current operating system. 64static enum os_type current_os = 65#if defined(__FreeBSD__) 66 os_freebsd 67#elif defined(__linux__) 68 os_linux 69#elif defined(__NetBSD__) 70 os_netbsd 71#elif defined(__SunOS__) 72 os_sunos 73#else 74 os_unsupported 75#endif 76 ; 77 78 79/// Checks if a directory entry exists and matches a specific type. 80/// 81/// \param dir The directory in which to look for the entry. 82/// \param name The name of the entry to look up. 83/// \param expected_type The expected type of the file as given by dir(5). 84/// 85/// \return True if the entry exists and matches the given type; false 86/// otherwise. 87static bool 88lookup(const char* dir, const char* name, const int expected_type) 89{ 90 DIR* dirp = opendir(dir); 91 ATF_REQUIRE(dirp != NULL); 92 93 bool found = false; 94 struct dirent* dp; 95 while (!found && (dp = readdir(dirp)) != NULL) { 96 if (strcmp(dp->d_name, name) == 0 && 97 dp->d_type == expected_type) { 98 found = true; 99 } 100 } 101 closedir(dirp); 102 return found; 103} 104 105 106/// Executes 'mount -t tmpfs' (or a similar variant). 107/// 108/// This function must be called from a subprocess, as it never returns. 109/// 110/// \param mount_point Location on which to mount a tmpfs. 111static void 112run_mount_tmpfs(const char* mount_point) 113{ 114 const char* mount_args[16]; 115 116 size_t last = 0; 117 switch (current_os) { 118 case os_freebsd: 119 mount_args[last++] = "mdmfs"; 120 mount_args[last++] = "-s16m"; 121 mount_args[last++] = "md"; 122 mount_args[last++] = mount_point; 123 break; 124 125 case os_linux: 126 mount_args[last++] = "mount"; 127 mount_args[last++] = "-ttmpfs"; 128 mount_args[last++] = "tmpfs"; 129 mount_args[last++] = mount_point; 130 break; 131 132 case os_netbsd: 133 mount_args[last++] = "mount"; 134 mount_args[last++] = "-ttmpfs"; 135 mount_args[last++] = "tmpfs"; 136 mount_args[last++] = mount_point; 137 break; 138 139 case os_sunos: 140 mount_args[last++] = "mount"; 141 mount_args[last++] = "-Ftmpfs"; 142 mount_args[last++] = "tmpfs"; 143 mount_args[last++] = mount_point; 144 break; 145 146 default: 147 err(123, "Don't know how to mount a file system for testing " 148 "purposes"); 149 } 150 mount_args[last] = NULL; 151 152 const char** arg; 153 printf("Mounting tmpfs onto %s with:", mount_point); 154 for (arg = &mount_args[0]; *arg != NULL; arg++) 155 printf(" %s", *arg); 156 printf("\n"); 157 158 const int ret = execvp(mount_args[0], KYUA_DEFS_UNCONST(mount_args)); 159 assert(ret == -1); 160 err(EXIT_FAILURE, "Failed to exec %s", mount_args[0]); 161}; 162 163 164/// Mounts a temporary file system. 165/// 166/// This is only provided for testing purposes. The mounted file system 167/// contains no valuable data. 168/// 169/// Note that the calling test case is skipped if the current operating system 170/// is not supported. 171/// 172/// \param mount_point The path on which the file system will be mounted. 173static void 174mount_tmpfs(const char* mount_point) 175{ 176 // SunOS's mount(8) requires paths to be absolute. To err on the side of 177 // caution, let's make it absolute in all cases. 178 //const fspath abs_mount_point = mount_point.is_absolute() ? 179 // mount_point : mount_point.to_absolute(); 180 181 pid_t pid = fork(); 182 ATF_REQUIRE(pid != -1); 183 if (pid == 0) 184 run_mount_tmpfs(mount_point); 185 int status; 186 ATF_REQUIRE(waitpid(pid, &status, 0) != -1); 187 ATF_REQUIRE(WIFEXITED(status)); 188 if (WEXITSTATUS(status) == 123) 189 atf_tc_skip("Don't know how to mount a file system for testing " 190 "purposes"); 191 else 192 ATF_REQUIRE_EQ(EXIT_SUCCESS, WEXITSTATUS(status)); 193} 194 195 196static bool 197lchmod_fails(void) 198{ 199 ATF_REQUIRE(mkdir("test", 0755) != -1); 200 return lchmod("test", 0700) == -1 && chmod("test", 0700) != -1; 201} 202 203 204ATF_TC_WITHOUT_HEAD(cleanup__file); 205ATF_TC_BODY(cleanup__file, tc) 206{ 207 atf_utils_create_file("root", "%s", ""); 208 ATF_REQUIRE(lookup(".", "root", DT_REG)); 209 ATF_REQUIRE(!kyua_error_is_set(kyua_fs_cleanup("root"))); 210 ATF_REQUIRE(!lookup(".", "root", DT_REG)); 211} 212 213 214ATF_TC_WITHOUT_HEAD(cleanup__subdir__empty); 215ATF_TC_BODY(cleanup__subdir__empty, tc) 216{ 217 ATF_REQUIRE(mkdir("root", 0755) != -1); 218 ATF_REQUIRE(lookup(".", "root", DT_DIR)); 219 ATF_REQUIRE(!kyua_error_is_set(kyua_fs_cleanup("root"))); 220 ATF_REQUIRE(!lookup(".", "root", DT_DIR)); 221} 222 223 224ATF_TC_WITHOUT_HEAD(cleanup__subdir__files_and_directories); 225ATF_TC_BODY(cleanup__subdir__files_and_directories, tc) 226{ 227 ATF_REQUIRE(mkdir("root", 0755) != -1); 228 atf_utils_create_file("root/.hidden_file", "%s", ""); 229 ATF_REQUIRE(mkdir("root/.hidden_dir", 0755) != -1); 230 atf_utils_create_file("root/.hidden_dir/a", "%s", ""); 231 atf_utils_create_file("root/file", "%s", ""); 232 atf_utils_create_file("root/with spaces", "%s", ""); 233 ATF_REQUIRE(mkdir("root/dir1", 0755) != -1); 234 ATF_REQUIRE(mkdir("root/dir1/dir2", 0755) != -1); 235 atf_utils_create_file("root/dir1/dir2/file", "%s", ""); 236 ATF_REQUIRE(mkdir("root/dir1/dir3", 0755) != -1); 237 ATF_REQUIRE(lookup(".", "root", DT_DIR)); 238 ATF_REQUIRE(!kyua_error_is_set(kyua_fs_cleanup("root"))); 239 ATF_REQUIRE(!lookup(".", "root", DT_DIR)); 240} 241 242 243ATF_TC_WITHOUT_HEAD(cleanup__subdir__unprotect_regular); 244ATF_TC_BODY(cleanup__subdir__unprotect_regular, tc) 245{ 246 ATF_REQUIRE(mkdir("root", 0755) != -1); 247 ATF_REQUIRE(mkdir("root/dir1", 0755) != -1); 248 ATF_REQUIRE(mkdir("root/dir1/dir2", 0755) != -1); 249 atf_utils_create_file("root/dir1/dir2/file", "%s", ""); 250 ATF_REQUIRE(chmod("root/dir1/dir2/file", 0000) != -1); 251 ATF_REQUIRE(chmod("root/dir1/dir2", 0000) != -1); 252 ATF_REQUIRE(chmod("root/dir1", 0000) != -1); 253 ATF_REQUIRE(!kyua_error_is_set(kyua_fs_cleanup("root"))); 254 ATF_REQUIRE(!lookup(".", "root", DT_DIR)); 255} 256 257 258ATF_TC(cleanup__subdir__unprotect_symlink); 259ATF_TC_HEAD(cleanup__subdir__unprotect_symlink, tc) 260{ 261 atf_tc_set_md_var(tc, "require.progs", "/bin/ls"); 262 // We are ensuring that chmod is not run on the target of a symlink, so 263 // we cannot be root (nor we don't want to, to prevent unprotecting a 264 // system file!). 265 atf_tc_set_md_var(tc, "require.user", "unprivileged"); 266} 267ATF_TC_BODY(cleanup__subdir__unprotect_symlink, tc) 268{ 269 ATF_REQUIRE(mkdir("root", 0755) != -1); 270 ATF_REQUIRE(mkdir("root/dir1", 0755) != -1); 271 ATF_REQUIRE(symlink("/bin/ls", "root/dir1/ls") != -1); 272 ATF_REQUIRE(chmod("root/dir1", 0555) != -1); 273 ATF_REQUIRE(!kyua_error_is_set(kyua_fs_cleanup("root"))); 274 ATF_REQUIRE(!lookup(".", "root", DT_DIR)); 275} 276 277 278ATF_TC_WITHOUT_HEAD(cleanup__subdir__links); 279ATF_TC_BODY(cleanup__subdir__links, tc) 280{ 281 ATF_REQUIRE(mkdir("root", 0755) != -1); 282 ATF_REQUIRE(mkdir("root/dir1", 0755) != -1); 283 ATF_REQUIRE(symlink("../../root", "root/dir1/loop") != -1); 284 ATF_REQUIRE(symlink("non-existent", "root/missing") != -1); 285 ATF_REQUIRE(lookup(".", "root", DT_DIR)); 286 kyua_error_t error = kyua_fs_cleanup("root"); 287 if (kyua_error_is_set(error)) { 288 if (lchmod_fails()) 289 atf_tc_expect_fail("lchmod(2) is not implemented in your system"); 290 kyua_error_free(error); 291 atf_tc_fail("kyua_fs_cleanup returned an error"); 292 } 293 ATF_REQUIRE(!lookup(".", "root", DT_DIR)); 294} 295 296 297ATF_TC(cleanup__mount_point__simple); 298ATF_TC_HEAD(cleanup__mount_point__simple, tc) 299{ 300 atf_tc_set_md_var(tc, "require.user", "root"); 301} 302ATF_TC_BODY(cleanup__mount_point__simple, tc) 303{ 304 ATF_REQUIRE(mkdir("root", 0755) != -1); 305 ATF_REQUIRE(mkdir("root/dir1", 0755) != -1); 306 atf_utils_create_file("root/zz", "%s", ""); 307 mount_tmpfs("root/dir1"); 308 ATF_REQUIRE(!kyua_error_is_set(kyua_fs_cleanup("root"))); 309 ATF_REQUIRE(!lookup(".", "root", DT_DIR)); 310} 311 312 313ATF_TC(cleanup__mount_point__overlayed); 314ATF_TC_HEAD(cleanup__mount_point__overlayed, tc) 315{ 316 atf_tc_set_md_var(tc, "require.user", "root"); 317} 318ATF_TC_BODY(cleanup__mount_point__overlayed, tc) 319{ 320 ATF_REQUIRE(mkdir("root", 0755) != -1); 321 ATF_REQUIRE(mkdir("root/dir1", 0755) != -1); 322 atf_utils_create_file("root/zz", "%s", ""); 323 mount_tmpfs("root/dir1"); 324 mount_tmpfs("root/dir1"); 325 ATF_REQUIRE(!kyua_error_is_set(kyua_fs_cleanup("root"))); 326 ATF_REQUIRE(!lookup(".", "root", DT_DIR)); 327} 328 329 330ATF_TC(cleanup__mount_point__nested); 331ATF_TC_HEAD(cleanup__mount_point__nested, tc) 332{ 333 atf_tc_set_md_var(tc, "require.user", "root"); 334} 335ATF_TC_BODY(cleanup__mount_point__nested, tc) 336{ 337 ATF_REQUIRE(mkdir("root", 0755) != -1); 338 ATF_REQUIRE(mkdir("root/dir1", 0755) != -1); 339 ATF_REQUIRE(mkdir("root/dir1/dir2", 0755) != -1); 340 ATF_REQUIRE(mkdir("root/dir3", 0755) != -1); 341 mount_tmpfs("root/dir1/dir2"); 342 mount_tmpfs("root/dir3"); 343 ATF_REQUIRE(mkdir("root/dir1/dir2/dir4", 0755) != -1); 344 mount_tmpfs("root/dir1/dir2/dir4"); 345 ATF_REQUIRE(mkdir("root/dir1/dir2/not-mount-point", 0755) != -1); 346 ATF_REQUIRE(!kyua_error_is_set(kyua_fs_cleanup("root"))); 347 ATF_REQUIRE(!lookup(".", "root", DT_DIR)); 348} 349 350 351ATF_TC(cleanup__mount_point__links); 352ATF_TC_HEAD(cleanup__mount_point__links, tc) 353{ 354 atf_tc_set_md_var(tc, "require.user", "root"); 355} 356ATF_TC_BODY(cleanup__mount_point__links, tc) 357{ 358 ATF_REQUIRE(mkdir("root", 0755) != -1); 359 ATF_REQUIRE(mkdir("root/dir1", 0755) != -1); 360 ATF_REQUIRE(mkdir("root/dir3", 0755) != -1); 361 mount_tmpfs("root/dir1"); 362 ATF_REQUIRE(symlink("../dir3", "root/dir1/link") != -1); 363 ATF_REQUIRE(!kyua_error_is_set(kyua_fs_cleanup("root"))); 364 ATF_REQUIRE(!lookup(".", "root", DT_DIR)); 365} 366 367 368ATF_TC(cleanup__mount_point__busy); 369ATF_TC_HEAD(cleanup__mount_point__busy, tc) 370{ 371 atf_tc_set_md_var(tc, "require.user", "root"); 372} 373ATF_TC_BODY(cleanup__mount_point__busy, tc) 374{ 375 ATF_REQUIRE(mkdir("root", 0755) != -1); 376 ATF_REQUIRE(mkdir("root/dir1", 0755) != -1); 377 mount_tmpfs("root/dir1"); 378 379 pid_t pid = fork(); 380 ATF_REQUIRE(pid != -1); 381 if (pid == 0) { 382 if (chdir("root/dir1") == -1) 383 abort(); 384 385 atf_utils_create_file("dont-delete-me", "%s", ""); 386 atf_utils_create_file("../../done", "%s", ""); 387 388 pause(); 389 exit(EXIT_SUCCESS); 390 } else { 391 fprintf(stderr, "Waiting for child to finish preparations\n"); 392 while (!atf_utils_file_exists("done")) {} 393 fprintf(stderr, "Child done; cleaning up\n"); 394 395 ATF_REQUIRE(kyua_error_is_set(kyua_fs_cleanup("root"))); 396 ATF_REQUIRE(atf_utils_file_exists("root/dir1/dont-delete-me")); 397 398 fprintf(stderr, "Killing child\n"); 399 ATF_REQUIRE(kill(pid, SIGKILL) != -1); 400 int status; 401 ATF_REQUIRE(waitpid(pid, &status, 0) != -1); 402 403 ATF_REQUIRE(!kyua_error_is_set(kyua_fs_cleanup("root"))); 404 ATF_REQUIRE(!lookup(".", "root", DT_DIR)); 405 } 406} 407 408 409ATF_TC_WITHOUT_HEAD(concat__one); 410ATF_TC_BODY(concat__one, tc) 411{ 412 char* path; 413 ATF_REQUIRE(!kyua_error_is_set(kyua_fs_concat(&path, "foo", NULL))); 414 ATF_REQUIRE_STREQ("foo", path); 415 free(path); 416} 417 418 419ATF_TC_WITHOUT_HEAD(concat__two); 420ATF_TC_BODY(concat__two, tc) 421{ 422 char* path; 423 ATF_REQUIRE(!kyua_error_is_set(kyua_fs_concat(&path, "foo", "bar", NULL))); 424 ATF_REQUIRE_STREQ("foo/bar", path); 425 free(path); 426} 427 428 429ATF_TC_WITHOUT_HEAD(concat__several); 430ATF_TC_BODY(concat__several, tc) 431{ 432 char* path; 433 ATF_REQUIRE(!kyua_error_is_set(kyua_fs_concat(&path, "/usr", ".", "bin", 434 "ls", NULL))); 435 ATF_REQUIRE_STREQ("/usr/./bin/ls", path); 436 free(path); 437} 438 439 440ATF_TC_WITHOUT_HEAD(current_path__ok); 441ATF_TC_BODY(current_path__ok, tc) 442{ 443 char* previous; 444 ATF_REQUIRE(!kyua_error_is_set(kyua_fs_current_path(&previous))); 445 446 ATF_REQUIRE(mkdir("root", 0755) != -1); 447 ATF_REQUIRE(chdir("root") != -1); 448 char* cwd; 449 ATF_REQUIRE(!kyua_error_is_set(kyua_fs_current_path(&cwd))); 450 451 char* exp_cwd; 452 ATF_REQUIRE(!kyua_error_is_set(kyua_fs_concat(&exp_cwd, previous, "root", 453 NULL))); 454 ATF_REQUIRE_STREQ(exp_cwd, cwd); 455 456 free(exp_cwd); 457 free(cwd); 458 free(previous); 459} 460 461 462ATF_TC_WITHOUT_HEAD(current_path__enoent); 463ATF_TC_BODY(current_path__enoent, tc) 464{ 465 char* previous; 466 ATF_REQUIRE(!kyua_error_is_set(kyua_fs_current_path(&previous))); 467 468 ATF_REQUIRE(mkdir("root", 0755) != -1); 469 ATF_REQUIRE(chdir("root") != -1); 470 ATF_REQUIRE(rmdir("../root") != -1); 471 char* cwd = (char*)0xdeadbeef; 472 kyua_error_t error = kyua_fs_current_path(&cwd); 473 ATF_REQUIRE(kyua_error_is_set(error)); 474 ATF_REQUIRE(kyua_error_is_type(error, "libc")); 475 ATF_REQUIRE_EQ(ENOENT, kyua_libc_error_errno(error)); 476 ATF_REQUIRE_EQ((char*)0xdeadbeef, cwd); 477 kyua_error_free(error); 478 479 free(previous); 480} 481 482 483ATF_TC_WITHOUT_HEAD(make_absolute__absolute); 484ATF_TC_BODY(make_absolute__absolute, tc) 485{ 486 char* absolute; 487 ATF_REQUIRE(!kyua_error_is_set(kyua_fs_make_absolute( 488 "/this/is/absolute", &absolute))); 489 ATF_REQUIRE_STREQ("/this/is/absolute", absolute); 490 free(absolute); 491} 492 493 494ATF_TC_WITHOUT_HEAD(make_absolute__relative); 495ATF_TC_BODY(make_absolute__relative, tc) 496{ 497 kyua_error_t error; 498 char* absolute; 499 500 DIR* previous = opendir("."); 501 ATF_REQUIRE(previous != NULL); 502 ATF_REQUIRE(chdir("/usr") != -1); 503 error = kyua_fs_make_absolute("bin/foobar", &absolute); 504 const int previous_fd = dirfd(previous); 505 ATF_REQUIRE(fchdir(previous_fd) != -1); 506 close(previous_fd); 507 508 ATF_REQUIRE(!kyua_error_is_set(error)); 509 ATF_REQUIRE_STREQ("/usr/bin/foobar", absolute); 510 free(absolute); 511} 512 513 514ATF_TC(unmount__ok); 515ATF_TC_HEAD(unmount__ok, tc) 516{ 517 atf_tc_set_md_var(tc, "require.user", "root"); 518} 519ATF_TC_BODY(unmount__ok, tc) 520{ 521 ATF_REQUIRE(mkdir("mount_point", 0755) != -1); 522 523 atf_utils_create_file("mount_point/test1", "%s", ""); 524 mount_tmpfs("mount_point"); 525 atf_utils_create_file("mount_point/test2", "%s", ""); 526 527 ATF_REQUIRE(!atf_utils_file_exists("mount_point/test1")); 528 ATF_REQUIRE( atf_utils_file_exists("mount_point/test2")); 529 kyua_fs_unmount("mount_point"); 530 ATF_REQUIRE( atf_utils_file_exists("mount_point/test1")); 531 ATF_REQUIRE(!atf_utils_file_exists("mount_point/test2")); 532} 533 534 535ATF_TC(unmount__fail); 536ATF_TC_HEAD(unmount__fail, tc) 537{ 538 atf_tc_set_md_var(tc, "require.user", "root"); 539} 540ATF_TC_BODY(unmount__fail, tc) 541{ 542 kyua_error_t error = kyua_fs_unmount("mount_point"); 543 ATF_REQUIRE(kyua_error_is_set(error)); 544 kyua_error_free(error); 545} 546 547 548ATF_TP_ADD_TCS(tp) 549{ 550 ATF_TP_ADD_TC(tp, cleanup__file); 551 ATF_TP_ADD_TC(tp, cleanup__subdir__empty); 552 ATF_TP_ADD_TC(tp, cleanup__subdir__files_and_directories); 553 ATF_TP_ADD_TC(tp, cleanup__subdir__unprotect_regular); 554 ATF_TP_ADD_TC(tp, cleanup__subdir__unprotect_symlink); 555 ATF_TP_ADD_TC(tp, cleanup__subdir__links); 556 ATF_TP_ADD_TC(tp, cleanup__mount_point__simple); 557 ATF_TP_ADD_TC(tp, cleanup__mount_point__overlayed); 558 ATF_TP_ADD_TC(tp, cleanup__mount_point__nested); 559 ATF_TP_ADD_TC(tp, cleanup__mount_point__links); 560 ATF_TP_ADD_TC(tp, cleanup__mount_point__busy); 561 562 ATF_TP_ADD_TC(tp, concat__one); 563 ATF_TP_ADD_TC(tp, concat__two); 564 ATF_TP_ADD_TC(tp, concat__several); 565 566 ATF_TP_ADD_TC(tp, current_path__ok); 567 ATF_TP_ADD_TC(tp, current_path__enoent); 568 569 ATF_TP_ADD_TC(tp, make_absolute__absolute); 570 ATF_TP_ADD_TC(tp, make_absolute__relative); 571 572 ATF_TP_ADD_TC(tp, unmount__ok); 573 ATF_TP_ADD_TC(tp, unmount__fail); 574 575 return atf_no_error(); 576} 577