--- /dev/null
+/* 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);
+}