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 #if defined(HAVE_CONFIG_H) 32 # include "config.h" 33 #endif 34 35 #if defined(HAVE_UNMOUNT) 36 # include <sys/param.h> 37 # include <sys/mount.h> 38 #endif 39 #include <sys/stat.h> 40 #include <sys/wait.h> 41 42 #include <assert.h> 43 #include <dirent.h> 44 #include <err.h> 45 #include <errno.h> 46 #include <stdarg.h> 47 #include <stdbool.h> 48 #include <stdio.h> 49 #include <stdlib.h> 50 #include <string.h> 51 #include <unistd.h> 52 53 #include "defs.h" 54 #include "error.h" 55 56 57 /// Specifies if a real unmount(2) is available. 58 /// 59 /// We use this as a constant instead of a macro so that we can compile both 60 /// versions of the unmount code unconditionally. This is a way to prevent 61 /// compilation bugs going unnoticed for long. 62 static const bool have_unmount2 = 63 #if defined(HAVE_UNMOUNT) 64 true; 65 #else 66 false; 67 #endif 68 69 70 #if !defined(UMOUNT) 71 /// Fake replacement value to the path to umount(8). 72 # define UMOUNT "do-not-use-this-value" 73 #else 74 # if defined(HAVE_UNMOUNT) 75 # error "umount(8) detected when unmount(2) is also available" 76 # endif 77 #endif 78 79 80 #if !defined(HAVE_UNMOUNT) 81 /// Fake unmount(2) function for systems without it. 82 /// 83 /// This is only provided to allow our code to compile in all platforms 84 /// regardless of whether they actually have an unmount(2) or not. 85 /// 86 /// \param unused_path The mount point to be unmounted. 87 /// \param unused_flags The flags to the unmount(2) call. 88 /// 89 /// \return -1 to indicate error, although this should never happen. 90 static int 91 unmount(const char* KYUA_DEFS_UNUSED_PARAM(path), 92 const int KYUA_DEFS_UNUSED_PARAM(flags)) 93 { 94 assert(false); 95 return -1; 96 } 97 #endif 98 99 100 /// Scans a directory and executes a callback on each entry. 101 /// 102 /// \param directory The directory to scan. 103 /// \param callback The function to execute on each entry. 104 /// \param argument A cookie to pass to the callback function. 105 /// 106 /// \return True if the directory scan and the calls to the callback function 107 /// are all successful; false otherwise. 108 /// 109 /// \note Errors are logged to stderr and do not stop the algorithm. 110 static bool 111 try_iterate_directory(const char* directory, 112 bool (*callback)(const char*, const void*), 113 const void* argument) 114 { 115 bool ok = true; 116 117 DIR* dirp = opendir(directory); 118 if (dirp == NULL) { 119 warn("opendir(%s) failed", directory); 120 ok &= false; 121 } else { 122 struct dirent* dp; 123 while ((dp = readdir(dirp)) != NULL) { 124 const char* name = dp->d_name; 125 if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0) 126 continue; 127 128 char* subdir; 129 const kyua_error_t error = kyua_fs_concat(&subdir, directory, name, 130 NULL); 131 if (kyua_error_is_set(error)) { 132 kyua_error_free(error); 133 warn("path concatenation failed"); 134 ok &= false; 135 } else { 136 ok &= callback(subdir, argument); 137 free(subdir); 138 } 139 } 140 closedir(dirp); 141 } 142 143 return ok; 144 } 145 146 147 /// Stats a file, without following links. 148 /// 149 /// \param path The file to stat. 150 /// \param [out] sb Pointer to the stat structure in which to place the result. 151 /// 152 /// \return The stat structure on success; none on failure. 153 /// 154 /// \note Errors are logged to stderr. 155 static bool 156 try_stat(const char* path, struct stat* sb) 157 { 158 if (lstat(path, sb) == -1) { 159 warn("lstat(%s) failed", path); 160 return false; 161 } else 162 return true; 163 } 164 165 166 /// Removes a directory. 167 /// 168 /// \param path The directory to remove. 169 /// 170 /// \return True on success; false otherwise. 171 /// 172 /// \note Errors are logged to stderr. 173 static bool 174 try_rmdir(const char* path) 175 { 176 if (rmdir(path) == -1) { 177 warn("rmdir(%s) failed", path); 178 return false; 179 } else 180 return true; 181 } 182 183 184 /// Removes a file. 185 /// 186 /// \param path The file to remove. 187 /// 188 /// \return True on success; false otherwise. 189 /// 190 /// \note Errors are logged to stderr. 191 static bool 192 try_unlink(const char* path) 193 { 194 if (unlink(path) == -1) { 195 warn("unlink(%s) failed", path); 196 return false; 197 } else 198 return true; 199 } 200 201 202 /// Unmounts a mount point. 203 /// 204 /// \param path The location to unmount. 205 /// 206 /// \return True on success; false otherwise. 207 /// 208 /// \note Errors are logged to stderr. 209 static bool 210 try_unmount(const char* path) 211 { 212 const kyua_error_t error = kyua_fs_unmount(path); 213 if (kyua_error_is_set(error)) { 214 kyua_error_warn(error, "Cannot unmount %s", path); 215 kyua_error_free(error); 216 return false; 217 } else 218 return true; 219 } 220 221 222 /// Attempts to weaken the permissions of a file. 223 /// 224 /// \param path The file to unprotect. 225 /// 226 /// \return True on success; false otherwise. 227 /// 228 /// \note Errors are logged to stderr. 229 static bool 230 try_unprotect(const char* path) 231 { 232 static const mode_t new_mode = 0700; 233 234 if (chmod(path, new_mode) == -1) { 235 warnx("chmod(%s, %04o) failed", path, new_mode); 236 return false; 237 } else 238 return true; 239 } 240 241 242 /// Attempts to weaken the permissions of a symbolic link. 243 /// 244 /// \param path The symbolic link to unprotect. 245 /// 246 /// \return True on success; false otherwise. 247 /// 248 /// \note Errors are logged to stderr. 249 static bool 250 try_unprotect_symlink(const char* path) 251 { 252 static const mode_t new_mode = 0700; 253 254 #if HAVE_WORKING_LCHMOD 255 if (lchmod(path, new_mode) == -1) { 256 warnx("lchmod(%s, %04o) failed", path, new_mode); 257 return false; 258 } else 259 return true; 260 #else 261 warnx("lchmod(%s, %04o) failed; system call not implemented", path, 262 new_mode); 263 return false; 264 #endif 265 } 266 267 268 /// Traverses a hierarchy unmounting any mount points in it. 269 /// 270 /// \param current_path The file or directory to traverse. 271 /// \param raw_parent_sb The stat structure of the enclosing directory. 272 /// 273 /// \return True on success; false otherwise. 274 /// 275 /// \note Errors are logged to stderr and do not stop the algorithm. 276 static bool 277 recursive_unmount(const char* current_path, const void* raw_parent_sb) 278 { 279 const struct stat* parent_sb = raw_parent_sb; 280 281 struct stat current_sb; 282 bool ok = try_stat(current_path, ¤t_sb); 283 if (ok) { 284 if (S_ISDIR(current_sb.st_mode)) { 285 assert(!S_ISLNK(current_sb.st_mode)); 286 ok &= try_iterate_directory(current_path, recursive_unmount, 287 ¤t_sb); 288 } 289 290 if (current_sb.st_dev != parent_sb->st_dev) 291 ok &= try_unmount(current_path); 292 } 293 294 return ok; 295 } 296 297 298 /// Traverses a hierarchy and removes all of its contents. 299 /// 300 /// This honors mount points: when a mount point is encountered, it is traversed 301 /// in search for other mount points, but no files within any of these are 302 /// removed. 303 /// 304 /// \param current_path The file or directory to traverse. 305 /// \param raw_parent_sb The stat structure of the enclosing directory. 306 /// 307 /// \return True on success; false otherwise. 308 /// 309 /// \note Errors are logged to stderr and do not stop the algorithm. 310 static bool 311 recursive_cleanup(const char* current_path, const void* raw_parent_sb) 312 { 313 const struct stat* parent_sb = raw_parent_sb; 314 315 struct stat current_sb; 316 bool ok = try_stat(current_path, ¤t_sb); 317 if (ok) { 318 // Weakening the protections of a file is just a best-effort operation. 319 // If this fails, we may still be able to do the file/directory removal 320 // later on, so ignore any failures from try_unprotect(). 321 // 322 // One particular case in which this fails is if try_unprotect() is run 323 // on a symbolic link that points to a file for which the unprotect is 324 // not possible, and lchmod(3) is not available. 325 if (S_ISLNK(current_sb.st_mode)) 326 try_unprotect_symlink(current_path); 327 else 328 try_unprotect(current_path); 329 330 if (current_sb.st_dev != parent_sb->st_dev) { 331 ok &= recursive_unmount(current_path, parent_sb); 332 if (ok) 333 ok &= recursive_cleanup(current_path, parent_sb); 334 } else { 335 if (S_ISDIR(current_sb.st_mode)) { 336 assert(!S_ISLNK(current_sb.st_mode)); 337 ok &= try_iterate_directory(current_path, recursive_cleanup, 338 ¤t_sb); 339 ok &= try_rmdir(current_path); 340 } else { 341 ok &= try_unlink(current_path); 342 } 343 } 344 } 345 346 return ok; 347 } 348 349 350 /// Unmounts a file system using unmount(2). 351 /// 352 /// \pre unmount(2) must be available; i.e. have_unmount2 must be true. 353 /// 354 /// \param mount_point The file system to unmount. 355 /// 356 /// \return An error object. 357 static kyua_error_t 358 unmount_with_unmount2(const char* mount_point) 359 { 360 assert(have_unmount2); 361 362 if (unmount(mount_point, 0) == -1) { 363 return kyua_libc_error_new(errno, "unmount(%s) failed", 364 mount_point); 365 } 366 367 return kyua_error_ok(); 368 } 369 370 371 /// Unmounts a file system using umount(8). 372 /// 373 /// \pre umount(2) must not be available; i.e. have_unmount2 must be false. 374 /// 375 /// \param mount_point The file system to unmount. 376 /// 377 /// \return An error object. 378 static kyua_error_t 379 unmount_with_umount8(const char* mount_point) 380 { 381 assert(!have_unmount2); 382 383 const pid_t pid = fork(); 384 if (pid == -1) { 385 return kyua_libc_error_new(errno, "fork() failed"); 386 } else if (pid == 0) { 387 const int ret = execlp(UMOUNT, "umount", mount_point, NULL); 388 assert(ret == -1); 389 err(EXIT_FAILURE, "Failed to execute " UMOUNT); 390 } 391 392 kyua_error_t error = kyua_error_ok(); 393 int status; 394 if (waitpid(pid, &status, 0) == -1) { 395 error = kyua_libc_error_new(errno, "waitpid(%d) failed", pid); 396 } else { 397 if (WIFEXITED(status)) { 398 if (WEXITSTATUS(status) == EXIT_SUCCESS) 399 assert(!kyua_error_is_set(error)); 400 else { 401 error = kyua_libc_error_new(EBUSY, "unmount(%s) failed", 402 mount_point); 403 } 404 } else 405 error = kyua_libc_error_new(EFAULT, "umount(8) crashed"); 406 } 407 return error; 408 } 409 410 411 /// Recursively removes a directory. 412 /// 413 /// \param root The directory or file to remove. Cannot be a mount point. 414 /// 415 /// \return An error object. 416 kyua_error_t 417 kyua_fs_cleanup(const char* root) 418 { 419 struct stat current_sb; 420 bool ok = try_stat(root, ¤t_sb); 421 if (ok) 422 ok &= recursive_cleanup(root, ¤t_sb); 423 424 if (!ok) { 425 warnx("Cleanup of '%s' failed", root); 426 return kyua_libc_error_new(EPERM, "Cleanup of %s failed", root); 427 } else 428 return kyua_error_ok(); 429 } 430 431 432 /// Concatenates a set of strings to form a path. 433 /// 434 /// \param [out] output Pointer to a dynamically-allocated string that will hold 435 /// the resulting path, if all goes well. 436 /// \param first First component of the path to concatenate. 437 /// \param ... All other components to concatenate. 438 /// 439 /// \return An error if there is not enough memory to fulfill the request; OK 440 /// otherwise. 441 kyua_error_t 442 kyua_fs_concat(char** const output, const char* first, ...) 443 { 444 va_list ap; 445 const char* component; 446 447 va_start(ap, first); 448 size_t length = strlen(first) + 1; 449 while ((component = va_arg(ap, const char*)) != NULL) { 450 length += 1 + strlen(component); 451 } 452 va_end(ap); 453 454 *output = (char*)malloc(length); 455 if (output == NULL) 456 return kyua_oom_error_new(); 457 char* iterator = *output; 458 459 int added_size; 460 added_size = snprintf(iterator, length, "%s", first); 461 iterator += added_size; length -= added_size; 462 463 va_start(ap, first); 464 while ((component = va_arg(ap, const char*)) != NULL) { 465 added_size = snprintf(iterator, length, "/%s", component); 466 iterator += added_size; length -= added_size; 467 } 468 va_end(ap); 469 470 return kyua_error_ok(); 471 } 472 473 474 /// Queries the path to the current directory. 475 /// 476 /// \param [out] out_cwd Dynamically-allocated pointer to a string holding the 477 /// current path. The caller must use free() to release it. 478 /// 479 /// \return An error object. 480 kyua_error_t 481 kyua_fs_current_path(char** out_cwd) 482 { 483 char* cwd; 484 #if defined(HAVE_GETCWD_DYN) 485 cwd = getcwd(NULL, 0); 486 #else 487 { 488 const char* static_cwd = ::getcwd(NULL, MAXPATHLEN); 489 const kyua_error_t error = kyua_fs_concat(&cwd, static_cwd, NULL); 490 if (kyua_error_is_set(error)) 491 return error; 492 } 493 #endif 494 if (cwd == NULL) { 495 return kyua_libc_error_new(errno, "getcwd() failed"); 496 } else { 497 *out_cwd = cwd; 498 return kyua_error_ok(); 499 } 500 } 501 502 503 /// Converts a path to absolute. 504 /// 505 /// \param original The path to convert; may already be absolute. 506 /// \param [out] output Pointer to a dynamically-allocated string that will hold 507 /// the absolute path, if all goes well. 508 /// 509 /// \return An error if there is not enough memory to fulfill the request; OK 510 /// otherwise. 511 kyua_error_t 512 kyua_fs_make_absolute(const char* original, char** const output) 513 { 514 if (original[0] == '/') { 515 *output = (char*)malloc(strlen(original) + 1); 516 if (output == NULL) 517 return kyua_oom_error_new(); 518 strcpy(*output, original); 519 return kyua_error_ok(); 520 } else { 521 char* current_path= NULL; /* LSC: needed when compiling in -O3 */ 522 kyua_error_t error; 523 524 error = kyua_fs_current_path(¤t_path); 525 if (kyua_error_is_set(error)) 526 return error; 527 528 error = kyua_fs_concat(output, current_path, original, NULL); 529 free(current_path); 530 return error; 531 } 532 } 533 534 535 /// Unmounts a file system. 536 /// 537 /// \param mount_point The file system to unmount. 538 /// 539 /// \return An error object. 540 kyua_error_t 541 kyua_fs_unmount(const char* mount_point) 542 { 543 kyua_error_t error; 544 545 // FreeBSD's unmount(2) requires paths to be absolute. To err on the side 546 // of caution, let's make it absolute in all cases. 547 char* abs_mount_point; 548 error = kyua_fs_make_absolute(mount_point, &abs_mount_point); 549 if (kyua_error_is_set(error)) 550 goto out; 551 552 static const int unmount_retries = 3; 553 static const int unmount_retry_delay_seconds = 1; 554 555 int retries = unmount_retries; 556 retry: 557 if (have_unmount2) { 558 error = unmount_with_unmount2(abs_mount_point); 559 } else { 560 error = unmount_with_umount8(abs_mount_point); 561 } 562 if (kyua_error_is_set(error)) { 563 assert(kyua_error_is_type(error, "libc")); 564 if (kyua_libc_error_errno(error) == EBUSY && retries > 0) { 565 kyua_error_warn(error, "%s busy; unmount retries left %d", 566 abs_mount_point, retries); 567 kyua_error_free(error); 568 retries--; 569 sleep(unmount_retry_delay_seconds); 570 goto retry; 571 } 572 } 573 574 out: 575 free(abs_mount_point); 576 return error; 577 } 578