Enable restorecon to properly label symlinks.

commit: 06d45512e2df93f65a51877a51549e522b4f2cf5 changed restorecon to only
operate on paths which had undergone a realpath transformation.  Unfortunately,
this made it impossible to directly restorecon a symlink, since the symlink
would be followed.  Change restorecon to only perform realpath on the directory
prefix, so that symlinks can be labeled.

Bug: 21732016
Change-Id: Iebb5d5e9c637c2ef3da5d5674f73babf094af131
diff --git a/src/android.c b/src/android.c
index 8f66a5a..f253954 100644
--- a/src/android.c
+++ b/src/android.c
@@ -31,6 +31,7 @@
 #include <limits.h>
 #include <sys/vfs.h>
 #include <linux/magic.h>
+#include <libgen.h>
 
 /*
  * XXX Where should this configuration file be located?
@@ -1231,7 +1232,7 @@
     struct statfs sfsb;
     FTS *fts;
     FTSENT *ftsent;
-    char *pathname;
+    char *pathname = NULL, *pathdnamer = NULL, *pathdname, *pathbname;
     char * paths[2] = { NULL , NULL };
     int ftsflags = FTS_NOCHDIR | FTS_XDEV | FTS_PHYSICAL;
     int error, sverrno;
@@ -1246,16 +1247,28 @@
     if (!fc_sehandle)
         return 0;
 
-    // convert passed-in pathname to canonical pathname
-    pathname = realpath(pathname_orig, NULL);
-    if (!pathname) {
-        sverrno = errno;
-        selinux_log(SELINUX_ERROR, "SELinux: Could not get canonical path %s restorecon: %s.\n",
-                pathname_orig, strerror(errno));
-        errno = sverrno;
-        error = -1;
-        goto cleanup;
+    /*
+     * Convert passed-in pathname to canonical pathname by resolving realpath of
+     * containing dir, then appending last component name.
+     */
+    pathbname = basename(pathname_orig);
+    if (!strcmp(pathbname, "/") || !strcmp(pathbname, ".") || !strcmp(pathbname, "..")) {
+        pathname = realpath(pathname_orig, NULL);
+        if (!pathname)
+            goto realpatherr;
+    } else {
+        pathdname = dirname(pathname_orig);
+        pathdnamer = realpath(pathdname, NULL);
+        if (!pathdnamer)
+            goto realpatherr;
+        if (!strcmp(pathdnamer, "/"))
+            error = asprintf(&pathname, "/%s", pathbname);
+        else
+            error = asprintf(&pathname, "%s/%s", pathdnamer, pathbname);
+        if (error < 0)
+            goto oom;
     }
+
     paths[0] = pathname;
     issys = (!strcmp(pathname, SYS_PATH)
             || !strncmp(pathname, SYS_PREFIX, sizeof(SYS_PREFIX)-1)) ? true : false;
@@ -1364,8 +1377,22 @@
     (void) fts_close(fts);
     errno = sverrno;
 cleanup:
+    free(pathdnamer);
     free(pathname);
     return error;
+oom:
+    sverrno = errno;
+    selinux_log(SELINUX_ERROR, "%s:  Out of memory\n", __FUNCTION__);
+    errno = sverrno;
+    error = -1;
+    goto cleanup;
+realpatherr:
+    sverrno = errno;
+    selinux_log(SELINUX_ERROR, "SELinux: Could not get canonical path for %s restorecon: %s.\n",
+            pathname_orig, strerror(errno));
+    errno = sverrno;
+    error = -1;
+    goto cleanup;
 }
 
 int selinux_android_restorecon(const char *file, unsigned int flags)