Fix static analysis issue
[platform/core/security/ode.git] / server / internal-encryption.cpp
index ed5b6fc..58edac5 100644 (file)
@@ -1,5 +1,5 @@
 /*
- *  Copyright (c) 2015-2017 Samsung Electronics Co., Ltd All Rights Reserved
+ *  Copyright (c) 2015-2023 Samsung Electronics Co., Ltd All Rights Reserved
  *
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
 #include <unistd.h>
 #include <sys/mount.h>
 #include <sys/reboot.h>
+#include <sys/wait.h>
 #include <limits.h>
 #include <stdlib.h>
 #include <string.h>
 
 #include <vconf.h>
 #include <tzplatform_config.h>
+#include <device/power.h>
 #include <klay/process.h>
 #include <klay/file-user.h>
 #include <klay/filesystem.h>
 #include <klay/dbus/connection.h>
+#include <klay/dbus/signal.h>
 #include <klay/error.h>
+#include <systemd/sd-bus.h>
 
 #include "misc.h"
 #include "logger.h"
 #include "ext4-tool.h"
 #include "internal-encryption.h"
 #include "internal-encryption-common.h"
+#include "upgrade-support.h"
+#include "file-footer.h"
 
 namespace ode {
 
 namespace {
 
 const char *PRIVILEGE_PLATFORM = "http://tizen.org/privilege/internal/default/platform";
+const mode_t MODE_0640 = S_IRUSR | S_IWUSR | S_IRGRP;
+const std::string ODE_OBJECT_PATH = "/Org/Tizen/OnDeviceEncryption";
+const std::string ODE_INTERFACE_EVENT = "org.tizen.OnDeviceEncryption.Event";
+const std::string ODE_SIGNAL_NAME = "unmount";
+const std::string manifest =
+       "<node>"
+       "       <interface name='" + ODE_INTERFACE_EVENT + "'>"
+       "       <signal name='" + ODE_SIGNAL_NAME + "'>"
+       "       </signal>"
+       "       </interface>"
+       "</node>";
 
 // watches systemd jobs
 class JobWatch {
@@ -101,7 +118,19 @@ JobWatch::~JobWatch() {
 bool JobWatch::waitForJob(const std::string& job) {
        while(true) {
                std::unique_lock<std::mutex> lock(jobsMutex);
-               jobsCv.wait(lock, [this]{ return !removedJobs.empty(); });
+               bool timeout = true;
+               auto sec = std::chrono::seconds(1);
+               jobsCv.wait_for(lock, 5*sec, [this, &timeout]{
+                               if (!removedJobs.empty()) {
+                                       timeout = false;
+                                       return true;
+                               }
+                               return false;});
+
+               if (timeout) {
+                       ERROR(SINK, "job: " + job + ", result: time out");
+                       return false;
+               }
 
                while(!removedJobs.empty()) {
                        bool match = (removedJobs.front().job == job);
@@ -120,7 +149,7 @@ void JobWatch::jobRemoved(const dbus::Variant& parameters)
        const char* unit;
        const char* result;
        parameters.get("(uoss)", &id, &job, &unit, &result);
-       INFO(SINK, "id:" + std::to_string(id) + " job:" + job + " unit:" + unit + " result:" + result);
+       WARN(SINK, "id:" + std::to_string(id) + " job:" + job + " unit:" + unit + " result:" + result);
 
        {
                std::lock_guard<std::mutex> guard(jobsMutex);
@@ -129,94 +158,112 @@ void JobWatch::jobRemoved(const dbus::Variant& parameters)
        jobsCv.notify_one();
 }
 
-void stopKnownSystemdUnits()
+void stopSystemdUnits()
 {
-       std::vector<std::string> knownSystemdUnits;
        dbus::Connection& systemDBus = dbus::Connection::getSystem();
        dbus::VariantIterator iter;
+       std::vector<std::string> preprocessUnits;
+       std::set<std::string> unitsToStop;
 
-       systemDBus.methodcall("org.freedesktop.systemd1",
-                                                       "/org/freedesktop/systemd1",
-                                                       "org.freedesktop.systemd1.Manager",
-                                                       "ListUnits",
-                                                       -1, "(a(ssssssouso))", "")
-                                                               .get("(a(ssssssouso))", &iter);
+       try {
+               systemDBus.emitSignal("",
+                                               ODE_OBJECT_PATH,
+                                               ODE_INTERFACE_EVENT,
+                                               ODE_SIGNAL_NAME, "()", nullptr);
+       } catch (runtime::Exception &e) {
+               ERROR(SINK, "Failed to emit signal : " + std::string(e.what()));
+       }
+
+       auto stopUnit = [&systemDBus](const std::string &unit) {
+               JobWatch watch(systemDBus);
+               WARN(SINK, "Stopping unit: " + unit);
+               const char* job = NULL;
+               try {
+                       systemDBus.methodcall("org.freedesktop.systemd1",
+                                                                       "/org/freedesktop/systemd1",
+                                                                       "org.freedesktop.systemd1.Manager",
+                                                                       "StopUnit",
+                                                                       -1, "(o)", "(ss)", unit.c_str(), "flush").get("(o)", &job);
+                       WARN(SINK, "Waiting for job: " + std::string(job));
+                       if (!watch.waitForJob(job))
+                               ERROR(SINK, "Stopping unit: " + unit + " failed");
+               } catch (runtime::Exception &e) {
+                       ERROR(SINK, std::string(e.what()));
+               }
+       };
+
+       try {
+               systemDBus.methodcall("org.freedesktop.systemd1",
+                                                               "/org/freedesktop/systemd1",
+                                                               "org.freedesktop.systemd1.Manager",
+                                                               "ListUnits",
+                                                               -1, "(a(ssssssouso))", "").get("(a(ssssssouso))", &iter);
+       } catch (runtime::Exception &e) {
+               ERROR(SINK, "Get list of systemd unit : " + std::string(e.what()));
+       }
 
        while (1) {
-               unsigned int dataUint;
+               unsigned int dataUnit;
                char *dataStr[9];
                int ret;
-
                ret = iter.get("(ssssssouso)", dataStr, dataStr + 1, dataStr + 2,
                                                dataStr + 3, dataStr + 4, dataStr + 5,
-                                               dataStr + 6, &dataUint, dataStr + 7,
-                                               dataStr + 8);
-
-               if (!ret) {
+                                               dataStr + 6, &dataUnit, dataStr + 7, dataStr + 8);
+               if (!ret)
                        break;
-               }
 
-               std::string unit(dataStr[0]);
-               if (unit == "security-manager.socket") {
-                       knownSystemdUnits.insert(knownSystemdUnits.begin(), unit);
-               } else if (unit.compare(0, 5, "user@") == 0 ||
-                       unit == "tlm.service" ||
-                       unit == "resourced.service" ||
-                       unit == "security-manager.service") {
-                       knownSystemdUnits.push_back(unit);
+               std::string unitName(dataStr[0]);
+               if (unitName == "security-manager.socket" ||
+                               unitName == "connman.socket" ||
+                               unitName == "msg-server.socket") {
+                       preprocessUnits.insert(preprocessUnits.begin(), unitName);
+               } else if (unitName.compare(0, 5, "user@") == 0 ||
+                               unitName == "tlm.service" ||
+                               unitName == "resourced.service" ||
+                               unitName == "security-manager.service") {
+                       preprocessUnits.push_back(unitName);
                }
        }
 
-       JobWatch watch(systemDBus);
-
-       for (const std::string& unit : knownSystemdUnits) {
-               INFO(SINK, "Stopping unit: " + unit);
-               const char* job = NULL;
-               systemDBus.methodcall("org.freedesktop.systemd1",
-                                                               "/org/freedesktop/systemd1",
-                                                               "org.freedesktop.systemd1.Manager",
-                                                               "StopUnit",
-                                                               -1, "(o)", "(ss)", unit.c_str(), "flush").get("(o)", &job);
-               INFO(SINK, "Waiting for job: " + std::string(job));
-               if (!watch.waitForJob(job))
-                       throw runtime::Exception("Stopping unit: " + unit + " failed");
+       for (auto &unit : preprocessUnits) {
+               stopUnit(unit);
        }
-}
-
-void stopDependedSystemdUnits()
-{
-       dbus::Connection& systemDBus = dbus::Connection::getSystem();
-       std::set<std::string> unitsToStop;
 
        for (pid_t pid : runtime::FileUser::getList(INTERNAL_PATH, true)) {
+               auto pgid = ::getpgid(pid);
+               if (pgid > 0 && pid != pgid) {
+                       WARN(SINK, "PGID doesn't match - pgid : " + std::to_string(pgid) + ", pid : " + std::to_string(pid));
+                       continue;
+               }
+
                try {
-                       char *unit;
+                       char *unit = nullptr;
                        systemDBus.methodcall("org.freedesktop.systemd1",
                                                                        "/org/freedesktop/systemd1",
                                                                        "org.freedesktop.systemd1.Manager",
                                                                        "GetUnitByPID",
                                                                        -1, "(o)", "(u)", (unsigned int)pid)
                                                                                .get("(o)", &unit);
-                       unitsToStop.insert(unit);
+
+                       char* tmp  = NULL;
+                       int err = sd_bus_path_decode(unit, "/org/freedesktop/systemd1/unit", &tmp);
+                       if (err <= 0 || tmp == NULL) {
+                               ERROR(SINK, "Failed to decode unit name: " + std::string(unit) + " error: " +
+                                           std::to_string(err));
+                               continue;
+                       }
+                       std::string unescapedName(tmp);
+                       free(tmp);
+
+                       unitsToStop.insert(unescapedName);
                } catch (runtime::Exception &e) {
-                       INFO(SINK, "Killing process: " + std::to_string(pid));
+                       ERROR(SINK, "Killing process: " + std::to_string(pid));
                        ::kill(pid, SIGKILL);
                }
        }
 
-       JobWatch watch(systemDBus);
-
-       for (const std::string& unit : unitsToStop) {
-               INFO(SINK, "Stopping unit: " + unit);
-               const char* job = NULL;
-               systemDBus.methodcall("org.freedesktop.systemd1",
-                                                               unit,
-                                                               "org.freedesktop.systemd1.Unit",
-                                                               "Stop",
-                                                               -1, "(o)", "(s)", "flush").get("(o)", &job);
-               INFO(SINK, "Waiting for job: " + std::string(job));
-               if (!watch.waitForJob(job))
-                       throw runtime::Exception("Stopping unit: " + unit + " failed");
+       for (auto unit : unitsToStop) {
+               stopUnit(unit);
        }
 }
 
@@ -237,7 +284,7 @@ void showProgressUI(const std::string type)
 
        INFO(SINK, "Waiting for job: " + std::string(job));
        if (!watch.waitForJob(job))
-               throw runtime::Exception("Starting unit: " + unit + " failed");
+               ERROR(SINK, "Starting unit: " + unit + " failed");
 }
 
 unsigned int getOptions()
@@ -266,27 +313,115 @@ void setOptions(unsigned int options)
        ::vconf_set_bool(VCONFKEY_ODE_FAST_ENCRYPTION, value);
 }
 
-void unmountInternalStorage(const std::string& source)
+void killProcesses(std::vector<const char*> &args)
 {
-       while(true) {
-               auto mntPaths = findMountPointsByDevice(source);
-               if(mntPaths.empty())
-                       break;
+       int pipeFd[2] = {0, };
+       pid_t pid = 0;
 
-               bool unmounted = true;
-               for(const auto& path : mntPaths) {
-                       INFO(SINK, "Unmounting " + path);
-                       if (::umount(path.c_str()) == -1) {
-                               // If it's busy or was already unmounted ignore the error
-                               if (errno != EBUSY && errno != EINVAL) {
-                                       throw runtime::Exception("umount() error: " + runtime::GetSystemErrorMessage());
-                               }
-                               unmounted = false;
-                       }
+       if (::pipe(pipeFd) < 0)
+               throw runtime::Exception("Failed to create pipe");
+
+       pid = ::fork();
+       if (pid < 0) {
+               ::close(pipeFd[0]);
+               ::close(pipeFd[1]);
+               throw runtime::Exception("Failed to fork");
+       }
+
+       if (pid == 0) {
+               ::close(pipeFd[0]);
+               if (::dup2(pipeFd[1], STDOUT_FILENO) < 0)
+                       ERROR(SINK, "Failed to copy STDOUT_FILENO");
+
+               if (::dup2(STDOUT_FILENO, STDERR_FILENO) < 0)
+                       ERROR(SINK, "Failed to copy STDERR_FILENO");
+
+               ::close(pipeFd[1]);
+
+               if (::execv(args[0], const_cast<char* const*>(args.data())) < 0)
+                       ERROR(SINK, "Failed to execute : " + std::to_string(errno));
+
+               std::quick_exit(EXIT_FAILURE);
+       }
+
+       ::close(pipeFd[1]);
+
+       for (int status = 0; ::waitpid(pid, &status, 0) == -1;) {
+               if (errno != EINTR) {
+                       ::close(pipeFd[0]);
+                       throw runtime::Exception("Failed to wait child process");
+               }
+       }
+
+       auto closeFile = [](FILE *fp) { ::fclose(fp); };
+       std::unique_ptr<FILE, decltype(closeFile)> fp(::fdopen(pipeFd[0], "r"), closeFile);
+       if (fp == nullptr) {
+               ::close(pipeFd[0]);
+               throw runtime::Exception("Failed to open pipe descriptor");
+       }
+
+       char *line = nullptr;
+       size_t len = 0;
+       try {
+               while ((::getline(&line, &len, fp.get())) != -1) {
+                       std::string log(line);
+                       log.erase(std::find_if(log.rbegin(), log.rend(),
+                                               [](unsigned char c){ return std::isgraph(c); }).base(), log.end());
+                       WARN(SINK, log);
                }
-               if (!unmounted)
-                       stopDependedSystemdUnits();
+       } catch (std::exception &e) {
+               ERROR(SINK, e.what());
        }
+       ::free(line);
+       ::close(pipeFd[0]);
+}
+
+bool isPartitionTerminated(const std::string &partition)
+{
+       bool ret = true;
+       const std::string cmd("fuser -m " + partition + " | grep -o '[0-9]*'");
+       char *line = nullptr;
+       size_t len = 0;
+
+       FILE *fp = ::popen(cmd.c_str(), "r");
+       if (fp == nullptr) {
+               ERROR(SINK, "Failed to get processes on partition");
+               return false;
+       }
+
+       if (::getline(&line, &len, fp) != -1)
+               ret = false;
+
+       ::free(line);
+       ::pclose(fp);
+
+       return ret;
+}
+
+void unmountInternalStorage(const std::string& source)
+{
+       if (::umount2("/opt/usr", MNT_DETACH) == -1) {
+               if (errno != EBUSY && errno != EINVAL) {
+                       throw runtime::Exception("umount() error : " + runtime::GetSystemErrorMessage());
+               }
+       }
+
+       do {
+               ::sync();
+               std::vector<const char*> args = {
+                       "/usr/bin/fuser", "-m", "-k", "-v", "-SIGTERM", source.c_str(), nullptr
+               };
+               try {
+                       killProcesses(args);
+                       ::usleep((useconds_t)((unsigned int)(500)*1000));
+
+                       args[4] = "-SIGKILL";
+                       killProcesses(args);
+                       ::usleep((useconds_t)((unsigned int)(200)*1000));
+               } catch (runtime::Exception &e) {
+                       ERROR(e.what());
+               }
+       } while (!isPartitionTerminated(source));
 }
 
 }
@@ -297,8 +432,11 @@ InternalEncryptionServer::InternalEncryptionServer(ServerContext& srv,
        keyServer(key)
 {
        server.expose(this, PRIVILEGE_PLATFORM, (int)(InternalEncryptionServer::setMountPassword)(std::string));
-       server.expose(this, PRIVILEGE_PLATFORM, (int)(InternalEncryptionServer::mount)());
+       server.expose(this, PRIVILEGE_PLATFORM, (int)(InternalEncryptionServer::mount)(std::vector<unsigned char>, unsigned int));
        server.expose(this, PRIVILEGE_PLATFORM, (int)(InternalEncryptionServer::umount)());
+       server.expose(this, PRIVILEGE_PLATFORM, (int)(InternalEncryptionServer::isMounted)());
+       server.expose(this, PRIVILEGE_PLATFORM, (int)(InternalEncryptionServer::prepareEncryption)(unsigned int));
+       server.expose(this, PRIVILEGE_PLATFORM, (int)(InternalEncryptionServer::prepareDecryption)());
        server.expose(this, PRIVILEGE_PLATFORM, (int)(InternalEncryptionServer::encrypt)(std::string, unsigned int));
        server.expose(this, PRIVILEGE_PLATFORM, (int)(InternalEncryptionServer::decrypt)(std::string));
        server.expose(this, "", (int)(InternalEncryptionServer::isPasswordInitialized)());
@@ -315,41 +453,82 @@ InternalEncryptionServer::InternalEncryptionServer(ServerContext& srv,
 
        std::string source = findDevPath();
 
-       engine.reset(new INTERNAL_ENGINE(
-               source, INTERNAL_PATH,
-               ProgressBar([](int v) {
-                       ::vconf_set_str(VCONFKEY_ODE_ENCRYPT_PROGRESS,
-                                                       std::to_string(v).c_str());
-               })
-       ));
+       if (getStateInternal() == State::Encrypted) {
+               //"error_partially_encrypted"
+               if (!FileFooter::exist(source) && !UpgradeSupport::checkUpgradeFlag()) {
+                       // Trigger key migration process
+                       UpgradeSupport::createUpgradeFlag();
+               }
+       }
+
+       engine.reset(new INTERNAL_ENGINE(source,
+                                        INTERNAL_PATH,
+                                        ProgressBar(VCONFKEY_ODE_ENCRYPT_PROGRESS)));
+
+       try {
+               dbus::Connection &systemDBus = dbus::Connection::getSystem();
+               systemDBus.registerObject(ODE_OBJECT_PATH, manifest, nullptr, nullptr);
+       } catch (runtime::Exception &e) {
+               ERROR(SINK, e.what());
+       }
 }
 
 InternalEncryptionServer::~InternalEncryptionServer()
 {
 }
 
+int InternalEncryptionServer::migrateMasterKey(const std::string& dev, const std::string& password)
+{
+       try {
+               BinaryData masterKey = UpgradeSupport::loadMasterKey(dev);
+
+               // encrypt the master key with given password
+               return keyServer.changePassword2(dev, masterKey, password);
+       } catch (const runtime::Exception&) {
+               ERROR(SINK, "Failed to load the master key stored during upgrade.");
+       }
+
+       return error::Unknown;
+}
+
 int InternalEncryptionServer::setMountPassword(const std::string& password)
 {
-       return keyServer.get(engine->getSource(), password, mountKey);
+       RequestLifetime rl(server);
+
+       const std::string& dev = engine->getSource();
+
+       // check if upgrade flag exists
+       if (UpgradeSupport::checkUpgradeFlag()) {
+               INFO(SINK, "Upgrade flag detected.");
+
+               int rc = migrateMasterKey(dev, password);
+               if (rc != error::None)
+                       return rc;
+               UpgradeSupport::removeUpgradeFlag();
+       }
+
+       return keyServer.get(dev, password, mountKey);
 }
 
-int InternalEncryptionServer::mount()
+int InternalEncryptionServer::mount(const std::vector<unsigned char> &mk, unsigned int options)
 {
-       if (mountKey.empty()) {
-               ERROR(SINK, "You need to call set_mount_password() first.");
+       RequestLifetime rl(server);
+
+       if (mountKey.empty() && mk.empty()) {
+               ERROR(SINK, "You need to set master key first.");
                return error::NoData;
        }
 
-       BinaryData key = mountKey;
+       BinaryData key = mk.empty() ? mountKey : mk;
        mountKey.clear();
 
-       if (getState() != State::Encrypted) {
-               INFO(SINK, "Cannot mount, SD partition's state incorrect.");
+       if (getStateInternal() != State::Encrypted) {
+               ERROR(SINK, "Cannot mount, SD partition's state incorrect.");
                return error::NoSuchDevice;
        }
 
        if (engine->isMounted()) {
-               INFO(SINK, "Partition already mounted.");
+               ERROR(SINK, "Partition already mounted.");
                return error::None;
        }
 
@@ -369,22 +548,39 @@ int InternalEncryptionServer::mount()
        return error::None;
 }
 
+int InternalEncryptionServer::isMounted()
+{
+       RequestLifetime rl(server);
+
+       int ret = 0;
+       try {
+               ret = engine->isMounted() ? 1 : 0;
+       } catch (runtime::Exception &e) {
+               ERROR(SINK, "Failed to access the mount flag");
+               return error::Unknown;
+       }
+       return ret;
+}
+
 int InternalEncryptionServer::umount()
 {
-       if (getState() != State::Encrypted) {
+       RequestLifetime rl(server);
+
+       if (getStateInternal() != State::Encrypted) {
                ERROR(SINK, "Cannot umount, partition's state incorrect.");
                return error::NoSuchDevice;
        }
 
        if (!engine->isMounted()) {
-               INFO(SINK, "Partition already umounted.");
+               ERROR(SINK, "Partition already umounted.");
                return error::None;
        }
 
        INFO(SINK, "Closing all processes using internal storage.");
        try {
-               stopDependedSystemdUnits();
+               stopSystemdUnits();
                INFO(SINK, "Umounting internal storage.");
+               unmountInternalStorage("/dev/mapper/userdata");
                engine->umount();
        } catch (runtime::Exception &e) {
                ERROR(SINK, "Umount failed: " + std::string(e.what()));
@@ -394,9 +590,62 @@ int InternalEncryptionServer::umount()
        return error::None;
 }
 
+int InternalEncryptionServer::prepareEncryption(unsigned int options)
+{
+       RequestLifetime rl(server);
+
+       if (getStateInternal() != State::Unencrypted) {
+               ERROR(SINK, "Cannot encrypt, partition's state incorrect.");
+               return error::NoSuchDevice;
+       }
+
+       try {
+               runtime::File file("/opt/etc/.odeprogress");
+               file.create(MODE_0640);
+       } catch (runtime::Exception &e) {
+               ERROR(SINK, "Failed to create the flag file: " + std::string(e.what()));
+               return error::Unknown;
+       }
+
+       setOptions(options & engine->getSupportedOptions());
+
+       ::vconf_set_str(VCONFKEY_ODE_CRYPTO_STATE, "prepared_encryption");
+       ::sync();
+       ::reboot(RB_AUTOBOOT);
+
+       return error::None;
+}
+
+int InternalEncryptionServer::prepareDecryption()
+{
+       RequestLifetime rl(server);
+
+       if (getStateInternal() != State::Encrypted) {
+               ERROR(SINK, "Cannot decrypt, partition's state incorrect.");
+               return error::NoSuchDevice;
+       }
+
+       try {
+               runtime::File file("/opt/etc/.odeprogress");
+               file.create(MODE_0640);
+       } catch (runtime::Exception &e) {
+               ERROR(SINK, "Failed to create the flag file: " + std::string(e.what()));
+               return error::Unknown;
+       }
+
+       ::vconf_set_str(VCONFKEY_ODE_CRYPTO_STATE, "prepared_decryption");
+       ::sync();
+       ::reboot(RB_AUTOBOOT);
+
+       return error::None;
+}
+
 int InternalEncryptionServer::encrypt(const std::string& password, unsigned int options)
 {
-       if (getState() != State::Unencrypted) {
+       RequestLifetime rl(server);
+
+       int state = getStateInternal();
+       if (state != State::Unencrypted && state != State::PreparedEncryption) {
                ERROR(SINK, "Cannot encrypt, partition's state incorrect.");
                return error::NoSuchDevice;
        }
@@ -406,34 +655,51 @@ int InternalEncryptionServer::encrypt(const std::string& password, unsigned int
        if (ret != error::None)
                return ret;
 
-       auto encryptWorker = [masterKey, options, this]() {
+       auto encryptWorker = [masterKey, options, this](RequestLifetime&& rl) {
                try {
+                       if (::device_power_request_lock(POWER_LOCK_DISPLAY, 0) != 0)
+                               ERROR(SINK, "Failed to request to lock display");
+
                        showProgressUI("encrypt");
                        ::sleep(1);
 
-                       INFO(SINK, "Closing all known systemd services that might be using internal storage.");
-                       stopKnownSystemdUnits();
-
-                       std::string source = engine->getSource();
-                       auto mntPaths = findMountPointsByDevice(source);
+                       runtime::File file("/opt/etc/.odeprogress");
+                       if (getStateInternal() == State::Unencrypted) {
+                               /* For backward compatibility */
+                               file.create(MODE_0640);
+                               std::string source = engine->getSource();
+                               auto mntPaths = findMountPointsByDevice(source);
 
-                       if (!mntPaths.empty()) {
-                               INFO(SINK, "Closing all processes using internal storage.");
-                               stopDependedSystemdUnits();
+                               if (!mntPaths.empty()) {
+                                       INFO(SINK, "Closing all processes using internal storage.");
+                                       stopSystemdUnits();
 
-                               INFO(SINK, "Unmounting internal storage.");
-                               unmountInternalStorage(source);
+                                       INFO(SINK, "Unmounting internal storage.");
+                                       unmountInternalStorage(source);
+                               }
+                               setOptions(options & engine->getSupportedOptions());
                        }
 
                        INFO(SINK, "Encryption started.");
                        ::vconf_set_str(VCONFKEY_ODE_CRYPTO_STATE, "error_partially_encrypted");
-                       engine->encrypt(masterKey, options);
-                       setOptions(options & getSupportedOptions());
+                       try {
+                               engine->encrypt(masterKey, getOptions());
+                       } catch (runtime::Exception &e) {
+                               ERROR(SINK, e.what());
+                               if (!engine->isStarted()) {
+                                       ::vconf_set_str(VCONFKEY_ODE_CRYPTO_STATE, "unencrypted");
+                                       file.remove();
+                               }
+                               ::sync();
+                               ::reboot(RB_AUTOBOOT);
+                       }
 
                        INFO(SINK, "Encryption completed.");
                        ::vconf_set_str(VCONFKEY_ODE_CRYPTO_STATE, "encrypted");
                        server.notify("InternalEncryptionServer::mount");
 
+                       file.remove();
+
                        INFO(SINK, "Syncing disk and rebooting.");
                        ::sync();
                        ::reboot(RB_AUTOBOOT);
@@ -442,7 +708,7 @@ int InternalEncryptionServer::encrypt(const std::string& password, unsigned int
                }
        };
 
-       std::thread asyncWork(encryptWorker);
+       std::thread asyncWork(encryptWorker, std::move(rl));
        asyncWork.detach();
 
        return error::None;
@@ -450,45 +716,70 @@ int InternalEncryptionServer::encrypt(const std::string& password, unsigned int
 
 int InternalEncryptionServer::decrypt(const std::string& password)
 {
-       if (getState() != State::Encrypted) {
+       RequestLifetime rl(server);
+
+       int state = getStateInternal();
+       if (state != State::Encrypted && state != State::PreparedDecryption) {
                ERROR(SINK, "Cannot decrypt, partition's state incorrect.");
                return error::NoSuchDevice;
        }
 
+       // check if key migration is needed
+       if (UpgradeSupport::checkUpgradeFlag()) {
+               INFO(SINK, "Upgrade flag detected.");
+               const std::string& dev = engine->getSource();
+               int rc = migrateMasterKey(dev, password);
+               if (rc == error::None)
+                       UpgradeSupport::removeUpgradeFlag();
+       }
+
        BinaryData masterKey;
        int ret = keyServer.get(engine->getSource(), password, masterKey);
        if (ret != error::None)
                return ret;
 
-       auto decryptWorker = [masterKey, this]() {
+       auto decryptWorker = [masterKey, this](RequestLifetime&& rl) {
                try {
+                       if (::device_power_request_lock(POWER_LOCK_DISPLAY, 0) != 0)
+                               ERROR(SINK, "Failed to request to lock display");
+
                        showProgressUI("decrypt");
                        ::sleep(1);
 
-                       if (engine->isMounted()) {
-                               INFO(SINK, "Closing all known systemd services that might be using internal storage.");
-                               stopKnownSystemdUnits();
-                               INFO(SINK, "Closing all processes using internal storage.");
-                               stopDependedSystemdUnits();
-
-                               INFO(SINK, "Umounting internal storage.");
-                               while (1) {
-                                       try {
-                                               engine->umount();
-                                               break;
-                                       } catch (runtime::Exception& e) {
-                                               stopDependedSystemdUnits();
-                                       }
+                       runtime::File file("/opt/etc/.odeprogress");
+                       if (getStateInternal() == State::Encrypted) {
+                               /* For backward compatibility */
+                               file.create(MODE_0640);
+
+                               if (engine->isMounted()) {
+                                       INFO(SINK, "Closing all processes using internal storage.");
+                                       stopSystemdUnits();
+
+                                       INFO(SINK, "Unmounting internal storage.");
+                                       unmountInternalStorage("/dev/mapper/userdata");
+                                       engine->umount();
                                }
                        }
 
                        INFO(SINK, "Decryption started.");
                        ::vconf_set_str(VCONFKEY_ODE_CRYPTO_STATE, "error_partially_decrypted");
-                       engine->decrypt(masterKey, getOptions());
+                       try {
+                               engine->decrypt(masterKey, getOptions());
+                       } catch (runtime::Exception &e) {
+                               ERROR(SINK, e.what());
+                               if (!engine->isStarted()) {
+                                       ::vconf_set_str(VCONFKEY_ODE_CRYPTO_STATE, "encrypted");
+                                       file.remove();
+                               }
+                               ::sync();
+                               ::reboot(RB_AUTOBOOT);
+                       }
 
                        INFO(SINK, "Decryption complete.");
                        ::vconf_set_str(VCONFKEY_ODE_CRYPTO_STATE, "unencrypted");
 
+                       file.remove();
+
                        INFO(SINK, "Syncing disk and rebooting.");
                        ::sync();
                        ::reboot(RB_AUTOBOOT);
@@ -497,7 +788,7 @@ int InternalEncryptionServer::decrypt(const std::string& password)
                }
        };
 
-       std::thread asyncWork(decryptWorker);
+       std::thread asyncWork(decryptWorker, std::move(rl));
        asyncWork.detach();
 
        return error::None;
@@ -505,17 +796,25 @@ int InternalEncryptionServer::decrypt(const std::string& password)
 
 int InternalEncryptionServer::recovery()
 {
-       int state = getState();
+       RequestLifetime rl(server);
+
+       int state = getStateInternal();
 
        if (state == State::Unencrypted)
                return error::NoSuchDevice;
+       if (state == State::PreparedEncryption) {
+               ::vconf_set_str(VCONFKEY_ODE_CRYPTO_STATE, "unencrypted");
+               ::sync();
+               return error::None;
+       }
+       if (state == State::PreparedDecryption) {
+               ::vconf_set_str(VCONFKEY_ODE_CRYPTO_STATE, "encrypted");
+               ::sync();
+               return error::None;
+       }
 
-       if (state == State::Corrupted)
-               Ext4Tool::mkfs(engine->getSource());
-
-       std::fstream fs;
-       fs.open("/opt/.factoryreset", std::ios::out);
-       fs.close();
+       runtime::File file("/opt/.factoryreset");
+       file.create(MODE_0640);
 
        ::sync();
        try {
@@ -559,6 +858,27 @@ int InternalEncryptionServer::verifyPassword(const std::string& password)
 
 int InternalEncryptionServer::getState()
 {
+       RequestLifetime rl(server);
+
+       return getStateInternal();
+}
+
+unsigned int InternalEncryptionServer::getSupportedOptions()
+{
+       RequestLifetime rl(server);
+
+       return engine->getSupportedOptions();
+}
+
+std::string InternalEncryptionServer::getDevicePath() const
+{
+       RequestLifetime rl(server);
+
+       return engine->getSource();
+}
+
+int InternalEncryptionServer::getStateInternal() const
+{
        char *value = ::vconf_get_str(VCONFKEY_ODE_CRYPTO_STATE);
        if (value == NULL) {
                throw runtime::Exception("Failed to get vconf value.");
@@ -571,20 +891,14 @@ int InternalEncryptionServer::getState()
                return State::Encrypted;
        else if (valueStr == "unencrypted")
                return State::Unencrypted;
+       else if (valueStr == "prepared_encryption")
+               return State::PreparedEncryption;
+       else if (valueStr == "prepared_decryption")
+               return State::PreparedDecryption;
        else if (valueStr == "error_partially_encrypted" || valueStr == "error_partially_decrypted")
                return State::Corrupted;
 
-       return State::Invalid;
-}
-
-unsigned int InternalEncryptionServer::getSupportedOptions()
-{
-       return engine->getSupportedOptions();
-}
-
-std::string InternalEncryptionServer::getDevicePath() const
-{
-       return engine->getSource();
+       return State::NotSupported;
 }
 
 } // namespace ode