MigrationUtils.kt
/*
* Copyright 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.privacysandbox.sdkruntime.client.loader.impl
import android.os.Build
import android.os.FileUtils
import android.system.ErrnoException
import android.system.Os
import android.util.Log
import androidx.annotation.DoNotInline
import androidx.annotation.RequiresApi
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
internal object MigrationUtils {
private const val LOG_TAG = "LocalSdkMigrationUtils"
/**
* Try to migrate all files from source to target that match requested prefix.
* Skip failed files.
*
* @return true if all files moved, or false if some fails happened.
*/
fun moveFiles(srcDir: File, destDir: File, prefix: String): Boolean {
if (srcDir == destDir) {
return true
}
val sourceFiles = srcDir.listFiles { _, name -> name.startsWith(prefix) }
?: emptyArray()
var hadFails = false
for (sourceFile in sourceFiles) {
val targetFile = File(destDir, sourceFile.name)
Log.d(LOG_TAG, "Migrating $sourceFile to $targetFile")
try {
copyFile(sourceFile, targetFile)
copyPermissions(sourceFile, targetFile)
if (!sourceFile.delete()) {
Log.w(LOG_TAG, "Failed to clean up $sourceFile")
hadFails = true
}
} catch (e: IOException) {
Log.w(LOG_TAG, "Failed to migrate $sourceFile", e)
hadFails = true
} catch (e: ErrnoException) {
Log.w(LOG_TAG, "Failed to migrate $sourceFile", e)
hadFails = true
}
}
return !hadFails
}
private fun copyFile(sourceFile: File, targetFile: File) {
if (targetFile.exists()) {
targetFile.delete()
}
FileInputStream(sourceFile).use { sourceStream ->
FileOutputStream(targetFile).use { targetStream ->
copy(sourceStream, targetStream)
Os.fsync(targetStream.fd)
}
}
}
private fun copyPermissions(sourceFile: File, targetFile: File) {
val stat = Os.stat(sourceFile.absolutePath)
Os.chmod(targetFile.absolutePath, stat.st_mode)
Os.chown(targetFile.absolutePath, stat.st_uid, stat.st_gid)
}
private fun copy(from: InputStream, to: OutputStream): Long {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
return Api29.copy(from, to)
}
return from.copyTo(to)
}
@RequiresApi(Build.VERSION_CODES.Q)
private object Api29 {
@DoNotInline
fun copy(from: InputStream, to: OutputStream): Long =
FileUtils.copy(from, to)
}
}