But can I write to it?

I just wanted to find out if a directory was writable for the user, and it turns out it is quite difficult to get that information in Golang. How difficult could it be?

Stat has that information

So my first inclination was that the os.Stat call has that information. It sort of does, in a way, but only for Linux. There is a Sys() method on the fs.FileInfo which returns a specific struct on Linux.

package main

import (
	"fmt"
	"os"
	"syscall"
)

func main() {
	info, err := os.Stat("/tmp/some.file")
	if err != nil {
		fmt.Printf("%s", err)
	}
	if data, ok := info.Sys().(*syscall.Stat_t); ok {
		fmt.Printf("UID: %d\n", data.Uid)
	}
}

Is an example of how you get to that part and if you call syscall.Getuid() you can check it if they are the same and therefore if you at least own the resource. However that does not mean it is writable yet.

Permission bit logic

I tried to finagle some bitwise logic with the permission bits, but again they only work on Linux and truth be told I never trusted myself that I got it to work.

Sidetrack to Java

So in Java there has been this thing since forever. You give it a path, and it tells you if it is writable. It is a static method and easy to use. Why does this not exist in Golang?!?!?

Solution

So I did finally create a solution that was tailored for Unix and Windows separately.

Windows

The Windows solution made me go down a rabbit hole, read up on Win32 API structs and methods on the Microsoft docs and dig deep down in the source of Go itself to figure out what I have access to. I will just show you the code:

package main

import (
	"fmt"
	"syscall"
)

// https://learn.microsoft.com/en-us/windows/win32/fileio/file-access-rights-constants
const FILE_APPEND_FILE = 0x00000002

// https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew#parameters at dwShareMode
const FILE_LOCK = 0x00000000

func main() {
	// Checks if directory is writable
	if hwnd, err := syscall.CreateFile(syscall.StringToUTF16Ptr("C:\\Windows\\system32"), FILE_APPEND_FILE, FILE_LOCK, nil, syscall.OPEN_EXISTING, syscall.FILE_FLAG_BACKUP_SEMANTICS|syscall.FILE_FLAG_OPEN_REPARSE_POINT, 0); err == nil {
		if err = syscall.CloseHandle(hwnd); err != nil {
			fmt.Printf("%s\n", err)
		}
		fmt.Printf("This directory is writable")
	}
}

So yeah, a syscall to CreateFile to open a handle to a directory. I cannot figure out why you need to create a file to get a handle to be able to tell if you can write to a directory, even more so because CreateDirectoryW is also an actual call in the Win32 API, which is the only one that can actually create a directory. This is so confusing.

Linux / MacOSX

The other solution was sort of similar but much easier. There is a nice syscall to Access.

package main

import (
	"fmt"
	"syscall"
)

func main() {
	// Checks if directory is writable
	if err := syscall.Access("/opt/", syscall.O_RDWR); err == nil {
		fmt.Printf("This directory is writable")
	}
}

Conclusion

I feel like all of this code can be hidden away in the Golang standard library and give us a nice os.IsWritable(path string) bool function signature for it.

#100DaysToOffload #devlife #golang