Skip to content

Commit

Permalink
Add basic C bindings to start and stop Syncthing
Browse files Browse the repository at this point in the history
  • Loading branch information
Martchus committed Jul 6, 2021
1 parent c84e8d1 commit 5a34c59
Show file tree
Hide file tree
Showing 4 changed files with 254 additions and 0 deletions.
11 changes: 11 additions & 0 deletions c-bindings/c_bindings.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#include "c_bindings.h"

libst_logging_callback_function_t libst_logging_callback_function = NULL;

void libst_invoke_logging_callback(int log_level, const char *message, size_t message_size)
{
if (!libst_logging_callback_function) {
return;
}
libst_logging_callback_function(log_level, message, message_size);
}
231 changes: 231 additions & 0 deletions c-bindings/c_bindings.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
package main

import (
"context"
"os"
"unsafe"
"path/filepath"
_ "net/http/pprof" // Need to import this to support STPROFILER.

"github.com/syncthing/syncthing/lib/build"
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/fs"
"github.com/syncthing/syncthing/lib/logger"
"github.com/syncthing/syncthing/lib/locations"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/svcutil"
"github.com/syncthing/syncthing/lib/syncthing"
"github.com/thejerf/suture/v4"
)

// include header for required C helper functions (so the following comment is NO comment)

// #include "c_bindings.h"
import "C"

var theApp *syncthing.App
var myID protocol.DeviceID

const (
tlsDefaultCommonName = "syncthing"
)

//export libst_own_device_id
func libst_own_device_id() string {
return myID.String()
}

//export libst_init_logging
func libst_init_logging() {
l.AddHandler(logger.LevelVerbose, func(level logger.LogLevel, msg string) {
runes := []byte(msg)
length := len(runes)
if length <= 0 {
return
}
C.libst_invoke_logging_callback(C.int(level), (*C.char)(unsafe.Pointer(&runes[0])), C.size_t(len(runes)))
})
}

// C&P from main.go; used to ensure that the config directory exists
func ensureDir(dir string, mode fs.FileMode) error {
fs := fs.NewFilesystem(fs.FilesystemTypeBasic, dir)
err := fs.MkdirAll(".", mode)
if err != nil {
return err
}

if fi, err := fs.Stat("."); err == nil {
// Apprently the stat may fail even though the mkdirall passed. If it
// does, we'll just assume things are in order and let other things
// fail (like loading or creating the config...).
currentMode := fi.Mode() & 0777
if currentMode != mode {
err := fs.Chmod(".", mode)
// This can fail on crappy filesystems, nothing we can do about it.
if err != nil {
l.Warnln(err)
}
}
}
return nil
}

//export libst_run_syncthing
func libst_run_syncthing(configDir string, dataDir string, guiAddress string, guiApiKey string, verbose bool, allowNewerConfig bool, noDefaultConfig bool, ensureConfigDirExists bool, ensureDataDirExists bool) int {
// return if already running (for simplicity we only allow one Syncthing instance at at time for now)
if theApp != nil {
return 0
}

// set specified GUI address and API key
if guiAddress != "" {
os.Setenv("STGUIADDRESS", guiAddress)
}
if guiApiKey != "" {
os.Setenv("STGUIAPIKEY", guiApiKey)
}

// set specified config dir
if configDir != "" {
if !filepath.IsAbs(configDir) {
var err error
configDir, err = filepath.Abs(configDir)
if err != nil {
l.Warnln("Failed to make config path absolute:", err)
return 3
}
}
if err := locations.SetBaseDir(locations.ConfigBaseDir, configDir); err != nil {
l.Warnln(err)
return 3
}
}

// set specified database dir
if dataDir != "" {
if !filepath.IsAbs(dataDir) {
var err error
dataDir, err = filepath.Abs(dataDir)
if err != nil {
l.Warnln("Failed to make database path absolute:", err)
return 3
}
}
if err := locations.SetBaseDir(locations.DataBaseDir, dataDir); err != nil {
l.Warnln(err)
return 3
}
}

// ensure that the config directory exists
if ensureConfigDirExists {
if err := ensureDir(locations.GetBaseDir(locations.ConfigBaseDir), 0700); err != nil {
l.Warnln("Failed to create config directory:", err)
return 4
}
}

// ensure that the database directory exists
if dataDir != "" && ensureDataDirExists {
if err := ensureDir(locations.GetBaseDir(locations.DataBaseDir), 0700); err != nil {
l.Warnln("Failed to create database directory:", err)
return 4
}
}

// ensure that we have a certificate and key
cert, certErr := syncthing.LoadOrGenerateCertificate(
locations.Get(locations.CertFile),
locations.Get(locations.KeyFile),
)
if certErr != nil {
l.Warnln("Failed to load/generate certificate:", certErr)
return 1
}

ctx, cancel := context.WithCancel(context.Background())
defer cancel()

// earlyService is a supervisor that runs the services needed for or
// before app startup; the event logger, and the config service.
spec := svcutil.SpecWithDebugLogger(l)
earlyService := suture.New("early", spec)
earlyService.ServeBackground(ctx)

evLogger := events.NewLogger()
earlyService.Add(evLogger)

// load config
configLocation := locations.Get(locations.ConfigFile)
l.Infoln("Loading config from:", configLocation)
cfgWrapper, cfgErr := syncthing.LoadConfigAtStartup(configLocation, cert, evLogger, allowNewerConfig, noDefaultConfig)
if cfgErr != nil {
l.Warnln("Failed to initialize config:", cfgErr)
return 2
}
if cfgService, ok := cfgWrapper.(suture.Service); ok {
earlyService.Add(cfgService)
}

// open database
dbFile := locations.Get(locations.Database)
l.Infoln("Opening database from:", dbFile)
ldb, dbErr := syncthing.OpenDBBackend(dbFile, config.TuningAuto)
if dbErr != nil {
l.Warnln("Error opening database:", dbErr)
return 4
}

appOpts := syncthing.Options{
AssetDir: os.Getenv("STGUIASSETS"),
ProfilerAddr: os.Getenv("STPROFILER"),
NoUpgrade: true,
Verbose: verbose,
}
var err error
theApp, err = syncthing.New(cfgWrapper, ldb, evLogger, cert, appOpts)
if err != nil {
l.Warnln("Failed to start Syncthing:", err)
return svcutil.ExitError.AsInt()
}

// start Syncthing and block until it has finished
returnCode := 0
if err := theApp.Start(); err != nil {
returnCode = svcutil.ExitError.AsInt()
}
returnCode = theApp.Wait().AsInt();
theApp = nil
return returnCode
}

//export libst_stop_syncthing
func libst_stop_syncthing() int {
if theApp != nil {
return int(theApp.Stop(svcutil.ExitSuccess))
} else {
return 0;
}
}

//export libst_reset_database
func libst_reset_database() {
os.RemoveAll(locations.Get(locations.Database))
}

//export libst_syncthing_version
func libst_syncthing_version() *C.char {
return C.CString(build.Version)
}

//export libst_long_syncthing_version
func libst_long_syncthing_version() *C.char {
return C.CString(build.LongVersion)
}

func main() {
// prevent "runtime.main_main·f: function main is undeclared in the main package"
}

11 changes: 11 additions & 0 deletions c-bindings/c_bindings.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#ifndef LIBSYNCTHING_INTERNAL_H
#define LIBSYNCTHING_INTERNAL_H

#include <stddef.h>

// allow registration of callback function
typedef void (*libst_logging_callback_function_t) (int logLevel, const char *msg, size_t msgSize);
extern libst_logging_callback_function_t libst_logging_callback_function;
extern void libst_invoke_logging_callback(int log_level, const char *message, size_t message_size);

#endif // LIBSYNCTHING_INTERNAL_H
1 change: 1 addition & 0 deletions c-bindings/debug.go

0 comments on commit 5a34c59

Please sign in to comment.