Use Pascal strings in place of null-termination; update documentation

This commit is contained in:
2026-01-21 18:17:21 -05:00
parent 868db8c038
commit a72be0393c
4 changed files with 99 additions and 20 deletions
+30
View File
@@ -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)),
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
MIT License 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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
+46 -3
View File
@@ -1,8 +1,48 @@
# cmutton # cmutton
Official C bindings for [libmutton](https://github.com/rwinkhart/libmutton). 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 # 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 `<FunctionName>_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 `<result>.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 <filename> ./cmutton.a`.
```c
#include <stdio.h>
#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 # Progress
- [ ] age - [ ] age
@@ -33,14 +73,17 @@ Build the C library + headers w/`go build -buildmode=c-archive`.
- [ ] DecryptFileToSlice(realPath string) ([]string, error) - [ ] DecryptFileToSlice(realPath string) ([]string, error)
- [ ] EncryptBytes(decBytes []byte) []byte - [ ] EncryptBytes(decBytes []byte) []byte
- [ ] RCWDArgument() - [ ] RCWDArgument()
- [ ] VAR: Daemonize bool
- [ ] VAR: RetryPassword bool
- [ ] global - [ ] global
- [X] DirInit(preserveOldCfgDir bool) (string, error) - [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] GetCurrentDeviceID() (string, error)
- [X] GetRealAgePath(vanityPath string) string - [X] GetRealAgePath(vanityPath string) string
- [X] GetRealPath(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 - [X] GetVanityPath(realPath string) string
- [ ] VAR (CB func): GetPassword
- [ ] syncclient - [ ] syncclient
- [ ] AddFolderRemote(vanityPath string) error - [ ] AddFolderRemote(vanityPath string) error
- [ ] GenDeviceID(oldDeviceID, prefix string) (string, string, bool, error) - [ ] GenDeviceID(oldDeviceID, prefix string) (string, string, bool, error)
+22 -16
View File
@@ -2,62 +2,68 @@ package main
/* /*
#include <stdlib.h> #include <stdlib.h>
typedef struct {
char* data;
int len;
} PascalString;
*/ */
import "C" import "C"
import ( import (
"github.com/rwinkhart/libmutton/global" "github.com/rwinkhart/libmutton/global"
) )
// TODO Use a nil pointer for blank deviceIDs, not FSMisc (requires changes to libmutton)
// DirInit returns: // DirInit returns:
// r0: oldDeviceID (FSMisc if none) // r0: err
// //
// r1: err // r1: oldDeviceID (FSMisc if none)
// //
//export DirInit //export DirInit
func DirInit(preserveOldCfgDir bool) (*C.char, *C.char) { func DirInit(preserveOldCfgDir bool) (*C.char, C.PascalString) {
oldDeviceID, err := global.DirInit(preserveOldCfgDir) oldDeviceID, err := global.DirInit(preserveOldCfgDir)
if err != nil { 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: // GetCurrentDeviceID returns:
// r0: currentDeviceID (FSMisc if none) // r0: err
// //
// r1: err // r1: currentDeviceID (FSMisc if none)
// //
//export GetCurrentDeviceID //export GetCurrentDeviceID
func GetCurrentDeviceID() (*C.char, *C.char) { func GetCurrentDeviceID() (*C.char, C.PascalString) {
currentDeviceID, err := global.GetCurrentDeviceID() currentDeviceID, err := global.GetCurrentDeviceID()
if err != nil { 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: // GetRealAgePath returns:
// realAgePath // realAgePath
// //
//export GetRealAgePath //export GetRealAgePath
func GetRealAgePath(vanityPath *C.char) *C.char { func GetRealAgePath(vanityPath *C.char) C.PascalString {
return C.CString(global.GetRealAgePath(C.GoString(vanityPath))) return getPascalString(global.GetRealAgePath(C.GoString(vanityPath)))
} }
// GetRealPath returns: // GetRealPath returns:
// realPath // realPath
// //
//export GetRealPath //export GetRealPath
func GetRealPath(vanityPath *C.char) *C.char { func GetRealPath(vanityPath *C.char) C.PascalString {
return C.CString(global.GetRealPath(C.GoString(vanityPath))) return getPascalString(global.GetRealPath(C.GoString(vanityPath)))
} }
// GetVanityPath returns: // GetVanityPath returns:
// vanityPath // vanityPath
// //
//export GetVanityPath //export GetVanityPath
func GetVanityPath(realPath *C.char) *C.char { func GetVanityPath(realPath *C.char) C.PascalString {
return C.CString(global.GetVanityPath(C.GoString(realPath))) return getPascalString(global.GetVanityPath(C.GoString(realPath)))
} }
func main() {} func main() {}