blob: 7667105b3f50d6720226dddde2ee0d124351e09f [file] [log] [blame]
/*
* Copyright (C) 2021 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 com.android.adblib
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.attribute.PosixFilePermission
import java.nio.file.attribute.PosixFilePermissions
/**
* Abstraction over the Linux style file mode (bits) of a file on a remote device.
*
* @see RemoteFileMode companion object for various ways of creating instances
*/
class RemoteFileMode private constructor(
/**
* The Linux style `mode` bits
*/
val modeBits: Int
) {
override fun equals(other: Any?): Boolean {
return (other is RemoteFileMode) && (modeBits == other.modeBits)
}
override fun hashCode(): Int {
return modeBits.hashCode()
}
override fun toString(): String {
return "${javaClass.simpleName}(${modeBits.toString(8)})"
}
/**
* The string representation (e.g. "rwxr-x---") corresponding to this instance
*/
val posixString: String
get() {
return PosixFilePermissions.toString(posixPermissions)
}
/**
* The [Set] of [PosixFilePermission] corresponding to this instance
*/
val posixPermissions: Set<PosixFilePermission>
get() {
return modeBitsToPosixPermissions(modeBits)
}
companion object {
/**
* Default permissions, to be used as fallback ("rw-r--r--")
*/
val DEFAULT: RemoteFileMode
get() = fromModeBits("644".toInt(8))
/**
* Returns a new [RemoteFileMode] instance initialized from Linux style file mode bits
*/
fun fromModeBits(modeBits: Int): RemoteFileMode {
return RemoteFileMode(modeBits)
}
/**
* Returns a new [RemoteFileMode] instance initialized from multiple
* [PosixFilePermission] values
*/
fun fromPosixPermissions(vararg permissions: PosixFilePermission): RemoteFileMode {
return RemoteFileMode(modeBitsFromPosixPermissions(permissions))
}
/**
* Returns a new [RemoteFileMode] instance initialized from a set of
* [PosixFilePermission] values
*/
fun fromPosixPermissions(permissions: Set<PosixFilePermission>): RemoteFileMode {
return fromPosixPermissions(*permissions.toTypedArray())
}
/**
* Returns a new [RemoteFileMode] instance initialized from Linux style file mode
* string, e.g. "rw-r--r"
*
* @throws IllegalArgumentException if the string is invalid, see
* [PosixFilePermissions.fromString]
*/
fun fromPosixString(value: String): RemoteFileMode {
val permissions = PosixFilePermissions.fromString(value)
return fromPosixPermissions(*permissions.toTypedArray())
}
/**
* Returns a new [RemoteFileMode] instance initialized from the file permissions
* of an actual file [Path].
*
* Returns `null` if the [Path] cannot be accessed for some
* reason, e.g. file does not exist. Please use the [DEFAULT] value as alternative
* if this is the case.
*
* Note: The returned [RemoteFileMode] may be an approximation if the underlying
* [Path.getFileSystem] does not support [PosixFilePermission], e.g. on the Windows
* platform.
*/
fun fromPath(path: Path): RemoteFileMode? {
return try {
val permissions = Files.getPosixFilePermissions(path)
fromPosixPermissions(permissions)
} catch (e: UnsupportedOperationException) {
// Some platforms (e.g. Windows) don't support posix file permissions
fromUnsupportedFileSystem(path)
} catch (e: Exception) {
null
}
}
internal fun fromUnsupportedFileSystem(path: Path): RemoteFileMode? {
try {
val permissions = HashSet<PosixFilePermission>()
if (Files.isReadable(path)) {
// Default for Linux is READ for all 3 permission groups
permissions.add(PosixFilePermission.OWNER_READ)
permissions.add(PosixFilePermission.GROUP_READ)
permissions.add(PosixFilePermission.OTHERS_READ)
}
if (Files.isWritable(path)) {
// Default for Linux is WRITE for owner permission group only
permissions.add(PosixFilePermission.OWNER_WRITE)
}
if (Files.isExecutable(path)) {
// Default for Linux is EXECUTE for all 3 permission groups
permissions.add(PosixFilePermission.OWNER_EXECUTE)
permissions.add(PosixFilePermission.GROUP_EXECUTE)
permissions.add(PosixFilePermission.OTHERS_EXECUTE)
}
return if (permissions.isEmpty()) {
// File probably does not exist on disk or is not accessible
null
} else {
fromPosixPermissions(permissions)
}
} catch (e: Exception) {
return null
}
}
private fun modeBitsFromPosixPermissions(posixPermissions: Array<out PosixFilePermission>): Int {
var modeBits = 0
posixPermissions.forEach { permission ->
modeBits = modeBits or modeBitFromPosixFilePermission(permission)
}
return modeBits
}
private fun modeBitsToPosixPermissions(modeBits: Int): Set<PosixFilePermission> {
val result = HashSet<PosixFilePermission>()
PosixFilePermission.values().forEach { permission ->
if ((modeBits and modeBitFromPosixFilePermission(permission)) != 0) {
result.add(permission)
}
}
return result
}
private fun modeBitFromPosixFilePermission(permission: PosixFilePermission): Int {
return when (permission) {
PosixFilePermission.OWNER_READ -> 256
PosixFilePermission.OWNER_WRITE -> 128
PosixFilePermission.OWNER_EXECUTE -> 64
PosixFilePermission.GROUP_READ -> 32
PosixFilePermission.GROUP_WRITE -> 16
PosixFilePermission.GROUP_EXECUTE -> 8
PosixFilePermission.OTHERS_READ -> 4
PosixFilePermission.OTHERS_WRITE -> 2
PosixFilePermission.OTHERS_EXECUTE -> 1
else -> 0
}
}
}
}