Add logic to handle file context updates.

Bug: 8116902

(cherry picked from commit 527959d207b5eb852612e91efc4880bde701fd2d)

Change-Id: Ib1061e9b804e29a57116656626999cfc7b1513e4
diff --git a/src/android.c b/src/android.c
index 5607bc7..578f0be 100644
--- a/src/android.c
+++ b/src/android.c
@@ -7,6 +7,7 @@
 #include <errno.h>
 #include <pwd.h>
 #include <grp.h>
+#include <dirent.h>
 #include <sys/mman.h>
 #include <sys/mount.h>
 #include <sys/types.h>
@@ -38,6 +39,11 @@
 	{ SELABEL_OPT_PATH, "/file_contexts" },
 	{ 0, NULL } };
 
+static const struct selinux_opt seopt_backup[] = {
+	{ SELABEL_OPT_PATH, "/data/security/file_contexts_backup" },
+	{ SELABEL_OPT_PATH, "/file_contexts" },
+	{ 0, NULL } };
+
 static const char *const sepolicy_file[] = {
         "/data/security/sepolicy",
         "/sepolicy",
@@ -573,12 +579,12 @@
 err:
 	if (isSystemServer)
 		selinux_log(SELINUX_ERROR,
-			    "%s:  Error setting context for system server: %s\n",
-			    __FUNCTION__, strerror(errno));
+				"%s:  Error setting context for system server: %s\n",
+				__FUNCTION__, strerror(errno));
 	else 
 		selinux_log(SELINUX_ERROR,
-			    "%s:  Error setting context for app with uid %d, seinfo %s: %s\n",
-			    __FUNCTION__, uid, seinfo, strerror(errno));
+				"%s:  Error setting context for app with uid %d, seinfo %s: %s\n",
+				__FUNCTION__, uid, seinfo, strerror(errno));
 
 	rc = -1;
 	goto out;
@@ -590,20 +596,40 @@
 
 static struct selabel_handle *sehandle = NULL;
 
-static struct selabel_handle *file_context_open(void)
-{
+static struct selabel_handle *get_selabel_handle(const struct selinux_opt opts[]) {
 	struct selabel_handle *h;
 	int i = 0;
 
 	h = NULL;
-	while ((h == NULL) && seopts[i].value) {
-		h = selabel_open(SELABEL_CTX_FILE, &seopts[i], 1);
+	while ((h == NULL) && opts[i].value) {
+		h = selabel_open(SELABEL_CTX_FILE, &opts[i], 1);
 		i++;
 	}
 
+	return h;
+}
+
+static struct selabel_handle *file_context_open(void)
+{
+	struct selabel_handle *h;
+
+	h = get_selabel_handle(seopts);
+
 	if (!h)
-		selinux_log(SELINUX_ERROR, "%s: Error getting sehandle label (%s)\n",
-			    __FUNCTION__, strerror(errno));
+		selinux_log(SELINUX_ERROR, "%s: Error getting file context handle (%s)\n",
+				__FUNCTION__, strerror(errno));
+	return h;
+}
+
+static struct selabel_handle *file_context_backup_open(void)
+{
+	struct selabel_handle *h;
+
+	h = get_selabel_handle(seopt_backup);
+
+	if (!h)
+		selinux_log(SELINUX_ERROR, "%s: Error getting backup file context handle (%s)\n",
+				__FUNCTION__, strerror(errno));
 	return h;
 }
 
@@ -663,10 +689,125 @@
 	goto out;
 }
 
+static int file_requires_fixup(const char *pathname,
+					struct selabel_handle *sehandle_old,
+					struct selabel_handle *sehandle_new)
+{
+	int ret;
+	struct stat sb;
+	char *current_context, *old_context, *new_context;
+
+	ret = 0;
+	old_context = NULL;
+	new_context = NULL;
+	current_context = NULL;
+
+	if (lstat(pathname, &sb) < 0) {
+		ret = -1;
+		goto err;
+	}
+
+	if (lgetfilecon(pathname, &current_context) < 0) {
+		ret = -1;
+		goto err;
+	}
+
+	if (selabel_lookup(sehandle_old, &old_context, pathname, sb.st_mode) < 0) {
+		ret = -1;
+		goto err;
+	}
+
+	if (selabel_lookup(sehandle_new, &new_context, pathname, sb.st_mode) < 0) {
+		ret = -1;
+		goto err;
+	}
+
+	ret = (strcmp(old_context, new_context) && !strcmp(current_context, old_context));
+	goto out;
+
+err:
+	selinux_log(SELINUX_ERROR,
+		"%s:  Error comparing context for %s (%s)\n",
+		__FUNCTION__,
+		pathname,
+		strerror(errno));
+
+out:
+	if (current_context)
+		freecon(current_context);
+	if (new_context)
+		freecon(new_context);
+	if (old_context)
+		freecon(old_context);
+	return ret;
+}
+
+static int fixcon_file(const char *pathname,
+		struct selabel_handle *sehandle_old,
+		struct selabel_handle *sehandle_new)
+{
+	int requires_fixup;
+
+	requires_fixup = file_requires_fixup(pathname, sehandle_old, sehandle_new);
+	if (requires_fixup < 0)
+		return -1;
+
+	if (requires_fixup)
+		selinux_android_restorecon(pathname);
+
+	return 0;
+}
+
+static int fixcon_recursive(const char *pathname,
+		struct selabel_handle *sehandle_old,
+		struct selabel_handle *sehandle_new)
+{
+	struct stat statresult;
+	if (lstat(pathname, &statresult) < 0)
+		return -1;
+
+	if (!S_ISDIR(statresult.st_mode))
+		return fixcon_file(pathname, sehandle_old, sehandle_new);
+
+	DIR *dir = opendir(pathname);
+	if (dir == NULL)
+		return -1;
+
+	struct dirent *entry;
+	while ((entry = readdir(dir)) != NULL) {
+		char entryname[PATH_MAX];
+		if (!strcmp(entry->d_name, ".."))
+			continue;
+		if (!strcmp(entry->d_name, "."))
+			continue;
+		sprintf(entryname, "%s/%s", pathname, entry->d_name);
+		fixcon_recursive(entryname, sehandle_old, sehandle_new);
+	}
+
+	if (closedir(dir) < 0)
+		return -1;
+
+	return fixcon_file(pathname, sehandle_old, sehandle_new);
+}
+
+int selinux_android_fixcon(const char *pathname)
+{
+	struct selabel_handle *sehandle_old, *sehandle_new;
+
+	sehandle_old = file_context_backup_open();
+	if (sehandle_old == NULL)
+		return -1;
+
+	sehandle_new = file_context_open();
+	if (sehandle_new == NULL)
+		return -1;
+
+	return fixcon_recursive(pathname, sehandle_old, sehandle_new);
+}
 
 struct selabel_handle* selinux_android_file_context_handle(void)
 {
-        return file_context_open();
+		return file_context_open();
 }
 
 int selinux_android_reload_policy(void)