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>
57 #include "config_cb.hpp"
62 // ----- private things --------
64 Notification listener. This function is started in a thread and listens for
65 changes to the config file. When it sees an interesting change (write close)
66 to the file, then it will read the new set of json, parse it, and drive the
69 We must watch the directory containing the file as if the file is edited and
70 replaced it's likely saved with a different referencing inode. We'd see the
71 first change, but not any subsequent changes as the inotify is based on inodes
72 and not directory entries.
74 void xapp::Config::Listener( ) {
75 const struct inotify_event* ie; // event that popped
76 int ifd; // the inotify file des
77 int wfd; // the watched file des
79 char rbuf[4096]; // large read buffer as the event is var len
80 char* dname; // directory name
81 char* bname; // basename
84 ifd = inotify_init1( 0 ); // initialise watcher setting blocking read (no option)
86 fprintf( stderr, "<XFCPP> ### ERR ### unable to initialise file watch %s\n", strerror( errno ) );
90 dname = strdup( fname.c_str() ); // defrock the file name into dir and basename
91 if( (tok = strrchr( dname, '/' )) != NULL ) {
93 bname = strdup( tok+1 );
96 dname = strdup( "." );
97 bname = strdup( fname.c_str() );
100 wfd = inotify_add_watch( ifd, dname, IN_MOVED_TO | IN_CLOSE_WRITE ); // we only care about close write changes
104 fprintf( stderr, "<XFCPP> ### ERR ### unable to add watch on config file %s: %s\n", fname.c_str(), strerror( errno ) );
110 n = read( ifd, rbuf, sizeof( rbuf ) ); // read the event
112 if( errno == EAGAIN ) {
115 fprintf( stderr, "<XFCPP> ### CRIT ### config listener read err: %s\n", strerror( errno ) );
120 ie = (inotify_event *) rbuf;
121 if( ie->len > 0 && strcmp( bname, ie->name ) == 0 ) {
123 auto njh = jparse( fname ); // reparse the file
126 if( njh != NULL && cb != NULL ) { // good parse, save and drive user callback
128 cb->Drive_cb( *this, user_cb_data );
136 Read a file containing json and parse into a framework Jhash.
138 Using C i/o will speed this up, but I can't imagine that we need
139 speed reading the config file once in a while.
140 The file read comes from a stack overflow example:
141 stackoverflow.com/questions/2912520/read-file-contents-into-a-string-in-c
143 std::shared_ptr<xapp::Jhash> xapp::Config::jparse( std::string ufname ) {
146 std::ifstream ifs( fname );
147 if( ! ifs.is_open() ) {
148 fprintf( stderr, "<XFCPP> ### WARN ### unable to open %s; %s\n", fname.c_str(), strerror( errno ) );
151 std::string st( (std::istreambuf_iterator<char>( ifs ) ), (std::istreambuf_iterator<char>() ) );
153 auto new_jh = std::shared_ptr<xapp::Jhash>( new xapp::Jhash( st.c_str() ) );
154 return new_jh->Parse_errors() ? NULL : new_jh;
158 Read the configuration file from what we find as the filename in the environment (assumed
159 to be referenced by $XAPP_DESCRIPTOR_PATH/config-file.json. If the env var is not
160 defined we assume ./. The data is then parsed with the assumption that it's json.
162 The actual meaning of the environment variable is confusing. The name is "path" which
163 should mean that this is the directory in which the config file lives, but the examples
164 elsewhere suggest that this is a filename (either fully qualified or relative). To prevent
165 errors, we use some intelligence to determine if it's a directory name or not if it comes to it.
167 std::shared_ptr<xapp::Jhash> xapp::Config::jparse( ) {
169 std::string filename;
172 data = getenv( (const char *) "XAPP_DESCRIPTOR_PATH" );
175 if( stat( data, &sb ) == 0 ) {
176 if( S_ISDIR( sb.st_mode ) ) {
177 filename.append( "/config-file.json" );
180 fprintf( stderr, "<XFCPP> ### ERR ### unable to stat env XAPP_DESCRIPTOR_PATH: %s\n", strerror( errno ) );
184 filename = "./config-file.json";
187 return jparse( filename );
190 // --------------------- construction, destruction -------------------------------------------
193 By default expect to find XAPP_DESCRIPTOR_PATH in the environment and assume that it
194 is the directory name where we can find config-file.json. The build process will
195 read and parse the json allowing the user xAPP to invoke the supplied "obvious"
196 functions to retrieve data. If there is something in an xAPP's config that isn't
197 standard, it can get the raw Jhash object and go at it directly. The idea is that
198 the common things should be fairly painless to extract from the json goop.
200 xapp::Config::Config() :
205 Similar, except that it allows the xAPP to supply the filename (testing?)
207 xapp::Config::Config( const std::string& fname) :
208 jh( jparse( fname ) )
213 Read and return the raw file blob as a single string. User can parse, or do
214 whatever they need (allows non-json things if necessary).
216 std::string xapp::Config::Get_contents( ) const {
219 if( ! fname.empty() ) {
220 std::ifstream ifs( fname );
221 std::string st( (std::istreambuf_iterator<char>( ifs ) ), (std::istreambuf_iterator<char>() ) );
228 // ----- convience function for things we think an xAPP will likely need to pull from the config
232 Suss out the port for the named "interface". The interface is likely the application
235 std::string xapp::Config::Get_port( const std::string& name ) const {
239 std::string rv = ""; // result value
240 std::string pname; // element port name in the json
247 if( jh->Set_blob( (const char *) "messaging" ) ) {
248 nele = jh->Array_len( (const char *) "ports" );
249 for( i = 0; i < nele; i++ ) {
250 if( jh->Set_blob_ele( (const char *) "ports", i ) ) {
251 pname = jh->String( (const char *) "name" );
252 if( pname.compare( name ) == 0 ) { // this element matches the name passed in
253 value = jh->Value( (const char *) "port" );
254 rv = std::to_string( (int) value );
255 jh->Unset_blob( ); // leave hash in a known state
260 jh->Unset_blob( ); // Jhash requires bump to root, and array reselct to move to next ele
261 jh->Set_blob( (const char *) "messaging" );
270 Suss out the named string from the controls object. If the resulting value is
271 missing or "", then the default is returned.
273 std::string xapp::Config::Get_control_str( const std::string& name, const std::string& defval ) const {
275 std::string rv; // result value
283 if( jh->Set_blob( (const char *) "controls" ) && jh->Exists( name.c_str() ) ) {
284 value = jh->String( name.c_str() );
285 if( value.compare( "" ) != 0 ) {
295 Convenience funciton without default. "" returned if not found.
296 No default value; returns "" if not set.
298 std::string xapp::Config::Get_control_str( const std::string& name ) const {
299 return Get_control_str( name, "" );
303 Suss out the named field from the controls object with the assumption that it is a boolean.
304 If the resulting value is missing then the defval is used.
306 bool xapp::Config::Get_control_bool( const std::string& name, bool defval ) const {
307 bool rv; // result value
315 if( jh->Set_blob( (const char *) "controls" ) && jh->Exists( name.c_str() ) ) {
316 rv = jh->Bool( name.c_str() );
325 Convenience function without default.
327 bool xapp::Config::Get_control_bool( const std::string& name ) const {
328 return Get_control_bool( name, false );
333 Suss out the named field from the controls object with the assumption that it is a value (float/int).
334 If the resulting value is missing then the defval is used.
336 double xapp::Config::Get_control_value( const std::string& name, double defval ) const {
338 auto rv = defval; // return value; set to default
344 if( jh->Set_blob( (const char *) "controls" ) && jh->Exists( name.c_str() ) ) {
345 rv = jh->Value( name.c_str() );
354 Convenience function. If value is undefined, then 0 is returned.
356 double xapp::Config::Get_control_value( const std::string& name ) const {
357 return Get_control_value( name, 0.0 );
361 // ---- notification support ---------------------------------------------------------------
365 Accept the user's notification function, and data that it needs (pointer to
366 something unknown), and stash that as a callback.
368 The fact that the user xAPP registers a callback also triggers the creation
369 of a thread to listen for changes on the config file.
371 void xapp::Config::Set_callback( notify_callback usr_func, void* usr_data ) {
372 cb = std::unique_ptr<Config_cb>( new Config_cb( usr_func, usr_data ) );
373 user_cb_data = usr_data;
375 if( listener == NULL ) { // start thread if needed
376 listener = new std::thread( &xapp::Config::Listener, this );