Serenity Operating System
at master 632 lines 19 kB view raw
1/* 2 * Copyright (c) 2018-2022, Andreas Kling <kling@serenityos.org> 3 * 4 * SPDX-License-Identifier: BSD-2-Clause 5 */ 6 7#include <AK/LexicalPath.h> 8#include <AK/Platform.h> 9#include <AK/ScopeGuard.h> 10#include <LibCore/DeprecatedFile.h> 11#include <LibCore/DirIterator.h> 12#include <LibCore/System.h> 13#include <errno.h> 14#include <fcntl.h> 15#include <libgen.h> 16#include <stdio.h> 17#include <string.h> 18#include <sys/stat.h> 19#include <unistd.h> 20#include <utime.h> 21 22#ifdef AK_OS_SERENITY 23# include <serenity.h> 24#endif 25 26// On Linux distros that use glibc `basename` is defined as a macro that expands to `__xpg_basename`, so we undefine it 27#if defined(AK_OS_LINUX) && defined(basename) 28# undef basename 29#endif 30 31namespace Core { 32 33ErrorOr<NonnullRefPtr<DeprecatedFile>> DeprecatedFile::open(DeprecatedString filename, OpenMode mode, mode_t permissions) 34{ 35 auto file = DeprecatedFile::construct(move(filename)); 36 if (!file->open_impl(mode, permissions)) 37 return Error::from_errno(file->error()); 38 return file; 39} 40 41DeprecatedFile::DeprecatedFile(DeprecatedString filename, Object* parent) 42 : IODevice(parent) 43 , m_filename(move(filename)) 44{ 45} 46 47DeprecatedFile::~DeprecatedFile() 48{ 49 if (m_should_close_file_descriptor == ShouldCloseFileDescriptor::Yes && mode() != OpenMode::NotOpen) 50 close(); 51} 52 53bool DeprecatedFile::open(int fd, OpenMode mode, ShouldCloseFileDescriptor should_close) 54{ 55 set_fd(fd); 56 set_mode(mode); 57 m_should_close_file_descriptor = should_close; 58 return true; 59} 60 61bool DeprecatedFile::open(OpenMode mode) 62{ 63 return open_impl(mode, 0666); 64} 65 66bool DeprecatedFile::open_impl(OpenMode mode, mode_t permissions) 67{ 68 VERIFY(!m_filename.is_null()); 69 int flags = 0; 70 if (has_flag(mode, OpenMode::ReadOnly) && has_flag(mode, OpenMode::WriteOnly)) { 71 flags |= O_RDWR | O_CREAT; 72 } else if (has_flag(mode, OpenMode::ReadOnly)) { 73 flags |= O_RDONLY; 74 } else if (has_flag(mode, OpenMode::WriteOnly)) { 75 flags |= O_WRONLY | O_CREAT; 76 bool should_truncate = !(has_flag(mode, OpenMode::Append) || has_flag(mode, OpenMode::MustBeNew)); 77 if (should_truncate) 78 flags |= O_TRUNC; 79 } 80 if (has_flag(mode, OpenMode::Append)) 81 flags |= O_APPEND; 82 if (has_flag(mode, OpenMode::Truncate)) 83 flags |= O_TRUNC; 84 if (has_flag(mode, OpenMode::MustBeNew)) 85 flags |= O_EXCL; 86 if (!has_flag(mode, OpenMode::KeepOnExec)) 87 flags |= O_CLOEXEC; 88 int fd = ::open(m_filename.characters(), flags, permissions); 89 if (fd < 0) { 90 set_error(errno); 91 return false; 92 } 93 94 set_fd(fd); 95 set_mode(mode); 96 return true; 97} 98 99int DeprecatedFile::leak_fd() 100{ 101 m_should_close_file_descriptor = ShouldCloseFileDescriptor::No; 102 return fd(); 103} 104 105bool DeprecatedFile::is_device() const 106{ 107 return is_device(fd()); 108} 109 110bool DeprecatedFile::is_device(DeprecatedString const& filename) 111{ 112 struct stat st; 113 if (stat(filename.characters(), &st) < 0) 114 return false; 115 return S_ISBLK(st.st_mode) || S_ISCHR(st.st_mode); 116} 117 118bool DeprecatedFile::is_device(int fd) 119{ 120 struct stat st; 121 if (fstat(fd, &st) < 0) 122 return false; 123 return S_ISBLK(st.st_mode) || S_ISCHR(st.st_mode); 124} 125 126bool DeprecatedFile::is_block_device() const 127{ 128 struct stat stat; 129 if (fstat(fd(), &stat) < 0) 130 return false; 131 return S_ISBLK(stat.st_mode); 132} 133 134bool DeprecatedFile::is_block_device(DeprecatedString const& filename) 135{ 136 struct stat st; 137 if (stat(filename.characters(), &st) < 0) 138 return false; 139 return S_ISBLK(st.st_mode); 140} 141 142bool DeprecatedFile::is_char_device() const 143{ 144 struct stat stat; 145 if (fstat(fd(), &stat) < 0) 146 return false; 147 return S_ISCHR(stat.st_mode); 148} 149 150bool DeprecatedFile::is_char_device(DeprecatedString const& filename) 151{ 152 struct stat st; 153 if (stat(filename.characters(), &st) < 0) 154 return false; 155 return S_ISCHR(st.st_mode); 156} 157 158bool DeprecatedFile::is_directory() const 159{ 160 return is_directory(fd()); 161} 162 163bool DeprecatedFile::is_directory(DeprecatedString const& filename) 164{ 165 struct stat st; 166 if (stat(filename.characters(), &st) < 0) 167 return false; 168 return S_ISDIR(st.st_mode); 169} 170 171bool DeprecatedFile::is_directory(int fd) 172{ 173 struct stat st; 174 if (fstat(fd, &st) < 0) 175 return false; 176 return S_ISDIR(st.st_mode); 177} 178 179bool DeprecatedFile::is_link() const 180{ 181 struct stat stat; 182 if (fstat(fd(), &stat) < 0) 183 return false; 184 return S_ISLNK(stat.st_mode); 185} 186 187bool DeprecatedFile::is_link(DeprecatedString const& filename) 188{ 189 struct stat st; 190 if (lstat(filename.characters(), &st) < 0) 191 return false; 192 return S_ISLNK(st.st_mode); 193} 194 195bool DeprecatedFile::looks_like_shared_library() const 196{ 197 return DeprecatedFile::looks_like_shared_library(m_filename); 198} 199 200bool DeprecatedFile::looks_like_shared_library(DeprecatedString const& filename) 201{ 202 return filename.ends_with(".so"sv) || filename.contains(".so."sv); 203} 204 205bool DeprecatedFile::can_delete_or_move(StringView path) 206{ 207 VERIFY(!path.is_empty()); 208 auto directory = LexicalPath::dirname(path); 209 auto directory_has_write_access = !Core::System::access(directory, W_OK).is_error(); 210 if (!directory_has_write_access) 211 return false; 212 213 auto stat_or_empty = [](StringView path) { 214 auto stat_or_error = Core::System::stat(path); 215 if (stat_or_error.is_error()) { 216 struct stat stat { }; 217 return stat; 218 } 219 return stat_or_error.release_value(); 220 }; 221 222 auto directory_stat = stat_or_empty(directory); 223 bool is_directory_sticky = directory_stat.st_mode & S_ISVTX; 224 if (!is_directory_sticky) 225 return true; 226 227 // Directory is sticky, only the file owner, directory owner, and root can modify (rename, remove) it. 228 auto user_id = geteuid(); 229 return user_id == 0 || directory_stat.st_uid == user_id || stat_or_empty(path).st_uid == user_id; 230} 231 232bool DeprecatedFile::exists(StringView filename) 233{ 234 return !Core::System::stat(filename).is_error(); 235} 236 237ErrorOr<size_t> DeprecatedFile::size(DeprecatedString const& filename) 238{ 239 struct stat st; 240 if (stat(filename.characters(), &st) < 0) 241 return Error::from_errno(errno); 242 return st.st_size; 243} 244 245DeprecatedString DeprecatedFile::real_path_for(DeprecatedString const& filename) 246{ 247 if (filename.is_null()) 248 return {}; 249 auto* path = realpath(filename.characters(), nullptr); 250 DeprecatedString real_path(path); 251 free(path); 252 return real_path; 253} 254 255DeprecatedString DeprecatedFile::current_working_directory() 256{ 257 char* cwd = getcwd(nullptr, 0); 258 if (!cwd) { 259 perror("getcwd"); 260 return {}; 261 } 262 263 auto cwd_as_string = DeprecatedString(cwd); 264 free(cwd); 265 266 return cwd_as_string; 267} 268 269DeprecatedString DeprecatedFile::absolute_path(DeprecatedString const& path) 270{ 271 if (DeprecatedFile::exists(path)) 272 return DeprecatedFile::real_path_for(path); 273 274 if (path.starts_with("/"sv)) 275 return LexicalPath::canonicalized_path(path); 276 277 auto working_directory = DeprecatedFile::current_working_directory(); 278 auto full_path = LexicalPath::join(working_directory, path); 279 280 return LexicalPath::canonicalized_path(full_path.string()); 281} 282 283#ifdef AK_OS_SERENITY 284 285ErrorOr<DeprecatedString> DeprecatedFile::read_link(DeprecatedString const& link_path) 286{ 287 // First, try using a 64-byte buffer, that ought to be enough for anybody. 288 char small_buffer[64]; 289 290 int rc = serenity_readlink(link_path.characters(), link_path.length(), small_buffer, sizeof(small_buffer)); 291 if (rc < 0) 292 return Error::from_errno(errno); 293 294 size_t size = rc; 295 // If the call was successful, the syscall (unlike the LibC wrapper) 296 // returns the full size of the link. Let's see if our small buffer 297 // was enough to read the whole link. 298 if (size <= sizeof(small_buffer)) 299 return DeprecatedString { small_buffer, size }; 300 // Nope, but at least now we know the right size. 301 char* large_buffer_ptr; 302 auto large_buffer = StringImpl::create_uninitialized(size, large_buffer_ptr); 303 304 rc = serenity_readlink(link_path.characters(), link_path.length(), large_buffer_ptr, size); 305 if (rc < 0) 306 return Error::from_errno(errno); 307 308 size_t new_size = rc; 309 if (new_size == size) 310 return { *large_buffer }; 311 312 // If we're here, the symlink has changed while we were looking at it. 313 // If it became shorter, our buffer is valid, we just have to trim it a bit. 314 if (new_size < size) 315 return DeprecatedString { large_buffer_ptr, new_size }; 316 // Otherwise, here's not much we can do, unless we want to loop endlessly 317 // in this case. Let's leave it up to the caller whether to loop. 318 errno = EAGAIN; 319 return Error::from_errno(errno); 320} 321 322#else 323 324// This is a sad version for other systems. It has to always make a copy of the 325// link path, and to always make two syscalls to get the right size first. 326ErrorOr<DeprecatedString> DeprecatedFile::read_link(DeprecatedString const& link_path) 327{ 328 struct stat statbuf = {}; 329 int rc = lstat(link_path.characters(), &statbuf); 330 if (rc < 0) 331 return Error::from_errno(errno); 332 char* buffer_ptr; 333 auto buffer = StringImpl::create_uninitialized(statbuf.st_size, buffer_ptr); 334 if (readlink(link_path.characters(), buffer_ptr, statbuf.st_size) < 0) 335 return Error::from_errno(errno); 336 // (See above.) 337 if (rc == statbuf.st_size) 338 return { *buffer }; 339 return DeprecatedString { buffer_ptr, (size_t)rc }; 340} 341 342#endif 343 344static RefPtr<DeprecatedFile> stdin_file; 345static RefPtr<DeprecatedFile> stdout_file; 346static RefPtr<DeprecatedFile> stderr_file; 347 348NonnullRefPtr<DeprecatedFile> DeprecatedFile::standard_input() 349{ 350 if (!stdin_file) { 351 stdin_file = DeprecatedFile::construct(); 352 stdin_file->open(STDIN_FILENO, OpenMode::ReadOnly, ShouldCloseFileDescriptor::No); 353 } 354 return *stdin_file; 355} 356 357NonnullRefPtr<DeprecatedFile> DeprecatedFile::standard_output() 358{ 359 if (!stdout_file) { 360 stdout_file = DeprecatedFile::construct(); 361 stdout_file->open(STDOUT_FILENO, OpenMode::WriteOnly, ShouldCloseFileDescriptor::No); 362 } 363 return *stdout_file; 364} 365 366NonnullRefPtr<DeprecatedFile> DeprecatedFile::standard_error() 367{ 368 if (!stderr_file) { 369 stderr_file = DeprecatedFile::construct(); 370 stderr_file->open(STDERR_FILENO, OpenMode::WriteOnly, ShouldCloseFileDescriptor::No); 371 } 372 return *stderr_file; 373} 374 375static DeprecatedString get_duplicate_name(DeprecatedString const& path, int duplicate_count) 376{ 377 if (duplicate_count == 0) { 378 return path; 379 } 380 LexicalPath lexical_path(path); 381 StringBuilder duplicated_name; 382 duplicated_name.append('/'); 383 auto& parts = lexical_path.parts_view(); 384 for (size_t i = 0; i < parts.size() - 1; ++i) { 385 duplicated_name.appendff("{}/", parts[i]); 386 } 387 auto prev_duplicate_tag = DeprecatedString::formatted("({})", duplicate_count); 388 auto title = lexical_path.title(); 389 if (title.ends_with(prev_duplicate_tag)) { 390 // remove the previous duplicate tag "(n)" so we can add a new tag. 391 title = title.substring_view(0, title.length() - prev_duplicate_tag.length()); 392 } 393 duplicated_name.appendff("{} ({})", title, duplicate_count); 394 if (!lexical_path.extension().is_empty()) { 395 duplicated_name.appendff(".{}", lexical_path.extension()); 396 } 397 return duplicated_name.to_deprecated_string(); 398} 399 400ErrorOr<void, DeprecatedFile::CopyError> DeprecatedFile::copy_file_or_directory(DeprecatedString const& dst_path, DeprecatedString const& src_path, RecursionMode recursion_mode, LinkMode link_mode, AddDuplicateFileMarker add_duplicate_file_marker, PreserveMode preserve_mode) 401{ 402 if (add_duplicate_file_marker == AddDuplicateFileMarker::Yes) { 403 int duplicate_count = 0; 404 while (access(get_duplicate_name(dst_path, duplicate_count).characters(), F_OK) == 0) { 405 ++duplicate_count; 406 } 407 if (duplicate_count != 0) { 408 return copy_file_or_directory(get_duplicate_name(dst_path, duplicate_count), src_path, RecursionMode::Allowed, LinkMode::Disallowed, AddDuplicateFileMarker::Yes, preserve_mode); 409 } 410 } 411 412 auto source_or_error = DeprecatedFile::open(src_path, OpenMode::ReadOnly); 413 if (source_or_error.is_error()) 414 return CopyError { errno, false }; 415 416 auto& source = *source_or_error.value(); 417 418 struct stat src_stat; 419 if (fstat(source.fd(), &src_stat) < 0) 420 return CopyError { errno, false }; 421 422 if (source.is_directory()) { 423 if (recursion_mode == RecursionMode::Disallowed) 424 return CopyError { errno, true }; 425 return copy_directory(dst_path, src_path, src_stat); 426 } 427 428 if (link_mode == LinkMode::Allowed) { 429 if (link(src_path.characters(), dst_path.characters()) < 0) 430 return CopyError { errno, false }; 431 432 return {}; 433 } 434 435 return copy_file(dst_path, src_stat, source, preserve_mode); 436} 437 438ErrorOr<void, DeprecatedFile::CopyError> DeprecatedFile::copy_file(DeprecatedString const& dst_path, struct stat const& src_stat, DeprecatedFile& source, PreserveMode preserve_mode) 439{ 440 int dst_fd = creat(dst_path.characters(), 0666); 441 if (dst_fd < 0) { 442 if (errno != EISDIR) 443 return CopyError { errno, false }; 444 445 auto dst_dir_path = DeprecatedString::formatted("{}/{}", dst_path, LexicalPath::basename(source.filename())); 446 dst_fd = creat(dst_dir_path.characters(), 0666); 447 if (dst_fd < 0) 448 return CopyError { errno, false }; 449 } 450 451 ScopeGuard close_fd_guard([dst_fd]() { ::close(dst_fd); }); 452 453 if (src_stat.st_size > 0) { 454 if (ftruncate(dst_fd, src_stat.st_size) < 0) 455 return CopyError { errno, false }; 456 } 457 458 for (;;) { 459 char buffer[32768]; 460 ssize_t nread = ::read(source.fd(), buffer, sizeof(buffer)); 461 if (nread < 0) { 462 return CopyError { errno, false }; 463 } 464 if (nread == 0) 465 break; 466 ssize_t remaining_to_write = nread; 467 char* bufptr = buffer; 468 while (remaining_to_write) { 469 ssize_t nwritten = ::write(dst_fd, bufptr, remaining_to_write); 470 if (nwritten < 0) 471 return CopyError { errno, false }; 472 473 VERIFY(nwritten > 0); 474 remaining_to_write -= nwritten; 475 bufptr += nwritten; 476 } 477 } 478 479 auto my_umask = umask(0); 480 umask(my_umask); 481 // NOTE: We don't copy the set-uid and set-gid bits unless requested. 482 if (!has_flag(preserve_mode, PreserveMode::Permissions)) 483 my_umask |= 06000; 484 485 if (fchmod(dst_fd, src_stat.st_mode & ~my_umask) < 0) 486 return CopyError { errno, false }; 487 488 if (has_flag(preserve_mode, PreserveMode::Ownership)) { 489 if (fchown(dst_fd, src_stat.st_uid, src_stat.st_gid) < 0) 490 return CopyError { errno, false }; 491 } 492 493 if (has_flag(preserve_mode, PreserveMode::Timestamps)) { 494 struct timespec times[2] = { 495#ifdef AK_OS_MACOS 496 src_stat.st_atimespec, 497 src_stat.st_mtimespec, 498#else 499 src_stat.st_atim, 500 src_stat.st_mtim, 501#endif 502 }; 503 if (utimensat(AT_FDCWD, dst_path.characters(), times, 0) < 0) 504 return CopyError { errno, false }; 505 } 506 507 return {}; 508} 509 510ErrorOr<void, DeprecatedFile::CopyError> DeprecatedFile::copy_directory(DeprecatedString const& dst_path, DeprecatedString const& src_path, struct stat const& src_stat, LinkMode link, PreserveMode preserve_mode) 511{ 512 if (mkdir(dst_path.characters(), 0755) < 0) 513 return CopyError { errno, false }; 514 515 DeprecatedString src_rp = DeprecatedFile::real_path_for(src_path); 516 src_rp = DeprecatedString::formatted("{}/", src_rp); 517 DeprecatedString dst_rp = DeprecatedFile::real_path_for(dst_path); 518 dst_rp = DeprecatedString::formatted("{}/", dst_rp); 519 520 if (!dst_rp.is_empty() && dst_rp.starts_with(src_rp)) 521 return CopyError { errno, false }; 522 523 DirIterator di(src_path, DirIterator::SkipParentAndBaseDir); 524 if (di.has_error()) 525 return CopyError { errno, false }; 526 527 while (di.has_next()) { 528 DeprecatedString filename = di.next_path(); 529 auto result = copy_file_or_directory( 530 DeprecatedString::formatted("{}/{}", dst_path, filename), 531 DeprecatedString::formatted("{}/{}", src_path, filename), 532 RecursionMode::Allowed, link, AddDuplicateFileMarker::Yes, preserve_mode); 533 if (result.is_error()) 534 return result.release_error(); 535 } 536 537 auto my_umask = umask(0); 538 umask(my_umask); 539 540 if (chmod(dst_path.characters(), src_stat.st_mode & ~my_umask) < 0) 541 return CopyError { errno, false }; 542 543 if (has_flag(preserve_mode, PreserveMode::Ownership)) { 544 if (chown(dst_path.characters(), src_stat.st_uid, src_stat.st_gid) < 0) 545 return CopyError { errno, false }; 546 } 547 548 if (has_flag(preserve_mode, PreserveMode::Timestamps)) { 549 struct timespec times[2] = { 550#ifdef AK_OS_MACOS 551 src_stat.st_atimespec, 552 src_stat.st_mtimespec, 553#else 554 src_stat.st_atim, 555 src_stat.st_mtim, 556#endif 557 }; 558 if (utimensat(AT_FDCWD, dst_path.characters(), times, 0) < 0) 559 return CopyError { errno, false }; 560 } 561 562 return {}; 563} 564 565ErrorOr<void> DeprecatedFile::link_file(DeprecatedString const& dst_path, DeprecatedString const& src_path) 566{ 567 int duplicate_count = 0; 568 while (access(get_duplicate_name(dst_path, duplicate_count).characters(), F_OK) == 0) { 569 ++duplicate_count; 570 } 571 if (duplicate_count != 0) { 572 return link_file(get_duplicate_name(dst_path, duplicate_count), src_path); 573 } 574 if (symlink(src_path.characters(), dst_path.characters()) < 0) 575 return Error::from_errno(errno); 576 return {}; 577} 578 579ErrorOr<void> DeprecatedFile::remove(StringView path, RecursionMode mode) 580{ 581 auto path_stat = TRY(Core::System::lstat(path)); 582 583 if (S_ISDIR(path_stat.st_mode) && mode == RecursionMode::Allowed) { 584 auto di = DirIterator(path, DirIterator::SkipParentAndBaseDir); 585 if (di.has_error()) 586 return di.error(); 587 588 while (di.has_next()) { 589 TRY(remove(di.next_full_path(), RecursionMode::Allowed)); 590 } 591 592 TRY(Core::System::rmdir(path)); 593 } else { 594 TRY(Core::System::unlink(path)); 595 } 596 597 return {}; 598} 599 600Optional<DeprecatedString> DeprecatedFile::resolve_executable_from_environment(StringView filename) 601{ 602 if (filename.is_empty()) 603 return {}; 604 605 // Paths that aren't just a file name generally count as already resolved. 606 if (filename.contains('/')) { 607 if (access(DeprecatedString { filename }.characters(), X_OK) != 0) 608 return {}; 609 610 return filename; 611 } 612 613 auto const* path_str = getenv("PATH"); 614 StringView path; 615 if (path_str) 616 path = { path_str, strlen(path_str) }; 617 if (path.is_empty()) 618 path = DEFAULT_PATH_SV; 619 620 auto directories = path.split_view(':'); 621 622 for (auto directory : directories) { 623 auto file = DeprecatedString::formatted("{}/{}", directory, filename); 624 625 if (access(file.characters(), X_OK) == 0) 626 return file; 627 } 628 629 return {}; 630}; 631 632}