Skip to content
This repository has been archived by the owner on Sep 11, 2020. It is now read-only.

Commit

Permalink
Merge pull request #54 from vmarkovtsev/cshared
Browse files Browse the repository at this point in the history
Add cshared files to allow building wrappers in other languages
  • Loading branch information
mcuadros authored Jun 15, 2016
2 parents 9774edb + bea4154 commit e305c47
Show file tree
Hide file tree
Showing 9 changed files with 1,320 additions and 0 deletions.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,14 @@ fmt.Println(commit)
```


Wrapping
--------

go-git can be wrapped into any language which supports shared library interop.
[Python wrapper](https://github.com/src-d/gypogit) already exists.
This is provided by "cshared" [cgo](https://golang.org/cmd/cgo/) files which can be built
with `go build -o libgogit.so -buildmode=c-shared github.com/src-d/go-git/cshared`.

Acknowledgements
----------------

Expand Down
79 changes: 79 additions & 0 deletions cshared/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
cshared
=======

Building
--------
go 1.6+
```
go build -o libgogit.so -buildmode=c-shared github.com/src-d/go-git/cshared
```
Two files must appear: libgogit.h and libgogit.so. The second must be
a shared library, not an ar archive (may happen when something goes wrong).
Check the exported symbols with `nm -g`.

How it works
------------

Nearly every public Go function is mirrored in the corresponding *_cshared.go
file. struct fields are also mirrored with getters and setters. The functions
are marked with `//export ...` "magic" cgo comment so that they appear
in defined symbols of a shared library built with `-buildmode=c-shared`.

Go pointers may not be passed out of cgo functions, so we maintain the
two-way registry of all active Go objects mapped to `Handle`-s (`uint64`).
Every time we need to return a reference to Go object outside, we call
`RegisterObject(interface{})` which returns a new `Handle` or reuses
an existing one if the object has already been registered. Then we
return the obtained `Handle`. When we need to receive a Go object reference
in cgo function parameters, we accept `uint64` and retrieve the `interface{}`
with `GetObject(Handle)` which can be casted to the underlying type with a
type assertion. When the object is no longer needed, we invoke
`UnregisterObject(Handle)`.

Although `interface{]` is just two `uintptr`-s inside, it is not a hashable
type and we cannot use it a as key in our backward registry mapping.
We are using the data `uintptr` as the key there. Since several distinct
objects may exist with the same data pointer (e.g. struct and first field
of the struct), the value of that mapping is a slice of `Handle`-s.

All the mentioned service functions are goroutine- and threadsafe.

`std_cshared.go` contains the cgo wrappers for standard library objects.

Debugging
---------
`c_dump_object()` prints the current state of the two-way object registry
to stdout. `c_set_trace()` activates echoing of `RegisterObject()` and
`UnregisterObject()` invocations.

Caveats
-------
Normally, we pass over a pointer to object as `interface{}` into `RegisterObject()`
so that it can be mutated later. It requires the corresponding
pointer-to-type type assertion in cgo functions. If you mess with this,
the cgo function will, of course, panic.

A cgo function is allowed to take Go's `string` parameters. `string`'s
data must point to some memory and cgo does not copy the incoming foreign
memory into Go memory automatically. What's worse, `string`-s are immutable
and when you copy it, the copy points to the same memory. This means that
if you pass in a `string` which was constructed using `malloc()`, for example,
and later `free()` it, all Go strings created from the function parameter
will point to the invalid memory. Actually, this allowance violates the
cgo pointer passing rules stated just several blocks of texts
below the example of string parameters - this is crazy, but we have to live
with this, as usual in Go world. So, *all incoming `string`-s must be immediately
safely copied with `CopyString()` once they are used*.

Returning strings and byte slices is also funny: you have to use `C.CString` -> `*C.char`
and additionally return the length as another result tuple member if needed.
`C.CString` copies the memory pointed by `string` to a `malloc()`-ed region
and it is the responsibility of the other side to `free()` it or it will leak
otherwise.

Another tricky part is in `c_std_map_get_str_str` and similar places
where you need to return `*C.char` from an unaddressed array accessed under
a pseudonym type through reflection. The only way I've found working
is using `reflect.Copy` to byte slice (copy) and then conversion to
`string` (copy), then `C.CString` (copy) and finally another (copy) on the
receiving side because the latter must be `free()`-d. Extremely efficient.
192 changes: 192 additions & 0 deletions cshared/auth_method_cshared.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
// +build ignore
package main

import (
"C"
"strings"

"golang.org/x/crypto/ssh"
"gopkg.in/src-d/go-git.v3/clients/http"
gssh "gopkg.in/src-d/go-git.v3/clients/ssh"
)

//export c_NewBasicAuth
func c_NewBasicAuth(username, password string) uint64 {
auth := http.NewBasicAuth(CopyString(username), CopyString(password))
return uint64(RegisterObject(auth))
}

//export c_ParseRawPrivateKey
func c_ParseRawPrivateKey(pemBytes []byte) (uint64, int, *C.char) {
pkey, err := ssh.ParseRawPrivateKey(pemBytes)
if err != nil {
return IH, ErrorCodeInternal, C.CString(err.Error())
}
// pointer is received - no need for &
return uint64(RegisterObject(pkey)), ErrorCodeSuccess, nil
}

//export c_ParsePrivateKey
func c_ParsePrivateKey(pemBytes []byte) (uint64, int, *C.char) {
signer, err := ssh.ParsePrivateKey(pemBytes)
if err != nil {
return IH, ErrorCodeInternal, C.CString(err.Error())
}
return uint64(RegisterObject(&signer)), ErrorCodeSuccess, nil
}

//export c_NewPublicKey
func c_NewPublicKey(key uint64) (uint64, int, *C.char) {
obj, ok := GetObject(Handle(key))
if !ok {
return IH, ErrorCodeNotFound, C.CString(MessageNotFound)
}
key_obj := obj.(ssh.PublicKey)
pkey, err := ssh.NewPublicKey(key_obj)
if err != nil {
return IH, ErrorCodeInternal, C.CString(err.Error())
}
return uint64(RegisterObject(&pkey)), ErrorCodeSuccess, nil
}

//export c_NewSignerFromKey
func c_NewSignerFromKey(key uint64) (uint64, int, *C.char) {
obj, ok := GetObject(Handle(key))
if !ok {
return IH, ErrorCodeNotFound, C.CString(MessageNotFound)
}
signer, err := ssh.NewSignerFromKey(obj)
if err != nil {
return IH, ErrorCodeInternal, C.CString(err.Error())
}
return uint64(RegisterObject(&signer)), ErrorCodeSuccess, nil
}

//export c_MarshalAuthorizedKey
func c_MarshalAuthorizedKey(key uint64) (*C.char, int) {
obj, ok := GetObject(Handle(key))
if !ok {
return nil, 0
}
obj_key := obj.(ssh.PublicKey)
mak := ssh.MarshalAuthorizedKey(obj_key)
return C.CString(string(mak)), len(mak)
}

//export c_ParsePublicKey
func c_ParsePublicKey(in []byte) (uint64, int, *C.char) {
pkey, err := ssh.ParsePublicKey(in)
if err != nil {
return IH, ErrorCodeInternal, C.CString(err.Error())
}
return uint64(RegisterObject(&pkey)), ErrorCodeSuccess, nil
}

//export c_ParseAuthorizedKey
func c_ParseAuthorizedKey(in []byte) (uint64, *C.char, *C.char, *C.char, int, int, *C.char) {
pkey, comment, options, rest, err := ssh.ParseAuthorizedKey(in)
if err != nil {
return IH, nil, nil, nil, 0, ErrorCodeInternal,
C.CString(err.Error())
}
pkey_handle := RegisterObject(&pkey)
mopt := strings.Join(options, "\xff")
return uint64(pkey_handle), C.CString(comment), C.CString(mopt),
C.CString(string(rest)), len(rest), ErrorCodeSuccess, nil
}

//export c_ssh_Password_New
func c_ssh_Password_New(user, pass string) uint64 {
obj := gssh.Password{User: CopyString(user), Pass: CopyString(pass)}
return uint64(RegisterObject(&obj))
}

//export c_ssh_Password_get_User
func c_ssh_Password_get_User(p uint64) *C.char {
obj, ok := GetObject(Handle(p))
if !ok {
return nil
}
return C.CString(obj.(*gssh.Password).User)
}

//export c_ssh_Password_set_User
func c_ssh_Password_set_User(p uint64, v string) {
obj, ok := GetObject(Handle(p))
if !ok {
return
}
obj.(*gssh.Password).User = CopyString(v)
}

//export c_ssh_Password_get_Pass
func c_ssh_Password_get_Pass(p uint64) *C.char {
obj, ok := GetObject(Handle(p))
if !ok {
return nil
}
return C.CString(obj.(*gssh.Password).Pass)
}

//export c_ssh_Password_set_Pass
func c_ssh_Password_set_Pass(p uint64, v string) {
obj, ok := GetObject(Handle(p))
if !ok {
return
}
obj.(*gssh.Password).Pass = CopyString(v)
}

//c_ssh_PublicKeys_New
func c_ssh_PublicKeys_New(user string, signer uint64) uint64 {
obj, ok := GetObject(Handle(signer))
if !ok {
return IH
}
pk := gssh.PublicKeys{User: CopyString(user), Signer: obj.(ssh.Signer)}
return uint64(RegisterObject(&pk))
}

//export c_ssh_PublicKeys_get_User
func c_ssh_PublicKeys_get_User(p uint64) *C.char {
obj, ok := GetObject(Handle(p))
if !ok {
return nil
}
return C.CString(obj.(*gssh.PublicKeys).User)
}

//export c_ssh_PublicKeys_set_User
func c_ssh_PublicKeys_set_User(p uint64, v string) {
obj, ok := GetObject(Handle(p))
if !ok {
return
}
obj.(*gssh.PublicKeys).User = CopyString(v)
}

//export c_ssh_PublicKeys_get_Signer
func c_ssh_PublicKeys_get_Signer(p uint64) uint64 {
obj, ok := GetObject(Handle(p))
if !ok {
return IH
}
handle, ok := GetHandle(&obj.(*gssh.PublicKeys).Signer)
if !ok {
return IH
}
return uint64(handle)
}

//export c_ssh_PublicKeys_set_Signer
func c_ssh_PublicKeys_set_Signer(p uint64, v uint64) {
obj, ok := GetObject(Handle(p))
if !ok {
return
}
signer, ok := GetObject(Handle(v))
if !ok {
return
}
obj.(*gssh.PublicKeys).Signer = *signer.(*ssh.Signer)
}
Loading

0 comments on commit e305c47

Please sign in to comment.