I need to query, monitor and possibly change the state of a few systemd services from a C++ application. It looks like sd-bus is the right way to do this, but I'm having a terrible time finding an example.
So, how do I:
1) Query the current status of a service via sd-bus, similar to systemctl status foo.service?
2) Monitor the status of a service such that I get a callback whenever it changes?
3) Change the status of a service, similar to systemctl start/stop/restart?
Thanks!
Using the sd-bus API is absolutely correct (header #include <systemd/sd-bus.h>)
First you need get access to a bus object:
I do this:
Systemctl::Systemctl() :
m_bus(nullptr)
{
int r = sd_bus_default_system(&m_bus);
if (r < 0)
throw exception("Could not open systemd bus");
}
If you're having problems opening the bus:
Run as root/sudo
Make some polkit policies to grant your user/group access to this command
Run the _user bus instead of the _system bus
Don't forget to release the bus when you are done:
Systemctl::~Systemctl()
{
sd_bus_unref(m_bus);
}
Now you had 3 questions:
Query the status
For each unit, I have a class which holds the escaped name (foo_2eservice) as m_name, and a reference to the bus in m_bus. Call this method with any property. You seem to be most interested in "ActiveState" or "SubState".
std::string Unit::GetPropertyString(const std::string& property) const
{
sd_bus_error err = SD_BUS_ERROR_NULL;
char* msg = nullptr;
int r;
r = sd_bus_get_property_string(m_bus,
"org.freedesktop.systemd1",
("/org/freedesktop/systemd1/unit/" + m_unit).c_str(),
"org.freedesktop.systemd1.Unit",
property.c_str(),
&err,
&msg);
if (r < 0)
{
std::string err_msg(err.message);
sd_bus_error_free(&err);
std::string err_str("Failed to get " + property + " for service "
+ m_name + ". Error: " + err_msg);
throw exception(err_str);
}
sd_bus_error_free(&err);
// Free memory (avoid leaking)
std::string ret(msg);
free (msg);
return ret;
}
Monitor the status of a service:
The first step is to set up a file-descriptor to subscribe to changes. In this case you are interested in subscribing to the "PropertiesChanged" signal. Note that you'll get a signal for any property changing, not just the state. In the sd_bus_add_match() call, there is room for a callback, though I haven't experimented with it.
void Systemctl::SubscribeToUnitChanges(const std::string& escaped_name)
{
/* This function is an easier helper, but it as only introduced in systemd 237
* Stretch is on 232 while buster is on 241 . Need re replace this as long as
* we still support stretch
sd_bus_match_signal(
m_bus,
nullptr, // slot
nullptr, // sender
std::string("/org/freedesktop/systemd1/unit/" + escaped_name).c_str(), // path
"org.freedesktop.DBus.Properties", // interface
"PropertiesChanged", // member
nullptr, // callback
nullptr // userdata
);
*/
std::string match = "type='signal'";
match += ",path='/org/freedesktop/systemd1/unit/" + escaped_name + "'" ;
match += ",interface='org.freedesktop.DBus.Properties'";
match += ",member='PropertiesChanged'";
sd_bus_add_match(
m_bus,
nullptr, // slot
match.c_str(),
nullptr, // callback
nullptr // userdata
);
}
Instead what I do is periodically poll the bus for the subscribed changes and update each unit:
bool Systemctl::ProcessBusChanges()
{
bool changed = false;
sd_bus_message* msg = nullptr;
// for each new message
std::list<std::string> escaped_names;
while( sd_bus_process(m_bus, &msg) )
{
// Note: Once sd_bus_process returns 0, We are supposed to call
// sd_bus_wait, or check for changes on sd_bus_get_fd before calling
// this function again. We're breaking that rule. I don't really know
// the consequences.
if (msg)
{
std::string path = strna( sd_bus_message_get_path(msg) );
sd_bus_message_unref(msg);
std::string escaped_name = path.erase(0, path.find_last_of('/')+1 );
escaped_names.push_back(escaped_name);
changed = true;
}
}
escaped_names.sort();
escaped_names.unique();
for (auto unit : escaped_names)
{
auto it = m_units.find(unit);
if (it != m_units.end())
it->second.RefreshDynamicProperties();
}
return changed;
}
If it tells us that the bus has changed, then I go ahead and read all of my monitored units on that bus.
Change the status
This one is easy. I use the following, where method is one of "StartUnit", "StopUnit", or "RestartUnit".
static void CallMethodSS(sd_bus* bus,
const std::string& name,
const std::string& method)
{
sd_bus_error err = SD_BUS_ERROR_NULL;
sd_bus_message* msg = nullptr;
int r;
r = sd_bus_call_method(bus,
"org.freedesktop.systemd1", /* <service> */
"/org/freedesktop/systemd1", /* <path> */
"org.freedesktop.systemd1.Manager", /* <interface> */
method.c_str(), /* <method> */
&err, /* object to return error in */
&msg, /* return message on success */
"ss", /* <input_signature (string-string)> */
name.c_str(), "replace" ); /* <arguments...> */
if (r < 0)
{
std::string err_str("Could not send " + method +
" command to systemd for service: " + name +
". Error: " + err.message );
sd_bus_error_free(&err);
sd_bus_message_unref(msg);
throw exception(err_str);
}
// Extra stuff that might be useful: display the response...
char* response;
r = sd_bus_message_read(msg, "o", &response);
if (r < 0)
{
LogError("Failed to parse response message: %s\n", strerror(-r) );
}
sd_bus_error_free(&err);
sd_bus_message_unref(msg);
}
Related
I know, I know - that question title is very much all over the place. However, I am not sure what could be an issue here that is causing what I am witnessing.
I have the following method in class Project that is being unit tested:
bool Project::DetermineID(std::string configFile, std::string& ID)
{
std::ifstream config;
config.open(configFile);
if (!config.is_open()) {
WARNING << "Failed to open the configuration file for processing ID at: " << configFile;
return false;
}
std::string line = "";
ID = "";
bool isConfigurationSection = false;
bool isConfiguration = false;
std::string tempID = "";
while (std::getline(config, line))
{
std::transform(line.begin(), line.end(), line.begin(), ::toupper); // transform the line to all capital letters
boost::trim(line);
if ((line.find("IDENTIFICATIONS") != std::string::npos) && (!isConfigurationSection)) {
// remove the "IDENTIFICATIONS" part from the current line we're working with
std::size_t idStartPos = line.find("IDENTIFICATIONS");
line = line.substr(idStartPos + strlen("IDENTIFICATIONS"), line.length() - idStartPos - strlen("IDENTIFICATIONS"));
boost::trim(line);
isConfigurationSection = true;
}
if ((line.find('{') != std::string::npos) && isConfigurationSection) {
std::size_t bracketPos = line.find('{');
// we are working within the ids configuration section
// determine if this is the first character of the line, or if there is an ID that precedes the {
if (bracketPos == 0) {
// is the first char
// remove the bracket and keep processing
line = line.substr(1, line.length() - 1);
boost::trim(line);
}
else {
// the text before { is a temp ID
tempID = line.substr(0, bracketPos - 1);
isConfiguration = true;
line = line.substr(bracketPos, line.length() - bracketPos);
boost::trim(line);
}
}
if ((line.find("PORT") != std::string::npos) && isConfiguration) {
std::size_t indexOfEqualSign = line.find('=');
if (indexOfEqualSign == std::string::npos) {
WARNING << "Unable to determine the port # assigned to " << tempID;
}
else {
std::string portString = "";
portString = line.substr(indexOfEqualSign + 1, line.length() - indexOfEqualSign - 1);
boost::trim(portString);
// confirm that the obtained port string is not an empty value
if (portString.empty()) {
WARNING << "Failed to obtain the \"Port\" value that is set to " << tempID;
}
else {
// attempt to convert the string to int
int workingPortNum = 0;
try {
workingPortNum = std::stoi(portString);
}
catch (...) {
WARNING << "Failed to convert the obtained \"Port\" value that is set to " << tempID;
}
if (workingPortNum != 0) {
// check if this port # is the same port # we are publishing data on
if (workingPortNum == this->port) {
ID = tempID;
break;
}
}
}
}
}
}
config.close();
if (ID.empty())
return false;
else
return true;
}
The goal of this method is to parse any text file for the ID portion, based on matching the port # that the application is publishing data to.
Format of the file is like this:
Idenntifications {
ID {
port = 1001
}
}
In a separate Visual Studio project that unit tests various methods, including this Project::DetermineID method.
#define STRINGIFY(x) #x
#define EXPAND(x) STRINGIFY(x)
TEST_CLASS(ProjectUnitTests) {
Project* parser;
std::string projectDirectory;
TEST_METHOD_INITIALIZE(ProjectUnitTestInitialization) {
projectDirectory = EXPAND(UNITTESTPRJ);
projectDirectory.erase(0, 1);
projectDirectory.erase(projectDirectory.size() - 2);
parser = Project::getClass(); // singleton method getter/initializer
}
// Other test methods are present and pass/fail accordingly
TEST_METHOD(DetermineID) {
std::string ID = "";
bool x = parser ->DetermineAdapterID(projectDirectory + "normal.cfg", ID);
Assert::IsTrue(x);
}
};
Now, when I run the tests, DetermineID fails and the stack trace states:
DetermineID
Source: Project Tests.cpp line 86
Duration: 2 sec
Message:
Assert failed
Stack Trace:
ProjectUnitTests::DetermineID() line 91
Now, in my test .cpp file, TEST_METHOD(DetermineID) { is present on line 86. But that method's } is located on line 91, as the stack trace indicates.
And, when debugging, the unit test passes, because the return of x in the TEST_METHOD is true.
Only when running the test individually or running all tests does that test method fail.
Some notes that may be relevant:
This is a single-threaded application with no tasks scheduled (no race condition to worry about supposedly)
There is another method in the Project class that also processes a file with an std::ifstream same as this method does
That method has its own test method that has been written and passes without any problems
The test method also access the "normal.cfg" file
Yes, this->port has an assigned value
Thus, my questions are:
Why does the stack trace reference the closing bracket for the test method instead of the single Assert within the method that is supposedly failing?
How to get the unit test to pass when it is ran? (Since it currently only plasses during debugging where I can confirm that x is true).
If the issue is a race condition where perhaps the other test method is accessing the "normal.cfg" file, why does the test method fail even when the method is individually ran?
Any support/assistance here is very much appreciated. Thank you!
I've been trying to write a libzmq wrapper for Swift by building off of an existing wrapper called SwiftyZeroMQ. However, for our purposes, we require the usage of raw UDP which means we need to use ZeroMQ's Radio/Dish draft method.
I've been able to successfully write a wrapper for receiving data via the Dish socket but I'm now trying to write a wrapper for sending data via the Radio socket. There doesn't seem to be any stuff online regarding how to write a function to send data via the Radio socket but I did come across this. This function in the libzmq repo tests the sending of data via the radio socket so I figured why not try and replicate the function in Swift.
Here's what I've come up with:
Inside the Socket.swift file:
public func sendRadioMessage(_ group: String, data: NSData) throws{
var msg = zmq_msg_t.init();
var result : Int32;
let flags: SocketSendRecvOption = .none
result = zmq_msg_init(&msg);
if (result == -1) { throw ZeroMQError.last }
defer {
// Clean up message on scope exit
zmq_msg_close(&msg)
}
print("initial msg data = \(zmq_msg_data(&msg))")
print("initial data = \(data)")
memcpy(zmq_msg_data(&msg), (data as NSData).bytes, data.length);
print("msg size = \(zmq_msg_size(&msg))")
print("msg = \(zmq_msg_data(&msg))")
result = zmq_msg_set_group(&msg, group);
if (result == -1) { throw ZeroMQError.last }
result = zmq_msg_send(&msg, self.handle, flags.rawValue);
if (result == -1) { throw ZeroMQError.last }
print("sent \(result) bytes")
}
}
and that function is then called like this:
public func send(data: String) -> Bool{
do{
try radio?.sendRadioMessage(group, data: data.data(using: .utf8) as! NSData);
}
catch{
print("SEND COMMUNICATION error - \(error)");
return false;
}
return true;
}
obj.send(data: "Test Data")
This is the console output when running the program:
It seems to me then that the memcpy function that I'm calling from Swift doesn't actually seem to be doing anything. Would this be due to the fact that I'm not passing the data payload properly to the memcpy function? I'm a bit stuck.
The API-documented zmq_msg_data() function is fine & safe to "read" a data-part from a delivered ZeroMQ message.
The context of use & the order of parameters :
void* memcpy( void* dest,
const void* src,
std::size_t count
);
shows, that your code tries to "store" data into a zmq_msg_t-instance, in spite of countless warnings in the ZeroMQ API documentation not to ever attempt to "touch", the less to manipulate data "directly", but by using member functions :
Never access zmq_msg_t members directly, instead always use the zmq_msg family of functions.
Here, possibly using rather :
int zmq_msg_init_data ( zmq_msg_t *msg,
void *data, // <------- payload to load into *msg
size_t size,
zmq_free_fn *ffn, // ref. API details
void *hint // on this
);
So the illustrative example might be something alike this :
//
// Initialising a message from a supplied buffer
//
// *********************************************
void my_free ( void *data, void *hint ) // ------- a dealloc helper
{
free ( data );
}
/* ... */
void *data = malloc ( 6 ); // -------------------- a mock-up payload data
assert ( data );
memcpy ( data, "ABCDEF", 6 );
zmq_msg_t msg;
rc = zmq_msg_init_data ( &msg, data, 6, my_free, NULL );
assert ( rc == 0
&& "INF: zmq_msg_init_data() failed, saying"
&& zmq_strerror ( zmq_errno() ) // ------- a non-POSIX system workaround
);
Also note, that a proposed defense of :
result = zmq_msg_init( &msg );
if ...
did gave a sense in v2.x & v3.x, yet after moving into v4.x, it started to do nothing , as it keeps in ZeroMQ v4.3+ API, as is documented in due form and shape :
Return value
The zmq_msg_init() function always returns zero.
Some version-control and redesign efforts might be needed so as to keep handling this consistently between the port-version and the actual API-version.
I'm attempting to do an HTTPS PUT from C++ on an ESP-EYE. I started with the C esp_https_example code and had that working with the same PEM and URL. I started to transition to using this from C++ as part of a project that is primarily written in C++. My call looks like:
static const char *URL = "https://signal.unexpectedeof.casa/on-air";
void https_with_url(void)
{
esp_http_client_config_t* config = (esp_http_client_config_t*)calloc(sizeof(esp_http_client_config_t), 1);
config->url = URL;
config->cert_pem = unexpectedeof_casa_root_cert_pem_start;
config->event_handler = _http_event_handler;
esp_http_client_handle_t client = esp_http_client_init(config);
esp_http_client_set_method(client, HTTP_METHOD_PUT);
esp_err_t err = esp_http_client_perform(client);
if (err == ESP_OK) {
ESP_LOGI(TAG, "HTTPS Status = %d, content_length = %d",
esp_http_client_get_status_code(client),
esp_http_client_get_content_length(client));
} else {
ESP_LOGE(TAG, "Error perform http request %s", esp_err_to_name(err));
}
esp_http_client_close(client);
esp_http_client_cleanup(client);
}
I believe the URL I'm providing isn't being copied or initialized correctly resulting in a url parsing error. When the function https_with_url is called I get this error:
E (13593) esp-tls: couldn't get hostname for :signal.unexpectedeof.casa:
E (13593) esp-tls: Failed to open new connection
E (13603) TRANS_SSL: Failed to open a new connection
E (13603) HTTP_CLIENT: Connection failed, sock < 0
E (13613) HTTPS_HANDLING: Error perform http request ESP_ERR_HTTP_CONNECT
I (13623) HTTPS_HANDLING: HTTP_EVENT_DISCONNECTED
I (13623) HTTPS_HANDLING: HTTP_EVENT_DISCONNECTED
Since I'm using C++ but the parse happens in the esp-idf C code I thought maybe I'm not passing the data correctly, but haven't made much progress. Switching from an inline string for the URL to the character array shown didn't make a difference.
esp-idf version 4.1.
Ended up being that I didn't handle the wifi event properly before the request was sent.
static void wifi_event_handler(void* arg, esp_event_base_t event_base,
int32_t event_id, void* event_data)
{
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
esp_wifi_connect();
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
if (s_retry_num < EXAMPLE_ESP_MAXIMUM_RETRY) {
esp_wifi_connect();
xEventGroupClearBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
s_retry_num++;
ESP_LOGI(LOGTAG, "retry to connect to the AP");
}
ESP_LOGI(LOGTAG,"connect to the AP fail");
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
ESP_LOGI(LOGTAG, "got ip:" IPSTR, IP2STR(&event->ip_info.ip));
s_retry_num = 0;
xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
}
}
void wifi_init_sta(void)
{
s_wifi_event_group = xEventGroupCreate();
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_netif_create_default_wifi_sta();
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL));
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &wifi_event_handler, NULL));
wifi_config_t wifi_config = { };
strcpy((char*)wifi_config.sta.ssid, CONFIG_ESP_WIFI_SSID);
strcpy((char*)wifi_config.sta.password, CONFIG_ESP_WIFI_PASSWORD);
ESP_LOGI(LOGTAG, "Setting WiFi configuration SSID %s...", wifi_config.sta.ssid);
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA) );
ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config) );
ESP_ERROR_CHECK(esp_wifi_start() );
ESP_LOGI(LOGTAG, "wifi_init_sta finished.");
}
Examples of this are in the esp-idf github repo under examples/protocols/wifi.
I am following the example in https://learn.microsoft.com/en-gb/windows/win32/api/iphlpapi/nf-iphlpapi-getpertcp6connectionestats?redirectedfrom=MSDN to get the TCP statistics. Although, I got it working and get the statistics in the first place, still I want to record them every a time interval (which I haven't managed to do so), and I have the following questions.
The SetPerTcpConnectionEStats () fails with status != NO_ERROR and equal to 5. Although, it fails, I can get the statistics. Why?
I want to get the statistics every, let's say 1 second. I have tried two different ways; a) to use a while loop and use a std::this_thread::sleep_for(1s), where I could get the statistics every ~1sec, but the whole app was stalling (is it because of the this), I supposed that I am blocking the operation of the main, and b) (since a) failed) I tried to call TcpStatistics() from another function (in different class) that is triggered every 1 sec (I store clientConnectRow to a global var). However, in that case (b), GetPerTcpConnectionEStats() fails with winStatus = 1214 (ERROR_INVALID_NETNAME) and of course TcpStatistics() cannot get any of the statistics.
a)
ClassB::ClassB()
{
UINT winStatus = GetTcpRow(localPort, hostPort, MIB_TCP_STATE_ESTAB, (PMIB_TCPROW)clientConnectRow);
ToggleAllEstats(clientConnectRow, TRUE);
thread t1(&ClassB::TcpStatistics, this, clientConnectRow);
t1.join();
}
ClassB::TcpStatistics()
{
while (true)
{
GetAndOutputEstats(row, TcpConnectionEstatsBandwidth)
// some more code here
this_thread::sleep_for(milliseconds(1000));
}
}
b)
ClassB::ClassB()
{
MIB_TCPROW client4ConnectRow;
void* clientConnectRow = NULL;
clientConnectRow = &client4ConnectRow;
UINT winStatus = GetTcpRow(localPort, hostPort, MIB_TCP_STATE_ESTAB, (PMIB_TCPROW)clientConnectRow);
m_clientConnectRow = clientConnectRow;
TcpStatistics();
}
ClassB::TcpStatistics()
{
ToggleAllEstats(m_clientConnectRow , TRUE);
void* row = m_clientConnectRow;
GetAndOutputEstats(row, TcpConnectionEstatsBandwidth)
// some more code here
}
ClassB::GetAndOutputEstats(void* row, TCP_ESTATS_TYPE type)
{
//...
winStatus = GetPerTcpConnectionEStats((PMIB_TCPROW)row, type, NULL, 0, 0, ros, 0, rosSize, rod, 0, rodSize);
if (winStatus != NO_ERROR) {wprintf(L"\nGetPerTcpConnectionEStats %s failed. status = %d", estatsTypeNames[type], winStatus); //
}
else { ...}
}
ClassA::FunA()
{
classB_ptr->TcpStatistics();
}
I found a work around for the second part of my question. I am posting it here, in case someone else find it useful. There might be other solutions too, more advanced, but this is how I did it myself. We have to first Obtain MIB_TCPROW corresponding to the TCP connection and then to Enable Estats collection before dumping current stats. So, what I did was to add all of these in a function and call this instead, every time I want to get the stats.
void
ClassB::FunSetTcpStats()
{
MIB_TCPROW client4ConnectRow;
void* clientConnectRow = NULL;
clientConnectRow = &client4ConnectRow;
//this is for the statistics
UINT winStatus = GetTcpRow(lPort, hPort, MIB_TCP_STATE_ESTAB, (PMIB_TCPROW)clientConnectRow); //lPort & hPort in htons!
if (winStatus != ERROR_SUCCESS) {
wprintf(L"\nGetTcpRow failed on the client established connection with %d", winStatus);
return;
}
//
// Enable Estats collection and dump current stats.
//
ToggleAllEstats(clientConnectRow, TRUE);
TcpStatistics(clientConnectRow); // same as GetAllEstats() in msdn
}
Im implementing a chat application using Jabber/XMPP and gloox framework which should send and receive messages concurrently in Ubuntu Linux.
My current code implementation is :
int main()
{
...
int temp = pthread_create(&iSend, NULL, SendMessage, &b);
int temp1 = pthread_create(&iRecv, NULL, ConnServer, &b);
}
void* ConnServer(void *athis)
{
UserClient *t = (UserClient*)athis;
t->ConnecttosServer();
}
bool UserClient::ConnecttosServer()
{
//JID jid( "map#jo-hely.hu/gloox" );
j = new Client( iUserJid, iUser.getPassword() );
j->registerMessageHandler( this);
j->registerConnectionListener( this );
j->registerMessageSessionHandler(this);
bool result = j->connect(false);
if(result == true)
{
iConnected = true;
ConnectionError er = ConnNoError;
ConnectionError er1 = ConnNoError;
while(er == ConnNoError || er1 == ConnNoError)
{
er = j->recv(500000);
sleep(2);
}
delete j;
}
...
}
void* SendMessage(void * athis )// JID *aDest)
{
UserClient *t = (UserClient*)athis;
//JID *t = (JID)dest;
string ip ;
cout << "enter here";
cin >> ip;
if(t->iConnected == true)
{
if(t->iMessageSession == NULL )
{
string aBody = "hello";
MessageSession *session = new MessageSession(t->j, t->iDestJid);
session->registerMessageHandler(t);
session->send(aBody.c_str());
}
}
}
The problem faced is both the threads are created and pthread_join( ) is called for both.
The iSend thread is scheduled first but gets suspended at cin. Once the recv( ) function is called, which runs in iRecv thread, the recv call back function handleMessage( ) is called. However the control never shifts back to the iSend thread which should call SendMessage( ) function.
Please help
I cannot see in there how SendMessage ever sends more than one "hello" message.
There are various memory issues here of course, eg j won't get deleted at all if connect failed, and as its scope is function-only there is no real need to create it with new at all.
You cannot count on the iSend thread being scheduled first. That is completely up to the operating system.