//OpenSCADA system file: tmess.cpp /*************************************************************************** * Copyright (C) 2003-2018 by Roman Savochenko, * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; version 2 of the License. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include #include #include #include #include #include "tsys.h" #include "resalloc.h" #include "tmess.h" #ifdef HAVE_LANGINFO_H # include #endif #ifdef HAVE_ICONV_H #include #endif #ifdef HAVE_LIBINTL_H #include #endif using namespace OSCADA; //************************************************* //* TMess * //************************************************* TMess::TMess( ) : IOCharSet("UTF-8"), mMessLevel(Info), mLogDir(DIR_STDOUT|DIR_ARCHIVE), mConvCode(true), mIsUTF8(true), mRes(true), mLang2CodeBase(mRes), mLang2Code(mRes) { openlog(PACKAGE, 0, LOG_USER); setenv("LC_NUMERIC", "C", 1); setlocale(LC_ALL, ""); #ifdef HAVE_LANGINFO_H IOCharSet = nl_langinfo(CODESET); #endif #ifdef HAVE_LIBINTL_H bindtextdomain(PACKAGE,LOCALEDIR); textdomain(PACKAGE); #endif string tLng = lang(); mLang2Code = tLng; if(mLang2Code.size() < 2 || mLang2Code.getVal() == "POSIX" || mLang2Code.getVal() == "C") mLang2Code = "en"; else mLang2Code = mLang2Code.getVal().substr(0,2); mIsUTF8 = (IOCharSet == "UTF-8" || IOCharSet == "UTF8" || IOCharSet == "utf8"); if(tLng == "C" || (mLang2Code.getVal() == "en" && (IOCharSet == "ISO-8859-1" || IOCharSet == "ANSI_X3.4-1968" || IOCharSet == "ASCII" || IOCharSet == "US-ASCII"))) mConvCode = false; } TMess::~TMess( ) { closelog(); } void TMess::setMessLevel( int level ) { mMessLevel = vmax(Debug, vmin(Crit,level)); SYS->modif(); } void TMess::setLogDirect( int dir ) { mLogDir = dir; SYS->modif(); } void TMess::put( const char *categ, int8_t level, const char *fmt, ... ) { char mess[STR_BUF_LEN]; va_list argptr; va_start(argptr,fmt); vsnprintf(mess,sizeof(mess),fmt,argptr); va_end(argptr); level = vmin(Emerg, vmax(-Emerg,level)); if(abs(level) < messLevel()) return; int64_t ctm = TSYS::curTime(); //string sMess = i2s(level) + "|" + categ + " | " + mess; string sMess = i2s(level) + "[" + categ + "] " + mess; if(mLogDir&DIR_SYSLOG) { int level_sys; switch((int8_t)abs(level)) { case Debug: level_sys = LOG_DEBUG; break; case Info: level_sys = LOG_INFO; break; case Notice: level_sys = LOG_NOTICE; break; case Warning: level_sys = LOG_WARNING;break; case Error: level_sys = LOG_ERR; break; case Crit: level_sys = LOG_CRIT; break; case Alert: level_sys = LOG_ALERT; break; case Emerg: level_sys = LOG_EMERG; break; default: level_sys = LOG_DEBUG; } syslog(level_sys, "%s", sMess.c_str()); } if(mLogDir&(DIR_STDOUT|DIR_STDERR) && SYS->cmdOpt("consoleCharSet").size()) sMess = Mess->codeConvOut(SYS->cmdOpt("consoleCharSet"), sMess); if(mLogDir&DIR_STDOUT) fprintf(stdout, "%s %s\n", atm2s(time(NULL),"%Y-%m-%dT%H:%M:%S").c_str(), sMess.c_str()); if(mLogDir&DIR_STDERR) fprintf(stderr, "%s %s\n", atm2s(time(NULL),"%Y-%m-%dT%H:%M:%S").c_str(), sMess.c_str()); if((mLogDir&DIR_ARCHIVE) && SYS->present("Archive")) SYS->archive().at().messPut(ctm/1000000, ctm%1000000, categ, level, mess); } void TMess::get( time_t b_tm, time_t e_tm, vector &recs, const string &category, int8_t level ) { if(mLogDir & DIR_ARCHIVE) SYS->archive().at().messGet(b_tm, e_tm, recs, category, level); } string TMess::lang( ) { char *lng = NULL; if( ((lng=getenv("LANGUAGE")) && strlen(lng)) || ((lng=getenv("LC_MESSAGES")) && strlen(lng)) || ((lng=getenv("LANG")) && strlen(lng)) ) return lng; else return "C"; } void TMess::setLang( const string &lng, bool init ) { char *prvLng = NULL; if((prvLng=getenv("LANGUAGE")) && strlen(prvLng)) setenv("LANGUAGE", lng.c_str(), 1); //else setenv("LC_MESSAGES", lng.c_str(), 1); else setenv("LANG", lng.c_str(), 1); //!!!! May be use further for the miss environment force set setlocale(LC_ALL, ""); #ifdef HAVE_LANGINFO_H IOCharSet = nl_langinfo(CODESET); #endif string tLng = lang(); mLang2Code = tLng; if(mLang2Code.size() < 2 || mLang2Code.getVal() == "POSIX" || mLang2Code.getVal() == "C") mLang2Code = "en"; else mLang2Code = mLang2Code.getVal().substr(0, 2); mIsUTF8 = (IOCharSet == "UTF-8" || IOCharSet == "UTF8" || IOCharSet == "utf8"); mConvCode = !(tLng == "C" || (mLang2Code.getVal() == "en" && (IOCharSet == "ISO-8859-1" || IOCharSet == "ANSI_X3.4-1968" || IOCharSet == "ASCII" || IOCharSet == "US-ASCII"))); if(init) SYS->sysModifFlgs &= ~TSYS::MDF_LANG; else { SYS->sysModifFlgs |= TSYS::MDF_LANG; SYS->modif(); } } string TMess::codeConv( const string &fromCH, const string &toCH, const string &mess ) { if(!mConvCode || fromCH == toCH) return mess; #ifdef HAVE_ICONV_H //Make convert to blocks 1000 bytes string buf; buf.reserve(mess.size()); # if defined(__UCLIBC__) const char *ibuf; # else char *ibuf; # endif char outbuf[1000], *obuf; size_t ilen, olen, chwrcnt = 0; iconv_t hd; hd = iconv_open(toCH.c_str(), fromCH.c_str()); if(hd == (iconv_t)(-1)) { //mess_crit("IConv", _("Error opening 'iconv': %s"), strerror(errno)); //But there can be a recursion for a wrong charset. return mess; } ibuf = (char *)mess.data(); ilen = mess.size(); while(ilen) { obuf = outbuf; olen = sizeof(outbuf)-1; size_t rez = iconv(hd, &ibuf, &ilen, &obuf, &olen); if(rez == (size_t)(-1) && (errno == EINVAL || errno == EBADF)) { //mess_crit("IConv", _("Error converting input sequence: %s"), strerror(errno)); //But there can be a recursion for a wrong charset. buf = mess; break; } if(obuf > outbuf) buf.append(outbuf, obuf-outbuf); if(rez == (size_t)(-1) && errno == EILSEQ) { buf += '?'; ilen--; ibuf++; chwrcnt++; } } iconv_close(hd); //> Deadlock possible on the error message print //if(chwrcnt) mess_err("IConv", _("Error converting %d symbols from '%s' to '%s' for the message part: '%s'(%d)"), // chwrcnt, fromCH.c_str(), toCH.c_str(), mess.substr(0,20).c_str(), mess.size()); return buf; #else return mess; #endif } const char *TMess::I18N( const char *mess, const char *d_name ) { #ifdef HAVE_LIBINTL_H return dgettext(d_name, mess); #else return mess; #endif } void TMess::setLang2CodeBase( const string &vl ) { mLang2CodeBase = vl; if((!mLang2CodeBase.empty() && mLang2CodeBase.size() < 2) || mLang2CodeBase.getVal() == "POSIX" || mLang2CodeBase.getVal() == "C") mLang2CodeBase = "en"; else mLang2CodeBase = mLang2CodeBase.getVal().substr(0,2); SYS->modif(); } void TMess::load( ) { //Load params from command line string argVl; if((argVl=SYS->cmdOpt("lang")).size()) setLang(argVl, true); if((argVl=SYS->cmdOpt("messLev")).size()) { int i = s2i(argVl); if(i >= Debug && i <= Emerg) setMessLevel(i); } if((argVl=SYS->cmdOpt("log")).size()) setLogDirect(s2i(argVl)); //Load params config-file setMessLevel(s2i(TBDS::genDBGet(SYS->nodePath()+"MessLev",i2s(messLevel()),"root",TBDS::OnlyCfg))); setLogDirect(s2i(TBDS::genDBGet(SYS->nodePath()+"LogTarget",i2s(logDirect()),"root",TBDS::OnlyCfg))); setLang(TBDS::genDBGet(SYS->nodePath()+"Lang",lang(),"root",TBDS::OnlyCfg), true); mLang2CodeBase = TBDS::genDBGet(SYS->nodePath()+"Lang2CodeBase",mLang2CodeBase,"root",TBDS::OnlyCfg); } void TMess::save( ) { TBDS::genDBSet(SYS->nodePath()+"MessLev",i2s(messLevel()),"root",TBDS::OnlyCfg); TBDS::genDBSet(SYS->nodePath()+"LogTarget",i2s(logDirect()),"root",TBDS::OnlyCfg); if(SYS->sysModifFlgs&TSYS::MDF_LANG) TBDS::genDBSet(SYS->nodePath()+"Lang",lang(),"root",TBDS::OnlyCfg); TBDS::genDBSet(SYS->nodePath()+"Lang2CodeBase",mLang2CodeBase,"root",TBDS::OnlyCfg); } const char *TMess::labDB( ) { return _("DB address in format \"{DB module}.{DB name}\".\n" "Set '*.*' for use the main work DB."); } const char *TMess::labSecCRON( ) { return _("Schedule wrotes in seconds periodic form or in the standard CRON form.\n" "The seconds periodic form is one real number (1.5, 1e-3).\n" "Cron is the standard form \"* * * * *\".\n" " Where items by order:\n" " - minutes (0-59);\n" " - hours (0-23);\n" " - days (1-31);\n" " - month (1-12);\n" " - week day (0[Sunday]-6).\n" " Where an item variants:\n" " - \"*\" - any value;\n" " - \"1,2,3\" - allowed values list;\n" " - \"1-5\" - raw range of allowed values;\n" " - \"*/2\" - range divider for allowed values.\n" "Examples:\n" " \"1e-3\" - call with a period of one millisecond;\n" " \"* * * * *\" - each minute;\n" " \"10 23 * * *\" - only at 23 hour and 10 minute for any day and month;\n" " \"*/2 * * * *\" - for minutes: 0,2,4,...,56,58;\n" " \"* 2-4 * * *\" - for any minutes in hours from 2 to 4(include)."); } const char *TMess::labSecCRONsel( ) { return "1;1e-3;* * * * *;10 * * * *;10-20 2 */2 * *"; } const char *TMess::labTaskPrior( ) { return _("Task priority level (-1...199), where:\n" " -1 - lowest priority batch policy;\n" " 0 - standard userspace priority;\n" " 1...99 - realtime priority level (round-robin), often allowed only for \"root\";\n" " 100...199 - realtime priority level (FIFO), often allowed only for \"root\"."); } const char *TMess::labMessCat( ) { return _("Specifies the category of the requested messages.\n" "Use template's symbols for group selection:\n '*' - any substring;\n '?' - any character.\n" "Regular expression enclosed between the symbols '/' (/mod_(System|LogicLev)/)."); } int TMess::getUTF8( const string &str, int off, int32_t *symb ) { if(off < 0 || off >= (int)str.size()) return 0; if(!isUTF8() || !(str[off]&0x80)) { if(symb) *symb = (uint8_t)str[off]; return 1; } int len = 0; int32_t rez = 0; if((str[off]&0xE0) == 0xC0) { len = 2; rez = str[off]&0x1F; } else if((str[off]&0xF0) == 0xE0) { len = 3; rez = str[off]&0x0F; } else if((str[off]&0xF8) == 0xF0) { len = 4; rez = str[off]&0x07; } if((off+len) > (int)str.size()) return 0; for(int iSmb = 1; iSmb < len; iSmb++) if((str[off+iSmb]&0xC0) != 0x80) return 0; else rez = (rez<<6) | (str[off+iSmb]&0x3F); if(symb) *symb = rez; return len; }