Symlink application lib directory when on SD card

This will help legacy games that use dlopen() to directly access the
/data/data/<app>/lib directory before the
ApplicationInfo.nativeLibraryDir was part of the API.

Change-Id: Ie9f3e7239b6334708b5d086ffafe66a507f6d9da
diff --git a/cmds/installd/commands.c b/cmds/installd/commands.c
index f6f80d1..a5b3e0e 100644
--- a/cmds/installd/commands.c
+++ b/cmds/installd/commands.c
@@ -936,3 +936,157 @@
 done:
     return 0;
 }
+
+int linklib(const char* dataDir, const char* asecLibDir)
+{
+    char libdir[PKG_PATH_MAX];
+    struct stat s, libStat;
+    int rc = 0;
+
+    const size_t libdirLen = strlen(dataDir) + strlen(PKG_LIB_POSTFIX);
+    if (libdirLen >= PKG_PATH_MAX) {
+        LOGE("library dir len too large");
+        rc = -1;
+        goto out;
+    }
+
+    if (snprintf(libdir, sizeof(libdir), "%s%s", dataDir, PKG_LIB_POSTFIX) != (ssize_t)libdirLen) {
+        LOGE("library dir not written successfully: %s\n", strerror(errno));
+        rc = -1;
+        goto out;
+    }
+
+    if (stat(dataDir, &s) < 0) return -1;
+
+    if (chown(dataDir, 0, 0) < 0) {
+        LOGE("failed to chown '%s': %s\n", dataDir, strerror(errno));
+        return -1;
+    }
+
+    if (chmod(dataDir, 0700) < 0) {
+        LOGE("failed to chmod '%s': %s\n", dataDir, strerror(errno));
+        rc = -1;
+        goto out;
+    }
+
+    if (lstat(libdir, &libStat) < 0) {
+        LOGE("couldn't stat lib dir: %s\n", strerror(errno));
+        rc = -1;
+        goto out;
+    }
+
+    if (S_ISDIR(libStat.st_mode)) {
+        if (delete_dir_contents(libdir, 1, 0) < 0) {
+            rc = -1;
+            goto out;
+        }
+    } else if (S_ISLNK(libStat.st_mode)) {
+        if (unlink(libdir) < 0) {
+            rc = -1;
+            goto out;
+        }
+    }
+
+    if (symlink(asecLibDir, libdir) < 0) {
+        LOGE("couldn't symlink directory '%s' -> '%s': %s\n", libdir, asecLibDir, strerror(errno));
+        rc = -errno;
+        goto out;
+    }
+
+    if (lchown(libdir, AID_SYSTEM, AID_SYSTEM) < 0) {
+        LOGE("cannot chown dir '%s': %s\n", libdir, strerror(errno));
+        unlink(libdir);
+        rc = -errno;
+        goto out;
+    }
+
+out:
+    if (chmod(dataDir, s.st_mode) < 0) {
+        LOGE("failed to chmod '%s': %s\n", dataDir, strerror(errno));
+        return -errno;
+    }
+
+    if (chown(dataDir, s.st_uid, s.st_gid) < 0) {
+        LOGE("failed to chown '%s' : %s\n", dataDir, strerror(errno));
+        return -errno;
+    }
+
+    return rc;
+}
+
+int unlinklib(const char* dataDir)
+{
+    char libdir[PKG_PATH_MAX];
+    struct stat s, libStat;
+    int rc = 0;
+
+    const size_t libdirLen = strlen(dataDir) + strlen(PKG_LIB_POSTFIX);
+    if (libdirLen >= PKG_PATH_MAX) {
+        return -1;
+    }
+
+    if (snprintf(libdir, sizeof(libdir), "%s%s", dataDir, PKG_LIB_POSTFIX) != (ssize_t)libdirLen) {
+        LOGE("library dir not written successfully: %s\n", strerror(errno));
+        return -1;
+    }
+
+    if (stat(dataDir, &s) < 0) {
+        LOGE("couldn't state data dir");
+        return -1;
+    }
+
+    if (chown(dataDir, 0, 0) < 0) {
+        LOGE("failed to chown '%s': %s\n", dataDir, strerror(errno));
+        return -1;
+    }
+
+    if (chmod(dataDir, 0700) < 0) {
+        LOGE("failed to chmod '%s': %s\n", dataDir, strerror(errno));
+        rc = -1;
+        goto out;
+    }
+
+    if (lstat(libdir, &libStat) < 0) {
+        LOGE("couldn't stat lib dir: %s\n", strerror(errno));
+        rc = -1;
+        goto out;
+    }
+
+    if (S_ISDIR(libStat.st_mode)) {
+        if (delete_dir_contents(libdir, 1, 0) < 0) {
+            rc = -1;
+            goto out;
+        }
+    } else if (S_ISLNK(libStat.st_mode)) {
+        if (unlink(libdir) < 0) {
+            rc = -1;
+            goto out;
+        }
+    }
+
+    if (mkdir(libdir, 0755) < 0) {
+        LOGE("cannot create dir '%s': %s\n", libdir, strerror(errno));
+        rc = -errno;
+        goto out;
+    }
+
+    if (chown(libdir, AID_SYSTEM, AID_SYSTEM) < 0) {
+        LOGE("cannot chown dir '%s': %s\n", libdir, strerror(errno));
+        unlink(libdir);
+        rc = -errno;
+        goto out;
+    }
+
+out:
+    if (chmod(dataDir, s.st_mode) < 0) {
+        LOGE("failed to chmod '%s': %s\n", dataDir, strerror(errno));
+        return -1;
+    }
+
+    if (chown(dataDir, s.st_uid, s.st_gid) < 0) {
+        LOGE("failed to chown '%s' : %s\n", dataDir, strerror(errno));
+        return -1;
+    }
+
+    return rc;
+}
diff --git a/cmds/installd/installd.c b/cmds/installd/installd.c
index c991845..9ba6402 100644
--- a/cmds/installd/installd.c
+++ b/cmds/installd/installd.c
@@ -101,6 +101,16 @@
     return movefiles();
 }
 
+static int do_linklib(char **arg, char reply[REPLY_MAX])
+{
+    return linklib(arg[0], arg[1]);
+}
+
+static int do_unlinklib(char **arg, char reply[REPLY_MAX])
+{
+    return unlinklib(arg[0]);
+}
+
 struct cmdinfo {
     const char *name;
     unsigned numargs;
@@ -121,6 +131,8 @@
     { "getsize",              4, do_get_size },
     { "rmuserdata",           2, do_rm_user_data },
     { "movefiles",            0, do_movefiles },
+    { "linklib",              2, do_linklib },
+    { "unlinklib",            1, do_unlinklib },
 };
 
 static int readx(int s, void *_buf, int count)
diff --git a/cmds/installd/installd.h b/cmds/installd/installd.h
index 479e4b2..59475e9 100644
--- a/cmds/installd/installd.h
+++ b/cmds/installd/installd.h
@@ -111,3 +111,5 @@
 int free_cache(int64_t free_size);
 int dexopt(const char *apk_path, uid_t uid, int is_public);
 int movefiles();
+int linklib(const char* target, const char* source);
+int unlinklib(const char* libPath);
diff --git a/core/tests/coretests/src/android/content/pm/PackageManagerTests.java b/core/tests/coretests/src/android/content/pm/PackageManagerTests.java
index 276e281c..d5f385b 100755
--- a/core/tests/coretests/src/android/content/pm/PackageManagerTests.java
+++ b/core/tests/coretests/src/android/content/pm/PackageManagerTests.java
@@ -45,6 +45,7 @@
 import android.util.Log;
 
 import java.io.File;
+import java.io.IOException;
 import java.io.InputStream;
 
 public class PackageManagerTests extends AndroidTestCase {
@@ -378,6 +379,18 @@
                     assertEquals(publicSrcPath, appInstallPath);
                     assertFalse((info.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0);
                     assertTrue(info.nativeLibraryDir.startsWith(dataDir.getPath()));
+
+                    // Make sure the native library dir is not a symlink
+                    final File nativeLibDir = new File(info.nativeLibraryDir);
+                    assertTrue("Native library dir should exist at " + info.nativeLibraryDir,
+                            nativeLibDir.exists());
+                    try {
+                        assertEquals("Native library dir should not be a symlink",
+                                info.nativeLibraryDir,
+                                nativeLibDir.getCanonicalPath());
+                    } catch (IOException e) {
+                        fail("Can't read " + nativeLibDir.getPath());
+                    }
                 } else if (rLoc == INSTALL_LOC_SD){
                     assertTrue("Application flags (" + info.flags
                             + ") should contain FLAG_EXTERNAL_STORAGE",
@@ -391,6 +404,19 @@
                     assertTrue("The native library path (" + info.nativeLibraryDir
                             + ") should start with " + SECURE_CONTAINERS_PREFIX,
                             info.nativeLibraryDir.startsWith(SECURE_CONTAINERS_PREFIX));
+
+                    // Make sure the native library in /data/data/<app>/lib is a
+                    // symlink to the ASEC
+                    final File nativeLibSymLink = new File(info.dataDir, "lib");
+                    assertTrue("Native library symlink should exist at " + nativeLibSymLink.getPath(),
+                            nativeLibSymLink.exists());
+                    try {
+                        assertEquals(nativeLibSymLink.getPath() + " should be a symlink to "
+                                + info.nativeLibraryDir, info.nativeLibraryDir, nativeLibSymLink
+                                .getCanonicalPath());
+                    } catch (IOException e) {
+                        fail("Can't read " + nativeLibSymLink.getPath());
+                    }
                 } else {
                     // TODO handle error. Install should have failed.
                     fail("Install should have failed");
@@ -1406,13 +1432,21 @@
                         receiver);
                 assertTrue(retCode);
                 ApplicationInfo info = getPm().getApplicationInfo(ip.pkg.packageName, 0);
-                assertNotNull(info);
+                assertNotNull("ApplicationInfo for recently installed application should exist",
+                        info);
                 if ((moveFlags & PackageManager.MOVE_INTERNAL) != 0) {
-                    assertTrue((info.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) == 0);
-                    assertTrue(info.nativeLibraryDir.startsWith(info.dataDir));
+                    assertTrue("ApplicationInfo.FLAG_EXTERNAL_STORAGE flag should NOT be set",
+                            (info.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) == 0);
+                    assertTrue("ApplicationInfo.nativeLibraryDir should start with " + info.dataDir,
+                            info.nativeLibraryDir.startsWith(info.dataDir));
                 } else if ((moveFlags & PackageManager.MOVE_EXTERNAL_MEDIA) != 0){
-                    assertTrue((info.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0);
-                    assertTrue(info.nativeLibraryDir.startsWith(SECURE_CONTAINERS_PREFIX));
+                    assertTrue("ApplicationInfo.FLAG_EXTERNAL_STORAGE flag should be set",
+                            (info.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0);
+                    assertTrue("ApplicationInfo.nativeLibraryDir should start with " + SECURE_CONTAINERS_PREFIX,
+                            info.nativeLibraryDir.startsWith(SECURE_CONTAINERS_PREFIX));
+                    final File nativeLibSymLink = new File(info.dataDir, "lib");
+                    assertTrue("The data directory should have a 'lib' symlink that points to the ASEC container",
+                            nativeLibSymLink.getCanonicalPath().startsWith(SECURE_CONTAINERS_PREFIX));
                 }
             }
         } catch (NameNotFoundException e) {
diff --git a/services/java/com/android/server/Installer.java b/services/java/com/android/server/Installer.java
index 1f34eba..85eca60 100644
--- a/services/java/com/android/server/Installer.java
+++ b/services/java/com/android/server/Installer.java
@@ -327,4 +327,33 @@
     public int moveFiles() {
         return execute("movefiles");
     }
+
+    public int linkNativeLibraryDirectory(String dataPath, String nativeLibPath) {
+        if (dataPath == null) {
+            Slog.e(TAG, "unlinkNativeLibraryDirectory dataPath is null");
+            return -1;
+        } else if (nativeLibPath == null) {
+            Slog.e(TAG, "unlinkNativeLibraryDirectory nativeLibPath is null");
+            return -1;
+        }
+
+        StringBuilder builder = new StringBuilder("linklib ");
+        builder.append(dataPath);
+        builder.append(' ');
+        builder.append(nativeLibPath);
+
+        return execute(builder.toString());
+    }
+
+    public int unlinkNativeLibraryDirectory(String dataPath) {
+        if (dataPath == null) {
+            Slog.e(TAG, "unlinkNativeLibraryDirectory dataPath is null");
+            return -1;
+        }
+
+        StringBuilder builder = new StringBuilder("unlinklib ");
+        builder.append(dataPath);
+
+        return execute(builder.toString());
+    }
 }
diff --git a/services/java/com/android/server/PackageManagerService.java b/services/java/com/android/server/PackageManagerService.java
index 050e0c8..adc8272 100644
--- a/services/java/com/android/server/PackageManagerService.java
+++ b/services/java/com/android/server/PackageManagerService.java
@@ -3290,7 +3290,11 @@
                     }
                 } else if (!isExternal(pkg)) {
                     Log.i(TAG, path + " changed; unpacking");
+                    mInstaller.unlinkNativeLibraryDirectory(dataPath.getPath());
                     NativeLibraryHelper.copyNativeBinariesLI(scanFile, sharedLibraryDir);
+                } else {
+                    mInstaller.linkNativeLibraryDirectory(dataPath.getPath(),
+                            pkg.applicationInfo.nativeLibraryDir);
                 }
             }
             pkg.mScanPath = path;
@@ -5010,10 +5014,6 @@
                 try { if (out != null) out.close(); } catch (IOException e) {}
             }
 
-            if (!temp) {
-                NativeLibraryHelper.copyNativeBinariesLI(codeFile, new File(libraryPath));
-            }
-
             return ret;
         }
 
@@ -9894,10 +9894,10 @@
                            synchronized (mPackages) {
                                PackageParser.Package pkg = mPackages.get(mp.packageName);
                                // Recheck for package again.
-                               if (pkg == null ) {
-                                   Slog.w(TAG, " Package " + mp.packageName +
-                                   " doesn't exist. Aborting move");
-                                   returnCode = PackageManager.MOVE_FAILED_DOESNT_EXIST;
+                                if (pkg == null) {
+                                    Slog.w(TAG, " Package " + mp.packageName
+                                            + " doesn't exist. Aborting move");
+                                    returnCode = PackageManager.MOVE_FAILED_DOESNT_EXIST;
                                } else if (!mp.srcArgs.getCodePath().equals(pkg.applicationInfo.sourceDir)) {
                                    Slog.w(TAG, "Package " + mp.packageName + " code path changed from " +
                                            mp.srcArgs.getCodePath() + " to " + pkg.applicationInfo.sourceDir +
@@ -9908,15 +9908,34 @@
                                    final String newCodePath = mp.targetArgs.getCodePath();
                                    final String newResPath = mp.targetArgs.getResourcePath();
                                    final String newNativePath = mp.targetArgs.getNativeLibraryPath();
-                                   pkg.mPath = newCodePath;
-                                   // Move dex files around
-                                   if (moveDexFilesLI(pkg)
-                                           != PackageManager.INSTALL_SUCCEEDED) {
-                                       // Moving of dex files failed. Set
-                                       // error code and abort move.
-                                       pkg.mPath = pkg.mScanPath;
-                                       returnCode = PackageManager.MOVE_FAILED_INSUFFICIENT_STORAGE;
-                                   } else {
+
+                                    if ((mp.flags & PackageManager.INSTALL_EXTERNAL) == 0) {
+                                        if (mInstaller
+                                                .unlinkNativeLibraryDirectory(pkg.applicationInfo.dataDir) < 0) {
+                                            returnCode = PackageManager.MOVE_FAILED_INSUFFICIENT_STORAGE;
+                                        } else {
+                                            NativeLibraryHelper.copyNativeBinariesLI(
+                                                    new File(newCodePath), new File(newNativePath));
+                                        }
+                                    } else {
+                                        if (mInstaller.linkNativeLibraryDirectory(
+                                                pkg.applicationInfo.dataDir, newNativePath) < 0) {
+                                            returnCode = PackageManager.MOVE_FAILED_INSUFFICIENT_STORAGE;
+                                        }
+                                    }
+
+                                    if (returnCode == PackageManager.MOVE_SUCCEEDED) {
+                                        pkg.mPath = newCodePath;
+                                        // Move dex files around
+                                        if (moveDexFilesLI(pkg) != PackageManager.INSTALL_SUCCEEDED) {
+                                            // Moving of dex files failed. Set
+                                            // error code and abort move.
+                                            pkg.mPath = pkg.mScanPath;
+                                            returnCode = PackageManager.MOVE_FAILED_INSUFFICIENT_STORAGE;
+                                        }
+                                    }
+
+                                    if (returnCode == PackageManager.MOVE_SUCCEEDED) {
                                        pkg.mScanPath = newCodePath;
                                        pkg.applicationInfo.sourceDir = newCodePath;
                                        pkg.applicationInfo.publicSourceDir = newResPath;