From 73ae5200a9701f104726b673752752250beafa4d Mon Sep 17 00:00:00 2001 From: Timothy Date: Wed, 30 Jun 2021 06:13:06 -0700 Subject: [PATCH] TextEditor+LibGUI: Use unveil and FileSystemAccessServer Making use of the new FileSystemAccessServer we are able to use unveil without restricting our ability to open and save files. A file argument will be unveiled automatically however all other files require user action via the FileSystemAccessServer to gain access. --- .../Applications/TextEditor/CMakeLists.txt | 2 +- .../Applications/TextEditor/MainWidget.cpp | 42 +++++++++++----- Userland/Applications/TextEditor/MainWidget.h | 25 +++++++++- Userland/Applications/TextEditor/main.cpp | 48 ++++++++++++++++++- Userland/Libraries/LibGUI/TextEditor.cpp | 7 ++- Userland/Libraries/LibGUI/TextEditor.h | 3 +- 6 files changed, 109 insertions(+), 18 deletions(-) diff --git a/Userland/Applications/TextEditor/CMakeLists.txt b/Userland/Applications/TextEditor/CMakeLists.txt index b68b8b5b15a..a586210eae2 100644 --- a/Userland/Applications/TextEditor/CMakeLists.txt +++ b/Userland/Applications/TextEditor/CMakeLists.txt @@ -2,7 +2,7 @@ serenity_component( TextEditor RECOMMENDED TARGETS TextEditor - DEPENDS ImageDecoder RequestServer WebContent + DEPENDS ImageDecoder RequestServer WebContent FileSystemAccessServer ) compile_gml(TextEditorWindow.gml TextEditorWindowGML.h text_editor_window_gml) diff --git a/Userland/Applications/TextEditor/MainWidget.cpp b/Userland/Applications/TextEditor/MainWidget.cpp index 77b40b9cab8..15728b0199b 100644 --- a/Userland/Applications/TextEditor/MainWidget.cpp +++ b/Userland/Applications/TextEditor/MainWidget.cpp @@ -44,6 +44,7 @@ namespace TextEditor { MainWidget::MainWidget() + : m_file_system_access_client(FileSystemAccessClient::construct()) { load_from_gml(text_editor_window_gml); @@ -259,9 +260,9 @@ MainWidget::MainWidget() }); m_open_action = GUI::CommonActions::make_open_action([this](auto&) { - Optional open_path = GUI::FilePicker::get_open_filepath(window()); + auto fd_response = m_file_system_access_client->prompt_open_file(Core::StandardPaths::home_directory(), Core::OpenMode::ReadWrite); - if (!open_path.has_value()) + if (fd_response.error() != 0) return; if (editor().document().is_modified()) { @@ -272,28 +273,36 @@ MainWidget::MainWidget() return; } - open_file(open_path.value()); + read_file_and_close(fd_response.fd()->take_fd(), fd_response.chosen_file().value()); }); m_save_as_action = GUI::CommonActions::make_save_as_action([&](auto&) { - Optional save_path = GUI::FilePicker::get_save_filepath(window(), m_name.is_null() ? "Untitled" : m_name, m_extension.is_null() ? "txt" : m_extension); - if (!save_path.has_value()) + auto response = m_file_system_access_client->prompt_save_file(m_name.is_null() ? "Untitled" : m_name, m_extension.is_null() ? "txt" : m_extension, Core::StandardPaths::home_directory(), Core::OpenMode::Truncate | Core::OpenMode::WriteOnly); + + if (response.error() != 0) return; - if (!m_editor->write_to_file(save_path.value())) { + if (!m_editor->write_to_file_and_close(response.fd()->take_fd())) { GUI::MessageBox::show(window(), "Unable to save file.\n", "Error", GUI::MessageBox::Type::Error); return; } // FIXME: It would be cool if this would propagate from GUI::TextDocument somehow. window()->set_modified(false); - set_path(save_path.value()); - dbgln("Wrote document to {}", save_path.value()); + set_path(response.chosen_file().value()); + dbgln("Wrote document to {}", response.chosen_file().value()); }); m_save_action = GUI::CommonActions::make_save_action([&](auto&) { if (!m_path.is_empty()) { - if (!m_editor->write_to_file(m_path)) { + auto response = m_file_system_access_client->request_file(m_path, Core::OpenMode::Truncate | Core::OpenMode::WriteOnly); + + if (response.error() != 0) + return; + + int fd = response.fd()->take_fd(); + + if (!m_editor->write_to_file_and_close(fd)) { GUI::MessageBox::show(window(), "Unable to save file.\n", "Error", GUI::MessageBox::Type::Error); } else { // FIXME: It would be cool if this would propagate from GUI::TextDocument somehow. @@ -647,10 +656,12 @@ void MainWidget::update_title() window()->set_title(builder.to_string()); } -bool MainWidget::open_file(const String& path) +bool MainWidget::read_file_and_close(int fd, String const& path) { - auto file = Core::File::construct(path); - if (!file->open(Core::OpenMode::ReadOnly) && file->error() != ENOENT) { + VERIFY(path.starts_with("/"sv)); + auto file = Core::File::construct(); + + if (!file->open(fd, Core::OpenMode::ReadOnly, Core::File::ShouldCloseFileDescriptor::Yes) && file->error() != ENOENT) { GUI::MessageBox::show(window(), String::formatted("Opening \"{}\" failed: {}", path, strerror(errno)), "Error", GUI::MessageBox::Type::Error); return false; } @@ -706,7 +717,12 @@ void MainWidget::drop_event(GUI::DropEvent& event) GUI::MessageBox::show(window(), "TextEditor can only open one file at a time!", "One at a time please!", GUI::MessageBox::Type::Error); return; } - open_file(urls.first().path()); + auto file_response = m_file_system_access_client->request_file(urls.first().path(), Core::OpenMode::ReadOnly); + + if (file_response.error() != 0) + return; + + read_file_and_close(file_response.fd()->take_fd(), urls.first().path()); } } diff --git a/Userland/Applications/TextEditor/MainWidget.h b/Userland/Applications/TextEditor/MainWidget.h index 4cccac8181e..190a2063497 100644 --- a/Userland/Applications/TextEditor/MainWidget.h +++ b/Userland/Applications/TextEditor/MainWidget.h @@ -8,22 +8,43 @@ #include #include +#include +#include +#include #include #include #include #include #include #include +#include #include namespace TextEditor { +class FileSystemAccessClient final + : public IPC::ServerConnection + , public FileSystemAccessClientEndpoint { + C_OBJECT(FileSystemAccessClient) + +public: + virtual void die() override + { + } + +private: + explicit FileSystemAccessClient() + : IPC::ServerConnection(*this, "/tmp/portal/filesystemaccess") + { + } +}; + class MainWidget final : public GUI::Widget { C_OBJECT(MainWidget); public: virtual ~MainWidget() override; - bool open_file(const String& path); + bool read_file_and_close(int fd, String const& path); bool request_close(); GUI::TextEditor& editor() { return *m_editor; } @@ -53,6 +74,8 @@ private: virtual void drop_event(GUI::DropEvent&) override; + NonnullRefPtr m_file_system_access_client; + RefPtr m_editor; String m_path; String m_name; diff --git a/Userland/Applications/TextEditor/main.cpp b/Userland/Applications/TextEditor/main.cpp index ad1a56b313d..a79cb0b6d57 100644 --- a/Userland/Applications/TextEditor/main.cpp +++ b/Userland/Applications/TextEditor/main.cpp @@ -7,6 +7,8 @@ #include "FileArgument.h" #include "MainWidget.h" #include +#include +#include #include #include #include @@ -30,6 +32,43 @@ int main(int argc, char** argv) parser.parse(argc, argv); + if (file_to_edit) { + FileArgument parsed_argument(file_to_edit); + + auto path_to_unveil = Core::File::real_path_for(parsed_argument.filename()); + if (unveil(path_to_unveil.characters(), "rwc") < 0) { + perror("unveil"); + return 1; + } + } + + if (unveil(Core::StandardPaths::config_directory().characters(), "rw") < 0) { + perror("unveil"); + return 1; + } + + if (unveil("/res", "r") < 0) { + perror("unveil"); + return 1; + } + + if (unveil("/tmp/portal/launch", "rw") < 0) { + perror("unveil"); + return 1; + } + + if (unveil("/tmp/portal/webcontent", "rw") < 0) { + perror("unveil"); + return 1; + } + + if (unveil("/tmp/portal/filesystemaccess", "rw") < 0) { + perror("unveil"); + return 1; + } + + unveil(nullptr, nullptr); + StringView preview_mode_view = preview_mode; auto app_icon = GUI::Icon::default_icon("app-text-editor"); @@ -67,8 +106,15 @@ int main(int argc, char** argv) if (file_to_edit) { // A file name was passed, parse any possible line and column numbers included. FileArgument parsed_argument(file_to_edit); - if (!text_widget.open_file(parsed_argument.filename())) + auto absolute_path = Core::File::real_path_for(parsed_argument.filename()); + auto file = Core::File::open(absolute_path, Core::OpenMode::ReadWrite); + + if (file.is_error()) return 1; + + if (!text_widget.read_file_and_close(file.value()->leak_fd(), absolute_path)) + return 1; + text_widget.editor().set_cursor_and_focus_line(parsed_argument.line().value_or(1) - 1, parsed_argument.column().value_or(0)); } text_widget.update_title(); diff --git a/Userland/Libraries/LibGUI/TextEditor.cpp b/Userland/Libraries/LibGUI/TextEditor.cpp index 3a9617d0e8b..bf7e18fa4d1 100644 --- a/Userland/Libraries/LibGUI/TextEditor.cpp +++ b/Userland/Libraries/LibGUI/TextEditor.cpp @@ -1198,7 +1198,7 @@ void TextEditor::timer_event(Core::TimerEvent&) update_cursor(); } -bool TextEditor::write_to_file(const String& path) +bool TextEditor::write_to_file(String const& path) { int fd = open(path.characters(), O_WRONLY | O_CREAT | O_TRUNC, 0666); if (fd < 0) { @@ -1206,6 +1206,11 @@ bool TextEditor::write_to_file(const String& path) return false; } + return write_to_file_and_close(fd); +} + +bool TextEditor::write_to_file_and_close(int fd) +{ ScopeGuard fd_guard = [fd] { close(fd); }; off_t file_size = 0; diff --git a/Userland/Libraries/LibGUI/TextEditor.h b/Userland/Libraries/LibGUI/TextEditor.h index ecbbf53eae7..a287c0c6b12 100644 --- a/Userland/Libraries/LibGUI/TextEditor.h +++ b/Userland/Libraries/LibGUI/TextEditor.h @@ -116,7 +116,8 @@ public: TextRange normalized_selection() const { return m_selection.normalized(); } void insert_at_cursor_or_replace_selection(const StringView&); - bool write_to_file(const String& path); + bool write_to_file(String const& path); + bool write_to_file_and_close(int fd); bool has_selection() const { return m_selection.is_valid(); } String selected_text() const; size_t number_of_selected_words() const;