summaryrefslogtreecommitdiffstats
path: root/src/platform-info-devices.c
diff options
context:
space:
mode:
authorFarshid Monhaseri <monhaseri.f@gmail.com>2020-11-10 13:05:37 +0330
committerFarshid Monhaseri <monhaseri.f@gmail.com>2020-11-10 09:55:46 +0000
commit172db50c33beeb53f8e75c115699179be880b960 (patch)
treeeb0b124e43cf409c4b12b72a4df1e12d3219936d /src/platform-info-devices.c
parente40da34d561aa802ea8f1d8f5bf7b5898f84b083 (diff)
Add device monitoring feature (hotplug device detection)
Changes: - Implement libudev 'monitor' interface to detect hotplug devices. - Forward udev rules as AFB 'subscribe' args to udev_monitoring objects. - Filter retrieved property fields through 'mask' argument from 'subscribe' args. - Generate detached threads (event loop) and data context for each client, so every user can subscribe for device events with their own udev rules and receive their desired device parameters. Bug-AGL: SPEC-3512 Signed-off-by: Farshid Monhaseri <monhaseri.f@gmail.com> Change-Id: I0fc007d9707deaf39d21147aa8240fde302f5f9e
Diffstat (limited to 'src/platform-info-devices.c')
-rw-r--r--src/platform-info-devices.c397
1 files changed, 397 insertions, 0 deletions
diff --git a/src/platform-info-devices.c b/src/platform-info-devices.c
new file mode 100644
index 0000000..89796f1
--- /dev/null
+++ b/src/platform-info-devices.c
@@ -0,0 +1,397 @@
+/*
+ * Copyright (C) 2020 "IoT.bzh"
+ *
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <string.h>
+#include <sys/select.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <pthread.h>
+#include <libudev.h>
+#include <unistd.h>
+#include "platform-info-devices.h"
+
+#define JSON_NEW_CONST_KEY \
+ (JSON_C_OBJECT_KEY_IS_CONSTANT | JSON_C_OBJECT_ADD_KEY_IS_NEW)
+#define UNUSED(x) (void)(x);
+
+static void* pinfo_device_client_new(void* req_ctx);
+static void pinfo_device_client_free(void* client_ctx);
+static void* pinfo_device_monitor_loop(pinfo_client_ctx_t* ctx);
+static void pinfo_device_monitor_detect(pinfo_client_ctx_t* ctx, struct json_object* jdevice);
+static json_object* pinfo_device_udevice_to_jdevice(struct udev_device* udevice, struct json_object* jmask);
+static void pinfo_device_jdev_destructor(json_object* jdevice, void* udevice);
+static int pinfo_device_filter_monitoring(pinfo_client_ctx_t* ctx);
+static json_object* pinfo_device_udevice_to_jlist(
+ struct udev_device* udevice,
+ struct udev_list_entry*(*udevice_elist)(struct udev_device*),
+ const char*(*elist_val_getter)(struct udev_device*,const char*),
+ const unsigned int jflags);
+static json_object* pinfo_device_udevice_to_jlist_mask(
+ struct udev_device* udevice,
+ const char*(*udevice_val_getter)(struct udev_device*,const char*),
+ json_object* jkeys,
+ unsigned jcpy_flags);
+
+static void
+pinfo_device_monitor_detect(pinfo_client_ctx_t* ctx, struct json_object* jdevice)
+{
+ afb_event_push((afb_event_t)ctx->ev_devs_changed,jdevice);
+}
+
+int
+pinfo_device_monitor(afb_req_t req) {
+ int res = PINFO_ERR;
+
+ if(afb_req_context(req,0,pinfo_device_client_new,pinfo_device_client_free,req)) {
+ res = PINFO_OK;
+ }
+
+ return res;
+}
+
+static void
+pinfo_device_client_free(void* client_ctx) {
+ if(client_ctx) {
+ pinfo_client_ctx_t* ctx = (pinfo_client_ctx_t*)client_ctx;
+ if(ctx) {
+ pthread_cancel(ctx->th);
+ udev_monitor_unref(ctx->umon_hndl);
+ udev_unref(ctx->udev_ctx);
+ afb_event_unref((afb_event_t)ctx->ev_devs_changed);
+ json_object_put(ctx->filter);
+ json_object_put(ctx->mask);
+ ctx->api_ctx->client_count--;
+ AFB_DEBUG("Client context released, client count: %d",ctx->api_ctx->client_count);
+ }
+ }
+}
+
+static void*
+pinfo_device_client_new(void* req_ctx) {
+ pinfo_client_ctx_t* ctx = NULL;
+ afb_req_t req = (afb_req_t)(req_ctx);
+
+ if(req) {
+ ctx = malloc(sizeof(*ctx));
+ if(ctx) {
+ ctx->udev_ctx = udev_new();
+ if(ctx->udev_ctx) {
+ ctx->umon_hndl = udev_monitor_new_from_netlink(ctx->udev_ctx,"udev");
+ ctx->filter = NULL;
+ ctx->mask = NULL;
+ ctx->umon_cb = NULL;
+
+ if(ctx->umon_hndl) {
+ json_object* jval = NULL;
+ json_object* jargs = afb_req_json(req);
+ pthread_attr_t attr;
+
+ ctx->ev_devs_changed = afb_api_make_event(req->api,"device_changed");
+ if(afb_event_is_valid(ctx->ev_devs_changed)) {
+ if(afb_req_subscribe(req,ctx->ev_devs_changed) == PINFO_OK) {
+ ctx->umon_cb = (void(*)(void*,struct json_object*))pinfo_device_monitor_detect;
+ if(json_object_object_get_ex(jargs,"filter",&jval) &&
+ json_object_is_type(jval,json_type_object)) {
+ json_object_deep_copy(jval,&ctx->filter,NULL);
+ }
+
+ if(json_object_object_get_ex(jargs,"mask",&jval) &&
+ json_object_is_type(jval,json_type_object)) {
+ json_object_deep_copy(jval,&ctx->mask,NULL);
+ }
+
+ if(pinfo_device_filter_monitoring(ctx) == PINFO_OK) {
+ pthread_attr_init(&attr);
+ pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
+ if(pthread_create(&ctx->th,&attr,
+ (void*(*)(void*))&pinfo_device_monitor_loop,(void*)ctx) == PINFO_OK) {
+ pinfo_api_ctx_t* api_ctx = NULL;
+ api_ctx = (pinfo_api_ctx_t*)afb_api_get_userdata(req->api);
+ AFB_REQ_DEBUG(req,"New client event-loop have been started ...");
+ //New client (new session) get a pointer of API context
+ //to access global(API) context
+ if(api_ctx) {
+ ctx->api_ctx = api_ctx;
+ api_ctx->client_count++;
+ AFB_REQ_INFO(req,"New client context allocated, client count: %d",
+ api_ctx->client_count);
+ }
+ } else {
+ AFB_REQ_ERROR(req,"Failed to run the new session monitoring thread.");
+ pinfo_device_client_free(ctx);
+ ctx = NULL;
+ }
+ pthread_attr_destroy(&attr);
+ } else {
+ AFB_REQ_ERROR(req,"Failed to apply the device monitoring filters.");
+ }
+ } else {
+ AFB_REQ_ERROR(req,"New client event subscribing failed.");
+ }
+ } else {
+ AFB_REQ_ERROR(req,"Invalid AFB event generated for new client.");
+ }
+ } else {
+ AFB_REQ_ERROR(req,
+ "Failed to generate new udev monitoring object for the new client.");
+ }
+ } else {
+ AFB_REQ_ERROR(req,
+ "Failed to generate new udev object for the new client.");
+ }
+ } else {
+ AFB_REQ_ERROR(req,"Failed to allocate memory for new client context.");
+ }
+ }
+
+ return (void*)ctx;
+}
+
+static void
+pinfo_device_jdev_destructor(struct json_object* jdevice, void* udevice)
+{
+ UNUSED(jdevice)
+ udev_device_unref((struct udev_device*)udevice);
+}
+
+static int
+pinfo_device_filter_monitoring(pinfo_client_ctx_t* ctx) {
+ int res = PINFO_OK;
+
+ if(ctx && ctx->umon_hndl &&
+ json_object_is_type(ctx->filter,json_type_object)) {
+ json_object* jprop_filter = NULL;
+ json_object* jval = NULL;
+
+ if(json_object_object_get_ex(ctx->filter,"properties",&jprop_filter)) {
+ const char* subsys_str = NULL;
+ const char* devtype_str = NULL;
+
+ if(json_object_object_get_ex(jprop_filter,"SUBSYSTEM",&jval) &&
+ json_object_is_type(jval,json_type_string)) {
+ subsys_str = json_object_get_string(jval);
+ }
+ jval = NULL;
+ if(json_object_object_get_ex(jprop_filter,"DEVTYPE",&jval) &&
+ json_object_is_type(jval,json_type_string)) {
+ devtype_str = json_object_get_string(jval);
+ }
+ if(devtype_str || subsys_str) {
+ res |= udev_monitor_filter_add_match_subsystem_devtype(
+ ctx->umon_hndl,subsys_str,devtype_str);
+ }
+ }
+
+ if(json_object_object_get_ex(ctx->filter,"tags",&jval)) {
+ if(json_object_is_type(jval,json_type_array)) {
+ const size_t tags_count = json_object_array_length(jval);
+
+ if(tags_count > 0) {
+ int tag_idx = 0;
+ json_object *tag_item = NULL;
+
+ for(tag_item = json_object_array_get_idx(jval,0);
+ tag_item && tag_idx < tags_count;
+ tag_item = json_object_array_get_idx(jval,++tag_idx)) {
+
+ if(json_object_is_type(tag_item,json_type_string)) {
+ res |= udev_monitor_filter_add_match_tag(
+ ctx->umon_hndl,
+ json_object_get_string(tag_item)
+ );
+ } else {
+ AFB_WARNING("Client passed invalid tag type,\
+ avoid the tag expression");
+ }
+ }
+ } else {
+ //Empty tags array
+ }
+ } else {
+ AFB_WARNING("Client passed invalid value for 'tags' field,\
+ the value type should be a json array with json string items,\
+ avoid the tags filtering");
+ }
+ }
+ }
+
+ return res;
+}
+
+
+static json_object*
+pinfo_device_udevice_to_jlist( struct udev_device* udevice,
+ struct udev_list_entry*(*udevice_elist)(struct udev_device*),
+ const char*(*elist_val_get)(struct udev_device*,const char*),
+ const unsigned int jflags) {
+ json_object* jobject = NULL;
+
+ if(udevice && udevice_elist) {
+ struct udev_list_entry* elist = udevice_elist(udevice);
+ struct udev_list_entry* elist_head = NULL;
+
+ jobject = json_object_new_object();
+ udev_list_entry_foreach(elist_head,elist) {
+ const char* skey = udev_list_entry_get_name(elist_head);
+ const char* svalue = elist_val_get(udevice,skey);
+
+ if(skey && svalue) {
+ json_object_object_add_ex(jobject,skey,json_object_new_string(svalue),jflags);
+ }
+ }
+ }
+
+ return jobject;
+}
+
+static json_object*
+pinfo_device_udevice_to_jlist_mask( struct udev_device* udevice,
+ const char*(*udevice_val_getter)(struct udev_device*,const char*),
+ json_object* jkeys,
+ unsigned jcpy_flags) {
+ json_object* jdev = NULL;
+
+ if(json_object_is_type(jkeys,json_type_array)) {
+ const int keys_count = json_object_array_length(jkeys);
+ if(keys_count > 0) {
+ int key_idx = 0;
+ jdev = json_object_new_object();
+
+ for(;key_idx < keys_count; ++key_idx) {
+ const char* skey = json_object_get_string(
+ json_object_array_get_idx(jkeys,key_idx));
+ const char* svalue = udevice_val_getter(udevice,skey);
+
+ if(svalue) {
+ json_object_object_add_ex(jdev,skey,json_object_new_string(svalue),jcpy_flags);
+ }
+ }
+ }
+ }
+
+ return jdev;
+}
+
+static json_object*
+pinfo_device_udevice_to_jdevice(struct udev_device* udevice, json_object* jmask) {
+ json_object *jdev = NULL;
+
+ if(udevice) {
+ json_object* jprops = NULL;
+ json_object* jattrs = NULL;
+
+ jdev = json_object_new_object();
+ if(json_object_is_type(jmask,json_type_object)) {
+ json_object* jprops_mask = NULL;
+ json_object* jattrs_mask = NULL;
+
+ if(json_object_object_get_ex(jmask,"properties",&jprops_mask)) {
+ jprops = pinfo_device_udevice_to_jlist_mask(
+ udevice,
+ udev_device_get_property_value,
+ jprops_mask,
+ JSON_NEW_CONST_KEY
+ );
+ }
+ if(json_object_object_get_ex(jmask,"attributes",&jattrs_mask)) {
+ jattrs = pinfo_device_udevice_to_jlist_mask(
+ udevice,
+ udev_device_get_sysattr_value,
+ jattrs_mask,
+ JSON_NEW_CONST_KEY
+ );
+ }
+ }
+
+ if(!jprops) {
+ jprops = pinfo_device_udevice_to_jlist(
+ udevice,
+ udev_device_get_properties_list_entry,
+ udev_device_get_property_value,
+ JSON_NEW_CONST_KEY
+ );
+ }
+
+ if(!jattrs) {
+ jattrs = pinfo_device_udevice_to_jlist(
+ udevice,
+ udev_device_get_sysattr_list_entry,
+ udev_device_get_sysattr_value,
+ JSON_NEW_CONST_KEY
+ );
+ }
+
+ if(json_object_is_type(jprops,json_type_object) &&
+ json_object_object_length(jprops) > 0) {
+ json_object_object_add_ex(
+ jdev,
+ "properties",
+ jprops,
+ JSON_NEW_CONST_KEY
+ );
+ }
+
+ if(json_object_is_type(jattrs,json_type_object) &&
+ json_object_object_length(jattrs) > 0) {
+ json_object_object_add_ex(
+ jdev,
+ "attributes",
+ jattrs,
+ JSON_NEW_CONST_KEY
+ );
+ }
+
+ json_object_set_userdata(jdev,udevice,(json_object_delete_fn*)pinfo_device_jdev_destructor);
+ }
+
+ return jdev;
+}
+
+static void*
+pinfo_device_monitor_loop(pinfo_client_ctx_t* ctx) {
+ if(ctx && ctx->umon_hndl) {
+ int fd;
+ int res = 0;
+
+ res = udev_monitor_enable_receiving(ctx->umon_hndl);
+
+ if(res >= 0) {
+ fd = udev_monitor_get_fd(ctx->umon_hndl);
+ while (1) {
+ int ret;
+ fd_set fds;
+
+ FD_ZERO(&fds);
+ FD_SET(fd,&fds);
+
+ ret = select(fd+1, &fds, NULL, NULL, NULL);
+ if(ret > 0 && FD_ISSET(fd,&fds)) {
+ struct udev_device* detected_dev = NULL;
+
+ detected_dev = udev_monitor_receive_device(ctx->umon_hndl);
+ if(detected_dev) {
+ json_object* jdetected_dev = NULL;
+ jdetected_dev = pinfo_device_udevice_to_jdevice(detected_dev,ctx->mask);
+ if(jdetected_dev) {
+ ctx->umon_cb(ctx,jdetected_dev);
+ }
+ }
+ }
+ }
+ }
+ }
+ return NULL;
+}