Merge "Delegate mkdirs() to vold when lacking perms." into klp-dev
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 8e9f3bb..0ba2ac5 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -92,6 +92,7 @@
import android.os.UserHandle;
import android.os.SystemVibrator;
import android.os.UserManager;
+import android.os.storage.IMountService;
import android.os.storage.StorageManager;
import android.print.IPrintManager;
import android.print.PrintManager;
@@ -864,7 +865,9 @@
if (mExternalObbDirs == null) {
mExternalObbDirs = Environment.buildExternalStorageAppObbDirs(getPackageName());
}
- return mExternalObbDirs;
+
+ // Create dirs if needed
+ return ensureDirsExistOrFilter(mExternalObbDirs);
}
}
@@ -2127,14 +2130,25 @@
* Ensure that given directories exist, trying to create them if missing. If
* unable to create, they are filtered by replacing with {@code null}.
*/
- private static File[] ensureDirsExistOrFilter(File[] dirs) {
+ private File[] ensureDirsExistOrFilter(File[] dirs) {
File[] result = new File[dirs.length];
for (int i = 0; i < dirs.length; i++) {
File dir = dirs[i];
if (!dir.exists()) {
if (!dir.mkdirs()) {
- Log.w(TAG, "Failed to ensure directory: " + dir);
- dir = null;
+ // Failing to mkdir() may be okay, since we might not have
+ // enough permissions; ask vold to create on our behalf.
+ final IMountService mount = IMountService.Stub.asInterface(
+ ServiceManager.getService("mount"));
+ int res = -1;
+ try {
+ res = mount.mkdirs(getPackageName(), dir.getAbsolutePath());
+ } catch (RemoteException e) {
+ }
+ if (res != 0) {
+ Log.w(TAG, "Failed to ensure directory: " + dir);
+ dir = null;
+ }
}
}
result[i] = dir;
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index 5b36bca..fc53580 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -109,29 +109,36 @@
// TODO: generalize further to create package-specific environment
// TODO: add support for secondary external storage
- private final File[] mExternalDirs;
- private final File mMediaDir;
+ /** External storage dirs, as visible to vold */
+ private final File[] mExternalDirsForVold;
+ /** External storage dirs, as visible to apps */
+ private final File[] mExternalDirsForApp;
+ /** Primary emulated storage dir for direct access */
+ private final File mEmulatedDirForDirect;
public UserEnvironment(int userId) {
// See storage config details at http://source.android.com/tech/storage/
String rawExternalStorage = System.getenv(ENV_EXTERNAL_STORAGE);
- String rawEmulatedStorageTarget = System.getenv(ENV_EMULATED_STORAGE_TARGET);
+ String rawEmulatedSource = System.getenv(ENV_EMULATED_STORAGE_SOURCE);
+ String rawEmulatedTarget = System.getenv(ENV_EMULATED_STORAGE_TARGET);
String rawMediaStorage = System.getenv(ENV_MEDIA_STORAGE);
if (TextUtils.isEmpty(rawMediaStorage)) {
rawMediaStorage = "/data/media";
}
- if (!TextUtils.isEmpty(rawEmulatedStorageTarget)) {
+ if (!TextUtils.isEmpty(rawEmulatedTarget)) {
// Device has emulated storage; external storage paths should have
// userId burned into them.
final String rawUserId = Integer.toString(userId);
- final File emulatedBase = new File(rawEmulatedStorageTarget);
+ final File emulatedSourceBase = new File(rawEmulatedSource);
+ final File emulatedTargetBase = new File(rawEmulatedTarget);
final File mediaBase = new File(rawMediaStorage);
// /storage/emulated/0
- mExternalDirs = new File[] { buildPath(emulatedBase, rawUserId) };
+ mExternalDirsForVold = new File[] { buildPath(emulatedSourceBase, rawUserId) };
+ mExternalDirsForApp = new File[] { buildPath(emulatedTargetBase, rawUserId) };
// /data/media/0
- mMediaDir = buildPath(mediaBase, rawUserId);
+ mEmulatedDirForDirect = buildPath(mediaBase, rawUserId);
} else {
// Device has physical external storage; use plain paths.
@@ -141,15 +148,16 @@
}
// /storage/sdcard0
- mExternalDirs = new File[] { new File(rawExternalStorage) };
+ mExternalDirsForVold = new File[] { new File(rawExternalStorage) };
+ mExternalDirsForApp = new File[] { new File(rawExternalStorage) };
// /data/media
- mMediaDir = new File(rawMediaStorage);
+ mEmulatedDirForDirect = new File(rawMediaStorage);
}
}
@Deprecated
public File getExternalStorageDirectory() {
- return mExternalDirs[0];
+ return mExternalDirsForApp[0];
}
@Deprecated
@@ -157,44 +165,56 @@
return buildExternalStoragePublicDirs(type)[0];
}
- public File[] getExternalDirs() {
- return mExternalDirs;
+ public File[] getExternalDirsForVold() {
+ return mExternalDirsForVold;
+ }
+
+ public File[] getExternalDirsForApp() {
+ return mExternalDirsForApp;
}
public File getMediaDir() {
- return mMediaDir;
+ return mEmulatedDirForDirect;
}
public File[] buildExternalStoragePublicDirs(String type) {
- return buildPaths(mExternalDirs, type);
+ return buildPaths(mExternalDirsForApp, type);
}
public File[] buildExternalStorageAndroidDataDirs() {
- return buildPaths(mExternalDirs, DIR_ANDROID, DIR_DATA);
+ return buildPaths(mExternalDirsForApp, DIR_ANDROID, DIR_DATA);
}
public File[] buildExternalStorageAndroidObbDirs() {
- return buildPaths(mExternalDirs, DIR_ANDROID, DIR_OBB);
+ return buildPaths(mExternalDirsForApp, DIR_ANDROID, DIR_OBB);
}
public File[] buildExternalStorageAppDataDirs(String packageName) {
- return buildPaths(mExternalDirs, DIR_ANDROID, DIR_DATA, packageName);
+ return buildPaths(mExternalDirsForApp, DIR_ANDROID, DIR_DATA, packageName);
+ }
+
+ public File[] buildExternalStorageAppDataDirsForVold(String packageName) {
+ return buildPaths(mExternalDirsForVold, DIR_ANDROID, DIR_DATA, packageName);
}
public File[] buildExternalStorageAppMediaDirs(String packageName) {
- return buildPaths(mExternalDirs, DIR_ANDROID, DIR_MEDIA, packageName);
+ return buildPaths(mExternalDirsForApp, DIR_ANDROID, DIR_MEDIA, packageName);
}
public File[] buildExternalStorageAppObbDirs(String packageName) {
- return buildPaths(mExternalDirs, DIR_ANDROID, DIR_OBB, packageName);
+ return buildPaths(mExternalDirsForApp, DIR_ANDROID, DIR_OBB, packageName);
+ }
+
+ public File[] buildExternalStorageAppObbDirsForVold(String packageName) {
+ return buildPaths(mExternalDirsForVold, DIR_ANDROID, DIR_OBB, packageName);
}
public File[] buildExternalStorageAppFilesDirs(String packageName) {
- return buildPaths(mExternalDirs, DIR_ANDROID, DIR_DATA, packageName, DIR_FILES);
+ return buildPaths(mExternalDirsForApp, DIR_ANDROID, DIR_DATA, packageName, DIR_FILES);
}
public File[] buildExternalStorageAppCacheDirs(String packageName) {
- return buildPaths(mExternalDirs, DIR_ANDROID, DIR_DATA, packageName, DIR_CACHE);
+ return buildPaths(mExternalDirsForApp, DIR_ANDROID, DIR_DATA, packageName, DIR_CACHE);
}
}
@@ -344,7 +364,7 @@
*/
public static File getExternalStorageDirectory() {
throwIfUserRequired();
- return sCurrentUser.getExternalDirs()[0];
+ return sCurrentUser.getExternalDirsForApp()[0];
}
/** {@hide} */
diff --git a/core/java/android/os/storage/IMountService.java b/core/java/android/os/storage/IMountService.java
index fc18617..51ba2f6 100644
--- a/core/java/android/os/storage/IMountService.java
+++ b/core/java/android/os/storage/IMountService.java
@@ -20,9 +20,7 @@
import android.os.IBinder;
import android.os.IInterface;
import android.os.Parcel;
-import android.os.Parcelable;
import android.os.RemoteException;
-import android.os.storage.StorageVolume;
/**
* WARNING! Update IMountService.h and IMountService.cpp if you change this
@@ -737,7 +735,25 @@
_data.recycle();
}
return _result;
+ }
+ @Override
+ public int mkdirs(String callingPkg, String path) throws RemoteException {
+ Parcel _data = Parcel.obtain();
+ Parcel _reply = Parcel.obtain();
+ int _result;
+ try {
+ _data.writeInterfaceToken(DESCRIPTOR);
+ _data.writeString(callingPkg);
+ _data.writeString(path);
+ mRemote.transact(Stub.TRANSACTION_mkdirs, _data, _reply, 0);
+ _reply.readException();
+ _result = _reply.readInt();
+ } finally {
+ _reply.recycle();
+ _data.recycle();
+ }
+ return _result;
}
}
@@ -811,6 +827,8 @@
static final int TRANSACTION_fixPermissionsSecureContainer = IBinder.FIRST_CALL_TRANSACTION + 33;
+ static final int TRANSACTION_mkdirs = IBinder.FIRST_CALL_TRANSACTION + 34;
+
/**
* Cast an IBinder object into an IMountService interface, generating a
* proxy if needed.
@@ -1154,6 +1172,15 @@
reply.writeInt(resultCode);
return true;
}
+ case TRANSACTION_mkdirs: {
+ data.enforceInterface(DESCRIPTOR);
+ String callingPkg = data.readString();
+ String path = data.readString();
+ int result = mkdirs(callingPkg, path);
+ reply.writeNoException();
+ reply.writeInt(result);
+ return true;
+ }
}
return super.onTransact(code, data, reply, flags);
}
@@ -1376,4 +1403,13 @@
*/
public int fixPermissionsSecureContainer(String id, int gid, String filename)
throws RemoteException;
+
+ /**
+ * Ensure that all directories along given path exist, creating parent
+ * directories as needed. Validates that given path is absolute and that it
+ * contains no relative "." or ".." paths or symlinks. Also ensures that
+ * path belongs to a volume managed by vold, and that path is either
+ * external storage data or OBB directory belonging to calling app.
+ */
+ public int mkdirs(String callingPkg, String path) throws RemoteException;
}
diff --git a/services/java/com/android/server/MountService.java b/services/java/com/android/server/MountService.java
index 1facb80..6ab86f5 100644
--- a/services/java/com/android/server/MountService.java
+++ b/services/java/com/android/server/MountService.java
@@ -19,6 +19,7 @@
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import android.Manifest;
+import android.app.AppOpsManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -2128,6 +2129,85 @@
}
@Override
+ public int mkdirs(String callingPkg, String appPath) {
+ final int userId = UserHandle.getUserId(Binder.getCallingUid());
+ final UserEnvironment userEnv = new UserEnvironment(userId);
+
+ // Validate that reported package name belongs to caller
+ final AppOpsManager appOps = (AppOpsManager) mContext.getSystemService(
+ Context.APP_OPS_SERVICE);
+ appOps.checkPackage(Binder.getCallingUid(), callingPkg);
+
+ try {
+ appPath = new File(appPath).getCanonicalPath();
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to resolve " + appPath + ": " + e);
+ return -1;
+ }
+
+ // Try translating the app path into a vold path, but require that it
+ // belong to the calling package.
+ String voldPath = maybeTranslatePathForVold(appPath,
+ userEnv.buildExternalStorageAppDataDirs(callingPkg),
+ userEnv.buildExternalStorageAppDataDirsForVold(callingPkg));
+ if (voldPath != null) {
+ try {
+ mConnector.execute("volume", "mkdirs", voldPath);
+ return 0;
+ } catch (NativeDaemonConnectorException e) {
+ return e.getCode();
+ }
+ }
+
+ voldPath = maybeTranslatePathForVold(appPath,
+ userEnv.buildExternalStorageAppObbDirs(callingPkg),
+ userEnv.buildExternalStorageAppObbDirsForVold(callingPkg));
+ if (voldPath != null) {
+ try {
+ mConnector.execute("volume", "mkdirs", voldPath);
+ return 0;
+ } catch (NativeDaemonConnectorException e) {
+ return e.getCode();
+ }
+ }
+
+ throw new SecurityException("Invalid mkdirs path: " + appPath);
+ }
+
+ /**
+ * Translate the given path from an app-visible path to a vold-visible path,
+ * but only if it's under the given whitelisted paths.
+ *
+ * @param path a canonicalized app-visible path.
+ * @param appPaths list of app-visible paths that are allowed.
+ * @param voldPaths list of vold-visible paths directly corresponding to the
+ * allowed app-visible paths argument.
+ * @return a vold-visible path representing the original path, or
+ * {@code null} if the given path didn't have an app-to-vold
+ * mapping.
+ */
+ @VisibleForTesting
+ public static String maybeTranslatePathForVold(
+ String path, File[] appPaths, File[] voldPaths) {
+ if (appPaths.length != voldPaths.length) {
+ throw new IllegalStateException("Paths must be 1:1 mapping");
+ }
+
+ for (int i = 0; i < appPaths.length; i++) {
+ final String appPath = appPaths[i].getAbsolutePath();
+ if (path.startsWith(appPath)) {
+ path = new File(voldPaths[i], path.substring(appPath.length() + 1))
+ .getAbsolutePath();
+ if (!path.endsWith("/")) {
+ path = path + "/";
+ }
+ return path;
+ }
+ }
+ return null;
+ }
+
+ @Override
public StorageVolume[] getVolumeList() {
final int callingUserId = UserHandle.getCallingUserId();
final boolean accessAll = (mContext.checkPermission(
@@ -2651,7 +2731,7 @@
if (forVold) {
return new File(Environment.getEmulatedStorageSource(userId), path).getAbsolutePath();
} else {
- return new File(userEnv.getExternalDirs()[0], path).getAbsolutePath();
+ return new File(userEnv.getExternalDirsForApp()[0], path).getAbsolutePath();
}
}