* Separate stdout and stderr buffers: vdc's return codes get sent to stdout, but the possible presence of other error messages in the output buffer will cause a valid return from vdc not to be parsed properly, and subsequent decryption to fail due to "misunderstood" return code. eg on the U11+ (htc_ocm) libc will generate an error to stderr due to a missing property area resulting a proper connection to vold being incorrectly parsed, and breaking decryption. * Improve logging. Change-Id: I57987ebe4ee6754a78e79ca177506098f8301f8f
1154 lines
33 KiB
C++
1154 lines
33 KiB
C++
/*
|
|
Copyright 2017 TeamWin
|
|
This file is part of TWRP/TeamWin Recovery Project.
|
|
|
|
TWRP is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
TWRP is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with TWRP. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <sys/mount.h>
|
|
#include <sys/time.h>
|
|
#include <dirent.h>
|
|
#include <fnmatch.h>
|
|
|
|
#include <signal.h>
|
|
#include <sys/types.h>
|
|
#include <sys/wait.h>
|
|
|
|
#include <fstream>
|
|
#include <string>
|
|
#include <vector>
|
|
#include <sstream>
|
|
|
|
#ifdef _USING_SHORT_SERVICE_NAMES
|
|
#include <map>
|
|
#endif
|
|
|
|
#include "../../partitions.hpp"
|
|
#include "../../twrp-functions.hpp"
|
|
|
|
using namespace std;
|
|
|
|
extern "C" {
|
|
#include <cutils/properties.h>
|
|
}
|
|
|
|
#include "vold_decrypt.h"
|
|
|
|
namespace {
|
|
|
|
/* Timeouts as defined by ServiceManager */
|
|
|
|
/* The maximum amount of time to wait for a service to start or stop,
|
|
* in micro-seconds (really an approximation) */
|
|
#define SLEEP_MAX_USEC 2000000 /* 2 seconds */
|
|
/* The minimal sleeping interval between checking for the service's state
|
|
* when looping for SLEEP_MAX_USEC */
|
|
#define SLEEP_MIN_USEC 200000 /* 200 msec */
|
|
|
|
|
|
/* vold response codes defined in ResponseCode.h */
|
|
// 200 series - Requested action has been successfully completed
|
|
#define COMMAND_OKAY 200
|
|
#define PASSWORD_TYPE_RESULT 213
|
|
|
|
|
|
#define LOGINFO(...) do { printf(__VA_ARGS__); if (fp_kmsg) { fprintf(fp_kmsg, "[VOLD_DECRYPT]I:" __VA_ARGS__); fflush(fp_kmsg); } } while (0)
|
|
#define LOGKMSG(...) do { if (fp_kmsg) { fprintf(fp_kmsg, "[VOLD_DECRYPT]K:" __VA_ARGS__); fflush(fp_kmsg); } } while (0)
|
|
#define LOGERROR(...) do { printf(__VA_ARGS__); if (fp_kmsg) { fprintf(fp_kmsg, "[VOLD_DECRYPT]E:" __VA_ARGS__); fflush(fp_kmsg); } } while (0)
|
|
|
|
FILE *fp_kmsg = NULL;
|
|
|
|
|
|
/* Debugging Functions */
|
|
#ifdef TW_CRYPTO_SYSTEM_VOLD_DEBUG
|
|
|
|
#ifndef VD_STRACE_BIN
|
|
#define VD_STRACE_BIN "/sbin/strace"
|
|
#endif
|
|
|
|
bool has_strace = false;
|
|
pid_t pid_strace = 0;
|
|
|
|
void Strace_init_Start(void) {
|
|
has_strace = TWFunc::Path_Exists(VD_STRACE_BIN);
|
|
if (!has_strace) {
|
|
LOGINFO("strace binary (%s) not found, disabling strace in vold_decrypt!\n", VD_STRACE_BIN);
|
|
return;
|
|
}
|
|
|
|
pid_t pid;
|
|
switch(pid = fork())
|
|
{
|
|
case -1:
|
|
LOGKMSG("forking strace_init failed: %d (%s)!\n", errno, strerror(errno));
|
|
return;
|
|
case 0: // child
|
|
execl(VD_STRACE_BIN, "strace", "-q", "-tt", "-ff", "-v", "-y", "-s", "1000", "-o", "/tmp/strace_init.log", "-p", "1" , NULL);
|
|
LOGKMSG("strace_init fork failed: %d (%s)!\n", errno, strerror(errno));
|
|
exit(-1);
|
|
default:
|
|
LOGKMSG("Starting strace_init (pid=%d)\n", pid);
|
|
pid_strace = pid;
|
|
return;
|
|
}
|
|
}
|
|
|
|
void Strace_init_Stop(void) {
|
|
if (pid_strace > 0) {
|
|
LOGKMSG("Stopping strace_init (pid=%d)\n", pid_strace);
|
|
int timeout;
|
|
int status;
|
|
pid_t retpid = waitpid(pid_strace, &status, WNOHANG);
|
|
|
|
kill(pid_strace, SIGTERM);
|
|
for (timeout = 5; retpid == 0 && timeout; --timeout) {
|
|
sleep(1);
|
|
retpid = waitpid(pid_strace, &status, WNOHANG);
|
|
}
|
|
if (retpid)
|
|
LOGKMSG("strace_init terminated successfully\n");
|
|
else {
|
|
// SIGTERM didn't work, kill it instead
|
|
kill(pid_strace, SIGKILL);
|
|
for (timeout = 5; retpid == 0 && timeout; --timeout) {
|
|
sleep(1);
|
|
retpid = waitpid(pid_strace, &status, WNOHANG);
|
|
}
|
|
if (retpid)
|
|
LOGKMSG("strace_init killed successfully\n");
|
|
else
|
|
LOGKMSG("strace_init took too long to kill, may be a zombie process\n");
|
|
}
|
|
}
|
|
}
|
|
#endif // TW_CRYPTO_SYSTEM_VOLD_DEBUG
|
|
|
|
|
|
/* Convert a binary key of specified length into an ascii hex string equivalent,
|
|
* without the leading 0x and with null termination
|
|
*
|
|
* Original code from cryptfs.c
|
|
*/
|
|
string convert_key_to_hex_ascii(const string& master_key) {
|
|
size_t i;
|
|
unsigned char nibble;
|
|
string master_key_ascii = "";
|
|
|
|
for (i = 0; i < master_key.size(); ++i) {
|
|
nibble = (master_key[i] >> 4) & 0xf;
|
|
nibble += nibble > 9 ? 0x57 : 0x30;
|
|
master_key_ascii += nibble;
|
|
|
|
nibble = master_key[i] & 0xf;
|
|
nibble += nibble > 9 ? 0x57 : 0x30;
|
|
master_key_ascii += nibble;
|
|
}
|
|
|
|
return master_key_ascii;
|
|
}
|
|
|
|
/* Helper Functions */
|
|
#define PATH_EXISTS(path) (access(path, F_OK) >= 0)
|
|
|
|
int vrename(const string& oldname, const string& newname, bool verbose = false) {
|
|
const char *old_name = oldname.c_str();
|
|
const char *new_name = newname.c_str();
|
|
|
|
if (!PATH_EXISTS(old_name))
|
|
return 0;
|
|
|
|
if (rename(old_name, new_name) < 0) {
|
|
LOGERROR("Moving %s to %s failed: %d (%s)\n", old_name, new_name, errno, strerror(errno));
|
|
return -1;
|
|
} else if (verbose)
|
|
LOGINFO("Renamed %s to %s\n", old_name, new_name);
|
|
else
|
|
LOGKMSG("Renamed %s to %s\n", old_name, new_name);
|
|
return 0;
|
|
}
|
|
|
|
int vsymlink(const string& oldname, const string& newname, bool verbose = false) {
|
|
const char *old_name = oldname.c_str();
|
|
const char *new_name = newname.c_str();
|
|
|
|
if (!PATH_EXISTS(old_name))
|
|
return 0;
|
|
|
|
if (symlink(old_name, new_name) < 0) {
|
|
LOGERROR("Symlink %s -> %s failed: %d (%s)\n", new_name, old_name, errno, strerror(errno));
|
|
return -1;
|
|
} else if (verbose)
|
|
LOGINFO("Symlinked %s -> %s\n", new_name, old_name);
|
|
else
|
|
LOGKMSG("Symlinked %s -> %s\n", new_name, old_name);
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* Properties and Services Functions */
|
|
string Wait_For_Property(const string& property_name, int utimeout = SLEEP_MAX_USEC, const string& expected_value = "not_empty") {
|
|
char prop_value[PROPERTY_VALUE_MAX];
|
|
|
|
if (expected_value == "not_empty") {
|
|
while (utimeout > 0) {
|
|
property_get(property_name.c_str(), prop_value, "error");
|
|
if (strcmp(prop_value, "error") != 0)
|
|
break;
|
|
LOGKMSG("waiting for %s to get set\n", property_name.c_str());
|
|
utimeout -= SLEEP_MIN_USEC;
|
|
usleep(SLEEP_MIN_USEC);;
|
|
}
|
|
}
|
|
else {
|
|
while (utimeout > 0) {
|
|
property_get(property_name.c_str(), prop_value, "error");
|
|
if (strcmp(prop_value, expected_value.c_str()) == 0)
|
|
break;
|
|
LOGKMSG("waiting for %s to change from '%s' to '%s'\n", property_name.c_str(), prop_value, expected_value.c_str());
|
|
utimeout -= SLEEP_MIN_USEC;
|
|
usleep(SLEEP_MIN_USEC);;
|
|
}
|
|
}
|
|
property_get(property_name.c_str(), prop_value, "error");
|
|
|
|
return prop_value;
|
|
}
|
|
|
|
string Get_Service_State(const string& initrc_svc) {
|
|
char prop_value[PROPERTY_VALUE_MAX];
|
|
string init_svc = "init.svc." + initrc_svc;
|
|
property_get(init_svc.c_str(), prop_value, "error");
|
|
return prop_value;
|
|
}
|
|
|
|
bool Service_Exists(const string& initrc_svc) {
|
|
return (Get_Service_State(initrc_svc) != "error");
|
|
}
|
|
|
|
bool Is_Service_Running(const string& initrc_svc) {
|
|
return (Get_Service_State(initrc_svc) == "running");
|
|
}
|
|
|
|
bool Is_Service_Stopped(const string& initrc_svc) {
|
|
return (Get_Service_State(initrc_svc) == "stopped");
|
|
}
|
|
|
|
bool Start_Service(const string& initrc_svc, int utimeout = SLEEP_MAX_USEC) {
|
|
string res = "error";
|
|
string init_svc = "init.svc." + initrc_svc;
|
|
|
|
property_set("ctl.start", initrc_svc.c_str());
|
|
|
|
res = Wait_For_Property(init_svc, utimeout, "running");
|
|
|
|
LOGINFO("Start service %s: %s.\n", initrc_svc.c_str(), res.c_str());
|
|
|
|
return (res == "running");
|
|
}
|
|
|
|
bool Stop_Service(const string& initrc_svc, int utimeout = SLEEP_MAX_USEC) {
|
|
string res = "error";
|
|
|
|
if (Service_Exists(initrc_svc)) {
|
|
string init_svc = "init.svc." + initrc_svc;
|
|
property_set("ctl.stop", initrc_svc.c_str());
|
|
res = Wait_For_Property(init_svc, utimeout, "stopped");
|
|
LOGINFO("Stop service %s: %s.\n", initrc_svc.c_str(), res.c_str());
|
|
}
|
|
|
|
return (res == "stopped");
|
|
}
|
|
|
|
|
|
/* Vendor, Firmware and fstab symlink Functions */
|
|
bool is_Vendor_Mounted(void) {
|
|
static int is_mounted = -1;
|
|
if (is_mounted < 0)
|
|
is_mounted = PartitionManager.Is_Mounted_By_Path("/vendor") ? 1 : 0;
|
|
return is_mounted;
|
|
}
|
|
|
|
bool is_Firmware_Mounted(void) {
|
|
static int is_mounted = -1;
|
|
if (is_mounted < 0)
|
|
is_mounted = PartitionManager.Is_Mounted_By_Path("/firmware") ? 1 : 0;
|
|
return is_mounted;
|
|
}
|
|
|
|
bool will_VendorBin_Be_Symlinked(void) {
|
|
return (!is_Vendor_Mounted() && TWFunc::Path_Exists("/system/vendor"));
|
|
}
|
|
|
|
bool Symlink_Vendor_Folder(void) {
|
|
bool is_vendor_symlinked = false;
|
|
|
|
if (is_Vendor_Mounted()) {
|
|
LOGINFO("vendor partition mounted, skipping /vendor substitution\n");
|
|
}
|
|
else if (TWFunc::Path_Exists("/system/vendor")) {
|
|
LOGINFO("Symlinking vendor folder...\n");
|
|
if (!TWFunc::Path_Exists("/vendor") || vrename("/vendor", "/vendor-orig") == 0) {
|
|
TWFunc::Recursive_Mkdir("/vendor/firmware/keymaster");
|
|
vsymlink("/system/vendor/lib64", "/vendor/lib64", true);
|
|
vsymlink("/system/vendor/lib", "/vendor/lib", true);
|
|
vsymlink("/system/vendor/bin", "/vendor/bin", true);
|
|
is_vendor_symlinked = true;
|
|
property_set("vold_decrypt.symlinked_vendor", "1");
|
|
}
|
|
}
|
|
return is_vendor_symlinked;
|
|
}
|
|
|
|
void Restore_Vendor_Folder(void) {
|
|
property_set("vold_decrypt.symlinked_vendor", "0");
|
|
TWFunc::removeDir("/vendor", false);
|
|
vrename("/vendor-orig", "/vendor");
|
|
}
|
|
|
|
bool Symlink_Firmware_Folder(void) {
|
|
bool is_firmware_symlinked = false;
|
|
|
|
if (is_Firmware_Mounted()) {
|
|
LOGINFO("firmware partition mounted, skipping /firmware substitution\n");
|
|
}
|
|
else {
|
|
LOGINFO("Symlinking firmware folder...\n");
|
|
if (!TWFunc::Path_Exists("/firmware") || vrename("/firmware", "/firmware-orig") == 0) {
|
|
TWFunc::Recursive_Mkdir("/firmware/image");
|
|
is_firmware_symlinked = true;
|
|
property_set("vold_decrypt.symlinked_firmware", "1");
|
|
}
|
|
}
|
|
return is_firmware_symlinked;
|
|
}
|
|
|
|
void Restore_Firmware_Folder(void) {
|
|
property_set("vold_decrypt.symlinked_firmware", "0");
|
|
TWFunc::removeDir("/firmware", false);
|
|
vrename("/firmware-orig", "/firmware");
|
|
}
|
|
|
|
int Find_Firmware_Files(const string& Path, vector<string> *FileList) {
|
|
int ret;
|
|
DIR* d;
|
|
struct dirent* de;
|
|
string FileName;
|
|
|
|
d = opendir(Path.c_str());
|
|
if (d == NULL) {
|
|
closedir(d);
|
|
return -1;
|
|
}
|
|
while ((de = readdir(d)) != NULL) {
|
|
if (de->d_type == DT_DIR) {
|
|
if (strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0)
|
|
continue;
|
|
FileName = Path + "/" + de->d_name;
|
|
ret = Find_Firmware_Files(FileName, FileList);
|
|
if (ret < 0)
|
|
return -1;
|
|
} else if (de->d_type == DT_REG) {
|
|
if (fnmatch("keymaste*.*", de->d_name, 0) == 0 || fnmatch("cmnlib.*", de->d_name, 0) == 0) {
|
|
FileName = Path + "/" + de->d_name;
|
|
FileList->push_back(FileName);
|
|
}
|
|
}
|
|
}
|
|
closedir(d);
|
|
return 0;
|
|
}
|
|
|
|
void Symlink_Firmware_Files(bool is_vendor_symlinked, bool is_firmware_symlinked) {
|
|
if (!is_vendor_symlinked && !is_firmware_symlinked)
|
|
return;
|
|
|
|
LOGINFO("Symlinking firmware files...\n");
|
|
|
|
vector<string> FirmwareFiles;
|
|
Find_Firmware_Files("/system", &FirmwareFiles);
|
|
|
|
for (size_t i = 0; i < FirmwareFiles.size(); ++i) {
|
|
string base_name = TWFunc::Get_Filename(FirmwareFiles[i]);
|
|
|
|
if (is_firmware_symlinked)
|
|
vsymlink(FirmwareFiles[i], "/firmware/image/" + base_name);
|
|
|
|
if (is_vendor_symlinked) {
|
|
vsymlink(FirmwareFiles[i], "/vendor/firmware/" + base_name);
|
|
vsymlink(FirmwareFiles[i], "/vendor/firmware/keymaster/" + base_name);
|
|
}
|
|
}
|
|
LOGINFO("%d file(s) symlinked.\n", (int)FirmwareFiles.size());
|
|
}
|
|
|
|
// Android 8.0 fs_mgr checks for "/sbin/recovery", in which case it will
|
|
// use /etc/recovery.fstab -> symlink it temporarily. Reference:
|
|
// https://android.googlesource.com/platform/system/core/+/android-8.0.0_r17/fs_mgr/fs_mgr_fstab.cpp#693
|
|
bool Symlink_Recovery_Fstab(void) {
|
|
bool is_fstab_symlinked = false;
|
|
|
|
if (vrename("/etc/recovery.fstab", "/etc/recovery-fstab-orig") == 0) {
|
|
is_fstab_symlinked = true;
|
|
|
|
// now attempt to symlink to /fstab.{ro.hardware}, but even if that
|
|
// fails, keep TWRP's fstab hidden since it cannot be parsed by fs_mgr
|
|
char prop_value[PROPERTY_VALUE_MAX];
|
|
property_get("ro.hardware", prop_value, "error");
|
|
if (strcmp(prop_value, "error")) {
|
|
string fstab_device = "/fstab."; fstab_device += prop_value;
|
|
vsymlink(fstab_device, "/etc/recovery.fstab");
|
|
}
|
|
}
|
|
return is_fstab_symlinked;
|
|
}
|
|
|
|
void Restore_Recovery_Fstab(void) {
|
|
unlink("/etc/recovery.fstab");
|
|
vrename("/etc/recovery-fstab-orig", "/etc/recovery.fstab");
|
|
}
|
|
|
|
|
|
/* Additional Services Functions */
|
|
#ifdef TW_CRYPTO_SYSTEM_VOLD_SERVICES
|
|
typedef struct {
|
|
string Service_Name;
|
|
string Service_Path;
|
|
string Service_Binary;
|
|
|
|
string VOLD_Service_Name;
|
|
string TWRP_Service_Name;
|
|
bool is_running;
|
|
bool resume;
|
|
bool bin_exists;
|
|
bool svc_exists;
|
|
} AdditionalService;
|
|
|
|
typedef struct {
|
|
string Service_Name;
|
|
string Service_Path;
|
|
string Service_Binary;
|
|
} RC_Service;
|
|
|
|
// expand_props() courtesy of platform_system_core_init_util.cpp
|
|
bool expand_props(const std::string& src, std::string* dst) {
|
|
const char* src_ptr = src.c_str();
|
|
|
|
if (!dst) {
|
|
return false;
|
|
}
|
|
|
|
/* - variables can either be $x.y or ${x.y}, in case they are only part
|
|
* of the string.
|
|
* - will accept $$ as a literal $.
|
|
* - no nested property expansion, i.e. ${foo.${bar}} is not supported,
|
|
* bad things will happen
|
|
* - ${x.y:-default} will return default value if property empty.
|
|
*/
|
|
while (*src_ptr) {
|
|
const char* c;
|
|
|
|
c = strchr(src_ptr, '$');
|
|
if (!c) {
|
|
dst->append(src_ptr);
|
|
return true;
|
|
}
|
|
|
|
dst->append(src_ptr, c);
|
|
c++;
|
|
|
|
if (*c == '$') {
|
|
dst->push_back(*(c++));
|
|
src_ptr = c;
|
|
continue;
|
|
} else if (*c == '\0') {
|
|
return true;
|
|
}
|
|
|
|
std::string prop_name;
|
|
std::string def_val;
|
|
if (*c == '{') {
|
|
c++;
|
|
const char* end = strchr(c, '}');
|
|
if (!end) {
|
|
// failed to find closing brace, abort.
|
|
return false;
|
|
}
|
|
prop_name = std::string(c, end);
|
|
c = end + 1;
|
|
size_t def = prop_name.find(":-");
|
|
if (def < prop_name.size()) {
|
|
def_val = prop_name.substr(def + 2);
|
|
prop_name = prop_name.substr(0, def);
|
|
}
|
|
} else {
|
|
prop_name = c;
|
|
c += prop_name.size();
|
|
}
|
|
|
|
if (prop_name.empty()) {
|
|
return false;
|
|
}
|
|
|
|
char prop_value[PROPERTY_VALUE_MAX];
|
|
property_get(prop_name.c_str(), prop_value, "");
|
|
std::string prop_val = prop_value;
|
|
if (prop_val.empty()) {
|
|
if (def_val.empty()) {
|
|
return false;
|
|
}
|
|
prop_val = def_val;
|
|
}
|
|
|
|
dst->append(prop_val);
|
|
src_ptr = c;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
string GetArgument(const string& line, size_t argument_number, bool expand_properties) {
|
|
size_t beg;
|
|
size_t end;
|
|
string argument;
|
|
|
|
beg = line.find_first_not_of(" \t\r");
|
|
if (beg == string::npos)
|
|
return "";
|
|
|
|
for (size_t i = 0; i < argument_number; ++i) {
|
|
end = line.find_first_of(" \t\r", beg);
|
|
if (end == string::npos)
|
|
return "";
|
|
|
|
beg = line.find_first_not_of(" \t\r", end);
|
|
if (beg == string::npos)
|
|
return "";
|
|
}
|
|
|
|
end = line.find_first_of(" \t\r", beg);
|
|
if (end == string::npos)
|
|
argument = line.substr(beg);
|
|
else
|
|
argument = line.substr(beg, end - beg); // exclude trailing whitespace
|
|
|
|
if (expand_properties) {
|
|
string expanded_property_argument;
|
|
if (expand_props(argument, &expanded_property_argument))
|
|
return expanded_property_argument;
|
|
else
|
|
return "";
|
|
} else {
|
|
return argument;
|
|
}
|
|
}
|
|
|
|
// Very simplified .rc parser to get services
|
|
void Parse_RC_File(const string& rc_file, vector<RC_Service>& RC_Services) {
|
|
ifstream file;
|
|
|
|
file.open(rc_file.c_str(), ios::in);
|
|
if (!file.is_open())
|
|
return;
|
|
|
|
size_t beg; // left trim
|
|
size_t end; // right trim
|
|
bool continuation = false; // backslash continuation
|
|
string line; // line
|
|
string real_line; // trimmed line with backslash continuation removal
|
|
vector<string> imports; // file names of imports (we don't want to recursively do while the file is open)
|
|
|
|
while (getline(file, line)) {
|
|
beg = line.find_first_not_of(" \t\r");
|
|
end = line.find_last_not_of(" \t\r");
|
|
if (end == string::npos)
|
|
end = line.length();
|
|
|
|
if (beg == string::npos) {
|
|
if (continuation)
|
|
continuation = false;
|
|
else
|
|
continue;
|
|
} else if (line[end] == '\\') {
|
|
continuation = true;
|
|
real_line += line.substr(beg, end - beg); // excluding backslash
|
|
continue;
|
|
} else if (continuation) {
|
|
continuation = false;
|
|
real_line += line.substr(beg, end - beg + 1);
|
|
} else {
|
|
real_line = line.substr(beg, end - beg + 1);
|
|
}
|
|
|
|
if (GetArgument(real_line, 0, false) == "import") {
|
|
// handle: import <file>
|
|
string file_name = GetArgument(real_line, 1, true);
|
|
if (file_name.empty()) {
|
|
// INVALID IMPORT
|
|
} else
|
|
imports.push_back(file_name);
|
|
} else if (GetArgument(real_line, 0, false) == "service") {
|
|
// handle: service <name> <path>
|
|
RC_Service svc;
|
|
|
|
svc.Service_Name = GetArgument(real_line, 1, false);
|
|
svc.Service_Path = GetArgument(real_line, 2, true);
|
|
|
|
if (svc.Service_Name.empty() || svc.Service_Path.empty()) {
|
|
// INVALID SERVICE ENTRY
|
|
} else {
|
|
beg = svc.Service_Path.find_last_of("/");
|
|
if (beg == string::npos)
|
|
svc.Service_Binary = svc.Service_Path;
|
|
else
|
|
svc.Service_Binary = svc.Service_Path.substr(beg + 1);
|
|
|
|
/*
|
|
#ifdef _USING_SHORT_SERVICE_NAMES
|
|
if (svc.Service_Name.length() > 16) {
|
|
LOGERROR("WARNING: Ignoring service %s (-> %s)\n"
|
|
" defined in %s is greater than 16 characters and will\n"
|
|
" not be able to be run by init on android-7.1 or below!\n",
|
|
svc.Service_Name.c_str(), svc.Service_Path.c_str(), rc_file.c_str()
|
|
);
|
|
}
|
|
else
|
|
#endif
|
|
*/
|
|
RC_Services.push_back(svc);
|
|
}
|
|
}
|
|
real_line.clear();
|
|
}
|
|
file.close();
|
|
|
|
for (size_t i = 0; i < imports.size(); ++i) {
|
|
Parse_RC_File(imports[i], RC_Services);
|
|
}
|
|
}
|
|
|
|
vector<AdditionalService> Get_List_Of_Additional_Services(void) {
|
|
vector<AdditionalService> services;
|
|
|
|
// Additional Services needed by vold_decrypt (eg qseecomd, hwservicemanager, etc)
|
|
vector<string> service_names = TWFunc::Split_String(TW_CRYPTO_SYSTEM_VOLD_SERVICES, " ");
|
|
for (size_t i = 0; i < service_names.size(); ++i) {
|
|
AdditionalService svc;
|
|
svc.Service_Name = service_names[i];
|
|
svc.bin_exists = false;
|
|
svc.svc_exists = false;
|
|
services.push_back(svc);
|
|
|
|
#ifdef _USING_SHORT_SERVICE_NAMES
|
|
// Fallback code for >16 character service names which
|
|
// allows for multiple definitions in custom .rc files
|
|
if (service_names[i].length() > 12) {
|
|
svc.Service_Name = service_names[i].substr(0, 12); // 16-4(prefix)=12
|
|
svc.bin_exists = false;
|
|
svc.svc_exists = false;
|
|
services.push_back(svc);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// Read list of all services defined in all .rc files
|
|
vector<RC_Service> RC_Services;
|
|
Parse_RC_File("/init.rc", RC_Services);
|
|
|
|
|
|
// Cross reference Additional Services against the .rc Services and establish
|
|
// availability of the binaries, otherwise disable it to avoid unnecessary
|
|
// delays and log spam.
|
|
// Also check for duplicate entries between TWRP and vold_decrypt so we can
|
|
// stop and restart any conflicting services.
|
|
for (size_t i = 0; i < RC_Services.size(); ++i) {
|
|
string prefix = RC_Services[i].Service_Name.substr(0, 4);
|
|
|
|
#ifdef _USING_SHORT_SERVICE_NAMES
|
|
map<string,size_t> rc_indeces;
|
|
#endif
|
|
|
|
if (prefix != "sys_" && prefix != "ven_") {
|
|
#ifdef _USING_SHORT_SERVICE_NAMES
|
|
if (RC_Services[i].Service_Name.length() > 12) {
|
|
// save this entry for potential binary name match
|
|
rc_indeces.insert(pair<string,size_t>(RC_Services[i].Service_Binary, i));
|
|
}
|
|
#endif
|
|
continue;
|
|
}
|
|
|
|
for (size_t j = 0; j < services.size(); ++j) {
|
|
string path = RC_Services[i].Service_Path;
|
|
if (prefix == "ven_" && will_VendorBin_Be_Symlinked()) {
|
|
path = "/system" + path; // vendor is going to get symlinked to /system/vendor
|
|
}
|
|
|
|
if (RC_Services[i].Service_Name == prefix + services[j].Service_Name) {
|
|
services[j].svc_exists = true;
|
|
|
|
if (!services[j].VOLD_Service_Name.empty() && TWFunc::Path_Exists(path)) {
|
|
// Duplicate match, log but use previous definition
|
|
LOGERROR("Service %s: VOLD_Service_Name already defined as %s\n", RC_Services[i].Service_Name.c_str(), services[j].VOLD_Service_Name.c_str());
|
|
}
|
|
else if (TWFunc::Path_Exists(path)) {
|
|
services[j].bin_exists = true;
|
|
services[j].VOLD_Service_Name = RC_Services[i].Service_Name; // prefix + service_name
|
|
services[j].Service_Path = RC_Services[i].Service_Path;
|
|
services[j].Service_Binary = RC_Services[i].Service_Binary;
|
|
|
|
if (Service_Exists(services[j].Service_Name))
|
|
services[j].TWRP_Service_Name = services[j].Service_Name;
|
|
else if (Service_Exists("sbin" + services[j].Service_Name))
|
|
services[j].TWRP_Service_Name = "sbin" + services[j].Service_Name;
|
|
else
|
|
services[j].TWRP_Service_Name.clear();
|
|
|
|
#ifdef _USING_SHORT_SERVICE_NAMES
|
|
if (services[j].TWRP_Service_Name.empty()) {
|
|
// Try matching Service_Binary (due to 16 character service_name limit in 7.1 and below)
|
|
map<string,size_t>::iterator it = rc_indeces.find(services[j].Service_Binary);
|
|
if (it != rc_indeces.end()) {
|
|
services[j].TWRP_Service_Name = RC_Services[it->second].Service_Name;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
LOGINFO("List of additional services for vold_decrypt:\n");
|
|
for (size_t i = 0; i < services.size(); ++i) {
|
|
if (!services[i].svc_exists) {
|
|
LOGINFO(" %s: Disabled due to lack of .rc service entry\n", services[i].Service_Name.c_str());
|
|
} else if (services[i].bin_exists) {
|
|
if (services[i].TWRP_Service_Name.empty()) {
|
|
LOGINFO(" %s: Enabled as %s -> %s\n",
|
|
services[i].Service_Name.c_str(),
|
|
services[i].VOLD_Service_Name.c_str(), services[i].Service_Path.c_str()
|
|
);
|
|
} else {
|
|
LOGINFO(" %s: Enabled as %s -> %s (temporarily replacing TWRP's %s service)\n",
|
|
services[i].Service_Name.c_str(),
|
|
services[i].VOLD_Service_Name.c_str(), services[i].Service_Path.c_str(),
|
|
services[i].TWRP_Service_Name.c_str()
|
|
);
|
|
}
|
|
}
|
|
else {
|
|
LOGINFO(" %s: Disabled due to lack of matching binary\n", services[i].Service_Name.c_str());
|
|
}
|
|
}
|
|
return services;
|
|
}
|
|
#endif
|
|
|
|
|
|
/* Misc Functions */
|
|
void Set_Needed_Properties(void) {
|
|
// vold won't start without ro.storage_structure on Kitkat
|
|
string sdkverstr = TWFunc::System_Property_Get("ro.build.version.sdk");
|
|
int sdkver = 20;
|
|
if (!sdkverstr.empty()) {
|
|
sdkver = atoi(sdkverstr.c_str());
|
|
}
|
|
if (sdkver <= 19) {
|
|
string ro_storage_structure = TWFunc::System_Property_Get("ro.storage_structure");
|
|
if (!ro_storage_structure.empty())
|
|
property_set("ro.storage_structure", ro_storage_structure.c_str());
|
|
}
|
|
}
|
|
|
|
|
|
/* vdc Functions */
|
|
typedef struct {
|
|
string Output; // Entire line excluding \n
|
|
int ResponseCode; // ResponseCode.h (int)
|
|
int Sequence; // Sequence (int)
|
|
int Message; // Message (string) but we're only interested in int
|
|
} vdc_ReturnValues;
|
|
|
|
int Exec_vdc_cryptfs(const string& command, const string& argument, vdc_ReturnValues* vdcResult) {
|
|
pid_t pid;
|
|
int status;
|
|
int pipe_fd[2][2];
|
|
|
|
vdcResult->Output.clear();
|
|
vdcResult->ResponseCode = vdcResult->Sequence = vdcResult->Message = -1;
|
|
|
|
for (int i = 0; i < 2; ++i) {
|
|
if (pipe(pipe_fd[i])) {
|
|
LOGERROR("exec_vdc_cryptfs: pipe() error!\n");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
const char *cmd[] = { "/system/bin/vdc", "cryptfs" };
|
|
const char *env[] = { "LD_LIBRARY_PATH=/system/lib64:/system/lib", NULL };
|
|
|
|
#ifdef TW_CRYPTO_SYSTEM_VOLD_DEBUG
|
|
string log_name = "/tmp/strace_vdc_" + command;
|
|
#endif
|
|
|
|
switch(pid = fork())
|
|
{
|
|
case -1:
|
|
LOGERROR("exec_vdc_cryptfs: fork failed: %d (%s)!\n", errno, strerror(errno));
|
|
return -1;
|
|
|
|
case 0: // child
|
|
fflush(stdout); fflush(stderr);
|
|
for (int i = 0; i < 2; ++i) {
|
|
close(pipe_fd[i][0]);
|
|
dup2(pipe_fd[i][1], ((i == 0) ? STDOUT_FILENO : STDERR_FILENO));
|
|
close(pipe_fd[i][1]);
|
|
}
|
|
|
|
#ifdef TW_CRYPTO_SYSTEM_VOLD_DEBUG
|
|
if (has_strace) {
|
|
if (argument.empty())
|
|
execl(VD_STRACE_BIN, "strace", "-q", "-tt", "-ff", "-v", "-y", "-s", "1000", "-o", log_name.c_str(),
|
|
"-E", env[0], cmd[0], cmd[1], command.c_str(), NULL);
|
|
else
|
|
execl(VD_STRACE_BIN, "strace", "-q", "-tt", "-ff", "-v", "-y", "-s", "1000", "-o", log_name.c_str(),
|
|
"-E", env[0], cmd[0], cmd[1], command.c_str(), argument.c_str(), NULL);
|
|
} else
|
|
#endif
|
|
if (argument.empty())
|
|
execle(cmd[0], cmd[0], cmd[1], command.c_str(), NULL, env);
|
|
else
|
|
execle(cmd[0], cmd[0], cmd[1], command.c_str(), argument.c_str(), NULL, env);
|
|
_exit(127);
|
|
break;
|
|
|
|
default:
|
|
{
|
|
int timeout = 30*100;
|
|
|
|
for (int i = 0; i < 2; ++i) {
|
|
close(pipe_fd[i][1]);
|
|
|
|
// Non-blocking read loop with timeout
|
|
int flags = fcntl(pipe_fd[i][0], F_GETFL, 0);
|
|
fcntl(pipe_fd[i][0], F_SETFL, flags | O_NONBLOCK);
|
|
}
|
|
|
|
char buffer[128];
|
|
ssize_t count;
|
|
string strout[2];
|
|
pid_t retpid = waitpid(pid, &status, WNOHANG);
|
|
while (true) {
|
|
for (int i = 0; i < 2; ++i) {
|
|
count = read(pipe_fd[i][0], buffer, sizeof(buffer));
|
|
if (count == -1) {
|
|
if (errno == EINTR)
|
|
continue;
|
|
else if (errno != EAGAIN)
|
|
LOGERROR("exec_vdc_cryptfs: read() error %d (%s)\n!", errno, strerror(errno));
|
|
} else if (count > 0) {
|
|
strout[i].append(buffer, count);
|
|
}
|
|
}
|
|
|
|
retpid = waitpid(pid, &status, WNOHANG);
|
|
if (retpid == 0 && --timeout)
|
|
usleep(10000);
|
|
else
|
|
break;
|
|
};
|
|
|
|
for (int i = 0; i < 2; ++i) {
|
|
close(pipe_fd[i][0]);
|
|
}
|
|
|
|
if (!strout[0].empty()) {
|
|
sscanf(strout[0].c_str(), "%d %d %d", &vdcResult->ResponseCode, &vdcResult->Sequence, &vdcResult->Message);
|
|
vdcResult->Output = "I:" + strout[0];
|
|
}
|
|
if (!strout[1].empty()) {
|
|
vdcResult->Output += "E:" + strout[1];
|
|
}
|
|
std::replace(vdcResult->Output.begin(), vdcResult->Output.end(), '\n', '|');
|
|
|
|
if (!vdcResult->Output.empty() && vdcResult->Output[vdcResult->Output.length() - 1] != '|')
|
|
vdcResult->Output += "|";
|
|
vdcResult->Output += "RC=" + TWFunc::to_string(WEXITSTATUS(status));
|
|
|
|
// Error handling
|
|
if (retpid == 0 && timeout == 0) {
|
|
LOGERROR("exec_vdc_cryptfs: took too long, killing process\n");
|
|
kill(pid, SIGKILL);
|
|
for (timeout = 5; retpid == 0 && timeout; --timeout) {
|
|
sleep(1);
|
|
retpid = waitpid(pid, &status, WNOHANG);
|
|
}
|
|
if (retpid)
|
|
LOGINFO("exec_vdc_cryptfs: process killed successfully\n");
|
|
else
|
|
LOGERROR("exec_vdc_cryptfs: process took too long to kill, may be a zombie process\n");
|
|
return VD_ERR_VOLD_OPERATION_TIMEDOUT;
|
|
} else if (retpid > 0) {
|
|
if (WIFSIGNALED(status)) {
|
|
LOGERROR("exec_vdc_cryptfs: process ended with signal: %d\n", WTERMSIG(status)); // Seg fault or some other non-graceful termination
|
|
return -1;
|
|
}
|
|
} else if (retpid < 0) { // no PID returned
|
|
if (errno == ECHILD)
|
|
LOGINFO("exec_vdc_cryptfs: no child process exist\n");
|
|
else {
|
|
LOGERROR("exec_vdc_cryptfs: Unexpected error %d (%s)\n", errno, strerror(errno));
|
|
return -1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
int Run_vdc(const string& Password) {
|
|
int res;
|
|
struct timeval t1, t2;
|
|
vdc_ReturnValues vdcResult;
|
|
|
|
LOGINFO("About to run vdc...\n");
|
|
|
|
// Wait for vold connection
|
|
gettimeofday(&t1, NULL);
|
|
t2 = t1;
|
|
while ((t2.tv_sec - t1.tv_sec) < 5) {
|
|
// cryptfs getpwtype returns: R1=213(PasswordTypeResult) R2=? R3="password", "pattern", "pin", "default"
|
|
res = Exec_vdc_cryptfs("getpwtype", "", &vdcResult);
|
|
if (vdcResult.ResponseCode == PASSWORD_TYPE_RESULT) {
|
|
res = 0;
|
|
break;
|
|
}
|
|
LOGINFO("Retrying connection to vold (Reason: %s)\n", vdcResult.Output.c_str());
|
|
usleep(SLEEP_MIN_USEC); // vdc usually usleep(10000), but that causes too many unnecessary attempts
|
|
gettimeofday(&t2, NULL);
|
|
}
|
|
|
|
if (res == 0 && (t2.tv_sec - t1.tv_sec) < 5)
|
|
LOGINFO("Connected to vold: %s\n", vdcResult.Output.c_str());
|
|
else if (res == VD_ERR_VOLD_OPERATION_TIMEDOUT)
|
|
return VD_ERR_VOLD_OPERATION_TIMEDOUT; // should never happen for getpwtype
|
|
else if (res)
|
|
return VD_ERR_FORK_EXECL_ERROR;
|
|
else if (vdcResult.ResponseCode != -1)
|
|
return VD_ERR_VOLD_UNEXPECTED_RESPONSE;
|
|
else
|
|
return VD_ERR_VDC_FAILED_TO_CONNECT;
|
|
|
|
|
|
// Input password from GUI, or default password
|
|
res = Exec_vdc_cryptfs("checkpw", Password, &vdcResult);
|
|
if (res == VD_ERR_VOLD_OPERATION_TIMEDOUT)
|
|
return VD_ERR_VOLD_OPERATION_TIMEDOUT;
|
|
else if (res)
|
|
return VD_ERR_FORK_EXECL_ERROR;
|
|
|
|
LOGINFO("vdc cryptfs result (passwd): %s\n", vdcResult.Output.c_str());
|
|
/*
|
|
if (res == 0 && vdcResult.ResponseCode != COMMAND_OKAY)
|
|
return VD_ERR_VOLD_UNEXPECTED_RESPONSE;
|
|
*/
|
|
|
|
if (vdcResult.Message != 0) {
|
|
// try falling back to Lollipop hex passwords
|
|
string hexPassword = convert_key_to_hex_ascii(Password);
|
|
res = Exec_vdc_cryptfs("checkpw", hexPassword, &vdcResult);
|
|
if (res == VD_ERR_VOLD_OPERATION_TIMEDOUT)
|
|
return VD_ERR_VOLD_OPERATION_TIMEDOUT;
|
|
else if (res)
|
|
return VD_ERR_FORK_EXECL_ERROR;
|
|
|
|
LOGINFO("vdc cryptfs result (hex_pw): %s\n", vdcResult.Output.c_str());
|
|
/*
|
|
if (res == 0 && vdcResult.ResponseCode != COMMAND_OKAY)
|
|
return VD_ERR_VOLD_UNEXPECTED_RESPONSE;
|
|
*/
|
|
}
|
|
|
|
// vdc's return value is dependant upon source origin, it will either
|
|
// return 0 or ResponseCode, so disregard and focus on decryption instead
|
|
if (vdcResult.Message == 0) {
|
|
// Decryption successful wait for crypto blk dev
|
|
Wait_For_Property("ro.crypto.fs_crypto_blkdev");
|
|
res = VD_SUCCESS;
|
|
} else if (vdcResult.ResponseCode != COMMAND_OKAY) {
|
|
res = VD_ERR_VOLD_UNEXPECTED_RESPONSE;
|
|
} else {
|
|
res = VD_ERR_DECRYPTION_FAILED;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
int Vold_Decrypt_Core(const string& Password) {
|
|
int res;
|
|
bool is_vendor_symlinked = false;
|
|
bool is_firmware_symlinked = false;
|
|
bool is_fstab_symlinked = false;
|
|
bool is_vold_running = false;
|
|
|
|
if (Password.empty()) {
|
|
LOGINFO("vold_decrypt: password is empty!\n");
|
|
return VD_ERR_PASSWORD_EMPTY;
|
|
}
|
|
|
|
// Mount system and check for vold and vdc
|
|
if (!PartitionManager.Mount_By_Path("/system", true)) {
|
|
return VD_ERR_UNABLE_TO_MOUNT_SYSTEM;
|
|
} else if (!TWFunc::Path_Exists("/system/bin/vold")) {
|
|
LOGINFO("ERROR: /system/bin/vold not found, aborting.\n");
|
|
return VD_ERR_MISSING_VOLD;
|
|
} else if (!TWFunc::Path_Exists("/system/bin/vdc")) {
|
|
LOGINFO("ERROR: /system/bin/vdc not found, aborting.\n");
|
|
return VD_ERR_MISSING_VDC;
|
|
}
|
|
|
|
fp_kmsg = fopen("/dev/kmsg", "a");
|
|
|
|
LOGINFO("TW_CRYPTO_USE_SYSTEM_VOLD := true\n");
|
|
|
|
// just cache the result to avoid unneeded duplicates in recovery.log
|
|
LOGINFO("Checking existence of vendor and firmware partitions...\n");
|
|
is_Vendor_Mounted();
|
|
is_Firmware_Mounted();
|
|
|
|
LOGINFO("Attempting to use system's vold for decryption...\n");
|
|
|
|
#ifdef TW_CRYPTO_SYSTEM_VOLD_DEBUG
|
|
Strace_init_Start();
|
|
#endif
|
|
|
|
#ifdef TW_CRYPTO_SYSTEM_VOLD_SERVICES
|
|
vector<AdditionalService> Services = Get_List_Of_Additional_Services();
|
|
|
|
// Check if TWRP is running any of the services
|
|
for (size_t i = 0; i < Services.size(); ++i) {
|
|
if (!Services[i].TWRP_Service_Name.empty() && !Is_Service_Stopped(Services[i].TWRP_Service_Name)) {
|
|
Services[i].resume = true;
|
|
Stop_Service(Services[i].TWRP_Service_Name);
|
|
} else
|
|
Services[i].resume = false;
|
|
}
|
|
#endif
|
|
|
|
LOGINFO("Setting up folders and permissions...\n");
|
|
is_fstab_symlinked = Symlink_Recovery_Fstab();
|
|
is_vendor_symlinked = Symlink_Vendor_Folder();
|
|
is_firmware_symlinked = Symlink_Firmware_Folder();
|
|
Symlink_Firmware_Files(is_vendor_symlinked, is_firmware_symlinked);
|
|
|
|
Set_Needed_Properties();
|
|
|
|
// Start services needed for vold decrypt
|
|
LOGINFO("Starting services...\n");
|
|
#ifdef TW_CRYPTO_SYSTEM_VOLD_SERVICES
|
|
for (size_t i = 0; i < Services.size(); ++i) {
|
|
if (Services[i].bin_exists)
|
|
Services[i].is_running = Start_Service(Services[i].VOLD_Service_Name);
|
|
}
|
|
#endif
|
|
is_vold_running = Start_Service("sys_vold");
|
|
|
|
if (is_vold_running) {
|
|
#ifdef TW_CRYPTO_SYSTEM_VOLD_SERVICES
|
|
for (size_t i = 0; i < Services.size(); ++i) {
|
|
if (Services[i].bin_exists && !Is_Service_Running(Services[i].VOLD_Service_Name) && Services[i].resume) {
|
|
// if system_service has died restart the twrp_service
|
|
LOGINFO("%s is not running, resuming %s!\n", Services[i].VOLD_Service_Name.c_str(), Services[i].TWRP_Service_Name.c_str());
|
|
Start_Service(Services[i].TWRP_Service_Name);
|
|
}
|
|
}
|
|
#endif
|
|
res = Run_vdc(Password);
|
|
|
|
if (res != 0) {
|
|
LOGINFO("Decryption failed\n");
|
|
}
|
|
} else {
|
|
LOGINFO("Failed to start vold\n");
|
|
res = VD_ERR_VOLD_FAILED_TO_START;
|
|
}
|
|
|
|
// Stop services needed for vold decrypt so /system can be unmounted
|
|
LOGINFO("Stopping services...\n");
|
|
Stop_Service("sys_vold");
|
|
#ifdef TW_CRYPTO_SYSTEM_VOLD_SERVICES
|
|
for (size_t i = 0; i < Services.size(); ++i) {
|
|
if (!Is_Service_Running(Services[i].VOLD_Service_Name) && Services[i].resume)
|
|
Stop_Service(Services[i].TWRP_Service_Name);
|
|
else if (Services[i].bin_exists)
|
|
Stop_Service(Services[i].VOLD_Service_Name);
|
|
}
|
|
#endif
|
|
|
|
if (is_firmware_symlinked)
|
|
Restore_Firmware_Folder();
|
|
if (is_vendor_symlinked)
|
|
Restore_Vendor_Folder();
|
|
if (is_fstab_symlinked)
|
|
Restore_Recovery_Fstab();
|
|
|
|
if (!PartitionManager.UnMount_By_Path("/system", true)) {
|
|
// PartitionManager failed to unmount /system, this should not happen,
|
|
// but in case it does, do a lazy unmount
|
|
LOGINFO("WARNING: system could not be unmounted normally!\n");
|
|
umount2("/system", MNT_DETACH);
|
|
}
|
|
|
|
LOGINFO("Finished.\n");
|
|
|
|
#ifdef TW_CRYPTO_SYSTEM_VOLD_SERVICES
|
|
// Restart previously running services
|
|
for (size_t i = 0; i < Services.size(); ++i) {
|
|
if (Services[i].resume)
|
|
Start_Service(Services[i].TWRP_Service_Name);
|
|
}
|
|
#endif
|
|
|
|
#ifdef TW_CRYPTO_SYSTEM_VOLD_DEBUG
|
|
Strace_init_Stop();
|
|
#endif
|
|
|
|
// Finish up and exit
|
|
if (fp_kmsg) {
|
|
fflush(fp_kmsg);
|
|
fclose(fp_kmsg);
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
/*
|
|
* Common vold Response Codes / Errors:
|
|
* 406 (OpFailedStorageNotFound) -> Problem reading or parsing fstab
|
|
*
|
|
*/
|
|
|
|
/* Main function separated from core in case we want to return error info */
|
|
int vold_decrypt(const string& Password) {
|
|
return Vold_Decrypt_Core(Password);
|
|
}
|