Skip to content
This repository has been archived by the owner on Oct 22, 2023. It is now read-only.

Commit

Permalink
Set mode for files in line with the platform-independent values used …
Browse files Browse the repository at this point in the history
…by Golang.

Still need to implement this for Windows.
  • Loading branch information
charleskorn committed Jun 27, 2021
1 parent f19f970 commit 98262d7
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ import java.util.concurrent.TimeUnit

// This is based on stat.go, stat_unix.go and stat_windows.go from github.com/tonistiigi/fsutil, which is what BuildKit uses internally.
//
// Note that Golang translates file modes from their native values to platform-independent values, so we have to do the same.
// See https://golang.org/pkg/io/fs/#FileMode.
//
// Some shortcuts have been taken, for example:
// - character and block devices are not handled correctly (devmajor and devminor are never populated)
// - there is no handling of hard links (and it looks like Docker doesn't handle these either)
Expand All @@ -70,6 +73,35 @@ interface StatFactory {
}
}

private enum class ModeBit(val golangValue: UInt, val posixValue: Int) {
Directory(0x8000_0000u, FileStat.S_IFDIR),
Symlink(0x800_0000u, FileStat.S_IFLNK),
Device(0x400_0000u, FileStat.S_IFBLK),
NamedPipe(0x200_0000u, FileStat.S_IFIFO),
Socket(0x100_0000u, FileStat.S_IFSOCK),
Setuid(0x80_0000u, FileStat.S_ISUID),
Setgid(0x40_0000u, FileStat.S_ISGID),
CharDevice(Device.golangValue or 0x20_0000u, FileStat.S_IFCHR),
Sticky(0x10_0000u, FileStat.S_ISVTX),
Regular(0u, FileStat.S_IFREG);

companion object {
val permissionMask: Int = "777".toInt(8)

val types = setOf(
Directory,
Symlink,
Device,
NamedPipe,
Socket,
CharDevice,
Regular
)

val typeFromPosixValue: Map<Int, ModeBit> = types.associateBy { it.posixValue }
}
}

abstract class PosixStatFactory(private val posix: POSIX) : StatFactory {
override fun createStat(path: Path, relativePath: String): Stat {
val details = posix.lstat(path.toString())
Expand All @@ -83,7 +115,7 @@ abstract class PosixStatFactory(private val posix: POSIX) : StatFactory {

return Stat(
relativePath,
details.mode(),
convertToGolangMode(details, path),
details.uid(),
details.gid(),
details.st_size(),
Expand All @@ -95,6 +127,22 @@ abstract class PosixStatFactory(private val posix: POSIX) : StatFactory {
)
}

private fun convertToGolangMode(stat: FileStat, path: Path): Int {
val permissionBits = stat.mode() and ModeBit.permissionMask
val typeBit = stat.mode() and FileStat.S_IFMT
val golangTypeBit = ModeBit.typeFromPosixValue[typeBit]?.golangValue

if (golangTypeBit == null) {
throw IllegalArgumentException("Unknown type bit in mode 0x${stat.mode().toString(16)} for path $path")
}

val setuidBit = if (stat.isSetuid) ModeBit.Setuid.golangValue else 0u
val setgidBit = if (stat.isSetgid) ModeBit.Setgid.golangValue else 0u
val stickyBit = if (stat.isSticky) ModeBit.Sticky.golangValue else 0u

return permissionBits or (golangTypeBit or setuidBit or setgidBit or stickyBit).toInt()
}

protected abstract fun getExtendedAttributes(path: Path): Map<String, ByteString>
}

Expand Down Expand Up @@ -228,8 +276,7 @@ class WindowsStatFactory(private val posix: POSIX) : StatFactory {
// the JVM's method, which does provide that level of precision. Yuck.
val modificationTime = Files.getLastModifiedTime(path).to(TimeUnit.NANOSECONDS)

val permissionMask: Int = "777".toInt(8)
val nonPermissionBits = if (pathIsSymlink) FileStat.S_IFLNK else details.mode() and permissionMask.inv()
val nonPermissionBits = if (pathIsSymlink) FileStat.S_IFLNK else details.mode() and ModeBit.permissionMask.inv()
val newMode = nonPermissionBits or "755".toInt(8)

return Stat(
Expand Down Expand Up @@ -279,7 +326,15 @@ class WindowsStatFactory(private val posix: POSIX) : StatFactory {

// Based on https://stackoverflow.com/a/58644115/1668119
private fun symlinkTarget(path: Path): String {
val handle = windowsMethods.CreateFileW(WindowsHelpers.toWPath(path.toString()), GENERIC_READ, FILE_SHARE_READ, null, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0)
val handle = windowsMethods.CreateFileW(
WindowsHelpers.toWPath(path.toString()),
GENERIC_READ,
FILE_SHARE_READ,
null,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS,
0
)

if (!handle.isValid) {
throwWindowsNativeMethodFailed(WindowsMethods::CreateFileW, posix)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -550,7 +550,6 @@ object FileSyncServiceSpec : Spek({
}
}

// Adjust file mode to match Golang behaviour: no bits set if normal file, otherwise use different bits to signal type (os/stat_darwin.go:12)
// Need to send parent directory if children included (even if directory was otherwise not included)
// Need to cover here:
// - directories - should be allocated an ID but should not be requestable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ object StatFactorySpec : Spek({
assertThat(stat.path, equalTo("the-test-file"))
}

it("returns the mode of the file, with the permission bits set to rwxr-xr-x") {
assertThat(stat.mode, equalTo(octal("100755")))
it("returns the Golang platform-independent mode of the file, with the permission bits set to rwxr-xr-x") {
assertThat(stat.mode, equalTo(octal("755")))
}

it("returns root as the owner of the file") {
Expand Down Expand Up @@ -121,8 +121,8 @@ object StatFactorySpec : Spek({
assertThat(stat.path, equalTo("the-test-link"))
}

it("returns the mode of the symlink, with the permission bits set to rwxr-xr-x") {
assertThat(stat.mode, equalTo(octal("120755")))
it("returns the Golang platform-independent mode of the symlink, with the permission bits set to rwxr-xr-x") {
assertThat(stat.mode, equalTo(octal("1000000755")))
}

it("returns root as the owner of the file") {
Expand Down Expand Up @@ -172,8 +172,8 @@ object StatFactorySpec : Spek({
assertThat(stat.path, equalTo("the-test-directory"))
}

it("returns the mode of the directory, with the permission bits set to rwxr-xr-x") {
assertThat(stat.mode, equalTo(octal("40755")))
it("returns the Golang platform-independent mode of the directory, with the permission bits set to rwxr-xr-x") {
assertThat(stat.mode, equalTo(octal("20000000755")))
}

it("returns root as the owner of the file") {
Expand Down Expand Up @@ -229,8 +229,8 @@ object StatFactorySpec : Spek({
assertThat(stat.path, equalTo("the-test-directory"))
}

it("returns the mode of the symlink, with the permission bits set to rwxr-xr-x") {
assertThat(stat.mode, equalTo(octal("120755")))
it("returns the Golang platform-independent mode of the symlink, with the permission bits set to rwxr-xr-x") {
assertThat(stat.mode, equalTo(octal("1000000755")))
}

it("returns root as the owner of the file") {
Expand Down Expand Up @@ -296,8 +296,8 @@ object StatFactorySpec : Spek({
assertThat(stat.path, equalTo("the-test-file"))
}

it("returns the mode of the file") {
assertThat(stat.mode, equalTo(octal("100751")))
it("returns the Golang platform-independent mode of the file") {
assertThat(stat.mode, equalTo(octal("751")))
}

it("returns the owner of the file") {
Expand Down Expand Up @@ -373,10 +373,10 @@ object StatFactorySpec : Spek({
assertThat(stat.path, equalTo("the-test-link"))
}

it("returns the mode of the symlink") {
it("returns the Golang platform-independent mode of the symlink") {
val expectedMode = when (Platform.getNativePlatform().os) {
Platform.OS.DARWIN -> octal("120755")
Platform.OS.LINUX -> octal("120777") // Symlinks are always 0777 on Linux
Platform.OS.DARWIN -> octal("1000000755")
Platform.OS.LINUX -> octal("1000000777") // Symlinks are always 0777 on Linux
else -> throw UnsupportedOperationException()
}

Expand Down Expand Up @@ -443,8 +443,8 @@ object StatFactorySpec : Spek({
assertThat(stat.path, equalTo("the-test-directory"))
}

it("returns the mode of the directory") {
assertThat(stat.mode, equalTo(octal("40751")))
it("returns the Golang platform-independent mode of the directory") {
assertThat(stat.mode, equalTo(octal("20000000751")))
}

it("returns the owner of the directory") {
Expand Down Expand Up @@ -518,10 +518,10 @@ object StatFactorySpec : Spek({
assertThat(stat.path, equalTo("the-test-directory"))
}

it("returns the mode of the symlink") {
it("returns the Golang platform-independent mode of the symlink") {
val expectedMode = when (Platform.getNativePlatform().os) {
Platform.OS.DARWIN -> octal("120755")
Platform.OS.LINUX -> octal("120777") // Symlinks are always 0777 on Linux
Platform.OS.DARWIN -> octal("1000000755")
Platform.OS.LINUX -> octal("1000000777") // Symlinks are always 0777 on Linux
else -> throw UnsupportedOperationException()
}

Expand Down Expand Up @@ -573,7 +573,7 @@ object StatFactorySpec : Spek({
}
})

private fun octal(value: String): Int = value.toInt(8)
private fun octal(value: String): Int = value.toUInt(8).toInt()

private fun runCommand(vararg args: String) {
val process = ProcessBuilder(args.toList())
Expand Down

0 comments on commit 98262d7

Please sign in to comment.