1 //===-- File.cpp ----------------------------------------------------------===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #include "lldb/Host/File.h" 10 11 #include <cerrno> 12 #include <climits> 13 #include <cstdarg> 14 #include <cstdio> 15 #include <fcntl.h> 16 17 #ifdef _WIN32 18 #include "lldb/Host/windows/windows.h" 19 #else 20 #include <sys/ioctl.h> 21 #include <sys/stat.h> 22 #include <termios.h> 23 #include <unistd.h> 24 #endif 25 26 #include "llvm/Support/ConvertUTF.h" 27 #include "llvm/Support/Errno.h" 28 #include "llvm/Support/FileSystem.h" 29 #include "llvm/Support/Process.h" 30 31 #include "lldb/Host/Config.h" 32 #include "lldb/Host/FileSystem.h" 33 #include "lldb/Host/Host.h" 34 #include "lldb/Utility/DataBufferHeap.h" 35 #include "lldb/Utility/FileSpec.h" 36 #include "lldb/Utility/Log.h" 37 38 using namespace lldb; 39 using namespace lldb_private; 40 using llvm::Expected; 41 42 Expected<const char *> 43 File::GetStreamOpenModeFromOptions(File::OpenOptions options) { 44 if (options & File::eOpenOptionAppend) { 45 if (options & File::eOpenOptionRead) { 46 if (options & File::eOpenOptionCanCreateNewOnly) 47 return "a+x"; 48 else 49 return "a+"; 50 } else if (options & File::eOpenOptionWrite) { 51 if (options & File::eOpenOptionCanCreateNewOnly) 52 return "ax"; 53 else 54 return "a"; 55 } 56 } else if (options & File::eOpenOptionRead && 57 options & File::eOpenOptionWrite) { 58 if (options & File::eOpenOptionCanCreate) { 59 if (options & File::eOpenOptionCanCreateNewOnly) 60 return "w+x"; 61 else 62 return "w+"; 63 } else 64 return "r+"; 65 } else if (options & File::eOpenOptionRead) { 66 return "r"; 67 } else if (options & File::eOpenOptionWrite) { 68 return "w"; 69 } 70 return llvm::createStringError( 71 llvm::inconvertibleErrorCode(), 72 "invalid options, cannot convert to mode string"); 73 } 74 75 Expected<File::OpenOptions> File::GetOptionsFromMode(llvm::StringRef mode) { 76 OpenOptions opts = 77 llvm::StringSwitch<OpenOptions>(mode) 78 .Cases("r", "rb", eOpenOptionRead) 79 .Cases("w", "wb", eOpenOptionWrite) 80 .Cases("a", "ab", 81 eOpenOptionWrite | eOpenOptionAppend | eOpenOptionCanCreate) 82 .Cases("r+", "rb+", "r+b", eOpenOptionRead | eOpenOptionWrite) 83 .Cases("w+", "wb+", "w+b", 84 eOpenOptionRead | eOpenOptionWrite | eOpenOptionCanCreate | 85 eOpenOptionTruncate) 86 .Cases("a+", "ab+", "a+b", 87 eOpenOptionRead | eOpenOptionWrite | eOpenOptionAppend | 88 eOpenOptionCanCreate) 89 .Default(OpenOptions()); 90 if (opts) 91 return opts; 92 return llvm::createStringError( 93 llvm::inconvertibleErrorCode(), 94 "invalid mode, cannot convert to File::OpenOptions"); 95 } 96 97 int File::kInvalidDescriptor = -1; 98 FILE *File::kInvalidStream = nullptr; 99 100 Status File::Read(void *buf, size_t &num_bytes) { 101 return std::error_code(ENOTSUP, std::system_category()); 102 } 103 Status File::Write(const void *buf, size_t &num_bytes) { 104 return std::error_code(ENOTSUP, std::system_category()); 105 } 106 107 bool File::IsValid() const { return false; } 108 109 Status File::Close() { return Flush(); } 110 111 IOObject::WaitableHandle File::GetWaitableHandle() { 112 return IOObject::kInvalidHandleValue; 113 } 114 115 Status File::GetFileSpec(FileSpec &file_spec) const { 116 file_spec.Clear(); 117 return std::error_code(ENOTSUP, std::system_category()); 118 } 119 120 int File::GetDescriptor() const { return kInvalidDescriptor; } 121 122 FILE *File::GetStream() { return nullptr; } 123 124 off_t File::SeekFromStart(off_t offset, Status *error_ptr) { 125 if (error_ptr) 126 *error_ptr = std::error_code(ENOTSUP, std::system_category()); 127 return -1; 128 } 129 130 off_t File::SeekFromCurrent(off_t offset, Status *error_ptr) { 131 if (error_ptr) 132 *error_ptr = std::error_code(ENOTSUP, std::system_category()); 133 return -1; 134 } 135 136 off_t File::SeekFromEnd(off_t offset, Status *error_ptr) { 137 if (error_ptr) 138 *error_ptr = std::error_code(ENOTSUP, std::system_category()); 139 return -1; 140 } 141 142 Status File::Read(void *dst, size_t &num_bytes, off_t &offset) { 143 return std::error_code(ENOTSUP, std::system_category()); 144 } 145 146 Status File::Write(const void *src, size_t &num_bytes, off_t &offset) { 147 return std::error_code(ENOTSUP, std::system_category()); 148 } 149 150 Status File::Flush() { return Status(); } 151 152 Status File::Sync() { return Flush(); } 153 154 void File::CalculateInteractiveAndTerminal() { 155 const int fd = GetDescriptor(); 156 if (!DescriptorIsValid(fd)) { 157 m_is_interactive = eLazyBoolNo; 158 m_is_real_terminal = eLazyBoolNo; 159 m_supports_colors = eLazyBoolNo; 160 return; 161 } 162 m_is_interactive = eLazyBoolNo; 163 m_is_real_terminal = eLazyBoolNo; 164 #if defined(_WIN32) 165 if (_isatty(fd)) { 166 m_is_interactive = eLazyBoolYes; 167 m_is_real_terminal = eLazyBoolYes; 168 #if defined(ENABLE_VIRTUAL_TERMINAL_PROCESSING) 169 m_supports_colors = eLazyBoolYes; 170 #endif 171 } 172 #else 173 if (isatty(fd)) { 174 m_is_interactive = eLazyBoolYes; 175 struct winsize window_size; 176 if (::ioctl(fd, TIOCGWINSZ, &window_size) == 0) { 177 if (window_size.ws_col > 0) { 178 m_is_real_terminal = eLazyBoolYes; 179 if (llvm::sys::Process::FileDescriptorHasColors(fd)) 180 m_supports_colors = eLazyBoolYes; 181 } 182 } 183 } 184 #endif 185 } 186 187 bool File::GetIsInteractive() { 188 if (m_is_interactive == eLazyBoolCalculate) 189 CalculateInteractiveAndTerminal(); 190 return m_is_interactive == eLazyBoolYes; 191 } 192 193 bool File::GetIsRealTerminal() { 194 if (m_is_real_terminal == eLazyBoolCalculate) 195 CalculateInteractiveAndTerminal(); 196 return m_is_real_terminal == eLazyBoolYes; 197 } 198 199 bool File::GetIsTerminalWithColors() { 200 if (m_supports_colors == eLazyBoolCalculate) 201 CalculateInteractiveAndTerminal(); 202 return m_supports_colors == eLazyBoolYes; 203 } 204 205 size_t File::Printf(const char *format, ...) { 206 va_list args; 207 va_start(args, format); 208 size_t result = PrintfVarArg(format, args); 209 va_end(args); 210 return result; 211 } 212 213 size_t File::PrintfVarArg(const char *format, va_list args) { 214 size_t result = 0; 215 char *s = nullptr; 216 result = vasprintf(&s, format, args); 217 if (s != nullptr) { 218 if (result > 0) { 219 size_t s_len = result; 220 Write(s, s_len); 221 result = s_len; 222 } 223 free(s); 224 } 225 return result; 226 } 227 228 Expected<File::OpenOptions> File::GetOptions() const { 229 return llvm::createStringError( 230 llvm::inconvertibleErrorCode(), 231 "GetOptions() not implemented for this File class"); 232 } 233 234 uint32_t File::GetPermissions(Status &error) const { 235 int fd = GetDescriptor(); 236 if (!DescriptorIsValid(fd)) { 237 error = std::error_code(ENOTSUP, std::system_category()); 238 return 0; 239 } 240 struct stat file_stats; 241 if (::fstat(fd, &file_stats) == -1) { 242 error.SetErrorToErrno(); 243 return 0; 244 } 245 error.Clear(); 246 return file_stats.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO); 247 } 248 249 Expected<File::OpenOptions> NativeFile::GetOptions() const { return m_options; } 250 251 int NativeFile::GetDescriptor() const { 252 if (DescriptorIsValid()) 253 return m_descriptor; 254 255 // Don't open the file descriptor if we don't need to, just get it from the 256 // stream if we have one. 257 if (StreamIsValid()) { 258 #if defined(_WIN32) 259 return _fileno(m_stream); 260 #else 261 return fileno(m_stream); 262 #endif 263 } 264 265 // Invalid descriptor and invalid stream, return invalid descriptor. 266 return kInvalidDescriptor; 267 } 268 269 IOObject::WaitableHandle NativeFile::GetWaitableHandle() { 270 return GetDescriptor(); 271 } 272 273 FILE *NativeFile::GetStream() { 274 if (!StreamIsValid()) { 275 if (DescriptorIsValid()) { 276 auto mode = GetStreamOpenModeFromOptions(m_options); 277 if (!mode) 278 llvm::consumeError(mode.takeError()); 279 else { 280 if (!m_own_descriptor) { 281 // We must duplicate the file descriptor if we don't own it because when you 282 // call fdopen, the stream will own the fd 283 #ifdef _WIN32 284 m_descriptor = ::_dup(GetDescriptor()); 285 #else 286 m_descriptor = dup(GetDescriptor()); 287 #endif 288 m_own_descriptor = true; 289 } 290 291 m_stream = llvm::sys::RetryAfterSignal(nullptr, ::fdopen, m_descriptor, 292 mode.get()); 293 294 // If we got a stream, then we own the stream and should no longer own 295 // the descriptor because fclose() will close it for us 296 297 if (m_stream) { 298 m_own_stream = true; 299 m_own_descriptor = false; 300 } 301 } 302 } 303 } 304 return m_stream; 305 } 306 307 Status NativeFile::Close() { 308 Status error; 309 if (StreamIsValid()) { 310 if (m_own_stream) { 311 if (::fclose(m_stream) == EOF) 312 error.SetErrorToErrno(); 313 } else if (m_options & eOpenOptionWrite) { 314 if (::fflush(m_stream) == EOF) 315 error.SetErrorToErrno(); 316 } 317 } 318 if (DescriptorIsValid() && m_own_descriptor) { 319 if (::close(m_descriptor) != 0) 320 error.SetErrorToErrno(); 321 } 322 m_descriptor = kInvalidDescriptor; 323 m_stream = kInvalidStream; 324 m_options = OpenOptions(0); 325 m_own_stream = false; 326 m_own_descriptor = false; 327 m_is_interactive = eLazyBoolCalculate; 328 m_is_real_terminal = eLazyBoolCalculate; 329 return error; 330 } 331 332 Status NativeFile::GetFileSpec(FileSpec &file_spec) const { 333 Status error; 334 #ifdef F_GETPATH 335 if (IsValid()) { 336 char path[PATH_MAX]; 337 if (::fcntl(GetDescriptor(), F_GETPATH, path) == -1) 338 error.SetErrorToErrno(); 339 else 340 file_spec.SetFile(path, FileSpec::Style::native); 341 } else { 342 error.SetErrorString("invalid file handle"); 343 } 344 #elif defined(__linux__) 345 char proc[64]; 346 char path[PATH_MAX]; 347 if (::snprintf(proc, sizeof(proc), "/proc/self/fd/%d", GetDescriptor()) < 0) 348 error.SetErrorString("cannot resolve file descriptor"); 349 else { 350 ssize_t len; 351 if ((len = ::readlink(proc, path, sizeof(path) - 1)) == -1) 352 error.SetErrorToErrno(); 353 else { 354 path[len] = '\0'; 355 file_spec.SetFile(path, FileSpec::Style::native); 356 } 357 } 358 #else 359 error.SetErrorString( 360 "NativeFile::GetFileSpec is not supported on this platform"); 361 #endif 362 363 if (error.Fail()) 364 file_spec.Clear(); 365 return error; 366 } 367 368 off_t NativeFile::SeekFromStart(off_t offset, Status *error_ptr) { 369 off_t result = 0; 370 if (DescriptorIsValid()) { 371 result = ::lseek(m_descriptor, offset, SEEK_SET); 372 373 if (error_ptr) { 374 if (result == -1) 375 error_ptr->SetErrorToErrno(); 376 else 377 error_ptr->Clear(); 378 } 379 } else if (StreamIsValid()) { 380 result = ::fseek(m_stream, offset, SEEK_SET); 381 382 if (error_ptr) { 383 if (result == -1) 384 error_ptr->SetErrorToErrno(); 385 else 386 error_ptr->Clear(); 387 } 388 } else if (error_ptr) { 389 error_ptr->SetErrorString("invalid file handle"); 390 } 391 return result; 392 } 393 394 off_t NativeFile::SeekFromCurrent(off_t offset, Status *error_ptr) { 395 off_t result = -1; 396 if (DescriptorIsValid()) { 397 result = ::lseek(m_descriptor, offset, SEEK_CUR); 398 399 if (error_ptr) { 400 if (result == -1) 401 error_ptr->SetErrorToErrno(); 402 else 403 error_ptr->Clear(); 404 } 405 } else if (StreamIsValid()) { 406 result = ::fseek(m_stream, offset, SEEK_CUR); 407 408 if (error_ptr) { 409 if (result == -1) 410 error_ptr->SetErrorToErrno(); 411 else 412 error_ptr->Clear(); 413 } 414 } else if (error_ptr) { 415 error_ptr->SetErrorString("invalid file handle"); 416 } 417 return result; 418 } 419 420 off_t NativeFile::SeekFromEnd(off_t offset, Status *error_ptr) { 421 off_t result = -1; 422 if (DescriptorIsValid()) { 423 result = ::lseek(m_descriptor, offset, SEEK_END); 424 425 if (error_ptr) { 426 if (result == -1) 427 error_ptr->SetErrorToErrno(); 428 else 429 error_ptr->Clear(); 430 } 431 } else if (StreamIsValid()) { 432 result = ::fseek(m_stream, offset, SEEK_END); 433 434 if (error_ptr) { 435 if (result == -1) 436 error_ptr->SetErrorToErrno(); 437 else 438 error_ptr->Clear(); 439 } 440 } else if (error_ptr) { 441 error_ptr->SetErrorString("invalid file handle"); 442 } 443 return result; 444 } 445 446 Status NativeFile::Flush() { 447 Status error; 448 if (StreamIsValid()) { 449 if (llvm::sys::RetryAfterSignal(EOF, ::fflush, m_stream) == EOF) 450 error.SetErrorToErrno(); 451 } else if (!DescriptorIsValid()) { 452 error.SetErrorString("invalid file handle"); 453 } 454 return error; 455 } 456 457 Status NativeFile::Sync() { 458 Status error; 459 if (DescriptorIsValid()) { 460 #ifdef _WIN32 461 int err = FlushFileBuffers((HANDLE)_get_osfhandle(m_descriptor)); 462 if (err == 0) 463 error.SetErrorToGenericError(); 464 #else 465 if (llvm::sys::RetryAfterSignal(-1, ::fsync, m_descriptor) == -1) 466 error.SetErrorToErrno(); 467 #endif 468 } else { 469 error.SetErrorString("invalid file handle"); 470 } 471 return error; 472 } 473 474 #if defined(__APPLE__) 475 // Darwin kernels only can read/write <= INT_MAX bytes 476 #define MAX_READ_SIZE INT_MAX 477 #define MAX_WRITE_SIZE INT_MAX 478 #endif 479 480 Status NativeFile::Read(void *buf, size_t &num_bytes) { 481 Status error; 482 483 #if defined(MAX_READ_SIZE) 484 if (num_bytes > MAX_READ_SIZE) { 485 uint8_t *p = (uint8_t *)buf; 486 size_t bytes_left = num_bytes; 487 // Init the num_bytes read to zero 488 num_bytes = 0; 489 490 while (bytes_left > 0) { 491 size_t curr_num_bytes; 492 if (bytes_left > MAX_READ_SIZE) 493 curr_num_bytes = MAX_READ_SIZE; 494 else 495 curr_num_bytes = bytes_left; 496 497 error = Read(p + num_bytes, curr_num_bytes); 498 499 // Update how many bytes were read 500 num_bytes += curr_num_bytes; 501 if (bytes_left < curr_num_bytes) 502 bytes_left = 0; 503 else 504 bytes_left -= curr_num_bytes; 505 506 if (error.Fail()) 507 break; 508 } 509 return error; 510 } 511 #endif 512 513 ssize_t bytes_read = -1; 514 if (DescriptorIsValid()) { 515 bytes_read = llvm::sys::RetryAfterSignal(-1, ::read, m_descriptor, buf, num_bytes); 516 if (bytes_read == -1) { 517 error.SetErrorToErrno(); 518 num_bytes = 0; 519 } else 520 num_bytes = bytes_read; 521 } else if (StreamIsValid()) { 522 bytes_read = ::fread(buf, 1, num_bytes, m_stream); 523 524 if (bytes_read == 0) { 525 if (::feof(m_stream)) 526 error.SetErrorString("feof"); 527 else if (::ferror(m_stream)) 528 error.SetErrorString("ferror"); 529 num_bytes = 0; 530 } else 531 num_bytes = bytes_read; 532 } else { 533 num_bytes = 0; 534 error.SetErrorString("invalid file handle"); 535 } 536 return error; 537 } 538 539 Status NativeFile::Write(const void *buf, size_t &num_bytes) { 540 Status error; 541 542 #if defined(MAX_WRITE_SIZE) 543 if (num_bytes > MAX_WRITE_SIZE) { 544 const uint8_t *p = (const uint8_t *)buf; 545 size_t bytes_left = num_bytes; 546 // Init the num_bytes written to zero 547 num_bytes = 0; 548 549 while (bytes_left > 0) { 550 size_t curr_num_bytes; 551 if (bytes_left > MAX_WRITE_SIZE) 552 curr_num_bytes = MAX_WRITE_SIZE; 553 else 554 curr_num_bytes = bytes_left; 555 556 error = Write(p + num_bytes, curr_num_bytes); 557 558 // Update how many bytes were read 559 num_bytes += curr_num_bytes; 560 if (bytes_left < curr_num_bytes) 561 bytes_left = 0; 562 else 563 bytes_left -= curr_num_bytes; 564 565 if (error.Fail()) 566 break; 567 } 568 return error; 569 } 570 #endif 571 572 ssize_t bytes_written = -1; 573 if (DescriptorIsValid()) { 574 bytes_written = 575 llvm::sys::RetryAfterSignal(-1, ::write, m_descriptor, buf, num_bytes); 576 if (bytes_written == -1) { 577 error.SetErrorToErrno(); 578 num_bytes = 0; 579 } else 580 num_bytes = bytes_written; 581 } else if (StreamIsValid()) { 582 bytes_written = ::fwrite(buf, 1, num_bytes, m_stream); 583 584 if (bytes_written == 0) { 585 if (::feof(m_stream)) 586 error.SetErrorString("feof"); 587 else if (::ferror(m_stream)) 588 error.SetErrorString("ferror"); 589 num_bytes = 0; 590 } else 591 num_bytes = bytes_written; 592 593 } else { 594 num_bytes = 0; 595 error.SetErrorString("invalid file handle"); 596 } 597 598 return error; 599 } 600 601 Status NativeFile::Read(void *buf, size_t &num_bytes, off_t &offset) { 602 Status error; 603 604 #if defined(MAX_READ_SIZE) 605 if (num_bytes > MAX_READ_SIZE) { 606 uint8_t *p = (uint8_t *)buf; 607 size_t bytes_left = num_bytes; 608 // Init the num_bytes read to zero 609 num_bytes = 0; 610 611 while (bytes_left > 0) { 612 size_t curr_num_bytes; 613 if (bytes_left > MAX_READ_SIZE) 614 curr_num_bytes = MAX_READ_SIZE; 615 else 616 curr_num_bytes = bytes_left; 617 618 error = Read(p + num_bytes, curr_num_bytes, offset); 619 620 // Update how many bytes were read 621 num_bytes += curr_num_bytes; 622 if (bytes_left < curr_num_bytes) 623 bytes_left = 0; 624 else 625 bytes_left -= curr_num_bytes; 626 627 if (error.Fail()) 628 break; 629 } 630 return error; 631 } 632 #endif 633 634 #ifndef _WIN32 635 int fd = GetDescriptor(); 636 if (fd != kInvalidDescriptor) { 637 ssize_t bytes_read = 638 llvm::sys::RetryAfterSignal(-1, ::pread, fd, buf, num_bytes, offset); 639 if (bytes_read < 0) { 640 num_bytes = 0; 641 error.SetErrorToErrno(); 642 } else { 643 offset += bytes_read; 644 num_bytes = bytes_read; 645 } 646 } else { 647 num_bytes = 0; 648 error.SetErrorString("invalid file handle"); 649 } 650 #else 651 std::lock_guard<std::mutex> guard(offset_access_mutex); 652 long cur = ::lseek(m_descriptor, 0, SEEK_CUR); 653 SeekFromStart(offset); 654 error = Read(buf, num_bytes); 655 if (!error.Fail()) 656 SeekFromStart(cur); 657 #endif 658 return error; 659 } 660 661 Status NativeFile::Write(const void *buf, size_t &num_bytes, off_t &offset) { 662 Status error; 663 664 #if defined(MAX_WRITE_SIZE) 665 if (num_bytes > MAX_WRITE_SIZE) { 666 const uint8_t *p = (const uint8_t *)buf; 667 size_t bytes_left = num_bytes; 668 // Init the num_bytes written to zero 669 num_bytes = 0; 670 671 while (bytes_left > 0) { 672 size_t curr_num_bytes; 673 if (bytes_left > MAX_WRITE_SIZE) 674 curr_num_bytes = MAX_WRITE_SIZE; 675 else 676 curr_num_bytes = bytes_left; 677 678 error = Write(p + num_bytes, curr_num_bytes, offset); 679 680 // Update how many bytes were read 681 num_bytes += curr_num_bytes; 682 if (bytes_left < curr_num_bytes) 683 bytes_left = 0; 684 else 685 bytes_left -= curr_num_bytes; 686 687 if (error.Fail()) 688 break; 689 } 690 return error; 691 } 692 #endif 693 694 int fd = GetDescriptor(); 695 if (fd != kInvalidDescriptor) { 696 #ifndef _WIN32 697 ssize_t bytes_written = 698 llvm::sys::RetryAfterSignal(-1, ::pwrite, m_descriptor, buf, num_bytes, offset); 699 if (bytes_written < 0) { 700 num_bytes = 0; 701 error.SetErrorToErrno(); 702 } else { 703 offset += bytes_written; 704 num_bytes = bytes_written; 705 } 706 #else 707 std::lock_guard<std::mutex> guard(offset_access_mutex); 708 long cur = ::lseek(m_descriptor, 0, SEEK_CUR); 709 SeekFromStart(offset); 710 error = Write(buf, num_bytes); 711 long after = ::lseek(m_descriptor, 0, SEEK_CUR); 712 713 if (!error.Fail()) 714 SeekFromStart(cur); 715 716 offset = after; 717 #endif 718 } else { 719 num_bytes = 0; 720 error.SetErrorString("invalid file handle"); 721 } 722 return error; 723 } 724 725 size_t NativeFile::PrintfVarArg(const char *format, va_list args) { 726 if (StreamIsValid()) { 727 return ::vfprintf(m_stream, format, args); 728 } else { 729 return File::PrintfVarArg(format, args); 730 } 731 } 732 733 mode_t File::ConvertOpenOptionsForPOSIXOpen(OpenOptions open_options) { 734 mode_t mode = 0; 735 if (open_options & eOpenOptionRead && open_options & eOpenOptionWrite) 736 mode |= O_RDWR; 737 else if (open_options & eOpenOptionWrite) 738 mode |= O_WRONLY; 739 740 if (open_options & eOpenOptionAppend) 741 mode |= O_APPEND; 742 743 if (open_options & eOpenOptionTruncate) 744 mode |= O_TRUNC; 745 746 if (open_options & eOpenOptionNonBlocking) 747 mode |= O_NONBLOCK; 748 749 if (open_options & eOpenOptionCanCreateNewOnly) 750 mode |= O_CREAT | O_EXCL; 751 else if (open_options & eOpenOptionCanCreate) 752 mode |= O_CREAT; 753 754 return mode; 755 } 756 757 char File::ID = 0; 758 char NativeFile::ID = 0; 759