diff options
-rw-r--r-- | conf.d/cmake/config.cmake | 3 | ||||
-rw-r--r-- | src/CMakeLists.txt | 5 | ||||
-rw-r--r-- | src/platform-info-binding.c | 133 | ||||
-rw-r--r-- | src/platform-info-binding.h | 4 | ||||
-rw-r--r-- | src/platform-info-devices.c | 397 | ||||
-rw-r--r-- | src/platform-info-devices.h | 50 |
6 files changed, 554 insertions, 38 deletions
diff --git a/conf.d/cmake/config.cmake b/conf.d/cmake/config.cmake index 8b44fd3..c410ff5 100644 --- a/conf.d/cmake/config.cmake +++ b/conf.d/cmake/config.cmake @@ -63,9 +63,10 @@ set (gcc_minimal_version 4.9) # PKG_CONFIG required packages # ----------------------------- set (PKG_REQUIRED_LIST - json-c>=0.12 + json-c>=0.13 afb-daemon afb-helpers + libudev ) # You can also consider to include libsystemd diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 45bc242..ae1da6a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -18,7 +18,10 @@ PROJECT_TARGET_ADD(platform-info) # Define project Targets - ADD_LIBRARY(${TARGET_NAME} MODULE platform-info-binding.c) + ADD_LIBRARY(${TARGET_NAME} MODULE + platform-info-binding.c + platform-info-devices.c + ) # Binder exposes a unique public entry point SET_TARGET_PROPERTIES(${TARGET_NAME} PROPERTIES diff --git a/src/platform-info-binding.c b/src/platform-info-binding.c index 5e78fd9..4ae3c70 100644 --- a/src/platform-info-binding.c +++ b/src/platform-info-binding.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2019 "IoT.bzh" + * Copyright (C) 2016-2020 "IoT.bzh" * * Author "Romain Forlot" <romain.forlot@iot.bzh> * @@ -23,6 +23,7 @@ #include <afb/afb-binding.h> #include "platform-info-binding.h" +#include "platform-info-devices.h" #ifndef PLATFORM_INFO_DIR #define PLATFORM_INFO_DIR "/etc/platform-info" @@ -33,30 +34,42 @@ #endif void afv_get(afb_req_t req) { - char *json_path = NULL, *full_json_path = NULL; - json_object *platform_info = (json_object*) afb_api_get_userdata(req->api); - json_object *args = afb_req_json(req), *result = NULL; - - switch (json_object_get_type(args)) { - case json_type_null: - result = platform_info; - break; - case json_type_string: - full_json_path = strdupa(json_object_get_string(args)); - result = platform_info; - for(json_path = strtok(full_json_path, "."); json_path && *json_path; json_path = strtok(NULL, ".")) { - if(! json_object_object_get_ex(result, json_path, &result)) { - afb_req_fail(req, "A key hasn't been found in JSON path.", json_path); - return; + pinfo_api_ctx_t *api_ctx = (pinfo_api_ctx_t*)afb_api_get_userdata(req->api); + + if(api_ctx) { + json_object *result = NULL; + json_object *args = afb_req_json(req); + + switch (json_object_get_type(args)) { + case json_type_null: + result = api_ctx->info; + break; + case json_type_string: { + char *json_path = NULL; + char *full_json_path = NULL; + + full_json_path = strdupa(json_object_get_string(args)); + result = api_ctx->info; + + for(json_path = strtok(full_json_path, "."); + json_path && *json_path; + json_path = strtok(NULL, ".")) { + if(! json_object_object_get_ex(result, json_path, &result)) { + afb_req_fail(req, "A key hasn't been found in JSON path.", json_path); + return; + } } + break; } - break; - default: - afb_req_fail(req, "Type error", "Argument type is unknown, you must provide a string only"); - return; - } + default: + afb_req_fail(req, "Type error", "Argument type is unknown, you must provide a string only"); + return; + } - afb_req_success(req, json_object_get(result), NULL); + afb_req_success(req, json_object_get(result), NULL); + } else { + afb_req_fail(req,"failed","The API contains no context!"); + } } void afv_set(afb_req_t req) { @@ -75,27 +88,60 @@ void afv_set(afb_req_t req) { afb_req_success(req, NULL, NULL); } -// TODO RFOR: interface with inotify and udev +// TODO RFOR: interface with inotify void afv_unsubscribe(afb_req_t req) { - afb_req_success(req, NULL, NULL); + pinfo_client_ctx_t* client_ctx = NULL; + client_ctx = (pinfo_client_ctx_t*)afb_req_context_get(req); + + if(client_ctx) { + const char* event_str = afb_req_value(req,"event"); + + if(event_str && strcmp("monitor-devices",event_str) == 0) { + afb_req_unsubscribe(req,client_ctx->ev_devs_changed); + afb_req_context_clear(req); + afb_req_success(req,NULL, NULL); + } else { + afb_req_fail(req,"failed","No 'event' value provided."); + } + } else { + afb_req_fail(req,"failed","No context available for the client."); + } } void afv_subscribe(afb_req_t req) { - afb_req_success(req, NULL, NULL); + const char* event_str = NULL; + event_str = afb_req_value(req,"event"); + + if(event_str && strcmp("monitor-devices",event_str) == 0) { + if(!afb_req_context_get(req)) { + if(pinfo_device_monitor(req) == PINFO_OK) { + afb_req_success(req,NULL,NULL); + } else { + afb_req_fail(req,"failed","Unable to create new context"); + } + } else { + afb_req_fail(req,"failed","The client already subscribed."); + } + } else { + afb_req_fail(req,"failed","Invalid event subscription"); + } } -int init(afb_api_t api) { +static json_object* +afv_static_info(const char* dir) { struct dirent* dir_ent = NULL; + DIR* dir_handle = opendir(dir); + json_object* static_info = NULL; - DIR* dir_handle = opendir(PLATFORM_INFO_DIR); if (! dir_handle) { AFB_ERROR("The directory %s does not exist.", PLATFORM_INFO_DIR); - return -1; + return NULL; } - json_object *json_file = json_object_new_object(), *current_file = NULL; + static_info = json_object_new_object(); while( (dir_ent = readdir(dir_handle)) != NULL) { if(dir_ent->d_type == DT_REG && dir_ent->d_name[0] != '.') { + json_object* current_file = NULL; size_t filepath_len = strlen(PLATFORM_INFO_DIR) + strlen(dir_ent->d_name) + 2; char *filepath = alloca(filepath_len); @@ -117,14 +163,35 @@ int init(afb_api_t api) { continue; } #endif - wrap_json_object_add(json_file, current_file); - AFB_DEBUG("JSON loaded: %s", json_object_get_string(json_file)); + wrap_json_object_add(static_info, current_file); + AFB_DEBUG("JSON loaded: %s", + json_object_to_json_string_ext(current_file,JSON_C_TO_STRING_PRETTY)); } } + return static_info; +} + +int init(afb_api_t api) { // Initializing the platform_info binding object and associated it to // the api - afb_api_set_userdata(api, (void*)json_file); + AFB_DEBUG("init() ..."); + pinfo_api_ctx_t *api_ctx = NULL; + int ret = PINFO_OK; + + api_ctx = malloc(sizeof(*api_ctx)); + + if(api_ctx) { + AFB_DEBUG("init() ... OK"); + api_ctx->info = afv_static_info(PLATFORM_INFO_DIR); + AFB_API_DEBUG(api,"The API static data: %s", + json_object_to_json_string_ext(api_ctx->info, JSON_C_TO_STRING_PRETTY)); + api_ctx->client_count = 0; + afb_api_set_userdata(api, (void*)api_ctx); + } else { + AFB_API_WARNING(api,"Failed to load the static data"); + ret = PINFO_ERR; + } - return 0; + return ret; } diff --git a/src/platform-info-binding.h b/src/platform-info-binding.h index 183fa86..8943080 100644 --- a/src/platform-info-binding.h +++ b/src/platform-info-binding.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016, 2018 "IoT.bzh" + * Copyright (C) 2016, 2018, 2020 "IoT.bzh" * * Author "Romain Forlot" <romain.forlot@iot.bzh> * @@ -23,5 +23,3 @@ struct key_search_t { afb_api_t api; json_object **result; }; - -json_object *platform_info; 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; +} diff --git a/src/platform-info-devices.h b/src/platform-info-devices.h new file mode 100644 index 0000000..ad658f9 --- /dev/null +++ b/src/platform-info-devices.h @@ -0,0 +1,50 @@ +/* + * 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. + */ + +#ifndef PLATFORM_INFO_DEVICE_H +#define PLATFORM_INFO_DEVICE_H + +#include <afb/afb-binding.h> +#include "json-c/json.h" + +#define PINFO_OK (0) +#define PINFO_ERR (-1) + +struct udev; +struct udev_monitor; +struct pthread_t; + +typedef struct { + struct json_object* info; + int client_count; +}pinfo_api_ctx_t; + +typedef struct { + struct udev *udev_ctx; + struct udev_monitor *umon_hndl; + struct json_object *filter; + struct json_object *mask; + pinfo_api_ctx_t *api_ctx; + void(*umon_cb)(void* client_ctx, struct json_object* jdevice); + pthread_t th; + afb_event_t ev_devs_changed; +}pinfo_client_ctx_t; + + +int pinfo_device_monitor(afb_req_t req); + +#endif |