I'm testing cgo and every simple hello world like code works well.
but i have a problem with C code below.
The C code is that traverse a directory tree and sums file size.
if i build with go command, then the build is OK with no error.
but when running, there is a "segmentation violation" error occurred
bash$./walkdir
fatal error: unexpected signal during runtime execution
[signal SIGSEGV: segmentation violation code=0x1 addr=0x1 pc=0x7f631e077c1a]
. . . .
-------------------------------------------------------------
package main
/*
#include <stdint.h>
#include <fts.h>
#include <sys/stat.h>
uintmax_t get_total_size(char *path)
{
uintmax_t total_size = 0;
FTS *fts = fts_open(&path, FTS_PHYSICAL, NULL);
FTSENT *fent;
while ((fent = fts_read(fts)) != NULL)
if (fent->fts_info == FTS_F)
total_size += fent->fts_statp->st_size;
fts_close(fts);
return total_size;
}
*/
import "C"
import "fmt"
func main() {
fmt.Println(C.get_total_size(C.CString("/usr")))
}
fts_open
is defined like this:
fts_open()
Thefts_open()
function takes a pointer to an array of character pointers naming one or more paths which make up a logical file hierarchy to be traversed. The array must be terminated by anull
pointer.
C does not have direct support for arrays; it only has pointers.
In your case you pass fts_open
a single valid pointer but it is not located in an array which has a NULL
pointer as the immediately following element, so fts_open
continues to scan the memory past &path
— looking for a NULL
pointer, — and eventually tries to read memory at some address it is forbidden to do so (usually because the page at that address was not allocated).
A way to fix it is to create that array and initialize it on the C side.
Looks like you're using a reasonably up-to-date standard of C, so let's just use direct literal to initialize the array:
package main
/*
#include <stddef.h> // for NULL
#include <stdint.h>
#include <stdlib.h> // for C.free
#include <fts.h>
#include <sys/stat.h>
uintmax_t get_total_size(char *path)
{
uintmax_t total_size = 0;
char * path_argv[2] = {path, NULL};
FTS *fts = fts_open(path_argv, FTS_PHYSICAL, NULL);
FTSENT *fent;
while ((fent = fts_read(fts)) != NULL)
if (fent->fts_info == FTS_F)
total_size += fent->fts_statp->st_size;
fts_close(fts);
return total_size;
}
*/
import "C"
import (
"fmt"
"unsafe"
)
func main() {
cpath := C.CString("/usr")
defer C.free(unsafe.Pointer(cpath))
fmt.Println(C.get_total_size(cpath))
}
Note that your program has one bug and one possible problem:
C.CString
allocates a chunk of memory by performing a call to malloc(3)
from the linked C library, and you did not free that memory block.NULL
is defined in "stddef.h"; you might or might not get an error when compiling.I've fixed both problems in my example.
A further improvement over our example might be leveraging the ability of fts_*
functions to scan multiple paths in a single run; if we were to implement that, it would have more sense to allocate the array for the 1st argument of fts_open
on the Go's side:
package main
/*
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <fts.h>
#include <sys/stat.h>
uintmax_t get_total_size(char * const *path_argv)
{
uintmax_t total_size = 0;
FTS *fts = fts_open(path_argv, FTS_PHYSICAL, NULL);
FTSENT *fent;
while ((fent = fts_read(fts)) != NULL)
if (fent->fts_info == FTS_F)
total_size += fent->fts_statp->st_size;
fts_close(fts);
return total_size;
}
*/
import "C"
import (
"fmt"
"unsafe"
)
func main() {
fmt.Println(getTotalSize("/usr", "/etc"))
}
func getTotalSize(paths ...string) uint64 {
argv := make([]*C.char, len(paths)+1)
for i, path := range paths {
argv[i] = C.CString(path)
defer C.free(unsafe.Pointer(argv[i]))
}
return uint64(C.get_total_size(&argv[0]))
}
Note that here we did not explicitly zero out the last argument of argv
because — contrary to C, — Go initializes each allocated memory block with zeroes, so once argv
is allocated, all its memory is already zeroed.