3 ==================================================================================
4 Copyright (c) 2020 AT&T Intellectual Property.
5 Copyright (c) 2020 Nokia
7 Licensed under the Apache License, Version 2.0 (the "License");
8 you may not use this file except in compliance with the License.
9 You may obtain a copy of the License at
11 http://www.apache.org/licenses/LICENSE-2.0
13 Unless required by applicable law or agreed to in writing, software
14 distributed under the License is distributed on an "AS IS" BASIS,
15 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 See the License for the specific language governing permissions and
17 limitations under the License.
18 ==================================================================================
23 Abstract: Support for reading config json.
25 The config structure allows simplified parsing of the json and
26 easy access to the things we belive all xAPPs will use (port
27 digging form the named "interface" and control settings). This
28 also supports the watching on the file and driving the user
29 callback when the config file appears to have changed (write
32 Accessing information from the json is serialised (mutex) as
33 with the random nature of file updates, we must ensure that
34 we don't change the json as we're trying to read from it. Locking
35 should be transparent to the user xAPP.
38 Author: E. Scott Daniels
44 #include <sys/inotify.h>
56 #include "config_cb.hpp"
61 // ----- private things --------
63 Notification listener. This function is started in a thread and listens for
64 changes to the config file. When it sees an interesting change (write close)
65 to the file, then it will read the new set of json, parse it, and drive the
68 We must watch the directory containing the file as if the file is edited and
69 replaced it's likely saved with a different referencing inode. We'd see the
70 first change, but not any subsequent changes as the inotify is based on inodes
71 and not directory entries.
73 void xapp::Config::Listener( ) {
74 const struct inotify_event* ie; // event that popped
75 int ifd; // the inotify file des
76 int wfd; // the watched file des
78 char rbuf[4096]; // large read buffer as the event is var len
79 char* dname; // directory name
80 char* bname; // basename
83 ifd = inotify_init1( 0 ); // initialise watcher setting blocking read (no option)
85 fprintf( stderr, "<XFCPP> ### ERR ### unable to initialise file watch %s\n", strerror( errno ) );
89 dname = strdup( fname.c_str() ); // defrock the file name into dir and basename
90 if( (tok = strrchr( dname, '/' )) != NULL ) {
92 bname = strdup( tok+1 );
95 dname = strdup( "." );
96 bname = strdup( fname.c_str() );
99 wfd = inotify_add_watch( ifd, dname, IN_MOVED_TO | IN_CLOSE_WRITE ); // we only care about close write changes
103 fprintf( stderr, "<XFCPP> ### ERR ### unable to add watch on config file %s: %s\n", fname.c_str(), strerror( errno ) );
109 n = read( ifd, rbuf, sizeof( rbuf ) ); // read the event
111 if( errno == EAGAIN ) {
114 fprintf( stderr, "<XFCPP ### CRIT ### config listener read err: %s\n", strerror( errno ) );
119 ie = (inotify_event *) rbuf;
120 if( ie->len > 0 && strcmp( bname, ie->name ) == 0 ) {
122 auto njh = jparse( fname ); // reparse the file
125 if( njh != NULL && cb != NULL ) { // good parse, save and drive user callback
127 cb->Drive_cb( *this, user_cb_data );
135 Read a file containing json and parse into a framework Jhash.
137 Using C i/o will speed this up, but I can't imagine that we need
138 speed reading the config file once in a while.
139 The file read comes from a stack overflow example:
140 stackoverflow.com/questions/2912520/read-file-contents-into-a-string-in-c
142 std::shared_ptr<xapp::Jhash> xapp::Config::jparse( std::string ufname ) {
145 std::ifstream ifs( fname );
146 std::string st( (std::istreambuf_iterator<char>( ifs ) ), (std::istreambuf_iterator<char>() ) );
148 auto new_jh = std::shared_ptr<xapp::Jhash>( new xapp::Jhash( st.c_str() ) );
149 return new_jh->Parse_errors() ? NULL : new_jh;
153 Read the configuration file from what we find as the filename in the environment (assumed
154 to be referenced by $XAPP_DESCRIPTOR_PATH/config-file.json. If the env var is not
155 defined we assume ./. The data is then parsed with the assumption that it's json.
157 The actual meaning of the environment variable is confusing. The name is "path" which
158 should mean that this is the directory in which the config file lives, but the examples
159 elsewhere suggest that this is a filename (either fully qualified or relative). For now
160 we will assume that it's a file name, though we could add some intelligence to determine
161 if it's a directory name or not if it comes to it.
163 std::shared_ptr<xapp::Jhash> xapp::Config::jparse( ) {
166 if( (data = getenv( (const char *) "XAPP_DESCRIPTOR_PATH" )) == NULL ) {
167 data = (const char *) "./config-file.json";
170 return jparse( std::string( data ) );
173 // --------------------- construction, destruction -------------------------------------------
176 By default expect to find XAPP_DESCRIPTOR_PATH in the environment and assume that it
177 is the directory name where we can find config-file.json. The build process will
178 read and parse the json allowing the user xAPP to invoke the supplied "obvious"
179 functions to retrieve data. If there is something in an xAPP's config that isn't
180 standard, it can get the raw Jhash object and go at it directly. The idea is that
181 the common things should be fairly painless to extract from the json goop.
183 xapp::Config::Config() :
188 Similar, except that it allows the xAPP to supply the filename (testing?)
190 xapp::Config::Config( const std::string& fname) :
191 jh( jparse( fname ) )
196 Read and return the raw file blob as a single string. User can parse, or do
197 whatever they need (allows non-json things if necessary).
199 std::string xapp::Config::Get_contents( ) const {
202 if( ! fname.empty() ) {
203 std::ifstream ifs( fname );
204 std::string st( (std::istreambuf_iterator<char>( ifs ) ), (std::istreambuf_iterator<char>() ) );
211 // ----- convience function for things we think an xAPP will likely need to pull from the config
215 Suss out the port for the named "interface". The interface is likely the application
218 std::string xapp::Config::Get_port( const std::string& name ) const {
222 std::string rv = ""; // result value
223 std::string pname; // element port name in the json
230 if( jh->Set_blob( (const char *) "messaging" ) ) {
231 nele = jh->Array_len( (const char *) "ports" );
232 for( i = 0; i < nele; i++ ) {
233 if( jh->Set_blob_ele( (const char *) "ports", i ) ) {
234 pname = jh->String( (const char *) "name" );
235 if( pname.compare( name ) == 0 ) { // this element matches the name passed in
236 value = jh->Value( (const char *) "port" );
237 rv = std::to_string( (int) value );
238 jh->Unset_blob( ); // leave hash in a known state
243 jh->Unset_blob( ); // Jhash requires bump to root, and array reselct to move to next ele
244 jh->Set_blob( (const char *) "messaging" );
253 Suss out the named string from the controls object. If the resulting value is
254 missing or "", then the default is returned.
256 std::string xapp::Config::Get_control_str( const std::string& name, const std::string& defval ) const {
258 std::string rv; // result value
266 if( jh->Set_blob( (const char *) "controls" ) && jh->Exists( name.c_str() ) ) {
267 value = jh->String( name.c_str() );
268 if( value.compare( "" ) != 0 ) {
278 Convenience funciton without default. "" returned if not found.
279 No default value; returns "" if not set.
281 std::string xapp::Config::Get_control_str( const std::string& name ) const {
282 return Get_control_str( name, "" );
286 Suss out the named field from the controls object with the assumption that it is a boolean.
287 If the resulting value is missing then the defval is used.
289 bool xapp::Config::Get_control_bool( const std::string& name, bool defval ) const {
290 bool rv; // result value
298 if( jh->Set_blob( (const char *) "controls" ) && jh->Exists( name.c_str() ) ) {
299 rv = jh->Bool( name.c_str() );
308 Convenience function without default.
310 bool xapp::Config::Get_control_bool( const std::string& name ) const {
311 return Get_control_bool( name, false );
316 Suss out the named field from the controls object with the assumption that it is a value (float/int).
317 If the resulting value is missing then the defval is used.
319 double xapp::Config::Get_control_value( const std::string& name, double defval ) const {
321 auto rv = defval; // return value; set to default
327 if( jh->Set_blob( (const char *) "controls" ) && jh->Exists( name.c_str() ) ) {
328 rv = jh->Value( name.c_str() );
337 Convenience function. If value is undefined, then 0 is returned.
339 double xapp::Config::Get_control_value( const std::string& name ) const {
340 return Get_control_value( name, 0.0 );
344 // ---- notification support ---------------------------------------------------------------
348 Accept the user's notification function, and data that it needs (pointer to
349 something unknown), and stash that as a callback.
351 The fact that the user xAPP registers a callback also triggers the creation
352 of a thread to listen for changes on the config file.
354 void xapp::Config::Set_callback( notify_callback usr_func, void* usr_data ) {
355 cb = std::unique_ptr<Config_cb>( new Config_cb( usr_func, usr_data ) );
356 user_cb_data = usr_data;
358 if( listener == NULL ) { // start thread if needed
359 listener = new std::thread( &xapp::Config::Listener, this );