/** * @file File.cpp * @author apio (cloudapio.eu) * @brief A C++-friendly API for file access. * * @copyright Copyright (c) 2023, the Luna authors. * */ #include #include #include #include #include static SharedPtr g_stdin = {}; static SharedPtr g_stdout = {}; static SharedPtr g_stderr = {}; static const char* stdio_mode_from_openmode(int mode) { mode &= ~(O_CREAT | O_EXCL); switch (mode) { case os::File::ReadOnly: return "r"; case os::File::ReadWrite: return "w+"; case os::File::WriteOnly: return "w"; case os::File::ReadAppend: return "a+"; case os::File::Append: return "a"; default: fail("Mode incompatible with fdopen()"); } } namespace os { File::File(Badge) { } File::~File() { if (m_file) fclose(m_file); } void File::initialize_standard_streams() { g_stdin = adopt_shared_if_nonnull(new (std::nothrow) File({})) .expect_release_value("Cannot open standard input stream"); g_stdin->m_file = stdin; g_stdout = adopt_shared_if_nonnull(new (std::nothrow) File({})) .expect_release_value("Cannot open standard output stream"); g_stdout->m_file = stdout; g_stderr = adopt_shared_if_nonnull(new (std::nothrow) File({})) .expect_release_value("Cannot open standard error stream"); g_stderr->m_file = stderr; } SharedPtr File::standard_input() { if (!g_stdin) initialize_standard_streams(); return g_stdin; } SharedPtr File::standard_output() { if (!g_stdout) initialize_standard_streams(); return g_stdout; } SharedPtr File::standard_error() { if (!g_stderr) initialize_standard_streams(); return g_stderr; } Result> File::construct(const Path& path, int flags, mode_t mode) { auto file = TRY(adopt_shared_if_nonnull(new (std::nothrow) File({}))); long rc = syscall(SYS_openat, path.dirfd(), path.name().chars(), flags, mode); int fd = TRY(Result::from_syscall(rc)); file->m_file = fdopen(fd, stdio_mode_from_openmode(flags)); if (!file->m_file) return err(errno); return file; } Result> File::open(const Path& path, OpenMode flags) { return construct(path, (int)flags, 0); } Result> File::open_or_create(const Path& path, OpenMode flags, mode_t mode) { return construct(path, (int)flags | O_CREAT, mode); } Result> File::create(const Path& path, OpenMode flags, mode_t mode) { return construct(path, (int)flags | (O_CREAT | O_EXCL), mode); } Result> File::open_input_file(StringView path) { if (path == "-"_sv) return standard_input(); return construct(path, O_RDONLY, 0); } Result File::raw_read(u8* buf, usize length) { size_t nread = fread(buf, 1, length, m_file); if (nread == 0 && ferror(m_file)) return err(errno); return nread; } Result File::raw_write(const u8* buf, usize length) { size_t nwrite = fwrite(buf, 1, length, m_file); if (nwrite == 0 && ferror(m_file)) return err(errno); return nwrite; } Result File::write(StringView str) { TRY(raw_write((const u8*)str.chars(), str.length())); return {}; } Result File::write(const Buffer& buf) { TRY(raw_write(buf.data(), buf.size())); return {}; } Result File::read_line() { Vector data; int current; while (true) { current = TRY(getchar()); if (current == EOF) break; TRY(data.try_append((char)current)); if (current == '\n') break; } if (!data.size()) return String {}; TRY(data.try_append('\0')); return String { data.release_data() }; } Result File::read_all_as_string() { StringBuilder sb; while (true) { auto line = TRY(read_line()); if (line.is_empty()) break; TRY(sb.add(line)); } return sb.string(); } Result File::read_all() { Buffer data; u8 buf[2048]; while (true) { usize nread = TRY(raw_read(buf, sizeof(buf))); TRY(data.append_data(buf, nread)); if (nread < sizeof(buf)) break; } return data; } Result File::read(Buffer& buf, usize size) { u8* slice = TRY(buf.slice(0, size)); usize nread = TRY(raw_read(slice, size)); TRY(buf.try_resize(nread)); return {}; } Result File::getchar() { int rc = fgetc(m_file); if (rc == EOF && ferror(m_file)) return err(errno); return rc; } void File::set_close_on_exec() { fcntl(fd(), F_SETFD, FD_CLOEXEC); } void File::rewind() { ::rewind(m_file); } void File::flush() { fflush(m_file); } void File::set_buffer(BufferingMode mode) { setvbuf(m_file, NULL, mode, 0); } // FIXME: Do not allocate memory for printing. Result print_impl(SharedPtr f, StringView fmt, va_list ap) { auto str = TRY(String::vformat(fmt, ap)); auto rc = f->write(str.view()); f->flush(); return rc; } Result print(StringView fmt, ...) { va_list ap; va_start(ap, fmt); auto rc = print_impl(File::standard_output(), fmt, ap); va_end(ap); return rc; } Result println(StringView fmt, ...) { va_list ap; va_start(ap, fmt); auto rc = print_impl(File::standard_output(), fmt, ap); va_end(ap); TRY(rc); return File::standard_output()->write("\n"_sv); } Result eprint(StringView fmt, ...) { va_list ap; va_start(ap, fmt); auto rc = print_impl(File::standard_error(), fmt, ap); va_end(ap); return rc; } Result eprintln(StringView fmt, ...) { va_list ap; va_start(ap, fmt); auto rc = print_impl(File::standard_error(), fmt, ap); va_end(ap); TRY(rc); return File::standard_error()->write("\n"_sv); } }