Support v2 fstab format

Auto detect and support both the v1 and v2 fstab formats
Support putting TWRP style flags in a separate /etc/twrp.flags file

twrp.flags format is the same as twrp.fstab (v1 with TWRP flags)

Support using a wildcard in a block device and find all partitions:
/usb-otg vfat /dev/block/sda*

Support using sysfs entries (voldmanaged) and read uevents and scan for
wildcard partitions from uevent data. (twvold?)

May not be complete for some of the newer flags found in fstabs in newer
build trees and there is a slim chance of a crash if the user removes a
removable device while TWRP is performing actions. May need to add some
kind of mutex to prevent the 2 threads from causing this crash. We need
to start somewhere though and this change is pretty innocuous when not
using a v2 fstab.

Change-Id: I617d97c7db332cbe671a9d2b8ad98b3d9c4f03cc
diff --git a/partition.cpp b/partition.cpp
index 4157e97..8b73f64 100644
--- a/partition.cpp
+++ b/partition.cpp
@@ -77,40 +77,47 @@
 
 using namespace std;
 
+static int auto_index = 0; // v2 fstab allows you to specify a mount point of "auto" with no /. These items are given a mount point of /auto* where * == auto_index
+
 extern struct selabel_handle *selinux_handle;
 extern bool datamedia;
 
 struct flag_list {
 	const char *name;
-	unsigned flag;
+	unsigned long flag;
 };
 
 const struct flag_list mount_flags[] = {
-	{ "noatime",    MS_NOATIME },
-	{ "noexec",     MS_NOEXEC },
-	{ "nosuid",     MS_NOSUID },
-	{ "nodev",      MS_NODEV },
-	{ "nodiratime", MS_NODIRATIME },
-	{ "ro",         MS_RDONLY },
-	{ "rw",         0 },
-	{ "remount",    MS_REMOUNT },
-	{ "bind",       MS_BIND },
-	{ "rec",        MS_REC },
+	{ "noatime",          MS_NOATIME },
+	{ "noexec",           MS_NOEXEC },
+	{ "nosuid",           MS_NOSUID },
+	{ "nodev",            MS_NODEV },
+	{ "nodiratime",       MS_NODIRATIME },
+	{ "ro",               MS_RDONLY },
+	{ "rw",               0 },
+	{ "remount",          MS_REMOUNT },
+	{ "bind",             MS_BIND },
+	{ "rec",              MS_REC },
 #ifdef MS_UNBINDABLE
-	{ "unbindable", MS_UNBINDABLE },
+	{ "unbindable",       MS_UNBINDABLE },
 #endif
 #ifdef MS_PRIVATE
-	{ "private",    MS_PRIVATE },
+	{ "private",          MS_PRIVATE },
 #endif
 #ifdef MS_SLAVE
-	{ "slave",      MS_SLAVE },
+	{ "slave",            MS_SLAVE },
 #endif
 #ifdef MS_SHARED
-	{ "shared",     MS_SHARED },
+	{ "shared",           MS_SHARED },
 #endif
-	{ "sync",       MS_SYNCHRONOUS },
-	{ "defaults",   0 },
-	{ 0,            0 },
+	{ "sync",             MS_SYNCHRONOUS },
+	{ 0,                  0 },
+};
+
+const char *ignored_mount_items[] = {
+	"defaults=",
+	"errors=",
+	NULL
 };
 
 enum TW_FSTAB_FLAGS {
@@ -141,6 +148,14 @@
 	TWFLAG_WIPEDURINGFACTORYRESET,
 	TWFLAG_WIPEINGUI,
 	TWFLAG_SLOTSELECT,
+	TWFLAG_WAIT,
+	TWFLAG_VERIFY,
+	TWFLAG_CHECK,
+	TWFLAG_ALTDEVICE,
+	TWFLAG_NOTRIM,
+	TWFLAG_VOLDMANAGED,
+	TWFLAG_FORMATTABLE,
+	TWFLAG_RESIZE,
 };
 
 /* Flags without a trailing '=' are considered dual format flags and can be
@@ -175,6 +190,14 @@
 	{ "wipeduringfactoryreset", TWFLAG_WIPEDURINGFACTORYRESET },
 	{ "wipeingui",              TWFLAG_WIPEINGUI },
 	{ "slotselect",             TWFLAG_SLOTSELECT },
+	{ "wait",                   TWFLAG_WAIT },
+	{ "verify",                 TWFLAG_VERIFY },
+	{ "check",                  TWFLAG_CHECK },
+	{ "altdevice",              TWFLAG_ALTDEVICE },
+	{ "notrim",                 TWFLAG_NOTRIM },
+	{ "voldmanaged=",           TWFLAG_VOLDMANAGED },
+	{ "formattable",            TWFLAG_FORMATTABLE },
+	{ "resize",                 TWFLAG_RESIZE },
 	{ 0,                        0 },
 };
 
@@ -192,6 +215,8 @@
 	Symlink_Mount_Point = "";
 	Mount_Point = "";
 	Backup_Path = "";
+	Wildcard_Block_Device = false;
+	Sysfs_Entry = "";
 	Actual_Block_Device = "";
 	Primary_Block_Device = "";
 	Alternate_Block_Device = "";
@@ -242,12 +267,15 @@
 	// Do nothing
 }
 
-bool TWPartition::Process_Fstab_Line(const char *fstab_line, bool Display_Error) {
+bool TWPartition::Process_Fstab_Line(const char *fstab_line, bool Display_Error, std::map<string, Flags_Map> *twrp_flags) {
 	char full_line[MAX_FSTAB_LINE_LENGTH];
 	char twflags[MAX_FSTAB_LINE_LENGTH] = "";
 	char* ptr;
 	int line_len = strlen(fstab_line), index = 0, item_index = 0;
 	bool skip = false;
+	int fstab_version = 1, mount_point_index = 0, fs_index = 1, block_device_index = 2;
+	TWPartition *additional_entry = NULL;
+	std::map<string, Flags_Map>::iterator it;
 
 	strlcpy(full_line, fstab_line, sizeof(full_line));
 	for (index = 0; index < line_len; index++) {
@@ -256,26 +284,43 @@
 		if (!skip && full_line[index] <= 32)
 			full_line[index] = '\0';
 	}
-	Mount_Point = full_line;
-	LOGINFO("Processing '%s'\n", Mount_Point.c_str());
-	Backup_Path = Mount_Point;
-	Storage_Path = Mount_Point;
-	Display_Name = full_line + 1;
-	Backup_Display_Name = Display_Name;
-	Storage_Name = Display_Name;
-	index = Mount_Point.size();
+	if (line_len < 10)
+		return false; // There can't possibly be a valid fstab line that is less than 10 chars
+	if (strncmp(fstab_line, "/dev/", strlen("/dev/")) == 0 || strncmp(fstab_line, "/devices/", strlen("/devices/")) == 0) {
+		fstab_version = 2;
+		block_device_index = 0;
+		mount_point_index = 1;
+		fs_index = 2;
+	}
+
+	index = 0;
 	while (index < line_len) {
 		while (index < line_len && full_line[index] == '\0')
 			index++;
 		if (index >= line_len)
 			continue;
 		ptr = full_line + index;
-		if (item_index == 0) {
+		if (item_index == mount_point_index) {
+			Mount_Point = ptr;
+			if (fstab_version == 2) {
+				additional_entry = PartitionManager.Find_Partition_By_Path(Mount_Point);
+				if (additional_entry) {
+					LOGINFO("Found an additional entry for '%s'\n", Mount_Point.c_str());
+				}
+			}
+			LOGINFO("Processing '%s'\n", Mount_Point.c_str());
+			Backup_Path = Mount_Point;
+			Storage_Path = Mount_Point;
+			Display_Name = ptr + 1;
+			Backup_Display_Name = Display_Name;
+			Storage_Name = Display_Name;
+			item_index++;
+		} else if (item_index == fs_index) {
 			// File System
 			Fstab_File_System = ptr;
 			Current_File_System = ptr;
 			item_index++;
-		} else if (item_index == 1) {
+		} else if (item_index == block_device_index) {
 			// Primary Block Device
 			if (Fstab_File_System == "mtd" || Fstab_File_System == "yaffs2") {
 				MTD_Name = ptr;
@@ -299,8 +344,19 @@
 				Find_Real_Block_Device(Primary_Block_Device, Display_Error);
 			}
 			item_index++;
-		} else if (item_index > 1) {
-			if (*ptr == '/') {
+		} else if (item_index > 2) {
+			if (fstab_version == 2) {
+				if (item_index == 3) {
+					Process_FS_Flags(ptr);
+					if (additional_entry) {
+						additional_entry->Save_FS_Flags(Fstab_File_System, Mount_Flags, Mount_Options);
+						return false; // We save the extra fs flags in the other partition entry and by returning false, this entry will be deleted
+					}
+				} else {
+					strlcpy(twflags, ptr, sizeof(twflags));
+				}
+				item_index++;
+			} else if (*ptr == '/') { // v2 fstab does not allow alternate block devices
 				// Alternate Block Device
 				Alternate_Block_Device = ptr;
 				Find_Real_Block_Device(Alternate_Block_Device, Display_Error);
@@ -323,7 +379,50 @@
 			index++;
 	}
 
-	if (!Is_File_System(Fstab_File_System) && !Is_Image(Fstab_File_System)) {
+	// override block devices from the v2 fstab with the ones we read from the twrp.flags file in case they are different
+	if (fstab_version == 2 && twrp_flags && twrp_flags->size() > 0) {
+		it = twrp_flags->find(Mount_Point);
+		if (it != twrp_flags->end()) {
+			if (!it->second.Primary_Block_Device.empty()) {
+				Primary_Block_Device = it->second.Primary_Block_Device;
+				Find_Real_Block_Device(Primary_Block_Device, Display_Error);
+			}
+			if (!it->second.Alternate_Block_Device.empty()) {
+				Alternate_Block_Device = it->second.Alternate_Block_Device;
+				Find_Real_Block_Device(Alternate_Block_Device, Display_Error);
+			}
+		}
+	}
+
+	if (strncmp(fstab_line, "/devices/", strlen("/devices/")) == 0) {
+		Sysfs_Entry = Primary_Block_Device;
+		Primary_Block_Device = "";
+		Is_Storage = true;
+		Removable = true;
+		Wipe_Available_in_GUI = true;
+		Wildcard_Block_Device = true;
+	}
+	if (Primary_Block_Device.find("*") != string::npos)
+		Wildcard_Block_Device = true;
+
+	if (Mount_Point == "auto") {
+		Mount_Point = "/auto";
+		char autoi[5];
+		sprintf(autoi, "%i", auto_index);
+		Mount_Point += autoi;
+		Backup_Path = Mount_Point;
+		Storage_Path = Mount_Point;
+		auto_index++;
+		Setup_File_System(Display_Error);
+		Display_Name = "Storage";
+		Backup_Display_Name = Display_Name;
+		Storage_Name = Display_Name;
+		Can_Be_Backed_Up = false;
+		Wipe_Available_in_GUI = true;
+		Is_Storage = true;
+		Removable = true;
+		Wipe_Available_in_GUI = true;
+	} else if (!Is_File_System(Fstab_File_System) && !Is_Image(Fstab_File_System)) {
 		if (Display_Error)
 			LOGERR("Unknown File System: '%s'\n", Fstab_File_System.c_str());
 		else
@@ -448,7 +547,8 @@
 		Storage_Name = "";
 		Backup_Display_Name = "";
 
-		Process_TW_Flags(twflags, Display_Error);
+		Process_TW_Flags(twflags, (fstab_version == 1), fstab_version);
+		Save_FS_Flags(Fstab_File_System, Mount_Flags, Mount_Options);
 
 		bool has_display_name = !Display_Name.empty();
 		bool has_storage_name = !Storage_Name.empty();
@@ -466,6 +566,21 @@
 		if (!has_display_name && has_backup_name)
 			Display_Name = Backup_Display_Name;
 	}
+
+	if (fstab_version == 2 && twrp_flags && twrp_flags->size() > 0) {
+		it = twrp_flags->find(Mount_Point);
+		if (it != twrp_flags->end()) {
+			char twrpflags[MAX_FSTAB_LINE_LENGTH] = "";
+			int skip = 0;
+			string Flags = it->second.Flags;
+			strcpy(twrpflags, Flags.c_str());
+			if (strlen(twrpflags) > strlen("flags=") && strncmp(twrpflags, "flags=", strlen("flags=")) == 0)
+				skip += strlen("flags=");
+			char* flagptr = twrpflags;
+			flagptr += skip;
+			Process_TW_Flags(flagptr, Display_Error, 1); // Forcing the fstab to ver 1 because this data is coming from the /etc/twrp.flags which should be using the TWRP v1 flags format
+		}
+	}
 	return true;
 }
 
@@ -599,35 +714,59 @@
 	Mount_Options = "";
 
 	// Avoid issues with potentially nested strtok by using strtok_r
-	ptr = strtok_r(options, ",", &savep);
-	while (ptr) {
-		const struct flag_list* mount_flag = mount_flags;
+	for (ptr = strtok_r(options, ",", &savep); ptr; ptr = strtok_r(NULL, ",", &savep)) {
+		char *equals = strstr(ptr, "=");
+		size_t name_len;
 
-		for (; mount_flag->name; mount_flag++) {
-			// mount_flags are never postfixed by '=',
-			// so only match identical strings (including length)
-			if (strcmp(ptr, mount_flag->name) == 0) {
-				Mount_Flags |= mount_flag->flag;
+		if (!equals)
+			name_len = strlen(ptr);
+		else
+			name_len = equals - ptr;
+
+		// There are some flags that we want to ignore in TWRP
+		bool found_match = false;
+		for (const char** ignored_mount_item = ignored_mount_items; *ignored_mount_item; ignored_mount_item++) {
+			if (strncmp(ptr, *ignored_mount_item, name_len) == 0) {
+				found_match = true;
 				break;
 			}
 		}
+		if (found_match)
+			continue;
 
-		if (mount_flag->flag == MS_RDONLY)
-			Mount_Read_Only = true;
-
-		if (mount_flag->name != 0) {
-			if (!Mount_Options.empty())
-				Mount_Options += ",";
-			Mount_Options += mount_flag->name;
-		} else {
-			LOGINFO("Unhandled mount flag: '%s'\n", ptr);
+		// mount_flags are never postfixed by '='
+		if (!equals) {
+			const struct flag_list* mount_flag = mount_flags;
+			for (; mount_flag->name; mount_flag++) {
+				if (strcmp(ptr, mount_flag->name) == 0) {
+					if (mount_flag->flag == MS_RDONLY)
+						Mount_Read_Only = true;
+					else
+						Mount_Flags |= (unsigned)mount_flag->flag;
+					found_match = true;
+					break;
+				}
+			}
+			if (found_match)
+				continue;
 		}
 
-		ptr = strtok_r(NULL, ",", &savep);
+		// If we aren't ignoring this flag and it's not a mount flag, then it must be a mount option
+		if (!Mount_Options.empty())
+			Mount_Options += ",";
+		Mount_Options += ptr;
 	}
 	free(options);
 }
 
+void TWPartition::Save_FS_Flags(const string& local_File_System, int local_Mount_Flags, const string& local_Mount_Options) {
+	partition_fs_flags_struct flags;
+	flags.File_System = local_File_System;
+	flags.Mount_Flags = local_Mount_Flags;
+	flags.Mount_Options = local_Mount_Options;
+	fs_flags.push_back(flags);
+}
+
 void TWPartition::Apply_TW_Flag(const unsigned flag, const char* str, const bool val) {
 	switch (flag) {
 		case TWFLAG_ANDSEC:
@@ -649,6 +788,12 @@
 			Can_Encrypt_Backup = val;
 			break;
 		case TWFLAG_DEFAULTS:
+		case TWFLAG_WAIT:
+		case TWFLAG_VERIFY:
+		case TWFLAG_CHECK:
+		case TWFLAG_NOTRIM:
+		case TWFLAG_VOLDMANAGED:
+		case TWFLAG_RESIZE:
 			// Do nothing
 			break;
 		case TWFLAG_DISPLAY:
@@ -713,6 +858,7 @@
 			}
 			break;
 		case TWFLAG_WIPEINGUI:
+		case TWFLAG_FORMATTABLE:
 			Wipe_Available_in_GUI = val;
 			if (Wipe_Available_in_GUI)
 				Can_Be_Wiped = true;
@@ -720,6 +866,9 @@
 		case TWFLAG_SLOTSELECT:
 			SlotSelect = true;
 			break;
+		case TWFLAG_ALTDEVICE:
+			Alternate_Block_Device = str;
+			break;
 		default:
 			// Should not get here
 			LOGINFO("Flag identified for processing, but later unmatched: %i\n", flag);
@@ -727,16 +876,20 @@
 	}
 }
 
-void TWPartition::Process_TW_Flags(char *flags, bool Display_Error) {
+void TWPartition::Process_TW_Flags(char *flags, bool Display_Error, int fstab_ver) {
 	char separator[2] = {'\n', 0};
 	char *ptr, *savep;
+	char source_separator = ';';
+
+	if (fstab_ver == 2)
+		source_separator = ',';
 
 	// Semicolons within double-quotes are not forbidden, so replace
 	// only the semicolons intended as separators with '\n' for strtok
 	for (unsigned i = 0, skip = 0; i < strlen(flags); i++) {
 		if (flags[i] == '\"')
 			skip = !skip;
-		if (!skip && flags[i] == ';')
+		if (!skip && flags[i] == source_separator)
 			flags[i] = separator[0];
 	}
 
@@ -925,7 +1078,7 @@
 }
 
 void TWPartition::Find_Real_Block_Device(string& Block, bool Display_Error) {
-	char device[512], realDevice[512];
+	char device[PATH_MAX], realDevice[PATH_MAX];
 
 	strcpy(device, Block.c_str());
 	memset(realDevice, 0, sizeof(realDevice));
@@ -1826,6 +1979,23 @@
 
 	Current_File_System = type;
 	blkid_free_probe(pr);
+	if (fs_flags.size() > 1) {
+		std::vector<partition_fs_flags_struct>::iterator iter;
+		std::vector<partition_fs_flags_struct>::iterator found = fs_flags.begin();
+
+		for (iter = fs_flags.begin(); iter != fs_flags.end(); iter++) {
+			if (iter->File_System == Current_File_System) {
+				found = iter;
+				break;
+			}
+		}
+		// If we don't find a match, we default the flags to the first set of flags that we received from the fstab
+		if (Mount_Flags != found->Mount_Flags || Mount_Options != found->Mount_Options) {
+			Mount_Flags = found->Mount_Flags;
+			Mount_Options = found->Mount_Options;
+			LOGINFO("Mount_Flags: %i, Mount_Options: %s\n", Mount_Flags, Mount_Options.c_str());
+		}
+	}
 }
 
 bool TWPartition::Wipe_EXT23(string File_System) {
@@ -2526,8 +2696,79 @@
 	return true;
 }
 
+bool TWPartition::Find_Wildcard_Block_Devices(const string& Device) {
+	int mount_point_index = 0; // we will need to create separate mount points for each partition found and we use this index to name each one
+	string Path = TWFunc::Get_Path(Device);
+	string Dev = TWFunc::Get_Filename(Device);
+	size_t wildcard_index = Dev.find("*");
+	if (wildcard_index != string::npos)
+		Dev = Dev.substr(0, wildcard_index);
+	wildcard_index = Dev.size();
+	DIR* d = opendir(Path.c_str());
+	if (d == NULL) {
+		LOGINFO("Error opening '%s': %s\n", Path.c_str(), strerror(errno));
+		return false;
+	}
+	struct dirent* de;
+	while ((de = readdir(d)) != NULL) {
+		if (de->d_type != DT_BLK || strlen(de->d_name) <= wildcard_index || strncmp(de->d_name, Dev.c_str(), wildcard_index) != 0)
+			continue;
+
+		string item = Path + "/";
+		item.append(de->d_name);
+		if (PartitionManager.Find_Partition_By_Block_Device(item))
+			continue;
+		TWPartition *part = new TWPartition;
+		char buffer[MAX_FSTAB_LINE_LENGTH];
+		sprintf(buffer, "%s %s-%i auto defaults defaults", item.c_str(), Mount_Point.c_str(), ++mount_point_index);
+		part->Process_Fstab_Line(buffer, false, NULL);
+		char display[MAX_FSTAB_LINE_LENGTH];
+		sprintf(display, "%s %i", Storage_Name.c_str(), mount_point_index);
+		part->Storage_Name = display;
+		part->Display_Name = display;
+		part->Primary_Block_Device = item;
+		part->Wildcard_Block_Device = false;
+		part->Is_SubPartition = true;
+		part->SubPartition_Of = Mount_Point;
+		part->Is_Storage = Is_Storage;
+		part->Can_Be_Mounted = true;
+		part->Removable = true;
+		part->Can_Be_Wiped = Can_Be_Wiped;
+		part->Wipe_Available_in_GUI = Wipe_Available_in_GUI;
+		part->Find_Actual_Block_Device();
+		part->Update_Size(false);
+		Has_SubPartition = true;
+		PartitionManager.Output_Partition(part);
+		PartitionManager.Add_Partition(part);
+	}
+	closedir(d);
+	return (mount_point_index > 0);
+}
+
 void TWPartition::Find_Actual_Block_Device(void) {
-	if (Is_Decrypted && !Decrypted_Block_Device.empty()) {
+	if (!Sysfs_Entry.empty() && Primary_Block_Device.empty() && Decrypted_Block_Device.empty()) {
+		/* Sysfs_Entry.empty() indicates if this is a sysfs entry that begins with /device/
+		 * If we have a syfs entry then we are looking for this device from a uevent add.
+		 * The uevent add will set the primary block device based on the data we receive from
+		 * after checking for adopted storage. If the device ends up being adopted, then the
+		 * decrypted block device will be set instead of the primary block device. */
+		Is_Present = false;
+		return;
+	}
+	if (Wildcard_Block_Device && !Is_Adopted_Storage) {
+		Is_Present = false;
+		Actual_Block_Device = "";
+		Can_Be_Mounted = false;
+		if (!Find_Wildcard_Block_Devices(Primary_Block_Device)) {
+			string Dev = Primary_Block_Device.substr(0, Primary_Block_Device.find("*"));
+			if (TWFunc::Path_Exists(Dev)) {
+				Is_Present = true;
+				Can_Be_Mounted = true;
+				Actual_Block_Device = Dev;
+			}
+		}
+		return;
+	} else if (Is_Decrypted && !Decrypted_Block_Device.empty()) {
 		Actual_Block_Device = Decrypted_Block_Device;
 		if (TWFunc::Path_Exists(Decrypted_Block_Device)) {
 			Is_Present = true;