Add fixed-size backend. 55/320155/8
authorMichal Bloch <m.bloch@samsung.com>
Mon, 24 Feb 2025 12:49:41 +0000 (13:49 +0100)
committerMichal Bloch <m.bloch@samsung.com>
Thu, 27 Feb 2025 15:28:30 +0000 (16:28 +0100)
Change-Id: I398db9e586102c97b44724ae52c112b5f2c50638

src/service/CMakeLists.txt
src/service/src/dir_backend_fixed_size.cpp [new file with mode: 0644]
src/service/src/dir_backend_fixed_size.hpp [new file with mode: 0644]
src/service/src/fs_helpers.cpp

index 3214eb97638a609252af8207e6278dbfa5928220..d5dced87b79ee26c4616ddf58d7cd66eba0bed74 100644 (file)
@@ -11,6 +11,7 @@ set(
        src/fs_helpers.cpp
        src/os_ops.cpp
        src/dir_backend_regular_dir.cpp
+       src/dir_backend_fixed_size.cpp
        src/tuple_g_variant_helpers.hpp
 )
 add_executable(sessiond ${sessiond_SRCS})
diff --git a/src/service/src/dir_backend_fixed_size.cpp b/src/service/src/dir_backend_fixed_size.cpp
new file mode 100644 (file)
index 0000000..2e921b6
--- /dev/null
@@ -0,0 +1,223 @@
+/* MIT License
+ *
+ * Copyright (c) 2025 Samsung Electronics Co., Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is furnished
+ * to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE. */
+
+#undef LOG_TAG
+#define LOG_TAG "SESSIOND"
+#include <dlog.h>
+
+#include "dir_backend_fixed_size.hpp"
+
+#include <sys/mount.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <string_view>
+
+namespace fs = std::filesystem;
+using namespace std::literals;
+
+// Should be unique per-backend.
+static const std::string TMP_NEW_PREFIX = ".tmpfixednew";
+
+fs::path DirBackendFixedSize::GetImagePathFromSubsessionPath(fs::path subsession_path)
+{
+       subsession_path.replace_filename(subsession_path.filename().native() + ".img");
+       return subsession_path;
+}
+
+/* TODO: perhaps there should also be a "do_exec" wrapper which would
+ * accept a child lambda and do the throwing fork and the waitpid */
+
+static int throwing_fork()
+{
+       const int child_pid = fork();
+       if (child_pid == -1) {
+               const auto err = errno;
+               throw std::system_error (err, std::generic_category (), "fork() failed!");
+       }
+       return child_pid;
+}
+
+static void throw_if_child_failed(int child_pid, std::string_view message_on_fail)
+{
+       int status;
+       const int ret = waitpid(child_pid, &status, 0);
+       if (ret == -1) {
+               const auto err = errno;
+               throw std::system_error (err, std::generic_category (), "waitpid() failed!");
+       }
+       if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
+               throw std::runtime_error (std::string(message_on_fail));
+}
+
+static void do_mkfs(const fs::path& image_path, int uid, int gid, uint64_t size_kB)
+{
+       const auto child_pid = throwing_fork();
+       if (child_pid == 0) {
+               const auto mkfs = "/usr/sbin/mkfs.ext2"sv;
+
+               /* Would ideally be std::format instead of this ugly stack,
+                * but some important downstream forks use obsolete gcc. */
+               std::string owner_arg = "root_owner=";
+               owner_arg += std::to_string(uid);
+               owner_arg += ":";
+               owner_arg += std::to_string(gid);
+
+               std::string size_arg = std::to_string(size_kB);
+
+               execl
+                       ( mkfs.data(), mkfs.data() /* argv[0] convention */
+                       , "-E", owner_arg.c_str()
+                       , "-m", "0"
+                       , image_path.c_str()
+                       , size_arg.c_str()
+                       , (char *) NULL
+               );
+
+               _exit(1);
+       } else {
+               throw_if_child_failed(child_pid, "mkfs.ext2 failed!");
+       }
+}
+
+static void do_mount(const fs::path& image_path, const fs::path& mount_path)
+{
+       /* Don't just call mount() since there's some extra steps involved
+        * in making it work properly, such as setting up a loop device,
+        * that the mount binary does for us. */
+
+       const auto child_pid = throwing_fork();
+       if (child_pid == 0) {
+               const auto mount_exe = "/usr/bin/mount"sv;
+
+               execl
+                       ( mount_exe.data(), mount_exe.data() /* argv[0] convention */
+                       , "-o", "loop"
+                       , image_path.c_str()
+                       , mount_path.c_str()
+                       , (char *) NULL
+               );
+
+               _exit(1);
+       } else {
+               throw_if_child_failed(child_pid, "mount failed!");
+       }
+}
+
+static void do_umount(const fs::path& path)
+{
+       const auto child_pid = throwing_fork();
+       if (child_pid == 0) {
+               const auto umount_exe = "/usr/bin/umount"sv;
+
+               execl
+                       ( umount_exe.data(), umount_exe.data() /* argv[0] convention */
+                       , path.c_str()
+                       , (char *) NULL
+               );
+
+               _exit(1);
+       } else {
+               throw_if_child_failed(child_pid, "umount failed!");
+       }
+}
+
+fs::path DirBackendAddFixedSize::AddSubsessionPrepare (const fs::path& subsession_path, int uid, int gid) const
+{
+       /* Work off a temp image first so that the "real" image
+        * can appear full-fledged atomically via rename. */
+       auto image_path = DirBackendFixedSize::GetImagePathFromSubsessionPath(subsession_path);
+       image_path.replace_filename(TMP_NEW_PREFIX + image_path.filename().native());
+
+       do_mkfs(image_path, uid, gid, size_kB);
+
+       auto tmp_subsession_path = subsession_path;
+       tmp_subsession_path.replace_filename(TMP_NEW_PREFIX + subsession_path.filename().native());
+
+       try {
+               fs::create_directory(tmp_subsession_path);
+       } catch (const std::exception& ex) {
+               fs::remove(image_path);
+               throw;
+       }
+
+       try {
+               do_mount(image_path, tmp_subsession_path);
+       } catch (const std::exception& ex) {
+               fs::remove(tmp_subsession_path);
+               fs::remove(image_path);
+               throw;
+       }
+
+       return tmp_subsession_path;
+}
+
+void DirBackendAddFixedSize::AddSubsessionCleanupFailure (const fs::path& tmpdir_path, const fs::path& subsession_path) const
+{
+       auto tmp_image_path = DirBackendFixedSize::GetImagePathFromSubsessionPath(subsession_path);
+       tmp_image_path.replace_filename(TMP_NEW_PREFIX + tmp_image_path.filename().native());
+
+       fs::remove(tmpdir_path);
+       fs::remove(tmp_image_path);
+}
+
+void DirBackendAddFixedSize::AddSubsessionFinalize (const fs::path& tmpdir_path, const fs::path& subsession_path) const
+{
+       try {
+               do_umount(tmpdir_path);
+               fs::remove(tmpdir_path);
+       } catch (const std::exception& ex) {
+               LOGE("umount & rmdir (%s) failed!", tmpdir_path.c_str());
+               throw;
+       }
+
+       const auto image_path = DirBackendFixedSize::GetImagePathFromSubsessionPath(subsession_path);
+       auto temp_image_path = image_path;
+       temp_image_path.replace_filename(TMP_NEW_PREFIX + image_path.filename().native());
+       fs::rename(temp_image_path, image_path);
+
+       /* Order matters - handle .img first and the directory last,
+        * since all other logic assumes that the existence of the dir
+        * signifies that a valid subsession exists. */
+       fs::create_directory(subsession_path);
+}
+
+void DirBackendFixedSize::RemoveSubsession (const fs::path& subsession_path) const
+{
+       fs::remove(subsession_path);
+
+       const auto image_path = GetImagePathFromSubsessionPath(subsession_path);
+       fs::remove(image_path);
+}
+
+void DirBackendFixedSize::SwitchSubsessionAway (const std::filesystem::path& subsession_path) const
+{
+       /* Note, we cannot keep the subsession mounted permanently,
+        * from Add until Remove, because reboots happen. */
+       do_umount(subsession_path);
+}
+
+void DirBackendFixedSize::SwitchSubsessionInto (const std::filesystem::path& subsession_path) const
+{
+       const auto image_path = GetImagePathFromSubsessionPath(subsession_path);
+       do_mount(image_path, subsession_path);
+}
diff --git a/src/service/src/dir_backend_fixed_size.hpp b/src/service/src/dir_backend_fixed_size.hpp
new file mode 100644 (file)
index 0000000..8cdca7f
--- /dev/null
@@ -0,0 +1,24 @@
+#pragma once
+
+#include "dir_backend.hpp"
+
+#include <cstdint>
+
+namespace fs = std::filesystem;
+
+struct DirBackendFixedSize : public DirBackend {
+       void RemoveSubsession (const fs::path& subsession_path) const override;
+       void SwitchSubsessionAway (const fs::path& subsession_path) const override;
+       void SwitchSubsessionInto (const fs::path& subsession_path) const override;
+
+       static fs::path GetImagePathFromSubsessionPath(fs::path subsession_path);
+};
+
+struct DirBackendAddFixedSize : public DirBackendAdd {
+       uint32_t size_kB;
+       DirBackendAddFixedSize(uint32_t s) : size_kB(s) { }
+
+       fs::path AddSubsessionPrepare (const fs::path& subsession_path, int uid, int gid) const override;
+       void AddSubsessionFinalize (const fs::path& tmpdir_path, const fs::path& subsession_path) const override;
+       void AddSubsessionCleanupFailure (const fs::path& tmpdir_path, const fs::path& subsession_path) const override;
+};
index 1b1df6d16697c4672aef891414edadabb18db039..87c26a19a1be442f17d45652b95fee722eb47d4d 100644 (file)
@@ -34,6 +34,7 @@
 #include "fs_helpers.hpp"
 
 #include "dir_backend_regular_dir.hpp"
+#include "dir_backend_fixed_size.hpp"
 
 namespace fs = std::filesystem;
 using namespace std::string_literals;
@@ -251,6 +252,10 @@ void add_user_subsession(const int session_uid, const std::string_view subsessio
 
 static const DirBackend& GetBackendOfSubsession(const fs::path& path)
 {
+       static const DirBackendFixedSize dbfs;
+       if (fs::exists(DirBackendFixedSize::GetImagePathFromSubsessionPath(path)))
+               return dbfs;
+
        static const DirBackendRegularDir dbrd;
        return dbrd;
 }