blob: 5259a17025075f7f5c3f16a566040a687cfbaa2d [file] [log] [blame]
E. Scott Danielsd486a172020-07-29 12:39:54 -04001// vi: ts=4 sw=4 noet:
2/*
3==================================================================================
4 Copyright (c) 2020 AT&T Intellectual Property.
5 Copyright (c) 2020 Nokia
6
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
10
11 http://www.apache.org/licenses/LICENSE-2.0
12
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==================================================================================
19*/
20
21/*
22 Mnemonic: config.cpp
23 Abstract: Support for reading config json.
24
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
30 close on the file).
31
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.
36
37 Date: 27 July 2020
38 Author: E. Scott Daniels
39*/
40
41#include <errno.h>
42#include <poll.h>
43#include <stdlib.h>
44#include <sys/inotify.h>
45#include <unistd.h>
46#include <string.h>
47
48#include <iostream>
49#include <sstream>
50#include <fstream>
51#include <thread>
52#include <memory>
53
54#include "jhash.hpp"
55#include "config.hpp"
56#include "config_cb.hpp"
57
58namespace xapp {
59
60
61// ----- private things --------
62/*
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
66 user callback.
67
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.
72*/
73void xapp::Config::Listener( ) {
E. Scott Danielsc85ac8b2020-08-19 09:51:33 -040074 const struct inotify_event* ie; // event that popped
E. Scott Danielsd486a172020-07-29 12:39:54 -040075 int ifd; // the inotify file des
76 int wfd; // the watched file des
E. Scott Danielsc85ac8b2020-08-19 09:51:33 -040077 ssize_t n;
E. Scott Danielsd486a172020-07-29 12:39:54 -040078 char rbuf[4096]; // large read buffer as the event is var len
79 char* dname; // directory name
80 char* bname; // basename
81 char* tok;
82
83 ifd = inotify_init1( 0 ); // initialise watcher setting blocking read (no option)
84 if( ifd < 0 ) {
85 fprintf( stderr, "<XFCPP> ### ERR ### unable to initialise file watch %s\n", strerror( errno ) );
86 return;
87 }
88
89 dname = strdup( fname.c_str() ); // defrock the file name into dir and basename
90 if( (tok = strrchr( dname, '/' )) != NULL ) {
91 *tok = 0;
92 bname = strdup( tok+1 );
93 } else {
czichy9fa77c92021-12-03 07:24:54 +020094 free (dname);
E. Scott Danielsd486a172020-07-29 12:39:54 -040095 dname = strdup( "." );
96 bname = strdup( fname.c_str() );
97 }
98
E. Scott Danielsc85ac8b2020-08-19 09:51:33 -040099 wfd = inotify_add_watch( ifd, dname, IN_MOVED_TO | IN_CLOSE_WRITE ); // we only care about close write changes
czichy9fa77c92021-12-03 07:24:54 +0200100 free (dname);
E. Scott Danielsd486a172020-07-29 12:39:54 -0400101
102 if( wfd < 0 ) {
103 fprintf( stderr, "<XFCPP> ### ERR ### unable to add watch on config file %s: %s\n", fname.c_str(), strerror( errno ) );
E. Scott Danielsc85ac8b2020-08-19 09:51:33 -0400104 free( bname );
E. Scott Danielsd486a172020-07-29 12:39:54 -0400105 return;
106 }
107
108 while( true ) {
109 n = read( ifd, rbuf, sizeof( rbuf ) ); // read the event
110 if( n < 0 ) {
111 if( errno == EAGAIN ) {
112 continue;
113 } else {
114 fprintf( stderr, "<XFCPP ### CRIT ### config listener read err: %s\n", strerror( errno ) );
115 return;
116 }
117 }
118
119 ie = (inotify_event *) rbuf;
120 if( ie->len > 0 && strcmp( bname, ie->name ) == 0 ) {
E. Scott Danielsc85ac8b2020-08-19 09:51:33 -0400121 // future: lock
E. Scott Danielsd486a172020-07-29 12:39:54 -0400122 auto njh = jparse( fname ); // reparse the file
E. Scott Danielsc85ac8b2020-08-19 09:51:33 -0400123 // future: unlock
E. Scott Danielsd486a172020-07-29 12:39:54 -0400124
125 if( njh != NULL && cb != NULL ) { // good parse, save and drive user callback
126 jh = njh;
127 cb->Drive_cb( *this, user_cb_data );
128 }
129 }
130 }
E. Scott Danielsd486a172020-07-29 12:39:54 -0400131}
132
133
134/*
135 Read a file containing json and parse into a framework Jhash.
136
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
141*/
142std::shared_ptr<xapp::Jhash> xapp::Config::jparse( std::string ufname ) {
143 fname = ufname;
144
145 std::ifstream ifs( fname );
146 std::string st( (std::istreambuf_iterator<char>( ifs ) ), (std::istreambuf_iterator<char>() ) );
147
148 auto new_jh = std::shared_ptr<xapp::Jhash>( new xapp::Jhash( st.c_str() ) );
149 return new_jh->Parse_errors() ? NULL : new_jh;
150}
151
152/*
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.
156
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.
162*/
163std::shared_ptr<xapp::Jhash> xapp::Config::jparse( ) {
E. Scott Danielsc85ac8b2020-08-19 09:51:33 -0400164 const char* data;
E. Scott Danielsd486a172020-07-29 12:39:54 -0400165
E. Scott Danielsc85ac8b2020-08-19 09:51:33 -0400166 if( (data = getenv( (const char *) "XAPP_DESCRIPTOR_PATH" )) == NULL ) {
167 data = (const char *) "./config-file.json";
E. Scott Danielsd486a172020-07-29 12:39:54 -0400168 }
169
170 return jparse( std::string( data ) );
171}
172
173// --------------------- construction, destruction -------------------------------------------
174
175/*
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.
182*/
183xapp::Config::Config() :
E. Scott Danielsc85ac8b2020-08-19 09:51:33 -0400184 jh( jparse() )
E. Scott Danielsd486a172020-07-29 12:39:54 -0400185{ /* empty body */ }
186
187/*
188 Similar, except that it allows the xAPP to supply the filename (testing?)
189*/
E. Scott Danielsc85ac8b2020-08-19 09:51:33 -0400190xapp::Config::Config( const std::string& fname) :
191 jh( jparse( fname ) )
E. Scott Danielsd486a172020-07-29 12:39:54 -0400192{ /* empty body */ }
193
194
195/*
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).
198*/
E. Scott Danielsc85ac8b2020-08-19 09:51:33 -0400199std::string xapp::Config::Get_contents( ) const {
E. Scott Danielsd486a172020-07-29 12:39:54 -0400200 std::string rv = "";
201
202 if( ! fname.empty() ) {
203 std::ifstream ifs( fname );
204 std::string st( (std::istreambuf_iterator<char>( ifs ) ), (std::istreambuf_iterator<char>() ) );
205 rv = st;
206 }
207
208 return rv;
209}
210
211// ----- convience function for things we think an xAPP will likely need to pull from the config
212
213
214/*
215 Suss out the port for the named "interface". The interface is likely the application
216 name.
217*/
E. Scott Danielsc85ac8b2020-08-19 09:51:33 -0400218std::string xapp::Config::Get_port( const std::string& name ) const {
E. Scott Danielsd486a172020-07-29 12:39:54 -0400219 int i;
220 int nele = 0;
221 double value;
222 std::string rv = ""; // result value
223 std::string pname; // element port name in the json
224
225 if( jh == NULL ) {
226 return rv;
227 }
228
229 jh->Unset_blob();
E. Scott Danielsc85ac8b2020-08-19 09:51:33 -0400230 if( jh->Set_blob( (const char *) "messaging" ) ) {
231 nele = jh->Array_len( (const char *) "ports" );
E. Scott Danielsd486a172020-07-29 12:39:54 -0400232 for( i = 0; i < nele; i++ ) {
E. Scott Danielsc85ac8b2020-08-19 09:51:33 -0400233 if( jh->Set_blob_ele( (const char *) "ports", i ) ) {
234 pname = jh->String( (const char *) "name" );
E. Scott Danielsd486a172020-07-29 12:39:54 -0400235 if( pname.compare( name ) == 0 ) { // this element matches the name passed in
E. Scott Danielsc85ac8b2020-08-19 09:51:33 -0400236 value = jh->Value( (const char *) "port" );
E. Scott Danielsd486a172020-07-29 12:39:54 -0400237 rv = std::to_string( (int) value );
238 jh->Unset_blob( ); // leave hash in a known state
239 return rv;
240 }
241 }
242
243 jh->Unset_blob( ); // Jhash requires bump to root, and array reselct to move to next ele
E. Scott Danielsc85ac8b2020-08-19 09:51:33 -0400244 jh->Set_blob( (const char *) "messaging" );
E. Scott Danielsd486a172020-07-29 12:39:54 -0400245 }
246 }
247
248 jh->Unset_blob();
249 return rv;
250}
251
252/*
253 Suss out the named string from the controls object. If the resulting value is
254 missing or "", then the default is returned.
255*/
E. Scott Danielsc85ac8b2020-08-19 09:51:33 -0400256std::string xapp::Config::Get_control_str( const std::string& name, const std::string& defval ) const {
E. Scott Danielsd486a172020-07-29 12:39:54 -0400257 std::string value;
258 std::string rv; // result value
259
260 rv = defval;
261 if( jh == NULL ) {
262 return rv;
263 }
264
265 jh->Unset_blob();
E. Scott Daniels23d0e612020-09-17 15:46:48 -0400266 if( jh->Set_blob( (const char *) "controls" ) && jh->Exists( name.c_str() ) ) {
267 value = jh->String( name.c_str() );
268 if( value.compare( "" ) != 0 ) {
269 rv = value;
E. Scott Danielsd486a172020-07-29 12:39:54 -0400270 }
271 }
272
273 jh->Unset_blob();
274 return rv;
275}
276
277/*
278 Convenience funciton without default. "" returned if not found.
279 No default value; returns "" if not set.
280*/
E. Scott Danielsc85ac8b2020-08-19 09:51:33 -0400281std::string xapp::Config::Get_control_str( const std::string& name ) const {
E. Scott Danielsd486a172020-07-29 12:39:54 -0400282 return Get_control_str( name, "" );
283}
284
285/*
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.
288*/
E. Scott Danielsc85ac8b2020-08-19 09:51:33 -0400289bool xapp::Config::Get_control_bool( const std::string& name, bool defval ) const {
E. Scott Danielsd486a172020-07-29 12:39:54 -0400290 bool rv; // result value
291
292 rv = defval;
293 if( jh == NULL ) {
294 return rv;
295 }
296
297 jh->Unset_blob();
E. Scott Danielsc85ac8b2020-08-19 09:51:33 -0400298 if( jh->Set_blob( (const char *) "controls" ) && jh->Exists( name.c_str() ) ) {
299 rv = jh->Bool( name.c_str() );
E. Scott Danielsd486a172020-07-29 12:39:54 -0400300 }
301
302 jh->Unset_blob();
303 return rv;
304}
305
306
307/*
308 Convenience function without default.
309*/
E. Scott Danielsc85ac8b2020-08-19 09:51:33 -0400310bool xapp::Config::Get_control_bool( const std::string& name ) const {
E. Scott Danielsd486a172020-07-29 12:39:54 -0400311 return Get_control_bool( name, false );
312}
313
314
315/*
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.
318*/
E. Scott Danielsc85ac8b2020-08-19 09:51:33 -0400319double xapp::Config::Get_control_value( const std::string& name, double defval ) const {
E. Scott Danielsd486a172020-07-29 12:39:54 -0400320
321 auto rv = defval; // return value; set to default
322 if( jh == NULL ) {
323 return rv;
324 }
325
326 jh->Unset_blob();
E. Scott Danielsc85ac8b2020-08-19 09:51:33 -0400327 if( jh->Set_blob( (const char *) "controls" ) && jh->Exists( name.c_str() ) ) {
328 rv = jh->Value( name.c_str() );
E. Scott Danielsd486a172020-07-29 12:39:54 -0400329 }
330
331 jh->Unset_blob();
332 return rv;
333}
334
335
336/*
337 Convenience function. If value is undefined, then 0 is returned.
338*/
E. Scott Danielsc85ac8b2020-08-19 09:51:33 -0400339double xapp::Config::Get_control_value( const std::string& name ) const {
E. Scott Danielsd486a172020-07-29 12:39:54 -0400340 return Get_control_value( name, 0.0 );
341}
342
343
344// ---- notification support ---------------------------------------------------------------
345
346
347/*
348 Accept the user's notification function, and data that it needs (pointer to
349 something unknown), and stash that as a callback.
350
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.
353*/
354void 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;
357
358 if( listener == NULL ) { // start thread if needed
359 listener = new std::thread( &xapp::Config::Listener, this );
360 }
361}
362
363} // namespace