1 /* Copyright (c) 2007 The NetBSD Foundation, 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 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND 14 * CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, 15 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 16 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 17 * IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY 18 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE 20 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER 22 * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 23 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN 24 * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ 25 26 #include "atf-c/detail/fs.h" 27 28 #if defined(HAVE_CONFIG_H) 29 #include "config.h" 30 #endif 31 32 #include <sys/types.h> 33 #include <sys/param.h> 34 #include <sys/mount.h> 35 #include <sys/stat.h> 36 #include <sys/wait.h> 37 38 #include <dirent.h> 39 #include <errno.h> 40 #include <libgen.h> 41 #include <stdarg.h> 42 #include <stdio.h> 43 #include <stdlib.h> 44 #include <string.h> 45 #include <unistd.h> 46 47 #include "atf-c/defs.h" 48 #include "atf-c/detail/sanity.h" 49 #include "atf-c/detail/text.h" 50 #include "atf-c/detail/user.h" 51 #include "atf-c/error.h" 52 53 /* --------------------------------------------------------------------- 54 * Prototypes for auxiliary functions. 55 * --------------------------------------------------------------------- */ 56 57 static atf_error_t copy_contents(const atf_fs_path_t *, char **); 58 static atf_error_t do_mkdtemp(char *); 59 static atf_error_t normalize(atf_dynstr_t *, char *); 60 static atf_error_t normalize_ap(atf_dynstr_t *, const char *, va_list); 61 static void replace_contents(atf_fs_path_t *, const char *); 62 static const char *stat_type_to_string(const int); 63 64 /* --------------------------------------------------------------------- 65 * The "unknown_file_type" error type. 66 * --------------------------------------------------------------------- */ 67 68 struct unknown_type_error_data { 69 const char *m_path; 70 int m_type; 71 }; 72 typedef struct unknown_type_error_data unknown_type_error_data_t; 73 74 static 75 void 76 unknown_type_format(const atf_error_t err, char *buf, size_t buflen) 77 { 78 const unknown_type_error_data_t *data; 79 80 PRE(atf_error_is(err, "unknown_type")); 81 82 data = atf_error_data(err); 83 snprintf(buf, buflen, "Unknown file type %d of %s", data->m_type, 84 data->m_path); 85 } 86 87 static 88 atf_error_t 89 unknown_type_error(const char *path, int type) 90 { 91 atf_error_t err; 92 unknown_type_error_data_t data; 93 94 data.m_path = path; 95 data.m_type = type; 96 97 err = atf_error_new("unknown_type", &data, sizeof(data), 98 unknown_type_format); 99 100 return err; 101 } 102 103 /* --------------------------------------------------------------------- 104 * Auxiliary functions. 105 * --------------------------------------------------------------------- */ 106 107 static 108 atf_error_t 109 copy_contents(const atf_fs_path_t *p, char **buf) 110 { 111 atf_error_t err; 112 char *str; 113 114 str = (char *)malloc(atf_dynstr_length(&p->m_data) + 1); 115 if (str == NULL) 116 err = atf_no_memory_error(); 117 else { 118 strcpy(str, atf_dynstr_cstring(&p->m_data)); 119 *buf = str; 120 err = atf_no_error(); 121 } 122 123 return err; 124 } 125 126 static 127 atf_error_t 128 do_mkdtemp(char *tmpl) 129 { 130 atf_error_t err; 131 132 PRE(strstr(tmpl, "XXXXXX") != NULL); 133 134 if (mkdtemp(tmpl) == NULL) 135 err = atf_libc_error(errno, "Cannot create temporary directory " 136 "with template '%s'", tmpl); 137 else 138 err = atf_no_error(); 139 140 return err; 141 } 142 143 static 144 atf_error_t 145 do_mkstemp(char *tmpl, int *fdout) 146 { 147 atf_error_t err; 148 149 PRE(strstr(tmpl, "XXXXXX") != NULL); 150 151 *fdout = mkstemp(tmpl); 152 if (*fdout == -1) 153 err = atf_libc_error(errno, "Cannot create temporary file " 154 "with template '%s'", tmpl); 155 156 else 157 err = atf_no_error(); 158 159 return err; 160 } 161 162 static 163 atf_error_t 164 normalize(atf_dynstr_t *d, char *p) 165 { 166 const char *ptr; 167 char *last; 168 atf_error_t err; 169 bool first; 170 171 PRE(strlen(p) > 0); 172 PRE(atf_dynstr_length(d) == 0); 173 174 if (p[0] == '/') 175 err = atf_dynstr_append_fmt(d, "/"); 176 else 177 err = atf_no_error(); 178 179 first = true; 180 last = NULL; /* Silence GCC warning. */ 181 ptr = strtok_r(p, "/", &last); 182 while (!atf_is_error(err) && ptr != NULL) { 183 if (strlen(ptr) > 0) { 184 err = atf_dynstr_append_fmt(d, "%s%s", first ? "" : "/", ptr); 185 first = false; 186 } 187 188 ptr = strtok_r(NULL, "/", &last); 189 } 190 191 return err; 192 } 193 194 static 195 atf_error_t 196 normalize_ap(atf_dynstr_t *d, const char *p, va_list ap) 197 { 198 char *str; 199 atf_error_t err; 200 va_list ap2; 201 202 err = atf_dynstr_init(d); 203 if (atf_is_error(err)) 204 goto out; 205 206 va_copy(ap2, ap); 207 err = atf_text_format_ap(&str, p, ap2); 208 va_end(ap2); 209 if (atf_is_error(err)) 210 atf_dynstr_fini(d); 211 else { 212 err = normalize(d, str); 213 free(str); 214 } 215 216 out: 217 return err; 218 } 219 220 static 221 void 222 replace_contents(atf_fs_path_t *p, const char *buf) 223 { 224 atf_error_t err; 225 226 PRE(atf_dynstr_length(&p->m_data) == strlen(buf)); 227 228 atf_dynstr_clear(&p->m_data); 229 err = atf_dynstr_append_fmt(&p->m_data, "%s", buf); 230 231 INV(!atf_is_error(err)); 232 } 233 234 static 235 const char * 236 stat_type_to_string(const int type) 237 { 238 const char *str; 239 240 if (type == atf_fs_stat_blk_type) 241 str = "block device"; 242 else if (type == atf_fs_stat_chr_type) 243 str = "character device"; 244 else if (type == atf_fs_stat_dir_type) 245 str = "directory"; 246 else if (type == atf_fs_stat_fifo_type) 247 str = "named pipe"; 248 else if (type == atf_fs_stat_lnk_type) 249 str = "symbolic link"; 250 else if (type == atf_fs_stat_reg_type) 251 str = "regular file"; 252 else if (type == atf_fs_stat_sock_type) 253 str = "socket"; 254 else if (type == atf_fs_stat_wht_type) 255 str = "whiteout"; 256 else { 257 UNREACHABLE; 258 str = NULL; 259 } 260 261 return str; 262 } 263 264 /* --------------------------------------------------------------------- 265 * The "atf_fs_path" type. 266 * --------------------------------------------------------------------- */ 267 268 /* 269 * Constructors/destructors. 270 */ 271 272 atf_error_t 273 atf_fs_path_init_ap(atf_fs_path_t *p, const char *fmt, va_list ap) 274 { 275 atf_error_t err; 276 va_list ap2; 277 278 va_copy(ap2, ap); 279 err = normalize_ap(&p->m_data, fmt, ap2); 280 va_end(ap2); 281 282 return err; 283 } 284 285 atf_error_t 286 atf_fs_path_init_fmt(atf_fs_path_t *p, const char *fmt, ...) 287 { 288 va_list ap; 289 atf_error_t err; 290 291 va_start(ap, fmt); 292 err = atf_fs_path_init_ap(p, fmt, ap); 293 va_end(ap); 294 295 return err; 296 } 297 298 atf_error_t 299 atf_fs_path_copy(atf_fs_path_t *dest, const atf_fs_path_t *src) 300 { 301 return atf_dynstr_copy(&dest->m_data, &src->m_data); 302 } 303 304 void 305 atf_fs_path_fini(atf_fs_path_t *p) 306 { 307 atf_dynstr_fini(&p->m_data); 308 } 309 310 /* 311 * Getters. 312 */ 313 314 atf_error_t 315 atf_fs_path_branch_path(const atf_fs_path_t *p, atf_fs_path_t *bp) 316 { 317 const size_t endpos = atf_dynstr_rfind_ch(&p->m_data, '/'); 318 atf_error_t err; 319 320 if (endpos == atf_dynstr_npos) 321 err = atf_fs_path_init_fmt(bp, "."); 322 else if (endpos == 0) 323 err = atf_fs_path_init_fmt(bp, "/"); 324 else 325 err = atf_dynstr_init_substr(&bp->m_data, &p->m_data, 0, endpos); 326 327 #if defined(HAVE_CONST_DIRNAME) 328 INV(atf_equal_dynstr_cstring(&bp->m_data, 329 dirname(atf_dynstr_cstring(&p->m_data)))); 330 #endif /* defined(HAVE_CONST_DIRNAME) */ 331 332 return err; 333 } 334 335 const char * 336 atf_fs_path_cstring(const atf_fs_path_t *p) 337 { 338 return atf_dynstr_cstring(&p->m_data); 339 } 340 341 atf_error_t 342 atf_fs_path_leaf_name(const atf_fs_path_t *p, atf_dynstr_t *ln) 343 { 344 size_t begpos = atf_dynstr_rfind_ch(&p->m_data, '/'); 345 atf_error_t err; 346 347 if (begpos == atf_dynstr_npos) 348 begpos = 0; 349 else 350 begpos++; 351 352 err = atf_dynstr_init_substr(ln, &p->m_data, begpos, atf_dynstr_npos); 353 354 #if defined(HAVE_CONST_BASENAME) 355 INV(atf_equal_dynstr_cstring(ln, 356 basename(atf_dynstr_cstring(&p->m_data)))); 357 #endif /* defined(HAVE_CONST_BASENAME) */ 358 359 return err; 360 } 361 362 bool 363 atf_fs_path_is_absolute(const atf_fs_path_t *p) 364 { 365 return atf_dynstr_cstring(&p->m_data)[0] == '/'; 366 } 367 368 bool 369 atf_fs_path_is_root(const atf_fs_path_t *p) 370 { 371 return atf_equal_dynstr_cstring(&p->m_data, "/"); 372 } 373 374 /* 375 * Modifiers. 376 */ 377 378 atf_error_t 379 atf_fs_path_append_ap(atf_fs_path_t *p, const char *fmt, va_list ap) 380 { 381 atf_dynstr_t aux; 382 atf_error_t err; 383 va_list ap2; 384 385 va_copy(ap2, ap); 386 err = normalize_ap(&aux, fmt, ap2); 387 va_end(ap2); 388 if (!atf_is_error(err)) { 389 const char *auxstr = atf_dynstr_cstring(&aux); 390 const bool needslash = auxstr[0] != '/'; 391 392 err = atf_dynstr_append_fmt(&p->m_data, "%s%s", 393 needslash ? "/" : "", auxstr); 394 395 atf_dynstr_fini(&aux); 396 } 397 398 return err; 399 } 400 401 atf_error_t 402 atf_fs_path_append_fmt(atf_fs_path_t *p, const char *fmt, ...) 403 { 404 va_list ap; 405 atf_error_t err; 406 407 va_start(ap, fmt); 408 err = atf_fs_path_append_ap(p, fmt, ap); 409 va_end(ap); 410 411 return err; 412 } 413 414 atf_error_t 415 atf_fs_path_append_path(atf_fs_path_t *p, const atf_fs_path_t *p2) 416 { 417 return atf_fs_path_append_fmt(p, "%s", atf_dynstr_cstring(&p2->m_data)); 418 } 419 420 atf_error_t 421 atf_fs_path_to_absolute(const atf_fs_path_t *p, atf_fs_path_t *pa) 422 { 423 atf_error_t err; 424 425 PRE(!atf_fs_path_is_absolute(p)); 426 427 err = atf_fs_getcwd(pa); 428 if (atf_is_error(err)) 429 goto out; 430 431 err = atf_fs_path_append_path(pa, p); 432 if (atf_is_error(err)) 433 atf_fs_path_fini(pa); 434 435 out: 436 return err; 437 } 438 439 /* 440 * Operators. 441 */ 442 443 bool atf_equal_fs_path_fs_path(const atf_fs_path_t *p1, 444 const atf_fs_path_t *p2) 445 { 446 return atf_equal_dynstr_dynstr(&p1->m_data, &p2->m_data); 447 } 448 449 /* --------------------------------------------------------------------- 450 * The "atf_fs_path" type. 451 * --------------------------------------------------------------------- */ 452 453 /* 454 * Constants. 455 */ 456 457 const int atf_fs_stat_blk_type = 1; 458 const int atf_fs_stat_chr_type = 2; 459 const int atf_fs_stat_dir_type = 3; 460 const int atf_fs_stat_fifo_type = 4; 461 const int atf_fs_stat_lnk_type = 5; 462 const int atf_fs_stat_reg_type = 6; 463 const int atf_fs_stat_sock_type = 7; 464 const int atf_fs_stat_wht_type = 8; 465 466 /* 467 * Constructors/destructors. 468 */ 469 470 atf_error_t 471 atf_fs_stat_init(atf_fs_stat_t *st, const atf_fs_path_t *p) 472 { 473 atf_error_t err; 474 const char *pstr = atf_fs_path_cstring(p); 475 476 if (lstat(pstr, &st->m_sb) == -1) { 477 err = atf_libc_error(errno, "Cannot get information of %s; " 478 "lstat(2) failed", pstr); 479 } else { 480 int type = st->m_sb.st_mode & S_IFMT; 481 err = atf_no_error(); 482 switch (type) { 483 case S_IFBLK: st->m_type = atf_fs_stat_blk_type; break; 484 case S_IFCHR: st->m_type = atf_fs_stat_chr_type; break; 485 case S_IFDIR: st->m_type = atf_fs_stat_dir_type; break; 486 case S_IFIFO: st->m_type = atf_fs_stat_fifo_type; break; 487 case S_IFLNK: st->m_type = atf_fs_stat_lnk_type; break; 488 case S_IFREG: st->m_type = atf_fs_stat_reg_type; break; 489 case S_IFSOCK: st->m_type = atf_fs_stat_sock_type; break; 490 #if defined(S_IFWHT) 491 case S_IFWHT: st->m_type = atf_fs_stat_wht_type; break; 492 #endif 493 default: 494 err = unknown_type_error(pstr, type); 495 } 496 } 497 498 return err; 499 } 500 501 void 502 atf_fs_stat_copy(atf_fs_stat_t *dest, const atf_fs_stat_t *src) 503 { 504 dest->m_type = src->m_type; 505 dest->m_sb = src->m_sb; 506 } 507 508 void 509 atf_fs_stat_fini(atf_fs_stat_t *st ATF_DEFS_ATTRIBUTE_UNUSED) 510 { 511 } 512 513 /* 514 * Getters. 515 */ 516 517 dev_t 518 atf_fs_stat_get_device(const atf_fs_stat_t *st) 519 { 520 return st->m_sb.st_dev; 521 } 522 523 ino_t 524 atf_fs_stat_get_inode(const atf_fs_stat_t *st) 525 { 526 return st->m_sb.st_ino; 527 } 528 529 mode_t 530 atf_fs_stat_get_mode(const atf_fs_stat_t *st) 531 { 532 return st->m_sb.st_mode & ~S_IFMT; 533 } 534 535 off_t 536 atf_fs_stat_get_size(const atf_fs_stat_t *st) 537 { 538 return st->m_sb.st_size; 539 } 540 541 int 542 atf_fs_stat_get_type(const atf_fs_stat_t *st) 543 { 544 return st->m_type; 545 } 546 547 bool 548 atf_fs_stat_is_owner_readable(const atf_fs_stat_t *st) 549 { 550 return st->m_sb.st_mode & S_IRUSR; 551 } 552 553 bool 554 atf_fs_stat_is_owner_writable(const atf_fs_stat_t *st) 555 { 556 return st->m_sb.st_mode & S_IWUSR; 557 } 558 559 bool 560 atf_fs_stat_is_owner_executable(const atf_fs_stat_t *st) 561 { 562 return st->m_sb.st_mode & S_IXUSR; 563 } 564 565 bool 566 atf_fs_stat_is_group_readable(const atf_fs_stat_t *st) 567 { 568 return st->m_sb.st_mode & S_IRGRP; 569 } 570 571 bool 572 atf_fs_stat_is_group_writable(const atf_fs_stat_t *st) 573 { 574 return st->m_sb.st_mode & S_IWGRP; 575 } 576 577 bool 578 atf_fs_stat_is_group_executable(const atf_fs_stat_t *st) 579 { 580 return st->m_sb.st_mode & S_IXGRP; 581 } 582 583 bool 584 atf_fs_stat_is_other_readable(const atf_fs_stat_t *st) 585 { 586 return st->m_sb.st_mode & S_IROTH; 587 } 588 589 bool 590 atf_fs_stat_is_other_writable(const atf_fs_stat_t *st) 591 { 592 return st->m_sb.st_mode & S_IWOTH; 593 } 594 595 bool 596 atf_fs_stat_is_other_executable(const atf_fs_stat_t *st) 597 { 598 return st->m_sb.st_mode & S_IXOTH; 599 } 600 601 /* --------------------------------------------------------------------- 602 * Free functions. 603 * --------------------------------------------------------------------- */ 604 605 const int atf_fs_access_f = 1 << 0; 606 const int atf_fs_access_r = 1 << 1; 607 const int atf_fs_access_w = 1 << 2; 608 const int atf_fs_access_x = 1 << 3; 609 610 /* 611 * An implementation of access(2) but using the effective user value 612 * instead of the real one. Also avoids false positives for root when 613 * asking for execute permissions, which appear in SunOS. 614 */ 615 atf_error_t 616 atf_fs_eaccess(const atf_fs_path_t *p, int mode) 617 { 618 atf_error_t err; 619 struct stat st; 620 bool ok; 621 622 PRE(mode & atf_fs_access_f || mode & atf_fs_access_r || 623 mode & atf_fs_access_w || mode & atf_fs_access_x); 624 625 if (lstat(atf_fs_path_cstring(p), &st) == -1) { 626 err = atf_libc_error(errno, "Cannot get information from file %s", 627 atf_fs_path_cstring(p)); 628 goto out; 629 } 630 631 err = atf_no_error(); 632 633 /* Early return if we are only checking for existence and the file 634 * exists (stat call returned). */ 635 if (mode & atf_fs_access_f) 636 goto out; 637 638 ok = false; 639 if (atf_user_is_root()) { 640 if (!ok && !(mode & atf_fs_access_x)) { 641 /* Allow root to read/write any file. */ 642 ok = true; 643 } 644 645 if (!ok && (st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) { 646 /* Allow root to execute the file if any of its execution bits 647 * are set. */ 648 ok = true; 649 } 650 } else { 651 if (!ok && (atf_user_euid() == st.st_uid)) { 652 ok = ((mode & atf_fs_access_r) && (st.st_mode & S_IRUSR)) || 653 ((mode & atf_fs_access_w) && (st.st_mode & S_IWUSR)) || 654 ((mode & atf_fs_access_x) && (st.st_mode & S_IXUSR)); 655 } 656 if (!ok && atf_user_is_member_of_group(st.st_gid)) { 657 ok = ((mode & atf_fs_access_r) && (st.st_mode & S_IRGRP)) || 658 ((mode & atf_fs_access_w) && (st.st_mode & S_IWGRP)) || 659 ((mode & atf_fs_access_x) && (st.st_mode & S_IXGRP)); 660 } 661 if (!ok && ((atf_user_euid() != st.st_uid) && 662 !atf_user_is_member_of_group(st.st_gid))) { 663 ok = ((mode & atf_fs_access_r) && (st.st_mode & S_IROTH)) || 664 ((mode & atf_fs_access_w) && (st.st_mode & S_IWOTH)) || 665 ((mode & atf_fs_access_x) && (st.st_mode & S_IXOTH)); 666 } 667 } 668 669 if (!ok) 670 err = atf_libc_error(EACCES, "Access check failed"); 671 672 out: 673 return err; 674 } 675 676 atf_error_t 677 atf_fs_exists(const atf_fs_path_t *p, bool *b) 678 { 679 atf_error_t err; 680 681 err = atf_fs_eaccess(p, atf_fs_access_f); 682 if (atf_is_error(err)) { 683 if (atf_error_is(err, "libc") && atf_libc_error_code(err) == ENOENT) { 684 atf_error_free(err); 685 err = atf_no_error(); 686 *b = false; 687 } 688 } else 689 *b = true; 690 691 return err; 692 } 693 694 atf_error_t 695 atf_fs_getcwd(atf_fs_path_t *p) 696 { 697 atf_error_t err; 698 char *cwd; 699 700 #if defined(HAVE_GETCWD_DYN) 701 cwd = getcwd(NULL, 0); 702 #else 703 cwd = getcwd(NULL, MAXPATHLEN); 704 #endif 705 if (cwd == NULL) { 706 err = atf_libc_error(errno, "Cannot determine current directory"); 707 goto out; 708 } 709 710 err = atf_fs_path_init_fmt(p, "%s", cwd); 711 free(cwd); 712 713 out: 714 return err; 715 } 716 717 atf_error_t 718 atf_fs_mkdtemp(atf_fs_path_t *p) 719 { 720 atf_error_t err; 721 char *buf; 722 mode_t mask; 723 724 mask = umask(0); 725 umask(mask & 077); 726 727 err = copy_contents(p, &buf); 728 if (atf_is_error(err)) 729 goto out; 730 731 err = do_mkdtemp(buf); 732 if (atf_is_error(err)) 733 goto out_buf; 734 735 replace_contents(p, buf); 736 737 INV(!atf_is_error(err)); 738 out_buf: 739 free(buf); 740 out: 741 umask(mask); 742 return err; 743 } 744 745 atf_error_t 746 atf_fs_mkstemp(atf_fs_path_t *p, int *fdout) 747 { 748 atf_error_t err; 749 char *buf; 750 int fd; 751 mode_t mask; 752 753 mask = umask(0); 754 umask(mask & 077); 755 756 err = copy_contents(p, &buf); 757 if (atf_is_error(err)) 758 goto out; 759 760 err = do_mkstemp(buf, &fd); 761 if (atf_is_error(err)) 762 goto out_buf; 763 764 replace_contents(p, buf); 765 *fdout = fd; 766 767 INV(!atf_is_error(err)); 768 out_buf: 769 free(buf); 770 out: 771 umask(mask); 772 return err; 773 } 774 775 atf_error_t 776 atf_fs_rmdir(const atf_fs_path_t *p) 777 { 778 atf_error_t err; 779 780 if (rmdir(atf_fs_path_cstring(p))) { 781 if (errno == EEXIST) { 782 /* Some operating systems (e.g. OpenSolaris 200906) return 783 * EEXIST instead of ENOTEMPTY for non-empty directories. 784 * Homogenize the return value so that callers don't need 785 * to bother about differences in operating systems. */ 786 errno = ENOTEMPTY; 787 } 788 err = atf_libc_error(errno, "Cannot remove directory"); 789 } else 790 err = atf_no_error(); 791 792 return err; 793 } 794 795 atf_error_t 796 atf_fs_unlink(const atf_fs_path_t *p) 797 { 798 atf_error_t err; 799 const char *path; 800 801 path = atf_fs_path_cstring(p); 802 803 if (unlink(path) != 0) 804 err = atf_libc_error(errno, "Cannot unlink file: '%s'", path); 805 else 806 err = atf_no_error(); 807 808 return err; 809 } 810