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

Commit e305c47

Browse files
authored
Merge pull request #54 from vmarkovtsev/cshared
Add cshared files to allow building wrappers in other languages
2 parents 9774edb + bea4154 commit e305c47

File tree

9 files changed

+1320
-0
lines changed

9 files changed

+1320
-0
lines changed

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,14 @@ fmt.Println(commit)
113113
```
114114

115115

116+
Wrapping
117+
--------
118+
119+
go-git can be wrapped into any language which supports shared library interop.
120+
[Python wrapper](https://github.com/src-d/gypogit) already exists.
121+
This is provided by "cshared" [cgo](https://golang.org/cmd/cgo/) files which can be built
122+
with `go build -o libgogit.so -buildmode=c-shared github.com/src-d/go-git/cshared`.
123+
116124
Acknowledgements
117125
----------------
118126

cshared/README.md

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
cshared
2+
=======
3+
4+
Building
5+
--------
6+
go 1.6+
7+
```
8+
go build -o libgogit.so -buildmode=c-shared github.com/src-d/go-git/cshared
9+
```
10+
Two files must appear: libgogit.h and libgogit.so. The second must be
11+
a shared library, not an ar archive (may happen when something goes wrong).
12+
Check the exported symbols with `nm -g`.
13+
14+
How it works
15+
------------
16+
17+
Nearly every public Go function is mirrored in the corresponding *_cshared.go
18+
file. struct fields are also mirrored with getters and setters. The functions
19+
are marked with `//export ...` "magic" cgo comment so that they appear
20+
in defined symbols of a shared library built with `-buildmode=c-shared`.
21+
22+
Go pointers may not be passed out of cgo functions, so we maintain the
23+
two-way registry of all active Go objects mapped to `Handle`-s (`uint64`).
24+
Every time we need to return a reference to Go object outside, we call
25+
`RegisterObject(interface{})` which returns a new `Handle` or reuses
26+
an existing one if the object has already been registered. Then we
27+
return the obtained `Handle`. When we need to receive a Go object reference
28+
in cgo function parameters, we accept `uint64` and retrieve the `interface{}`
29+
with `GetObject(Handle)` which can be casted to the underlying type with a
30+
type assertion. When the object is no longer needed, we invoke
31+
`UnregisterObject(Handle)`.
32+
33+
Although `interface{]` is just two `uintptr`-s inside, it is not a hashable
34+
type and we cannot use it a as key in our backward registry mapping.
35+
We are using the data `uintptr` as the key there. Since several distinct
36+
objects may exist with the same data pointer (e.g. struct and first field
37+
of the struct), the value of that mapping is a slice of `Handle`-s.
38+
39+
All the mentioned service functions are goroutine- and threadsafe.
40+
41+
`std_cshared.go` contains the cgo wrappers for standard library objects.
42+
43+
Debugging
44+
---------
45+
`c_dump_object()` prints the current state of the two-way object registry
46+
to stdout. `c_set_trace()` activates echoing of `RegisterObject()` and
47+
`UnregisterObject()` invocations.
48+
49+
Caveats
50+
-------
51+
Normally, we pass over a pointer to object as `interface{}` into `RegisterObject()`
52+
so that it can be mutated later. It requires the corresponding
53+
pointer-to-type type assertion in cgo functions. If you mess with this,
54+
the cgo function will, of course, panic.
55+
56+
A cgo function is allowed to take Go's `string` parameters. `string`'s
57+
data must point to some memory and cgo does not copy the incoming foreign
58+
memory into Go memory automatically. What's worse, `string`-s are immutable
59+
and when you copy it, the copy points to the same memory. This means that
60+
if you pass in a `string` which was constructed using `malloc()`, for example,
61+
and later `free()` it, all Go strings created from the function parameter
62+
will point to the invalid memory. Actually, this allowance violates the
63+
cgo pointer passing rules stated just several blocks of texts
64+
below the example of string parameters - this is crazy, but we have to live
65+
with this, as usual in Go world. So, *all incoming `string`-s must be immediately
66+
safely copied with `CopyString()` once they are used*.
67+
68+
Returning strings and byte slices is also funny: you have to use `C.CString` -> `*C.char`
69+
and additionally return the length as another result tuple member if needed.
70+
`C.CString` copies the memory pointed by `string` to a `malloc()`-ed region
71+
and it is the responsibility of the other side to `free()` it or it will leak
72+
otherwise.
73+
74+
Another tricky part is in `c_std_map_get_str_str` and similar places
75+
where you need to return `*C.char` from an unaddressed array accessed under
76+
a pseudonym type through reflection. The only way I've found working
77+
is using `reflect.Copy` to byte slice (copy) and then conversion to
78+
`string` (copy), then `C.CString` (copy) and finally another (copy) on the
79+
receiving side because the latter must be `free()`-d. Extremely efficient.

cshared/auth_method_cshared.go

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
// +build ignore
2+
package main
3+
4+
import (
5+
"C"
6+
"strings"
7+
8+
"golang.org/x/crypto/ssh"
9+
"gopkg.in/src-d/go-git.v3/clients/http"
10+
gssh "gopkg.in/src-d/go-git.v3/clients/ssh"
11+
)
12+
13+
//export c_NewBasicAuth
14+
func c_NewBasicAuth(username, password string) uint64 {
15+
auth := http.NewBasicAuth(CopyString(username), CopyString(password))
16+
return uint64(RegisterObject(auth))
17+
}
18+
19+
//export c_ParseRawPrivateKey
20+
func c_ParseRawPrivateKey(pemBytes []byte) (uint64, int, *C.char) {
21+
pkey, err := ssh.ParseRawPrivateKey(pemBytes)
22+
if err != nil {
23+
return IH, ErrorCodeInternal, C.CString(err.Error())
24+
}
25+
// pointer is received - no need for &
26+
return uint64(RegisterObject(pkey)), ErrorCodeSuccess, nil
27+
}
28+
29+
//export c_ParsePrivateKey
30+
func c_ParsePrivateKey(pemBytes []byte) (uint64, int, *C.char) {
31+
signer, err := ssh.ParsePrivateKey(pemBytes)
32+
if err != nil {
33+
return IH, ErrorCodeInternal, C.CString(err.Error())
34+
}
35+
return uint64(RegisterObject(&signer)), ErrorCodeSuccess, nil
36+
}
37+
38+
//export c_NewPublicKey
39+
func c_NewPublicKey(key uint64) (uint64, int, *C.char) {
40+
obj, ok := GetObject(Handle(key))
41+
if !ok {
42+
return IH, ErrorCodeNotFound, C.CString(MessageNotFound)
43+
}
44+
key_obj := obj.(ssh.PublicKey)
45+
pkey, err := ssh.NewPublicKey(key_obj)
46+
if err != nil {
47+
return IH, ErrorCodeInternal, C.CString(err.Error())
48+
}
49+
return uint64(RegisterObject(&pkey)), ErrorCodeSuccess, nil
50+
}
51+
52+
//export c_NewSignerFromKey
53+
func c_NewSignerFromKey(key uint64) (uint64, int, *C.char) {
54+
obj, ok := GetObject(Handle(key))
55+
if !ok {
56+
return IH, ErrorCodeNotFound, C.CString(MessageNotFound)
57+
}
58+
signer, err := ssh.NewSignerFromKey(obj)
59+
if err != nil {
60+
return IH, ErrorCodeInternal, C.CString(err.Error())
61+
}
62+
return uint64(RegisterObject(&signer)), ErrorCodeSuccess, nil
63+
}
64+
65+
//export c_MarshalAuthorizedKey
66+
func c_MarshalAuthorizedKey(key uint64) (*C.char, int) {
67+
obj, ok := GetObject(Handle(key))
68+
if !ok {
69+
return nil, 0
70+
}
71+
obj_key := obj.(ssh.PublicKey)
72+
mak := ssh.MarshalAuthorizedKey(obj_key)
73+
return C.CString(string(mak)), len(mak)
74+
}
75+
76+
//export c_ParsePublicKey
77+
func c_ParsePublicKey(in []byte) (uint64, int, *C.char) {
78+
pkey, err := ssh.ParsePublicKey(in)
79+
if err != nil {
80+
return IH, ErrorCodeInternal, C.CString(err.Error())
81+
}
82+
return uint64(RegisterObject(&pkey)), ErrorCodeSuccess, nil
83+
}
84+
85+
//export c_ParseAuthorizedKey
86+
func c_ParseAuthorizedKey(in []byte) (uint64, *C.char, *C.char, *C.char, int, int, *C.char) {
87+
pkey, comment, options, rest, err := ssh.ParseAuthorizedKey(in)
88+
if err != nil {
89+
return IH, nil, nil, nil, 0, ErrorCodeInternal,
90+
C.CString(err.Error())
91+
}
92+
pkey_handle := RegisterObject(&pkey)
93+
mopt := strings.Join(options, "\xff")
94+
return uint64(pkey_handle), C.CString(comment), C.CString(mopt),
95+
C.CString(string(rest)), len(rest), ErrorCodeSuccess, nil
96+
}
97+
98+
//export c_ssh_Password_New
99+
func c_ssh_Password_New(user, pass string) uint64 {
100+
obj := gssh.Password{User: CopyString(user), Pass: CopyString(pass)}
101+
return uint64(RegisterObject(&obj))
102+
}
103+
104+
//export c_ssh_Password_get_User
105+
func c_ssh_Password_get_User(p uint64) *C.char {
106+
obj, ok := GetObject(Handle(p))
107+
if !ok {
108+
return nil
109+
}
110+
return C.CString(obj.(*gssh.Password).User)
111+
}
112+
113+
//export c_ssh_Password_set_User
114+
func c_ssh_Password_set_User(p uint64, v string) {
115+
obj, ok := GetObject(Handle(p))
116+
if !ok {
117+
return
118+
}
119+
obj.(*gssh.Password).User = CopyString(v)
120+
}
121+
122+
//export c_ssh_Password_get_Pass
123+
func c_ssh_Password_get_Pass(p uint64) *C.char {
124+
obj, ok := GetObject(Handle(p))
125+
if !ok {
126+
return nil
127+
}
128+
return C.CString(obj.(*gssh.Password).Pass)
129+
}
130+
131+
//export c_ssh_Password_set_Pass
132+
func c_ssh_Password_set_Pass(p uint64, v string) {
133+
obj, ok := GetObject(Handle(p))
134+
if !ok {
135+
return
136+
}
137+
obj.(*gssh.Password).Pass = CopyString(v)
138+
}
139+
140+
//c_ssh_PublicKeys_New
141+
func c_ssh_PublicKeys_New(user string, signer uint64) uint64 {
142+
obj, ok := GetObject(Handle(signer))
143+
if !ok {
144+
return IH
145+
}
146+
pk := gssh.PublicKeys{User: CopyString(user), Signer: obj.(ssh.Signer)}
147+
return uint64(RegisterObject(&pk))
148+
}
149+
150+
//export c_ssh_PublicKeys_get_User
151+
func c_ssh_PublicKeys_get_User(p uint64) *C.char {
152+
obj, ok := GetObject(Handle(p))
153+
if !ok {
154+
return nil
155+
}
156+
return C.CString(obj.(*gssh.PublicKeys).User)
157+
}
158+
159+
//export c_ssh_PublicKeys_set_User
160+
func c_ssh_PublicKeys_set_User(p uint64, v string) {
161+
obj, ok := GetObject(Handle(p))
162+
if !ok {
163+
return
164+
}
165+
obj.(*gssh.PublicKeys).User = CopyString(v)
166+
}
167+
168+
//export c_ssh_PublicKeys_get_Signer
169+
func c_ssh_PublicKeys_get_Signer(p uint64) uint64 {
170+
obj, ok := GetObject(Handle(p))
171+
if !ok {
172+
return IH
173+
}
174+
handle, ok := GetHandle(&obj.(*gssh.PublicKeys).Signer)
175+
if !ok {
176+
return IH
177+
}
178+
return uint64(handle)
179+
}
180+
181+
//export c_ssh_PublicKeys_set_Signer
182+
func c_ssh_PublicKeys_set_Signer(p uint64, v uint64) {
183+
obj, ok := GetObject(Handle(p))
184+
if !ok {
185+
return
186+
}
187+
signer, ok := GetObject(Handle(v))
188+
if !ok {
189+
return
190+
}
191+
obj.(*gssh.PublicKeys).Signer = *signer.(*ssh.Signer)
192+
}

0 commit comments

Comments
 (0)