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

Commit 98262d7

Browse files
committed
Set mode for files in line with the platform-independent values used by Golang.
Still need to implement this for Windows.
1 parent f19f970 commit 98262d7

File tree

3 files changed

+78
-24
lines changed

3 files changed

+78
-24
lines changed

libs/docker-client/src/main/kotlin/batect/docker/build/buildkit/services/StatFactory.kt

Lines changed: 59 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ import java.util.concurrent.TimeUnit
4848

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

76+
private enum class ModeBit(val golangValue: UInt, val posixValue: Int) {
77+
Directory(0x8000_0000u, FileStat.S_IFDIR),
78+
Symlink(0x800_0000u, FileStat.S_IFLNK),
79+
Device(0x400_0000u, FileStat.S_IFBLK),
80+
NamedPipe(0x200_0000u, FileStat.S_IFIFO),
81+
Socket(0x100_0000u, FileStat.S_IFSOCK),
82+
Setuid(0x80_0000u, FileStat.S_ISUID),
83+
Setgid(0x40_0000u, FileStat.S_ISGID),
84+
CharDevice(Device.golangValue or 0x20_0000u, FileStat.S_IFCHR),
85+
Sticky(0x10_0000u, FileStat.S_ISVTX),
86+
Regular(0u, FileStat.S_IFREG);
87+
88+
companion object {
89+
val permissionMask: Int = "777".toInt(8)
90+
91+
val types = setOf(
92+
Directory,
93+
Symlink,
94+
Device,
95+
NamedPipe,
96+
Socket,
97+
CharDevice,
98+
Regular
99+
)
100+
101+
val typeFromPosixValue: Map<Int, ModeBit> = types.associateBy { it.posixValue }
102+
}
103+
}
104+
73105
abstract class PosixStatFactory(private val posix: POSIX) : StatFactory {
74106
override fun createStat(path: Path, relativePath: String): Stat {
75107
val details = posix.lstat(path.toString())
@@ -83,7 +115,7 @@ abstract class PosixStatFactory(private val posix: POSIX) : StatFactory {
83115

84116
return Stat(
85117
relativePath,
86-
details.mode(),
118+
convertToGolangMode(details, path),
87119
details.uid(),
88120
details.gid(),
89121
details.st_size(),
@@ -95,6 +127,22 @@ abstract class PosixStatFactory(private val posix: POSIX) : StatFactory {
95127
)
96128
}
97129

130+
private fun convertToGolangMode(stat: FileStat, path: Path): Int {
131+
val permissionBits = stat.mode() and ModeBit.permissionMask
132+
val typeBit = stat.mode() and FileStat.S_IFMT
133+
val golangTypeBit = ModeBit.typeFromPosixValue[typeBit]?.golangValue
134+
135+
if (golangTypeBit == null) {
136+
throw IllegalArgumentException("Unknown type bit in mode 0x${stat.mode().toString(16)} for path $path")
137+
}
138+
139+
val setuidBit = if (stat.isSetuid) ModeBit.Setuid.golangValue else 0u
140+
val setgidBit = if (stat.isSetgid) ModeBit.Setgid.golangValue else 0u
141+
val stickyBit = if (stat.isSticky) ModeBit.Sticky.golangValue else 0u
142+
143+
return permissionBits or (golangTypeBit or setuidBit or setgidBit or stickyBit).toInt()
144+
}
145+
98146
protected abstract fun getExtendedAttributes(path: Path): Map<String, ByteString>
99147
}
100148

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

231-
val permissionMask: Int = "777".toInt(8)
232-
val nonPermissionBits = if (pathIsSymlink) FileStat.S_IFLNK else details.mode() and permissionMask.inv()
279+
val nonPermissionBits = if (pathIsSymlink) FileStat.S_IFLNK else details.mode() and ModeBit.permissionMask.inv()
233280
val newMode = nonPermissionBits or "755".toInt(8)
234281

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

280327
// Based on https://stackoverflow.com/a/58644115/1668119
281328
private fun symlinkTarget(path: Path): String {
282-
val handle = windowsMethods.CreateFileW(WindowsHelpers.toWPath(path.toString()), GENERIC_READ, FILE_SHARE_READ, null, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0)
329+
val handle = windowsMethods.CreateFileW(
330+
WindowsHelpers.toWPath(path.toString()),
331+
GENERIC_READ,
332+
FILE_SHARE_READ,
333+
null,
334+
OPEN_EXISTING,
335+
FILE_FLAG_BACKUP_SEMANTICS,
336+
0
337+
)
283338

284339
if (!handle.isValid) {
285340
throwWindowsNativeMethodFailed(WindowsMethods::CreateFileW, posix)

libs/docker-client/src/unitTest/kotlin/batect/docker/build/buildkit/services/FileSyncServiceSpec.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -550,7 +550,6 @@ object FileSyncServiceSpec : Spek({
550550
}
551551
}
552552

553-
// 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)
554553
// Need to send parent directory if children included (even if directory was otherwise not included)
555554
// Need to cover here:
556555
// - directories - should be allocated an ID but should not be requestable

libs/docker-client/src/unitTest/kotlin/batect/docker/build/buildkit/services/StatFactorySpec.kt

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,8 @@ object StatFactorySpec : Spek({
6262
assertThat(stat.path, equalTo("the-test-file"))
6363
}
6464

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

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

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

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

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

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

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

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

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

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

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

@@ -443,8 +443,8 @@ object StatFactorySpec : Spek({
443443
assertThat(stat.path, equalTo("the-test-directory"))
444444
}
445445

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

450450
it("returns the owner of the directory") {
@@ -518,10 +518,10 @@ object StatFactorySpec : Spek({
518518
assertThat(stat.path, equalTo("the-test-directory"))
519519
}
520520

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

@@ -573,7 +573,7 @@ object StatFactorySpec : Spek({
573573
}
574574
})
575575

576-
private fun octal(value: String): Int = value.toInt(8)
576+
private fun octal(value: String): Int = value.toUInt(8).toInt()
577577

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

0 commit comments

Comments
 (0)