Using cgo to call C code from within Go code

I lack a deep understanding of how C works, but I wanted to call some of the functions within the pdh.dll Windows library from my Go code. I explored a few rabbit holes, so I figured I would post how I got this working. If you want to skip to running my examples on your own, you can download them here.

First off, if you don’t know anything about cgo (like myself) then I recommend learning the basics by reading C? Go? Cgo!

Before running any code using cgo or gcc you have to have gcc installed on your machine. I found the easiest install method to be tdm-gcc. Simply download the file corresponding to the architecture of your machine then run it.

Including a Windows Library

In my example code, pdh.go demonstrates what I set out to do. Here is the code:

package pdh

// #cgo LDFLAGS: -L./lib -lpdh
// #include <pdh.h>
import "C"

func PdhValidatePath(path string) uint32 {
    ret := C.PdhValidatePath(C.CString(path))
    return uint32(ret)
}

// #cgo LDFLAGS: -L./lib -lpdh tells the gcc compiler to look for the pdh library within the standard lib directory (note that this directory is not within the Go project directory).

// #include <pdh.h> tells the gcc compiler to include pdh.h. This means that all of the functions within pdh.h can be called using C.<function name>. An example of this is shown by the line ret := C.PdhValidatePath(C.CString(path))

Including Your Own C

If you want to write your own C class, then I recommend taking a look at the package named increment within my example. In the package directory you can see increment.c and increment.h. These files were translated into increment.lib by running the following commands:

gcc -O2 -c increment.c
ar q increment.lib increment.o

Since increment.lib and increment.h exist within the package directory, they can be referenced with just a simple #include statement as shown here:

package increment

// #include <increment.h>
import "C"

func Increment(i int) int{
   return int(C.x(C.int(i)))
}

Including a Standard Library With Custom C Code

My package titled stdlib demonstrates how to include a standard library with custom C code that references that standard library.

package stdlib

/*
#include 
#include 

void myprint(char* s) {
	printf("%s\n", s);
}
*/
import "C"

import "unsafe"

func PrintText() {
	cs := C.CString("Hello from stdio\n")
	C.myprint(cs)
	C.free(unsafe.Pointer(cs))
}

func Random() int {
	return int(C.rand())
}

func Seed(i int) {
	C.srand(C.uint(i))
}

In this code, you can see that there is a C function called myprint that is defined in the cgo preample. The myprint function can be used to print text using the stdio printf function. This is a pretty simple example, but it is a good starting point for anyone that has a reason to write code in C rather than Go.

Advertisement

Go Glob Case Insensitive

With Go, the filepath Glob() function is a really fast way to find all files that match a particular path, with optional wild cards. I was disappointed, though, that there is not a way to force case-insensitivity to be performed by the Glob() function to the entire pattern that is passed in.

What I found was that the Glob() function is case-insensitive on Windows and case-sensitive on Linux, due to the way that each OS handles file paths. With a regex pattern you can easily force case-insensitivity to the entire pattern using (?i), unfortunately this doesn’t work with Glob() because it doesn’t use regex patterns. Don’t give up hope, though, there is a way to do this.

The Glob() function allows the use of the ‘[‘ and ‘]’ runes to define patterns within the pattern string that is passed into the Glob() function. So the best way I found to do a case-insensitive search is to replace every Letter category Unicode code-point (aka rune) with a lowercase and uppercase version of the rune surrounded by square brackets. So ‘a’ would become ‘[aA]’ and ‘test’ would become ‘[tT][eE][sS][tT]’. In case you don’t know what is included in the Unicode Letter category, you can view the contents here. I believe that the Letter category is made up of the combination of the Ll, Lm, Lo, Lt, and Lu categories. There is no need to alter the path on Windows, since it is already case-insensitive, so I wrote a function to only be performed if the OS is not Windows.

Here is the Go code that I used to perform a conversion of a file path string into a case-insensitive version of the same file path:

func InsensitiveFilepath(path string) string {
   if runtime.GOOS == "windows" {
      return path
   }

   p := ""
   for _, r := range path {
      if unicode.IsLetter(r) {
         p += fmt.Sprintf("[%c%c]", unicode.ToLower(r), unicode.ToUpper(r))
      } else {
         p += string(r)
      }
   }
   return p
}

Here is what it looks like when it runs:

fmt.Println(InsensitiveFilepath("/home/user/test/*"))

/[hH][oO][mM][eE]/[uU][sS][eE][rR]/[tT][eE][sS][tT]/*

You can play with it yourself on the the Go playground!

Go File Permissions on Linux

I wrote an application in Go, aka Golang, that I use to search for text matching a regex pattern within text files. Up to now, everything has worked well with my testing on Windows 10. However, I put my compiled application on to a Linux machine (specifically RHEL) today and I was getting errors with my os.MkdirAll() call. The function call looked like this in my code:

if _, err := os.Stat(logDirectory); os.IsNotExist(err) {
   os.MkdirAll(logDirectory, 0777)
}

Basically this code creates a directory using the path specified in the variable logDirectory with the permissions 777 (Read, Write, and Execute for all users) if the directory does not already exist. If the directory does not exist it will create the directory as well as any necessary parent directories that don’t already exist.

What I found is that my application creates the non-existent directory, but the permissions specified (0777) aren’t what are actually applied to the newly created directory. What ends up being applied is the default permissions according to the umask that is set. So you can see below that my umask is set to 0777 (meaning grant no permissions to newly created files and directories) so the newly created directory has the permissions 0000.

filepermissions.png

If you are unfamiliar with umask, like I was, then here is a basic description as I understand it. Umask is the default permission given when a new file or folder is created on a Linux/Unix machine. Umask accepts values between 0 and 7 that are the negated form of normal permissions. Meaning if you want the default permissions on a directory to be 765 then you should set the umask value to 012. Or if you want the default permissions to be 555, then you should set umask to 222. You can set umask in a shell simply by typing ‘umask’ followed by the desired value. You can view what umask is set to by entering ‘umask’ with no value after it. For example:

umask.png

So back to the issue my Go application was experiencing. I thought this must be some sort of bug when running Go on Linux. Through some Googling, I found that many other Go users have thought the same thing as me. However, the creators of Go state that this is not a bug. Basically their answer is that Go is built to run on various OS’s, but it still has to honor the behaviors expected on each OS. Meaning that in my case, I should not expect file permissions to be handled consistently between my testing on Windows 10 and Linux RHEL. This makes sense. Although, it makes me wonder why the Go MkdirAll() function accepts a parameter for setting the permissions if it isn’t going to be honored.

The Solution

The solution I was able to come up with to solve this issue is that you must use the os.Chmod() function to set the permissions of a directory after creating the directory with the os.MkdirAll() function. Yes it seems weird that I am stating in code to set the permissions to 0777 twice, but that’s the way it is. So here is my updated code:

if _, err := os.Stat(logDirectory); os.IsNotExist(err) {
   os.MkdirAll(logDirectory, 0777)
   os.Chmod(logDirectory, 0777)
}

Here are the results of my test on Linux using this new code:

filepermissionswithchmod.png

As you can see, umask was set to 0777 which means that a newly created directory should have the default permissions of 0000. However, thanks to the addition of os.Chmod() the logs directory now has the permissions 0777. Now that the logs directory has the desired permissions, my Go application is able to run as expected and create new log files when needed.

Additional Information

Unix Permissions Calculator

Umask Explanation

Discussion on how Go’s os.Mkdir() function should handle permission bits.

Linux Chmod Command