See parent: pwsafe | ABI spec: transport | Reference impl: file-plugin
A plugin is a shared library (.so) with a single exported entry point: pws_plugin_init
#include "transport.h"
#include <string.h> // memset
// Identity string — survives link-time optimization
// Format: PWS_TRANSPORT_INFO:<abi>:<schemes>:<description>
static const char ident[]
__attribute__((section(".comment"), used)) =
"PWS_TRANSPORT_INFO:1:myscheme:My transport";
static int my_fetch(const char *url, const char *local_path) {
// download url → local_path
return 0; // or errno on failure
}
static int my_store(const char *local_path, const char *url) {
// upload local_path → url
return 0;
}
static int my_exists(const char *url) {
// return 0 if exists, ENOENT if not
return ENOENT;
}
static int my_lock(const char *url, char *token_out, size_t len) {
return ENOTSUP; // no locking support
}
static int my_unlock(const char *url, const char *token) {
return ENOTSUP;
}
static void my_cleanup(void) { }
static PWSTransport t = {
PWSTransport_ABI_VERSION,
"myscheme",
my_fetch, my_store, my_exists, my_lock, my_unlock, my_cleanup
};
extern "C" void pws_plugin_init(pws_register_fn_t reg) {
reg(&t);
}
Locking is optional. If your transport has no server-side locking, return ENOTSUP from lock() and unlock(). The transport layer treats ENOTSUP as 'proceed without a lock, warn once'.
If you implement locking, store the token internally — the caller passes an empty string for token to unlock(). Your plugin must maintain its own URL → token map.
IMPORTANT: once the first lock is acquired, all transport operations (fetch, store, lock, unlock) run in the lock daemon child process, not in the parent. The child inherits your plugin's loaded shared library and function pointers, but NOT any state set by the parent after fork.
In practice: initialise all state before the first lock() call, or initialise lazily inside each function. Do not rely on parent-set state for post-fork calls.
• 0 — success
• ENOENT — resource not found (404 equivalent)
• EBUSY — locked by another client (423 equivalent)
• ENOTSUP — operation not supported (lock/unlock not available)
• EIO — I/O or protocol error
• EPERM — authentication failure
Any POSIX errno value is acceptable; the above are the ones the transport layer handles specially.
List all schemes in the identity string, comma-separated: PWS_TRANSPORT_INFO:1:https,http:WebDAV transport
Create a symlink for each additional scheme: pwsafe-http.so → pwsafe-https.so. The loader discovers it via the symlink but loads the same .so.
add_library(pwsafe-myscheme MODULE transport-myscheme.cpp)
set_target_properties(pwsafe-myscheme PROPERTIES
PREFIX ""
SUFFIX ".so"
POSITION_INDEPENDENT_CODE ON)
target_link_libraries(pwsafe-myscheme PRIVATE <your-deps>)
# Copy to build dir so binary can find it
add_custom_command(TARGET pwsafe-myscheme POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
$<TARGET_FILE:pwsafe-myscheme>
${CMAKE_BINARY_DIR}/pwsafe-myscheme.so)
Compile standalone, gate live-server sections behind an env var, skip with exit 77 if unset:
g++ -std=c++17 -o /tmp/test_myplugin test_myplugin.cpp -ldl
# Live tests:
MYSCHEME_TEST_URL=myscheme://server/path /tmp/test_myplugin
Reference implementations: file:// plugin (simplest) — https:/http: plugin (full locking)