Created
February 9, 2025 16:49
-
-
Save jaredrummler/da1a8e1213e7cbb1b0b97d006676370e to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright 2024 GoatBytes.IO | |
* | |
* 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 io.goatbytes.unix | |
data class FilePermissions( | |
val userPermission: PermissionGroup.User, | |
val groupPermission: PermissionGroup.Group, | |
val otherPermission: PermissionGroup.Other, | |
override val setUID: Boolean, | |
override val setGID: Boolean, | |
override val sticky: Boolean, | |
) : SpecialPermission { | |
companion object { | |
// file mode: | |
const val S_IRGRP = 32 // read permission, group | |
const val S_IROTH = 4 // read permission, others | |
const val S_IRUSR = 256 // read permission, owner | |
const val S_IRWXG = 56 // read, write, execute/search by group | |
const val S_IRWXO = 7 // read, write, execute/search by others | |
const val S_IRWXU = 448 // read, write, execute/search by owner | |
const val S_ISGID = 1024 // set-group-ID on execution | |
const val S_ISUID = 2048 // set-user-ID on execution | |
const val S_ISVTX = 512 // on directories, restricted deletion flag | |
const val S_IWGRP = 16 // write permission, group | |
const val S_IWOTH = 2 // write permission, others | |
const val S_IWUSR = 128 // write permission, owner | |
const val S_IXGRP = 8 // execute/search permission, group | |
const val S_IXOTH = 1 // execute/search permission, others | |
const val S_IXUSR = 64 // execute/search permission, owner | |
// file type bits: | |
const val S_IFMT = 61440 // type of file | |
const val S_IFBLK = 24576 // block special | |
const val S_IFCHR = 8192 // character special | |
const val S_IFDIR = 16384 // directory | |
const val S_IFIFO = 4096 // FIFO special | |
const val S_IFLNK = 40960 // symbolic link | |
const val S_IFREG = 32768 // regular | |
const val S_IFSOCK = 49152 // socket special | |
const val S_IFWHT = 57344 // whiteout special | |
// file types: | |
const val TYPE_BLOCK_SPECIAL = 'b' | |
const val TYPE_CHARACTER_SPECIAL = 'c' | |
const val TYPE_DIRECTORY = 'd' | |
const val TYPE_FIFO = 'p' | |
const val TYPE_SYMBOLIC_LINK = 'l' | |
const val TYPE_REGULAR = '-' | |
const val TYPE_SOCKET = 's' | |
const val TYPE_WHITEOUT = 'w' | |
const val TYPE_UNKNOWN = '?' | |
private const val REGEX_NUMERIC_NOTATION = "^[0-7]{3,4}\$" | |
private const val REGEX_SYMBOLIC_NOTATION = "^(?<type>[bcdpl\\-sw?])?" + | |
"(?<owner>[Ssr\\-][Ssw\\-][Ttx\\-])" + | |
"(?<group>[Ssr\\-][Ssw\\-][Ttx\\-])" + | |
"(?<other>[Ssr\\-][Ssw\\-][Ttx\\-])$" | |
private val regexSymbolicNotation by lazy { REGEX_SYMBOLIC_NOTATION.toRegex() } | |
private val regexNumericNotation by lazy { REGEX_NUMERIC_NOTATION.toRegex() } | |
fun fromStMode(mode: Int): FilePermissions { | |
return fromSymbolicNotation(toSymbolicNotation(mode)) | |
} | |
infix fun ofMode(mode: String): FilePermissions { | |
require(regexNumericNotation.matches(mode)) { | |
"Invalid permissions. Expected permission mode, got '$mode'." | |
} | |
return fromSymbolicNotation(convertNumericToSymbolicNotation(mode)) | |
} | |
fun fromSymbolicNotation(permissions: String): FilePermissions { | |
requireNotNull(regexSymbolicNotation.matchEntire(permissions)) { | |
"Invalid permissions. Expected symbolic permission notation, got '$permissions'" | |
}.let { match -> | |
operator fun MatchResult.get(name: String) = groups[name]!!.value | |
Triple(match["owner"], match["group"], match["other"]) | |
}.let { (owner, group, other) -> | |
return FilePermissions( | |
userPermission = PermissionGroup.User( | |
canRead = owner[0] != '-', | |
canWrite = owner[1] != '-', | |
canExecute = owner[2] != '-' | |
), | |
groupPermission = PermissionGroup.Group( | |
canRead = group[0] != '-', | |
canWrite = group[1] != '-', | |
canExecute = group[2] != '-' | |
), | |
otherPermission = PermissionGroup.Other( | |
canRead = other[0] != '-', | |
canWrite = other[1] != '-', | |
canExecute = other[2] != '-' | |
), | |
setUID = owner[2] == 's' || owner[2] == 'S', | |
setGID = group[2] == 's' || group[2] == 'S', | |
sticky = other[2] == 't' || other[2] == 'T' | |
) | |
} | |
} | |
/** | |
* Converts the file permissions mode to the numeric notation. | |
* | |
* @param st_mode | |
* Mode (permissions) of file. Corresponds to C's `struct stat` from `<stat.h>`. | |
* See [android.system.StructStat.st_mode] | |
* @return The permission represented as a numeric notation. | |
*/ | |
fun toNumericNotation(st_mode: Int): String { | |
var i = 0 | |
/* owner */ | |
i += if (((st_mode and S_IRUSR) != 0)) S_IRUSR else 0 | |
i += if (((st_mode and S_IWUSR) != 0)) S_IWUSR else 0 | |
when (st_mode and (S_IXUSR or S_ISUID)) { | |
S_IXUSR -> i += S_IXUSR | |
S_ISUID -> i += S_ISUID | |
S_IXUSR or S_ISUID -> i += S_IXUSR + S_ISUID | |
} | |
/* group */ | |
i += if (((st_mode and S_IRGRP) != 0)) S_IRGRP else 0 | |
i += if (((st_mode and S_IWGRP) != 0)) S_IWGRP else 0 | |
when (st_mode and (S_IXGRP or S_ISGID)) { | |
S_IXGRP -> i += S_IXGRP | |
S_ISGID -> i += S_ISGID | |
S_IXGRP or S_ISGID -> i += S_IXGRP + S_ISGID | |
} | |
/* other */ | |
i += if ((st_mode and S_IROTH) != 0) S_IROTH else 0 | |
i += if ((st_mode and S_IWOTH) != 0) S_IWOTH else 0 | |
when (st_mode and (S_IXOTH or S_ISVTX)) { | |
S_IXOTH -> i += S_IXOTH | |
S_ISVTX -> i += S_ISVTX | |
S_IXOTH or S_ISVTX -> i += S_IXOTH + S_ISVTX | |
} | |
return i.toUInt().toString(3) // TODO("Integer.toOctalString(i)") | |
} | |
/** | |
* Converts the file permissions mode to the symbolic notation. | |
* | |
* @param st_mode | |
* Mode (permissions) of file. Corresponds to C's `struct stat` from `<stat.h>`. | |
* See [android.system.StructStat.st_mode] | |
* @return The permission represented as a symbolic notation. | |
*/ | |
fun toSymbolicNotation(st_mode: Int): String { | |
var p = "" | |
p += when (st_mode and S_IFMT) { | |
S_IFDIR -> TYPE_DIRECTORY | |
S_IFCHR -> TYPE_CHARACTER_SPECIAL | |
S_IFBLK -> TYPE_BLOCK_SPECIAL | |
S_IFREG -> TYPE_REGULAR | |
S_IFLNK -> TYPE_SYMBOLIC_LINK | |
S_IFSOCK -> TYPE_SOCKET | |
S_IFIFO -> TYPE_FIFO | |
S_IFWHT -> TYPE_WHITEOUT | |
else -> TYPE_UNKNOWN | |
} | |
/* owner */ | |
p += if (((st_mode and S_IRUSR) != 0)) 'r' else '-' | |
p += if (((st_mode and S_IWUSR) != 0)) 'w' else '-' | |
when (st_mode and (S_IXUSR or S_ISUID)) { | |
0 -> p += '-' | |
S_IXUSR -> p += 'x' | |
S_ISUID -> p += 'S' | |
S_IXUSR or S_ISUID -> p += 's' | |
} | |
/* group */ | |
p += if (((st_mode and S_IRGRP) != 0)) 'r' else '-' | |
p += if (((st_mode and S_IWGRP) != 0)) 'w' else '-' | |
when (st_mode and (S_IXGRP or S_ISGID)) { | |
0 -> p += '-' | |
S_IXGRP -> p += 'x' | |
S_ISGID -> p += 'S' | |
S_IXGRP or S_ISGID -> p += 's' | |
} | |
/* other */ | |
p += if ((st_mode and S_IROTH) != 0) 'r' else '-' | |
p += if ((st_mode and S_IWOTH) != 0) 'w' else '-' | |
when (st_mode and (S_IXOTH or S_ISVTX)) { | |
0 -> p += '-' | |
S_IXOTH -> p += 'x' | |
S_ISVTX -> p += 'T' | |
S_IXOTH or S_ISVTX -> p += 't' | |
} | |
return p | |
} | |
/** | |
* | |
* Converts the numeric to the symbolic permission notation. | |
* | |
* Example: `convertNumericToSymbolicNotation("644")` would return "rw-r--r--" | |
* | |
* @param mode | |
* An octal (base-8) notation as shown by `stat -c %a`. This notation consists of at | |
* least three digits. Each of the three rightmost digits represents a different component of | |
* the permissions: owner, group, and others. | |
* @return the symbolic notation of the permission. | |
*/ | |
fun convertNumericToSymbolicNotation(mode: String): String { | |
require(mode.matches("[0-7]+".toRegex())) | |
var arr = mode.toCharArray() | |
require(arr.size in 3..4) { "Invalid length" } | |
val length = arr.size | |
val special: String | |
if (length == 4) { | |
special = parseSpecialFromChar(arr[0]) | |
arr = mode.substring(1).toCharArray() | |
} else { | |
special = "---" | |
} | |
var permissions = "" | |
for (i in 0..2) { | |
val s = special[i] | |
when (arr[i]) { | |
'0' -> permissions += if (s == '-') "---" else "--" + s.uppercaseChar() | |
'1' -> permissions += if (s == '-') "--x" else "--$s" | |
'2' -> permissions += "-w-" | |
'3' -> permissions += if (s == '-') "-wx" else "-w$s" | |
'4' -> permissions += if (s == '-') "r--" else "r-" + s.uppercaseChar() | |
'5' -> permissions += if (s == '-') "r-x" else "r-$s" | |
'6' -> permissions += "rw-" | |
'7' -> permissions += if (s == '-') "rwx" else "rw$s" | |
} | |
} | |
return permissions | |
} | |
/** | |
* | |
* Converts the symbolic to the numeric permission notation. | |
* | |
* Example: `convertSymbolicToNumericNotation("rwxr-xr-x")` would return "755" | |
* | |
* @param permissions | |
* The first character (optional) indicates the file type and is not related to permissions. | |
* The remaining nine characters are in three sets, each representing a class of | |
* permissions as three characters. The first set represents the user class. The second set | |
* represents the group class. The third set represents the others class. Examples: | |
* "-rwxr-xr-x", "rw-r--r--", "drwxr-xr-x" | |
* @return the mode | |
*/ | |
fun convertSymbolicToNumericNotation(permissions: String): String { | |
var permissions = permissions | |
require(permissions.matches("[rwxSTstdcb\\-lp?]+".toRegex())) | |
val length = permissions.length | |
if (length == 10) { | |
// If the value has the character defining the file type, remove that character. | |
permissions = permissions.substring(1) | |
} | |
require(permissions.length == 9) | |
val special: Int = parseSpecialPermissions(permissions) | |
val user = parsePermissions(permissions.substring(0, 3)) | |
val group = parsePermissions(permissions.substring(3, 6)) | |
val other = parsePermissions(permissions.substring(6, 9)) | |
return "$special$user$group$other" | |
} | |
private fun parsePermissions(str: String): Int { | |
require(str.matches("[rwxSTst\\-]+".toRegex())) { "Invalid characters in $str" } | |
require(str.length == 3) { "Invalid length. Expected 3, got ${str.length}" } | |
val permission = str.lowercase() | |
var mode = 0 | |
if (permission[0] == 'r') { | |
mode += 4 | |
} | |
if (permission[1] == 'w') { | |
mode += 2 | |
} | |
if ((permission[2] == 'x') || (permission[2] == 's') || (permission[2] == 't')) { | |
mode += 1 | |
} | |
return mode | |
} | |
private fun parseSpecialFromChar(c: Char): String = when (c) { | |
'0' -> "---" | |
'1' -> "--t" | |
'2' -> "-s-" | |
'3' -> "-st" | |
'4' -> "s--" | |
'5' -> "s-t" | |
'6' -> "ss-" | |
'7' -> "sst" | |
else -> "---" | |
} | |
private fun parseSpecialPermissions(str: String): Int { | |
var str = str | |
require(str.matches("[rwxSTstdcb\\-lp?]+".toRegex())) { "Invalid permission: '$str'" } | |
val length = str.length | |
if (length == 10) { | |
// If the value has the character defining the file type, remove that character. | |
str = str.substring(1) | |
} | |
require(length == 9) | |
val permission = str.lowercase() | |
var mode = 0 | |
if (permission[2] == 's') { | |
mode += 4 | |
} | |
if (permission[5] == 's') { | |
mode += 2 | |
} | |
if (permission[8] == 't') { | |
mode += 1 | |
} | |
return mode | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright 2024 GoatBytes.IO | |
* | |
* 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 io.goatbytes.unix | |
interface Permission { | |
val canRead: Boolean | |
val canWrite: Boolean | |
val canExecute: Boolean | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright 2024 GoatBytes.IO | |
* | |
* 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 io.goatbytes.unix | |
sealed interface PermissionGroup : Permission { | |
data class User( | |
override val canRead: Boolean, | |
override val canWrite: Boolean, | |
override val canExecute: Boolean | |
) : PermissionGroup | |
data class Group( | |
override val canRead: Boolean, | |
override val canWrite: Boolean, | |
override val canExecute: Boolean | |
) : PermissionGroup | |
data class Other( | |
override val canRead: Boolean, | |
override val canWrite: Boolean, | |
override val canExecute: Boolean | |
) : PermissionGroup | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright 2024 GoatBytes.IO | |
* | |
* 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 io.goatbytes.unix | |
interface SpecialPermission { | |
val setUID: Boolean | |
val setGID: Boolean | |
val sticky: Boolean | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment