diff --git a/packages/base/any/onlp/src/onlplib/.module b/packages/base/any/onlp/src/onlplib/.module index f3d0bc49..1d11f804 100644 --- a/packages/base/any/onlp/src/onlplib/.module +++ b/packages/base/any/onlp/src/onlplib/.module @@ -1,2 +1,2 @@ name: onlplib -depends: cjson_util +depends: [ cjson_util, BigList ] diff --git a/packages/base/any/onlp/src/onlplib/module/inc/onlplib/file_uds.h b/packages/base/any/onlp/src/onlplib/module/inc/onlplib/file_uds.h new file mode 100644 index 00000000..3760ad46 --- /dev/null +++ b/packages/base/any/onlp/src/onlplib/module/inc/onlplib/file_uds.h @@ -0,0 +1,109 @@ +/************************************************************ + * + * + * Copyright 2017 Big Switch Networks, Inc. + * + * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/legal/epl-v10.html + * + * 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. + * + * + ************************************************************ + * + * This module provides a domain socket registration and handling + * service. + * + * Clients register a filesystem path they wish to publish + * as a domain socket their handlers are called when + * the domain socket is accessed. + * + * Some ONLP data can only be retreived by accessing another process + * Which cannot be part of the ONLP layer itself for various reasons. + * + * The ONLP File APIs support unix domain sockets for all operations + * as if they were regular files. This module provides a common + * framework for clients to implement the server side of the + * domain socket as well. + * + * Some examples of how this is used: + * - Reporting the switch internal thermal temperature + * - This can only be accessed by the code managing the switch. + * - In this case the switch management agent exports a domain socket + * that reports the temperature when the socket is read and the + * thermali implementation uses that domain socket to satisfy + * the request for the OID. + * + * - SFP Access through the switch + * - Some platforms implement SFP I2C access through a bus connected + * to the switch itself. + * - Only the agent running the switch can access the SFP eeproms. + * - In this case the strategy is for the switch agent to export domain + * sockets for each SFP which can be used to read the SFP status/eeprom + * etc. The SFPI interface then reads these domain sockets to get the + * required information. + * + * Standardizing on this method allows all system ONLP clients to access + * all data, even if that data is present only in seperate processes. + * + * + ***********************************************************/ +#ifndef __ONLPLIB_FILE_UDS_H__ +#define __ONLPLIB_FILE_UDS_H__ + +#include + +/** + * @brief This is the handle for the service object. + */ +typedef struct onlp_file_uds_s onlp_file_uds_t; + + +/** + * @brief Create a domain socket service manager. + * @param fuds Receives the service object pointer. + */ +int onlp_file_uds_create(onlp_file_uds_t** fuds); + +/** + * @brief This is the prototype for your service handler function. + * @param fd The client file descriptor. This is the descriptor accepted + * on your behalf by the service manager when someone attempts to open your domain socket. + * @param cookie Private callback pointer. + */ +typedef int (*onlp_file_uds_handler_t)(int fd, void* cookie); + +/** + * @brief Add a domain socket service path to an existing service manager. + * @param fuds The service manager + * @param path The domain socket filesystem path you would like to register. + * @param handler The connection handler for the domain socket. + * @param cookie Cookie for you connection handler. + */ +int onlp_file_uds_add(onlp_file_uds_t* fuds, + const char* path, + onlp_file_uds_handler_t handler, void* cookie); + +/** + * @brief Remove a domain socket service path from an existing service manager. + * @param fuds The service manager. + * @param path The domain socket service path to remove. + */ +void onlp_file_uds_remove(onlp_file_uds_t* fuds, const char* path); + +/** + * @brief Destroy a service manager object. + * @param fuds The object pointer. + * @notes All registered services will be destroyed. + */ +void onlp_file_uds_destroy(onlp_file_uds_t* fuds); + +#endif /* __ONLPLIB_FILE_UDS_H__ */ diff --git a/packages/base/any/onlp/src/onlplib/module/src/file_uds.c b/packages/base/any/onlp/src/onlplib/module/src/file_uds.c new file mode 100644 index 00000000..bd344d89 --- /dev/null +++ b/packages/base/any/onlp/src/onlplib/module/src/file_uds.c @@ -0,0 +1,405 @@ +/************************************************************ + * + * + * Copyright 2017 Big Switch Networks, Inc. + * + * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/legal/epl-v10.html + * + * 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 +#include "onlplib_log.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * Read/write an eventfd. + */ +static void +eventfd_write__(int fd) +{ + uint64_t val = 1; + write(fd, &val, sizeof(val)); +} +static void +eventfd_read__(int fd) +{ + uint64_t val; + read(fd, &val, sizeof(val)); +} + + +/** + * This represents a single domain socket service. + */ +typedef struct onlp_file_uds_service_s { + /** domain socket file path */ + const char* path; + + /** Listening descriptor */ + int lfd; + + /** client handler */ + onlp_file_uds_handler_t handler; + void* cookie; + + /** service is active. */ + int active; + +} onlp_file_uds_service_t; + +/** + * Destroy a file service. + */ +static void +onlp_file_uds_service_clear__(onlp_file_uds_service_t* p) +{ + if(p) { + if(p->lfd > 0) { + close(p->lfd); + } + if(p->path) { + aim_free((char*)p->path); + } + memset(p, 0, sizeof(*p)); + } +} +static void +onlp_file_uds_service_destroy__(onlp_file_uds_service_t* p) +{ + if(p) { + onlp_file_uds_service_clear__(p); + aim_free(p); + } +} + +/** + * Create a file service. + */ +static int +onlp_file_uds_service_create__(onlp_file_uds_service_t** rvp, + const char* path, + onlp_file_uds_handler_t handler, void* cookie) +{ + struct sockaddr_un addr; + + onlp_file_uds_service_t* rv = aim_zmalloc(sizeof(*rv)); + + rv->path = aim_strdup(path); + char* cmd = aim_fstrdup("mkdir -p `dirname %s`", path); + if(system(cmd) != 0) { + AIM_LOG_ERROR("Failed to create uds directory for %s", path); + aim_free(cmd); + goto failed; + } + aim_free(cmd); + + if ((rv->lfd = socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0)) == -1) { + AIM_LOG_ERROR("socket: %{errno}", errno); + goto failed; + } + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, path, sizeof(addr.sun_path)-1); + unlink(path); + + if(bind(rv->lfd, (struct sockaddr*)&addr, sizeof(addr)) == -1) { + AIM_LOG_ERROR("bind: %{errno}", errno); + goto failed; + } + + if (listen(rv->lfd, 1) == -1) { + AIM_LOG_ERROR("listen: %{errno}", errno); + goto failed; + } + + rv->handler = handler; + rv->cookie = cookie; + *rvp = rv; + + return 0; + + failed: + onlp_file_uds_service_destroy__(rv); + return -1; +} + +/** + * This is the control object for a UDS service group. + */ +struct onlp_file_uds_s { + + /** Thread signal. Used to wake up the service thread when required. */ + int eventfd; + + /** Service worker thread */ + pthread_t thread; + volatile int running; + volatile int terminate; + + /** Service client list */ + biglist_locked_t* list; +}; + + +/** + * Add a descriptor to an epoll set. + */ +static int +epoll_add__(int epoll_fd, int add_fd, uint32_t events, void* data, + int* counter, const char* name) +{ + struct epoll_event ev = {0}; + ev.data.ptr = data; + ev.events = events; + if(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, add_fd, &ev) != 0) { + if(errno != EEXIST) { + AIM_LOG_ERROR("epoll_ctl returned %{errno} for %s", errno, name); + return -1; + } + } + + if(counter) { + (*counter)++; + } + + return 0; +} + +/** + * Service a connection. + */ +static void +accept__(onlp_file_uds_service_t* ufp) +{ + int fd; + + if((fd = accept(ufp->lfd, NULL, 0)) > 0) { + ufp->handler(fd, ufp->cookie); + } + close(fd); +} + +/** + * This is the service list entry. + */ + +/** + * The service worker thread. + * + * All registered services are polled for incoming connections + * and handled in series. + * + * These are designed for simple transactions and not + * long-lived connections. + */ +static void* +uds_thread_worker__(void* p) +{ + volatile onlp_file_uds_t* control = (onlp_file_uds_t*)p; + + int epollfd; + if((epollfd = epoll_create(256)) == -1) { + AIM_LOG_ERROR("epoll_create(): %{errno}", errno); + return NULL; + } + + control->running = 1; + for(;;) { + + biglist_t* ble; + onlp_file_uds_service_t* ufp; + int nfd = 0; + + if(control->terminate) { + /** Request for termination. */ + break; + } + + /** control->eventfd wakes us up */ + if(epoll_add__(epollfd, control->eventfd, EPOLLIN, NULL, &nfd, "eventfd") < 0) { + return NULL; + } + + /** Add all active descriptors. */ + biglist_lock(control->list); + BIGLIST_FOREACH_DATA(ble, control->list->list, onlp_file_uds_service_t*, ufp) { + switch(ufp->active) + { + case 1: + /* Service is active. Wait on it. */ + epoll_add__(epollfd, ufp->lfd, EPOLLIN, ufp, &nfd, ufp->path); + break; + case -1: + /* Service deletion request. */ + AIM_LOG_MSG("Removing %s...", ufp->path); + epoll_ctl(epollfd, EPOLL_CTL_DEL, ufp->lfd, NULL); + onlp_file_uds_service_clear__(ufp); + break; + case 0: + /* Service is inactive. */ + break; + } + } + + biglist_unlock(control->list); + + struct epoll_event* events = aim_zmalloc(sizeof(*events)*nfd); + int rv = epoll_wait(epollfd, events, nfd, -1); + + if(rv < 0) { + if(errno != EINTR) { + AIM_LOG_ERROR("epoll_wait() returned %{errno}", errno); + break; + } + } + else if(rv == 0) { + /** Shouldn't happen with infinite timeout */ + } + else { + int i; + for(i = 0; i < rv; i++) { + if(events[i].events & EPOLLIN) { + onlp_file_uds_service_t* ufp = (onlp_file_uds_service_t*)events[i].data.ptr; + if(ufp == NULL) { + eventfd_read__(control->eventfd); + } + else { + if(ufp->active == 1) { + accept__(ufp); + } + } + } + } + } + aim_free(events); + } + control->running = 0; + return NULL; +} + +int +onlp_file_uds_create(onlp_file_uds_t** rvp) +{ + onlp_file_uds_t* rv = aim_zmalloc(sizeof(*rv)); + if((rv->eventfd = eventfd(0, 0)) == -1) { + AIM_LOG_ERROR("eventfd: %{errno}", errno); + goto failed; + } + if((rv->list = biglist_locked_create()) == NULL) { + goto failed; + } + + rv->running = 0; + + if(pthread_create(&rv->thread, NULL, uds_thread_worker__, rv) != 0) { + AIM_LOG_ERROR("pthread_create failed: %{errno}", errno); + goto failed; + } + + *rvp = rv; + return 0; + + failed: + onlp_file_uds_destroy(rv); + return -1; +} + +void +onlp_file_uds_destroy(onlp_file_uds_t* p) +{ + if(p) { + if(p->running == 1) { + p->terminate = 1; + eventfd_write__(p->eventfd); + pthread_join(p->thread, NULL); + } + biglist_locked_free_all(p->list, (biglist_free_f)onlp_file_uds_service_destroy__); + aim_free(p); + } +} + +static onlp_file_uds_service_t* +find_uds_locked__(biglist_t* list, const char* path) +{ + biglist_t* ble; + onlp_file_uds_service_t* ufp; + BIGLIST_FOREACH_DATA(ble, list, onlp_file_uds_service_t*, ufp) { + if(ufp->path) { + if(!strcmp(path, ufp->path)) { + return ufp; + } + } + } + return NULL; +} + +int +onlp_file_uds_add(onlp_file_uds_t* fuds, const char* path, + onlp_file_uds_handler_t handler, void* cookie) +{ + int rv = 0; + biglist_lock(fuds->list); + if(find_uds_locked__(fuds->list->list, path)) { + AIM_LOG_ERROR("Cannot add duplicate UDS for %s", path); + rv = -1; + } + else { + onlp_file_uds_service_t* ufp; + if(onlp_file_uds_service_create__(&ufp, path, handler, cookie) >= 0) { + ufp->active = 1; + fuds->list->list = biglist_append(fuds->list->list, ufp); + } + } + biglist_unlock(fuds->list); + eventfd_write__(fuds->eventfd); + return rv; +} + +void +onlp_file_uds_remove(onlp_file_uds_t* fuds, const char* path) +{ + biglist_lock(fuds->list); + onlp_file_uds_service_t* ufp = find_uds_locked__(fuds->list->list, path); + if(ufp) { + /** Request deactivation */ + ufp->active = -1; + } + biglist_unlock(fuds->list); +}