Merge "Support renaming files with no database row" into rvc-dev
diff --git a/src/com/android/providers/media/DatabaseHelper.java b/src/com/android/providers/media/DatabaseHelper.java
index 55ec897..afa3d63 100644
--- a/src/com/android/providers/media/DatabaseHelper.java
+++ b/src/com/android/providers/media/DatabaseHelper.java
@@ -135,7 +135,7 @@
public void onUpdate(@NonNull DatabaseHelper helper, @NonNull String volumeName,
long oldId, int oldMediaType, boolean oldIsDownload,
long newId, int newMediaType, boolean newIsDownload,
- String ownerPackage, String oldPath);
+ String oldOwnerPackage, String newOwnerPackage, String oldPath);
public void onDelete(@NonNull DatabaseHelper helper, @NonNull String volumeName, long id,
int mediaType, boolean isDownload, String ownerPackage, String path);
}
@@ -269,15 +269,16 @@
final long newId = Long.parseLong(split[4]);
final int newMediaType = Integer.parseInt(split[5]);
final boolean newIsDownload = Integer.parseInt(split[6]) != 0;
- final String ownerPackage = split[7];
- // Path can include ':', assume rest of split[8..length] is path.
- final String oldPath = String.join(":", Arrays.copyOfRange(split, 8, split.length));
+ final String oldOwnerPackage = split[7];
+ final String newOwnerPackage = split[8];
+ // Path can include ':', assume rest of split[9..length] is path.
+ final String oldPath = String.join(":", Arrays.copyOfRange(split, 9, split.length));
Trace.beginSection("_UPDATE");
try {
mFilesListener.onUpdate(DatabaseHelper.this, volumeName, oldId,
oldMediaType, oldIsDownload, newId, newMediaType, newIsDownload,
- ownerPackage, oldPath);
+ oldOwnerPackage, newOwnerPackage, oldPath);
} finally {
Trace.endSection();
}
@@ -989,7 +990,8 @@
final String updateArg =
"old.volume_name||':'||old._id||':'||old.media_type||':'||old.is_download"
+ "||':'||new._id||':'||new.media_type||':'||new.is_download"
- + "||':'||ifnull(old.owner_package_name,'null')||':'||old._data";
+ + "||':'||ifnull(old.owner_package_name,'null')"
+ + "||':'||ifnull(new.owner_package_name,'null')||':'||old._data";
final String deleteArg =
"old.volume_name||':'||old._id||':'||old.media_type||':'||old.is_download"
+ "||':'||ifnull(old.owner_package_name,'null')||':'||old._data";
@@ -1247,7 +1249,7 @@
static final int VERSION_O = 800;
static final int VERSION_P = 900;
static final int VERSION_Q = 1023;
- static final int VERSION_R = 1113;
+ static final int VERSION_R = 1114;
static final int VERSION_LATEST = VERSION_R;
/**
@@ -1387,6 +1389,9 @@
if (fromVersion < 1113) {
// Empty version bump to ensure triggers are recreated
}
+ if (fromVersion < 1114) {
+ // Empty version bump to ensure triggers are recreated
+ }
if (recomputeDataValues) {
recomputeDataValues(db, internal);
diff --git a/src/com/android/providers/media/MediaProvider.java b/src/com/android/providers/media/MediaProvider.java
index 8c44116..21d1a2a 100644
--- a/src/com/android/providers/media/MediaProvider.java
+++ b/src/com/android/providers/media/MediaProvider.java
@@ -564,9 +564,10 @@
public void onUpdate(@NonNull DatabaseHelper helper, @NonNull String volumeName,
long oldId, int oldMediaType, boolean oldIsDownload,
long newId, int newMediaType, boolean newIsDownload,
- String ownerPackage, String oldPath) {
+ String oldOwnerPackage, String newOwnerPackage, String oldPath) {
final boolean isDownload = oldIsDownload || newIsDownload;
- handleUpdatedRowForFuse(oldPath, ownerPackage, oldId, newId);
+ handleUpdatedRowForFuse(oldPath, oldOwnerPackage, oldId, newId);
+ handleOwnerPackageNameChange(oldPath, oldOwnerPackage, newOwnerPackage);
acceptWithExpansion(helper::notifyUpdate, volumeName, oldId, oldMediaType, isDownload);
if (newMediaType != oldMediaType) {
@@ -1342,6 +1343,55 @@
mimeType.startsWith(supportedPrimaryMimeType));
}
+ /**
+ * Removes owner package for the renamed path if the calling package doesn't own the db row
+ *
+ * When oldPath is renamed to newPath, if newPath exists in the database, and caller is not the
+ * owner of the file, owner package is set to 'null'. This prevents previous owner of newPath
+ * from accessing renamed file.
+ * @return {@code true} if
+ * <ul>
+ * <li> there is no corresponding database row for given {@code path}
+ * <li> shared calling package is the owner of the database row
+ * <li> owner package name is already set to 'null'
+ * <li> updating owner package name to 'null' was successful.
+ * </ul>
+ * Returns {@code false} otherwise.
+ */
+ private boolean maybeRemoveOwnerPackageForFuseRename(@NonNull DatabaseHelper helper,
+ @NonNull String path) {
+
+ final Uri uri = Files.getContentUriForPath(path);
+ final int match = matchUri(uri, isCallingPackageAllowedHidden());
+ final String ownerPackageName;
+ final String selection = MediaColumns.DATA + " =? AND "
+ + MediaColumns.OWNER_PACKAGE_NAME + " != 'null'";
+ final String[] selectionArgs = new String[] {path};
+
+ final SQLiteQueryBuilder qbForQuery =
+ getQueryBuilder(TYPE_QUERY, match, uri, Bundle.EMPTY, null);
+ try (Cursor c = qbForQuery.query(helper, new String[] {FileColumns.OWNER_PACKAGE_NAME},
+ selection, selectionArgs, null, null, null, null, null)) {
+ if (!c.moveToFirst()) {
+ // We don't need to remove owner_package from db row if path doesn't exist in
+ // database or owner_package is already set to 'null'
+ return true;
+ }
+ ownerPackageName = c.getString(0);
+ if (isCallingIdentitySharedPackageName(ownerPackageName)) {
+ // We don't need to remove owner_package from db row if calling package is the owner
+ // of the database row
+ return true;
+ }
+ }
+
+ final SQLiteQueryBuilder qbForUpdate =
+ getQueryBuilder(TYPE_UPDATE, match, uri, Bundle.EMPTY, null);
+ ContentValues values = new ContentValues();
+ values.put(FileColumns.OWNER_PACKAGE_NAME, "null");
+ return qbForUpdate.update(helper, values, selection, selectionArgs) == 1;
+ }
+
private boolean updateDatabaseForFuseRename(@NonNull DatabaseHelper helper,
@NonNull String oldPath, @NonNull String newPath, @NonNull ContentValues values) {
return updateDatabaseForFuseRename(helper, oldPath, newPath, values, Bundle.EMPTY);
@@ -1623,26 +1673,34 @@
if (!isMimeTypeSupportedInPath(newPath, newMimeType)) {
return OsConstants.EPERM;
}
- return renameFileUncheckedForFuse(oldPath, newPath);
+ return renameFileForFuse(oldPath, newPath, /* bypassRestrictions */ false) ;
}
private int renameFileUncheckedForFuse(String oldPath, String newPath) {
- final String newMimeType = MimeUtils.resolveMimeType(new File(newPath));
+ return renameFileForFuse(oldPath, newPath, /* bypassRestrictions */ true) ;
+ }
+
+ private int renameFileForFuse(String oldPath, String newPath, boolean bypassRestrictions) {
final DatabaseHelper helper;
try {
helper = getDatabaseForUri(Files.getContentUriForPath(oldPath));
} catch (VolumeNotFoundException e) {
- throw new IllegalStateException("Volume not found while trying to update database for"
- + oldPath + ". Rename failed due to database update error", e);
+ throw new IllegalStateException("Failed to update database row with " + oldPath, e);
}
helper.beginTransaction();
try {
+ final String newMimeType = MimeUtils.resolveMimeType(new File(newPath));
final String oldMimeType = MimeUtils.resolveMimeType(new File(oldPath));
if (!updateDatabaseForFuseRename(helper, oldPath, newPath,
getContentValuesForFuseRename(newPath, oldMimeType, newMimeType))) {
- Log.e(TAG, "Calling package doesn't have write permission to rename file.");
- return OsConstants.EPERM;
+ if (!bypassRestrictions) {
+ Log.e(TAG, "Calling package doesn't have write permission to rename file.");
+ return OsConstants.EPERM;
+ } else if (!maybeRemoveOwnerPackageForFuseRename(helper, newPath)) {
+ Log.wtf(TAG, "Couldn't clear owner package name for " + newPath);
+ return OsConstants.EPERM;
+ }
}
// Try renaming oldPath to newPath in lower file system.
@@ -5210,6 +5268,16 @@
mCallingIdentity.get().addDeletedRowId(path, rowId);
}
+ private void handleOwnerPackageNameChange(@NonNull String oldPath,
+ @NonNull String oldOwnerPackage, @NonNull String newOwnerPackage) {
+ if (Objects.equals(oldOwnerPackage, newOwnerPackage)) {
+ return;
+ }
+ // Invalidate saved owned ID's of the previous owner of the renamed path, this prevents old
+ // owner from gaining access to replaced file.
+ invalidateLocalCallingIdentityCache(oldOwnerPackage, "owner_package_changed:" + oldPath);
+ }
+
/**
* Return the {@link MediaColumns#DATA} field for the given {@code Uri}.
*/