summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--conf.d/cmake/config.cmake3
-rw-r--r--src/CMakeLists.txt5
-rw-r--r--src/platform-info-binding.c133
-rw-r--r--src/platform-info-binding.h4
-rw-r--r--src/platform-info-devices.c397
-rw-r--r--src/platform-info-devices.h50
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