mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-22 20:18:36 +00:00
Fix attachment dedupe race where original may be deleted before new usage is recorded.
This commit is contained in:
committed by
Clark Chen
parent
bfcd57881e
commit
ec373b5b4d
@@ -619,9 +619,13 @@ public class AttachmentTable extends DatabaseTable {
|
||||
SQLiteDatabase database = databaseHelper.getSignalWritableDatabase();
|
||||
ContentValues values = new ContentValues();
|
||||
DataInfo oldInfo = getAttachmentDataFileInfo(attachmentId, DATA);
|
||||
DataInfo dataInfo = setAttachmentData(inputStream, attachmentId);
|
||||
DataInfo dataInfo = storeAttachmentStream(inputStream);
|
||||
File transferFile = getTransferFile(databaseHelper.getSignalReadableDatabase(), attachmentId);
|
||||
boolean updated = false;
|
||||
|
||||
database.beginTransaction();
|
||||
try {
|
||||
dataInfo = deduplicateAttachment(dataInfo, attachmentId);
|
||||
if (oldInfo != null) {
|
||||
updateAttachmentDataHash(database, oldInfo.hash, dataInfo);
|
||||
}
|
||||
@@ -637,14 +641,18 @@ public class AttachmentTable extends DatabaseTable {
|
||||
}
|
||||
|
||||
values.put(TRANSFER_STATE, TRANSFER_PROGRESS_DONE);
|
||||
values.put(TRANSFER_FILE, (String)null);
|
||||
values.put(TRANSFER_FILE, (String) null);
|
||||
|
||||
values.put(TRANSFORM_PROPERTIES, TransformProperties.forSkipTransform().serialize());
|
||||
|
||||
if (database.update(TABLE_NAME, values, PART_ID_WHERE, attachmentId.toStrings()) == 0) {
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
dataInfo.file.delete();
|
||||
} else {
|
||||
updated = database.update(TABLE_NAME, values, PART_ID_WHERE, attachmentId.toStrings()) > 0;
|
||||
|
||||
database.setTransactionSuccessful();
|
||||
} finally {
|
||||
database.endTransaction();
|
||||
}
|
||||
|
||||
if (updated) {
|
||||
long threadId = SignalDatabase.messages().getThreadIdForMessage(mmsId);
|
||||
|
||||
if (!SignalDatabase.messages().isStory(mmsId)) {
|
||||
@@ -654,11 +662,16 @@ public class AttachmentTable extends DatabaseTable {
|
||||
notifyConversationListeners(threadId);
|
||||
notifyConversationListListeners();
|
||||
notifyAttachmentListeners();
|
||||
} else {
|
||||
if (!dataInfo.file.delete()) {
|
||||
Log.w(TAG, "Failed to delete unused attachment");
|
||||
}
|
||||
}
|
||||
|
||||
if (transferFile != null) {
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
transferFile.delete();
|
||||
if (!transferFile.delete()) {
|
||||
Log.w(TAG, "Unable to delete transfer file.");
|
||||
}
|
||||
}
|
||||
|
||||
if (placeholder != null && MediaUtil.isAudio(placeholder)) {
|
||||
@@ -861,9 +874,11 @@ public class AttachmentTable extends DatabaseTable {
|
||||
}
|
||||
}
|
||||
|
||||
DataInfo dataInfo = setAttachmentData(destination,
|
||||
mediaStream.getStream(),
|
||||
databaseAttachment.getAttachmentId());
|
||||
DataInfo dataInfo = storeAttachmentStream(destination, mediaStream.getStream());
|
||||
|
||||
database.beginTransaction();
|
||||
try {
|
||||
dataInfo = deduplicateAttachment(dataInfo, databaseAttachment.getAttachmentId());
|
||||
|
||||
ContentValues contentValues = new ContentValues();
|
||||
contentValues.put(SIZE, dataInfo.length);
|
||||
@@ -878,7 +893,13 @@ public class AttachmentTable extends DatabaseTable {
|
||||
databaseAttachment.getAttachmentId(),
|
||||
isSingleUseOfData ? dataInfo.hash : oldDataInfo.hash,
|
||||
contentValues);
|
||||
|
||||
Log.i(TAG, "[updateAttachmentData] Updated " + updateCount + " rows.");
|
||||
|
||||
database.setTransactionSuccessful();
|
||||
} finally {
|
||||
database.endTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1114,25 +1135,9 @@ public class AttachmentTable extends DatabaseTable {
|
||||
|
||||
}
|
||||
|
||||
private @NonNull DataInfo setAttachmentData(@NonNull Uri uri,
|
||||
@Nullable AttachmentId attachmentId)
|
||||
throws MmsException
|
||||
{
|
||||
private @NonNull DataInfo storeAttachmentStream(@NonNull InputStream in) throws MmsException {
|
||||
try {
|
||||
InputStream inputStream = PartAuthority.getAttachmentStream(context, uri);
|
||||
return setAttachmentData(inputStream, attachmentId);
|
||||
} catch (IOException e) {
|
||||
throw new MmsException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private @NonNull DataInfo setAttachmentData(@NonNull InputStream in,
|
||||
@Nullable AttachmentId attachmentId)
|
||||
throws MmsException
|
||||
{
|
||||
try {
|
||||
File dataFile = newFile();
|
||||
return setAttachmentData(dataFile, in, attachmentId);
|
||||
return storeAttachmentStream(newFile(), in);
|
||||
} catch (IOException e) {
|
||||
throw new MmsException(e);
|
||||
}
|
||||
@@ -1152,11 +1157,11 @@ public class AttachmentTable extends DatabaseTable {
|
||||
return PartFileProtector.protect(() -> File.createTempFile("part", ".mms", partsDirectory));
|
||||
}
|
||||
|
||||
private @NonNull DataInfo setAttachmentData(@NonNull File destination,
|
||||
@NonNull InputStream in,
|
||||
@Nullable AttachmentId attachmentId)
|
||||
throws MmsException
|
||||
{
|
||||
/**
|
||||
* Reads the entire stream and saves to disk. If you need to deduplicate attachments, call {@link #deduplicateAttachment(DataInfo, AttachmentId)}
|
||||
* afterwards and use the {@link DataInfo} returned by it instead.
|
||||
*/
|
||||
private @NonNull DataInfo storeAttachmentStream(@NonNull File destination, @NonNull InputStream in) throws MmsException {
|
||||
try {
|
||||
File tempFile = newFile();
|
||||
MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
|
||||
@@ -1171,32 +1176,33 @@ public class AttachmentTable extends DatabaseTable {
|
||||
throw new IllegalStateException("Couldn't rename " + tempFile.getPath() + " to " + destination.getPath());
|
||||
}
|
||||
|
||||
SQLiteDatabase db = databaseHelper.getSignalWritableDatabase();
|
||||
|
||||
db.beginTransaction();
|
||||
try {
|
||||
Optional<DataInfo> sharedDataInfo = findDuplicateDataFileInfo(db, hash, attachmentId);
|
||||
if (sharedDataInfo.isPresent()) {
|
||||
Log.i(TAG, "[setAttachmentData] Duplicate data file found! " + sharedDataInfo.get().file.getAbsolutePath());
|
||||
if (!destination.equals(sharedDataInfo.get().file) && destination.delete()) {
|
||||
Log.i(TAG, "[setAttachmentData] Deleted original file. " + destination);
|
||||
}
|
||||
db.setTransactionSuccessful();
|
||||
return sharedDataInfo.get();
|
||||
} else {
|
||||
Log.i(TAG, "[setAttachmentData] No matching attachment data found. " + destination.getAbsolutePath());
|
||||
db.setTransactionSuccessful();
|
||||
}
|
||||
} finally {
|
||||
db.endTransaction();
|
||||
}
|
||||
|
||||
return new DataInfo(destination, length, out.first, hash);
|
||||
} catch (IOException | NoSuchAlgorithmException e) {
|
||||
throw new MmsException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private @NonNull DataInfo deduplicateAttachment(@NonNull DataInfo dataInfo, @Nullable AttachmentId attachmentId) throws MmsException {
|
||||
SQLiteDatabase db = databaseHelper.getSignalWritableDatabase();
|
||||
|
||||
if (!db.inTransaction()) {
|
||||
throw new IllegalStateException("Must be in a transaction!");
|
||||
}
|
||||
|
||||
Optional<DataInfo> sharedDataInfo = findDuplicateDataFileInfo(db, dataInfo.hash, attachmentId);
|
||||
if (sharedDataInfo.isPresent()) {
|
||||
Log.i(TAG, "[setAttachmentData] Duplicate data file found! " + sharedDataInfo.get().file.getAbsolutePath());
|
||||
if (!dataInfo.file.equals(sharedDataInfo.get().file) && dataInfo.file.delete()) {
|
||||
Log.i(TAG, "[setAttachmentData] Deleted original file. " + dataInfo.file);
|
||||
}
|
||||
return sharedDataInfo.get();
|
||||
} else {
|
||||
Log.i(TAG, "[setAttachmentData] No matching attachment data found. " + dataInfo.file.getAbsolutePath());
|
||||
}
|
||||
|
||||
return dataInfo;
|
||||
}
|
||||
|
||||
private static @NonNull Optional<DataInfo> findDuplicateDataFileInfo(@NonNull SQLiteDatabase database,
|
||||
@NonNull String hash,
|
||||
@Nullable AttachmentId excludedAttachmentId)
|
||||
@@ -1361,7 +1367,7 @@ public class AttachmentTable extends DatabaseTable {
|
||||
long uniqueId = System.currentTimeMillis();
|
||||
|
||||
if (attachment.getUri() != null) {
|
||||
dataInfo = setAttachmentData(attachment.getUri(), null);
|
||||
dataInfo = deduplicateAttachment(storeAttachmentStream(PartAuthority.getAttachmentStream(context, attachment.getUri())), attachmentId);
|
||||
Log.d(TAG, "Wrote part to file: " + dataInfo.file.getAbsolutePath());
|
||||
}
|
||||
|
||||
@@ -1437,6 +1443,8 @@ public class AttachmentTable extends DatabaseTable {
|
||||
notifyPacks = attachment.isSticker() && !hasStickerAttachments();
|
||||
|
||||
database.setTransactionSuccessful();
|
||||
} catch (IOException e) {
|
||||
throw new MmsException(e);
|
||||
} finally {
|
||||
database.endTransaction();
|
||||
}
|
||||
|
||||
@@ -133,9 +133,10 @@ public class ApplicationMigrations {
|
||||
static final int DEDUPE_DB_MIGRATION_2 = 89;
|
||||
static final int EMOJI_VERSION_8 = 90;
|
||||
static final int SVR2_MIRROR = 91;
|
||||
static final int ATTACHMENT_CLEANUP_3 = 92;
|
||||
}
|
||||
|
||||
public static final int CURRENT_VERSION = 91;
|
||||
public static final int CURRENT_VERSION = 92;
|
||||
|
||||
/**
|
||||
* This *must* be called after the {@link JobManager} has been instantiated, but *before* the call
|
||||
@@ -601,6 +602,10 @@ public class ApplicationMigrations {
|
||||
jobs.put(Version.SVR2_MIRROR, new Svr2MirrorMigrationJob());
|
||||
}
|
||||
|
||||
if (lastSeenVersion < Version.ATTACHMENT_CLEANUP_3) {
|
||||
jobs.put(Version.ATTACHMENT_CLEANUP_3, new AttachmentCleanupMigrationJob());
|
||||
}
|
||||
|
||||
return jobs;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user