cf_rcfile.cpp

Go to the documentation of this file.
00001 /*
00002  !concept {:name => "Lua based config files"}
00003 */
00004 
00005 #include "cf_rcfile.h"
00006 
00007 #if __FEATURE_RCFILE__
00008 
00009 #include "er_errors.h"
00010 #include "lua_cl2.h"
00011 #include "utils_cl2.h"
00012 
00013 struct _cf_RcFile {
00014   // Contains the named configuration values as global variables.
00015   lua_State *L;
00016 };
00017 
00018 #ifdef __EPOC32__
00019 #define RCFILE_BASENAME "config.txt"
00020 #else
00021 #define RCFILE_BASENAME ".cl2rc"
00022 #endif
00023 
00024 #define RCFILE_DIR CONFIG_DIR
00025 #define RCFILE_FILE (RCFILE_DIR DIR_SEP RCFILE_BASENAME)
00026 
00027 #define return_with_error(s...) { if (error) *error = gx_error_new(domain_cl2app, code_unspecified_error, s); return FALSE; }
00028 #define return_with_oom { if (error) *error = gx_error_no_memory; return FALSE; }
00029 
00030 // See also boost/preprocessor/stringize.hpp for similar definitions.
00031 #define STRINGIZE_DETAIL(x) #x
00032 #define STRINGIZE(x) STRINGIZE_DETAIL(x)
00033 
00034 #define EVALUATE(_s) { /*logt(_s);*/ if (luaL_dostring(L, _s)) throw LuaException(); }
00035 
00036 /***koog 
00037     (require codegen/cpp-include) 
00038     (display-cpp-string-lit-decl/from-file 
00039     "CF_VALIDATE_IN_LUA"
00040     "cf_validate.lua")
00041 ***/
00042 #define CF_VALIDATE_IN_LUA \
00043 "function is_non_empty_string (s)\n" \
00044    "return (s ~= '')\n" \
00045 "end\n" \
00046 "function validate (n, rt, chk)\n" \
00047    "local v = _G[n]\n" \
00048    "if v then\n" \
00049       "local t = type(v)\n" \
00050       "if t ~= 'function' and t ~= rt then\n" \
00051    "error(string.format('value %q not of required type %q', n, rt))\n" \
00052       "end\n" \
00053       "if chk and t ~= 'function' then\n" \
00054    "if not chk(v) then\n" \
00055       "error(string.format('value %q is not valid', n))\n" \
00056    "end\n" \
00057       "end\n" \
00058    "end\n" \
00059 "end\n" \
00060 "validate('compress_logs', 'boolean', nil)\n" \
00061 "validate('database_dir', 'string', is_non_empty_string)\n" \
00062 "validate('database_disk_threshold', 'number', nil)\n" \
00063 "validate('iap', 'number', nil)\n" \
00064 "validate('jid', 'string', is_non_empty_string)\n" \
00065 "validate('mcc', 'number', nil)\n" \
00066 "validate('operator_name', 'string', is_non_empty_string)\n" \
00067 "validate('remokon_host', 'string', is_non_empty_string)\n" \
00068 "validate('remokon_password', 'string', is_non_empty_string)\n" \
00069 "validate('remokon_port', 'number', nil)\n" \
00070 "validate('upload_url', 'string', is_non_empty_string)\n" \
00071 "validate('username', 'string', cl2.is_ascii_ident)\n" \
00072 "if iap == nil then\n" \
00073    "iap = IAP_DEFAULT\n" \
00074 "end\n" \
00075 "if upload_url == nil then\n" \
00076    "upload_url = UPLOAD_URL_DEFAULT\n" \
00077 "end\n" \
00078 "if remokon_host == nil then\n" \
00079    "remokon_host = REMOKON_HOST_DEFAULT\n" \
00080 "end\n" \
00081 "if jid == nil then\n" \
00082    "jid = JID_DEFAULT\n" \
00083 "end\n"
00084 /***end***/
00085 
00086 // This function validates the configuration, checking the types of
00087 // the configured values and such. Any defaults can also be set here
00088 // for values that are not present.
00089 static void ValidateAdjustConfig(lua_State *L)
00090 {
00091   // We first make any defaults available to Lua by defining them as
00092   // globals. (Defaults are typically given as Lua expressions to
00093   // begin with, but use STRINGIZE if you need to.) Then we invoke the
00094   // generated Lua code that does the validation etc.
00095   const char* s = 
00096     "IAP_DEFAULT = " __IAP_ID_EXPR__ ";\n"
00097     "UPLOAD_URL_DEFAULT = " __UPLOAD_URL__ ";\n"
00098     "REMOKON_HOST_DEFAULT = " __REMOKON_HOST__ ";\n"
00099     "JID_DEFAULT = " __REMOKON_JID__ ";\n"
00100     CF_VALIDATE_IN_LUA;
00101   EVALUATE(s);
00102 }
00103 
00104 static gboolean ReadRcFile(cf_RcFile* self, lua_State *L, GError** error)
00105 {
00106   (void)self;
00107 
00108   int errCode;
00109 
00110   if ((errCode = luaL_loadfile(L, RCFILE_FILE)) != 0) {
00111     if (errCode == LUA_ERRFILE) {
00112       // Could not open or read the file. This is okay since a
00113       // configuration file is not compulsory.
00114       logg("no (readable) configuration file '%s'", RCFILE_FILE);
00115       ValidateAdjustConfig(L);
00116       return TRUE;
00117     }
00118 
00119 #if __DO_LOGGING__
00120     // An error occurred, and hence an error message should have been pushed by Lua.
00121     if (!lua_isnone(L, -1)) {
00122       const char* s = lua_tostring(L, -1);
00123       if (s) logt(s);
00124     }
00125 #endif /* __DO_LOGGING__ */
00126 
00127     if (error) 
00128       *error = gx_error_new(domain_cl2app, code_unspecified_error, "error parsing configuration file '%s'", RCFILE_FILE);
00129     return FALSE;
00130   }
00131   logg("config file '%s' parsed OK", RCFILE_FILE);
00132 
00133   if (lua_pcall(L, 0, 1, 0)) {
00134     if (error)
00135       *error = gx_error_new(domain_cl2app, code_unspecified_error, "error evaluating configuration file '%s'", RCFILE_FILE);
00136     return FALSE;
00137   }
00138   logt("config file evaluated OK");
00139 
00140   ValidateAdjustConfig(L);
00141 
00142 #if __DO_LOGGING__
00143   EVALUATE("do\n" 
00144      "  local function f (n, fm) local v = _G[n]; if v ~= nil then if type(v) == 'function' then cl2.log(string.format('%s configured to <function>', n)) else cl2.log(string.format('%s configured to ' .. fm .. ' :: %s', n, v, type(v))); end; end; end;"
00145      "  f('username', '%q');"
00146      "  f('upload_url', '%q');"
00147      "  f('remokon_host', '%q');"
00148      "  f('iap', '%q');"
00149      "  f('database_disk_threshold', '%q');"
00150      "end");
00151 #endif /* __DO_LOGGING__ */
00152   
00153   return TRUE;
00154 }
00155 
00156 extern "C" cf_RcFile* cf_RcFile_new(GError** error) 
00157 {
00158   cf_RcFile* self = g_try_new0(cf_RcFile, 1);
00159   if (G_UNLIKELY(!self)) {
00160     if (error) *error = gx_error_no_memory;
00161     return NULL;
00162   }
00163 
00164   // Reentering the same VM instance to fetch some config info seems
00165   // to work. Not sure if it is quite safe, though, so you may want to
00166   // consider that when adding cl2 module using functions into your
00167   // config file.
00168   // 
00169   // Also note that *any* unprotected error occurring in this VM will
00170   // bring down the entire process. This is by design, as we consider
00171   // OOM errors and errors in config files very severe.
00172   self->L = cl_lua_new_libs();
00173   if (G_UNLIKELY(!self->L)) {
00174     if (error) *error = gx_error_no_memory;
00175     goto fail;
00176   }
00177 
00178   try {
00179     if (G_UNLIKELY(!ReadRcFile(self, self->L, error))) {
00180       goto fail;
00181     }
00182   } catch(const LuaException&) {
00183     lua_set_gerror(self->L, error);
00184     goto fail;
00185   }
00186 
00187   return self;
00188 
00189  fail:
00190   cf_RcFile_destroy(self);
00191   return NULL;
00192 }
00193   
00194 extern "C" void cf_RcFile_destroy(cf_RcFile* self)
00195 {
00196   if (self) {
00197     if (self->L)
00198       lua_close(self->L);
00199     g_free(self);
00200   }
00201 }
00202 
00203 class Fail {};
00204 
00205 static void get_value(lua_State *L, const char* name)
00206 {
00207   // It is important to clear the stack occasionally. Note that we do
00208   // not pop any return value to prevent it from being collected
00209   // immediately.
00210   lua_settop(L, 0);
00211 
00212   // Not documented, but returns nil on non-existent key. Or produces
00213   // an error if there is no stack space, presumably.
00214   lua_getglobal(L, name);
00215   if (lua_isnil(L, -1)) {
00216     throw Fail();
00217   }
00218   if (lua_isfunction(L, -1)) {
00219     // Should always return the requested number of values, even if
00220     // must pad with nil values.
00221     lua_call(L, 0, 1); // void
00222     if (lua_isnil(L, -1)) {
00223       throw Fail();
00224     }
00225   }
00226 }
00227 
00228 extern "C" int cf_RcFile_get_int_or(cf_RcFile* self, const char* name, int dval)
00229 {
00230   try {
00231     get_value(self->L, name);
00232   } catch(const Fail& e) {
00233     return dval;
00234   }
00235   if (!lua_isnumber(self->L, -1)) {
00236     return dval;
00237   }
00238   return lua_tointeger(self->L, -1);
00239 }
00240 
00241 extern "C" gboolean cf_RcFile_get_bool_or(cf_RcFile* self, const char* name, gboolean dval)
00242 {
00243   try {
00244     get_value(self->L, name);
00245   } catch(const Fail& e) {
00246     return dval;
00247   }
00248   if (!lua_isboolean(self->L, -1)) {
00249     return dval;
00250   }
00251   return lua_toboolean(self->L, -1);
00252 }
00253 
00254 // Note that unless you want to strdup the returned value, and if you want to keep it around for a while, then you better make sure (in your config file) that you are returning a pointer to a global Lua string.
00255 // xxx We might want to internally query and strdup settings that cannot change during runtime.
00256 extern "C" const char* cf_RcFile_get_str_maybe(cf_RcFile* self, const char* name)
00257 {
00258   try {
00259     get_value(self->L, name);
00260   } catch(const Fail& e) {
00261     return NULL;
00262   }
00263   return lua_tostring(self->L, -1); // NULL if of wrong type
00264 }
00265 
00266 extern "C" const char* cf_RcFile_get_str_or(cf_RcFile* self, const char* name, const char* dval)
00267 {
00268   const char* val = cf_RcFile_get_str_maybe(self, name);
00269   if (!val)
00270     return dval;
00271   return val;
00272 }
00273 
00274 extern "C" int cf_RcFile_vm_id(cf_RcFile* self)
00275 {
00276   return (int)self->L;
00277 }
00278 
00279 #endif /* __FEATURE_RCFILE__ */
00280 
00281 /**
00282 
00283 cf_rcfile.cpp
00284 
00285 Copyright 2009 Helsinki Institute for Information Technology (HIIT)
00286 and the authors. All rights reserved.
00287 
00288 Authors: Tero Hasu <tero.hasu@hut.fi>
00289 
00290 Permission is hereby granted, free of charge, to any person
00291 obtaining a copy of this software and associated documentation files
00292 (the "Software"), to deal in the Software without restriction,
00293 including without limitation the rights to use, copy, modify, merge,
00294 publish, distribute, sublicense, and/or sell copies of the Software,
00295 and to permit persons to whom the Software is furnished to do so,
00296 subject to the following conditions:
00297 
00298 The above copyright notice and this permission notice shall be
00299 included in all copies or substantial portions of the Software.
00300 
00301 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
00302 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
00303 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
00304 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
00305 BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
00306 ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
00307 CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
00308 SOFTWARE.
00309 
00310  **/

ContextLogger2—ContextLogger2 Logger Daemon Internals—Generated on Mon May 2 13:49:52 2011 by Doxygen 1.6.1