diff --git a/0.go b/0.go new file mode 100644 index 0000000..3d7833c --- /dev/null +++ b/0.go @@ -0,0 +1,30 @@ +package main + +/* +typedef struct { + char* data; + int len; +} PascalString; +*/ +import "C" + +// getPascalString returns a pascal +// string struct for the input Go string. +func getPascalString(goString string) C.PascalString { + goStringBytes := []byte(goString) + goStringPtr := C.CBytes(goStringBytes) + return C.PascalString{ + data: (*C.char)(goStringPtr), + len: C.int(len(goStringBytes)), + } +} + +// getPascalStringFromBytes returns a pascal +// string struct for the input Go byte slice. +func getPascalStringFromBytes(goBytes []byte) C.PascalString { + goBytesPtr := C.CBytes(goBytes) + return C.PascalString{ + data: (*C.char)(goBytesPtr), + len: C.int(len(goBytes)), + } +} diff --git a/LICENSE b/LICENSE index 5ba00b0..a0230e3 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Randall Winkhart +Copyright (c) 2025-2026 Randall Winkhart Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 86b1d2c..dd8d911 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,48 @@ # cmutton Official C bindings for [libmutton](https://github.com/rwinkhart/libmutton). +This repository contains what is needed to generate a C library+headers for interfacing with libmutton +in native code. The bindings were written by hand and cover all libmutton functionality that is possible +to export to C. + # Usage -Build the C library + headers w/`go build -buildmode=c-archive`. +### Building +`go build -buildmode=c-archive`. +### Functions +All relevant exported libmutton functions have C functions of the same name. +Functions with multiple return values all have CGO-generated structs to store the return values. +These structs are named `_return`, as per CGO. +All functions continue to perform the same basic operations, with a couple caveats: +- Anything that would normally return a Go error now returns a null-terminated *C.char containing the error string. +These are safe to null-terminate because their values are much more predictable than other strings. +Errors are always the *first* return value, so they can always be referenced with `.r0`. +- Anything that would normally return a Go string or byte slice now returns a C.PascalString struct. +This is to avoid bugs with null-terminated strings. + +Additionally, since comments tend to fall out of date, please rely on the [Go documentation for libmutton](https://pkg.go.dev/github.com/rwinkhart/libmutton). Documentation for the CGO bindings present in this repo only specify return values. +### Example (libmutton's global.DirInit) +Build the following example with `gcc ./cmutton.a`. +```c +#include +#include "cmutton.h" // import cmutton + +int main() { + // use CGO-generated struct to get multiple return values + struct DirInit_return result = DirInit(1); + + // familiar error handling pattern + if (result.r0 != NULL) { + printf("Error: %s\n", result.r0); + exit(1); // result.r0 not freed since program exits + } + + // print Pascal string using "%.*s" with printf and supplying both length and data + printf("Old device ID: %.*s\n", result.r1.len, result.r1.data); + + // be sure to free the data! + free(result.r1.data); +} +``` # Progress - [ ] age @@ -33,14 +73,17 @@ Build the C library + headers w/`go build -buildmode=c-archive`. - [ ] DecryptFileToSlice(realPath string) ([]string, error) - [ ] EncryptBytes(decBytes []byte) []byte - [ ] RCWDArgument() + - [ ] VAR: Daemonize bool + - [ ] VAR: RetryPassword bool - [ ] global - [X] DirInit(preserveOldCfgDir bool) (string, error) - - [ ] ~~GenDeviceIDList() ([]fs.DirEntry, error)~~ + - [ ] ~~GenDeviceIDList() ([]fs.DirEntry, error)~~ (not for use outside of libmutton) - [X] GetCurrentDeviceID() (string, error) - [X] GetRealAgePath(vanityPath string) string - [X] GetRealPath(vanityPath string) string - - [ ] ~~GetSysProcAttr() *syscall.SysProcAttr~~ + - [ ] ~~GetSysProcAttr() *syscall.SysProcAttr~~ (not for use outside of libmutton) - [X] GetVanityPath(realPath string) string + - [ ] VAR (CB func): GetPassword - [ ] syncclient - [ ] AddFolderRemote(vanityPath string) error - [ ] GenDeviceID(oldDeviceID, prefix string) (string, string, bool, error) diff --git a/global.go b/global.go index a0527eb..6a41e86 100644 --- a/global.go +++ b/global.go @@ -2,62 +2,68 @@ package main /* #include +typedef struct { + char* data; + int len; +} PascalString; */ import "C" import ( "github.com/rwinkhart/libmutton/global" ) +// TODO Use a nil pointer for blank deviceIDs, not FSMisc (requires changes to libmutton) + // DirInit returns: -// r0: oldDeviceID (FSMisc if none) +// r0: err // -// r1: err +// r1: oldDeviceID (FSMisc if none) // //export DirInit -func DirInit(preserveOldCfgDir bool) (*C.char, *C.char) { +func DirInit(preserveOldCfgDir bool) (*C.char, C.PascalString) { oldDeviceID, err := global.DirInit(preserveOldCfgDir) if err != nil { - return nil, C.CString(err.Error()) + return C.CString(err.Error()), C.PascalString{data: nil, len: 0} } - return C.CString(oldDeviceID), nil + return nil, getPascalString(oldDeviceID) } // GetCurrentDeviceID returns: -// r0: currentDeviceID (FSMisc if none) +// r0: err // -// r1: err +// r1: currentDeviceID (FSMisc if none) // //export GetCurrentDeviceID -func GetCurrentDeviceID() (*C.char, *C.char) { +func GetCurrentDeviceID() (*C.char, C.PascalString) { currentDeviceID, err := global.GetCurrentDeviceID() if err != nil { - return nil, C.CString(err.Error()) + return C.CString(err.Error()), C.PascalString{data: nil, len: 0} } - return C.CString(currentDeviceID), nil + return nil, getPascalString(currentDeviceID) } // GetRealAgePath returns: // realAgePath // //export GetRealAgePath -func GetRealAgePath(vanityPath *C.char) *C.char { - return C.CString(global.GetRealAgePath(C.GoString(vanityPath))) +func GetRealAgePath(vanityPath *C.char) C.PascalString { + return getPascalString(global.GetRealAgePath(C.GoString(vanityPath))) } // GetRealPath returns: // realPath // //export GetRealPath -func GetRealPath(vanityPath *C.char) *C.char { - return C.CString(global.GetRealPath(C.GoString(vanityPath))) +func GetRealPath(vanityPath *C.char) C.PascalString { + return getPascalString(global.GetRealPath(C.GoString(vanityPath))) } // GetVanityPath returns: // vanityPath // //export GetVanityPath -func GetVanityPath(realPath *C.char) *C.char { - return C.CString(global.GetVanityPath(C.GoString(realPath))) +func GetVanityPath(realPath *C.char) C.PascalString { + return getPascalString(global.GetVanityPath(C.GoString(realPath))) } func main() {}