Timeout for decrypt

Sometimes, usually because of proprietary binaries related to keymaster,
decrypt will hang waiting for the keymaster to initialize forever. This patch
enables a timeout so that we don't get stuck trying to decrypt forever.
A timeout is especially important when dealing with the default password
because the user has no option to cancel when TWRP tries to decrypt.

NOTE: This patch only adds a timeout for FDE. FBE will require some special
handling because we need access to some static data and that data is not
available across a fork.

Special thanks to nkk71 for cleaning up some issues in my patch set.

Change-Id: Iccf2fe769ac27a7dcd6bfebfe7d2e9eddd034308
diff --git a/partitionmanager.cpp b/partitionmanager.cpp
index 908730e..c1b857c 100644
--- a/partitionmanager.cpp
+++ b/partitionmanager.cpp
@@ -1513,7 +1513,7 @@
 
 int TWPartitionManager::Decrypt_Device(string Password) {
 #ifdef TW_INCLUDE_CRYPTO
-	char crypto_state[PROPERTY_VALUE_MAX], crypto_blkdev[PROPERTY_VALUE_MAX], cPassword[255];
+	char crypto_state[PROPERTY_VALUE_MAX], crypto_blkdev[PROPERTY_VALUE_MAX];
 	std::vector<TWPartition*>::iterator iter;
 
 	// Mount any partitions that need to be mounted for decrypt
@@ -1549,8 +1549,25 @@
 		return -1;
 	}
 
-	strcpy(cPassword, Password.c_str());
-	int pwret = cryptfs_check_passwd(cPassword);
+	int pwret = -1;
+	pid_t pid = fork();
+	if (pid < 0) {
+		LOGERR("fork failed\n");
+		return -1;
+	} else if (pid == 0) {
+		// Child process
+		char cPassword[255];
+		strcpy(cPassword, Password.c_str());
+		int ret = cryptfs_check_passwd(cPassword);
+		exit(ret);
+	} else {
+		// Parent
+		int status;
+		if (TWFunc::Wait_For_Child_Timeout(pid, &status, "Decrypt", 30))
+			pwret = -1;
+		else
+			pwret = WEXITSTATUS(status) ? -1 : 0;
+	}
 
 	// Unmount any partitions that were needed for decrypt
 	for (iter = Partitions.begin(); iter != Partitions.end(); iter++) {
diff --git a/twrp-functions.cpp b/twrp-functions.cpp
index 3c6c55b..c964357 100644
--- a/twrp-functions.cpp
+++ b/twrp-functions.cpp
@@ -142,6 +142,41 @@
 	return 0;
 }
 
+int TWFunc::Wait_For_Child_Timeout(pid_t pid, int *status, const string& Child_Name, int timeout) {
+	pid_t retpid = waitpid(pid, status, WNOHANG);
+	for (; retpid == 0 && timeout; --timeout) {
+		sleep(1);
+		retpid = waitpid(pid, status, WNOHANG);
+	}
+	if (retpid == 0 && timeout == 0) {
+		LOGERR("%s took too long, killing process\n", Child_Name.c_str());
+		kill(pid, SIGKILL);
+		int died = 0;
+		for (timeout = 5; retpid == 0 && timeout; --timeout) {
+			sleep(1);
+			retpid = waitpid(pid, status, WNOHANG);
+		}
+		if (retpid)
+			LOGINFO("Child process killed successfully\n");
+		else
+			LOGINFO("Child process took too long to kill, may be a zombie process\n");
+		return -1;
+	} else if (retpid > 0) {
+		if (WIFSIGNALED(*status)) {
+			gui_msg(Msg(msg::kError, "pid_signal={1} process ended with signal: {2}")(Child_Name)(WTERMSIG(*status))); // Seg fault or some other non-graceful termination
+			return -1;
+		}
+	} else if (retpid < 0) { // no PID returned
+		if (errno == ECHILD)
+			LOGERR("%s no child process exist\n", Child_Name.c_str());
+		else {
+			LOGERR("%s Unexpected error %d\n", Child_Name.c_str(), errno);
+			return -1;
+		}
+	}
+	return 0;
+}
+
 bool TWFunc::Path_Exists(string Path) {
 	// Check to see if the Path exists
 	struct stat st;
diff --git a/twrp-functions.hpp b/twrp-functions.hpp
index ebbe99d..82a4c1b 100644
--- a/twrp-functions.hpp
+++ b/twrp-functions.hpp
@@ -52,6 +52,7 @@
 	static int Exec_Cmd(const string& cmd, string &result);                     //execute a command and return the result as a string by reference
 	static int Exec_Cmd(const string& cmd);                                     //execute a command
 	static int Wait_For_Child(pid_t pid, int *status, string Child_Name);       // Waits for pid to exit and checks exit status
+	static int Wait_For_Child_Timeout(pid_t pid, int *status, const string& Child_Name, int timeout); // Waits for a pid to exit until the timeout is hit. If timeout is hit, kill the chilld.
 	static bool Path_Exists(string Path);                                       // Returns true if the path exists
 	static Archive_Type Get_File_Type(string fn);                               // Determines file type, 0 for unknown, 1 for gzip, 2 for OAES encrypted
 	static int Try_Decrypting_File(string fn, string password); // -1 for some error, 0 for failed to decrypt, 1 for decrypted, 3 for decrypted and found gzip format