summaryrefslogtreecommitdiffstats
path: root/src/platform-info-devices.c
diff options
context:
space:
mode:
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;
+}