//OpenSCADA module BD.DBF file: dbf_mod.cpp /*************************************************************************** * Copyright (C) 2003-2023 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 #include #include "dbf.h" #include "dbf_mod.h" //************************************************ //* Modul info! * #define MOD_ID "DBF" #define MOD_NAME trS("DB DBF") #define MOD_TYPE SDB_ID #define VER_TYPE SDB_VER #define MOD_VER "2.4.10" #define AUTHORS trS("Roman Savochenko") #define DESCRIPTION trS("DB module. Provides support of the DBF files version 3.0.") #define LICENSE "GPL2" #define FEATURES "LIST, GET, SEEK, SET, DEL, FIX" //************************************************ BDDBF::BDMod *BDDBF::mod; extern "C" { #ifdef MOD_INCL TModule::SAt bd_DBF_module( int n_mod ) #else TModule::SAt module( int n_mod ) #endif { if(n_mod == 0) return TModule::SAt(MOD_ID, MOD_TYPE, VER_TYPE); return TModule::SAt(""); } #ifdef MOD_INCL TModule *bd_DBF_attach( const TModule::SAt &AtMod, const string &source ) #else TModule *attach( const TModule::SAt &AtMod, const string &source ) #endif { if(AtMod == TModule::SAt(MOD_ID,MOD_TYPE,VER_TYPE)) return new BDDBF::BDMod(source); return NULL; } } using namespace BDDBF; //************************************************ //* BDDBF::BDMod * //************************************************ BDMod::BDMod( string name ) : TTypeBD(MOD_ID) { mod = this; modInfoMainSet(MOD_NAME, MOD_TYPE, MOD_VER, AUTHORS, DESCRIPTION, LICENSE, name); } BDMod::~BDMod( ) { } string BDMod::features( ) { return FEATURES; } TBD *BDMod::openBD( const string &iid ) { return new MBD(iid, &owner().openDB_E()); } //************************************************ //* BDDBF::MBD * //************************************************ MBD::MBD( string iid, TElem *cf_el ) : TBD(iid, cf_el) { } MBD::~MBD( ) { } void MBD::postDisable( int flag ) { TBD::postDisable(flag); if(flag&NodeRemove && owner().fullDeleteDB()) if(rmdir(addr().c_str()) != 0) mess_sys(TMess::Warning, _("Error deleting DB.")); } void MBD::enable( ) { char buf[prmStrBuf_SZ]; char *rez = getcwd(buf, sizeof(buf)); if(chdir(addr().c_str()) != 0 && mkdir(addr().c_str(),S_IRWXU|S_IRGRP|S_IROTH) != 0) throw err_sys(_("Error creating the DB directory '%s'."), addr().c_str()); if(rez && chdir(buf)) throw err_sys(_("Error restoring previous directory as the current.")); TBD::enable(); } void MBD::allowList( vector &list ) const { if(!enableStat()) return; string nfile; list.clear(); struct stat file_stat; DIR *IdDir = opendir(addr().c_str()); if(IdDir == NULL) return; dirent *scan_rez = NULL, *scan_dirent = (dirent*)malloc(offsetof(dirent,d_name) + NAME_MAX + 1); while(readdir_r(IdDir,scan_dirent,&scan_rez) == 0 && scan_rez) { nfile = scan_rez->d_name; if(nfile == ".." || nfile == "." || nfile.rfind(".") == string::npos || nfile.substr(nfile.rfind(".")) != ".dbf") continue; stat((addr()+"/"+nfile).c_str(),&file_stat); if((file_stat.st_mode&S_IFMT) != S_IFREG) continue; list.push_back(nfile.substr(0,nfile.rfind("."))); } free(scan_dirent); closedir(IdDir); } void MBD::transCloseCheck( ) { if(enableStat()) { vector t_list; list(t_list); for(unsigned i_l = 0; i_l < t_list.size(); i_l++) { AutoHD t = at(t_list[i_l]); if(t.at().mModify && (SYS->sysTm()-t.at().mModify) > 10) t.at().save(); } } } TTable *MBD::openTable( const string &nm, bool create ) { if(!enableStat()) throw err_sys(_("Error opening the table '%s': the DB is disabled."), nm.c_str()); //Set file extend string tblNm = nm; if(!(tblNm.size() > 4 && tblNm.substr(tblNm.size()-4,4) == ".dbf")) tblNm += ".dbf"; string nTable = addr() + '/' + tblNm; TBasaDBF *basa = new TBasaDBF(); if(basa->LoadFile((char*)nTable.c_str()) == -1 && !create) { delete basa; throw err_sys(_("Error opening the table.")); } return new MTable(nm, this, nTable, basa); } void MBD::cntrCmdProc( XMLNode *opt ) { //Get page info if(opt->name() == "info") { TBD::cntrCmdProc(opt); ctrRemoveNode(opt, "/sql"); ctrRemoveNode(opt, "/prm/cfg/TRTM_CLS_ON_OPEN"); ctrRemoveNode(opt, "/prm/cfg/TRTM_CLS_ON_REQ"); ctrMkNode("fld",opt,-1,"/prm/cfg/ADDR",EVAL_STR,enableStat()?R_R___:RWRW__,"root",SDB_ID,3, "dest","sel_ed","select","/prm/cfg/dbFsList","help", _("For DBF, a DB address is the directory which contains files of the tables (*.dbf).\n" "For example: /opt/dbf.")); return; } //Process command to page string a_path = opt->attr("path"); if(a_path == "/prm/cfg/dbFsList" && ctrChkNode(opt)) TSYS::ctrListFS(opt, addr()); else TBD::cntrCmdProc(opt); } //************************************************ //* BDDBF::MTable * //************************************************ MTable::MTable( const string &inm, MBD *iown, const string &inTable, TBasaDBF *ibasa ) : TTable(inm), nTable(inTable), basa(ibasa), mModify(0) { setNodePrev(iown); codepage = owner().codePage().size() ? owner().codePage() : Mess->charset(); } MTable::~MTable( ) { delete basa; } void MTable::postDisable( int flag ) { if(mModify) save(); if(flag&NodeRemove) { string n_tbl = name(); //Set file extend if(!(n_tbl.size() > 4 && n_tbl.substr(n_tbl.size()-4,4) == ".dbf")) n_tbl += ".dbf"; if(remove((owner().addr()+"/"+n_tbl).c_str()) < 0) mess_sys(TMess::Error, "%s", strerror(errno)); } } MBD &MTable::owner( ) const { return (MBD&)TTable::owner(); } bool MTable::fieldSeek( int i_ln, TConfig &cfg, const string &cacheKey ) { int i_clm; //cfg.cfgToDefault(); //reset the not key and viewed fields cfg.setTrcSet(true); ResAlloc res(mRes, false); if((i_ln=findKeyLine(cfg,i_ln,true)) < 0) return false; //Get config fields list vector cf_el; cfg.cfgList(cf_el); //Seek and get data for(unsigned i_cf = 0; i_cf < cf_el.size(); i_cf++) { TCfg &e_cfg = cfg.cfg(cf_el[i_cf]); // Find column db_str_rec *fld_rec; for(i_clm = 0; (fld_rec=basa->getField(i_clm)) != NULL; i_clm++) if(cf_el[i_cf].compare(0,10,fld_rec->name) == 0) break; if(fld_rec == NULL) continue; // Get table volume string val; if(basa->GetFieldIt(i_ln,i_clm,val) < 0) throw err_sys(_("Error the cell.")); // Write value setVal(e_cfg, val); } return true; } void MTable::fieldGet( TConfig &cfg ) { int i_ln, i_clm; //Alloc resource ResAlloc res(mRes, false); //Get key line if((i_ln=findKeyLine(cfg)) < 0) throw err_sys(_("The field is not present.")); //Get config fields list vector cf_el; cfg.cfgList(cf_el); //Write data to cfg for(unsigned i_cf = 0; i_cf < cf_el.size(); i_cf++) { TCfg &e_cfg = cfg.cfg(cf_el[i_cf]); // Find column db_str_rec *fld_rec; for(i_clm = 0; (fld_rec = basa->getField(i_clm)) != NULL; i_clm++) if(cf_el[i_cf].compare(0,10,fld_rec->name) == 0) break; if(fld_rec == NULL) continue; // Get table volume string val; if(basa->GetFieldIt(i_ln,i_clm,val) < 0) throw err_sys(_("Error the cell.")); // Write value setVal(e_cfg, val); } } void MTable::fieldSet( TConfig &cfg ) { int i_ln, i_clm; //Alloc resource ResAlloc res(mRes, true); //Get config fields list vector cf_el; cfg.cfgList(cf_el); //Check for write access if(!(access(nTable.c_str(),F_OK|W_OK) == 0 || (access(nTable.c_str(),F_OK) != 0 && access(owner().addr().c_str(),W_OK) == 0))) throw err_sys(_("Access to the file '%s' is read only."), nTable.c_str()); bool forceUpdt = cfg.reqKeys(), appMode = forceUpdt || (cfg.incomplTblStruct() && !basa->isEmpty()); //Only for append no present fields //Check and fix structure of table for(unsigned i_cf = 0; i_cf < cf_el.size(); i_cf++) { TCfg &e_cfg = cfg.cfg(cf_el[i_cf]); // Find column db_str_rec *fld_rec; for(i_clm = 0;(fld_rec = basa->getField(i_clm)) != NULL;i_clm++) if(cf_el[i_cf].compare(0,10,fld_rec->name) == 0) break; if(fld_rec == NULL) { // Create new column db_str_rec n_rec; fieldPrmSet(e_cfg, n_rec); if(basa->addField(i_cf,&n_rec) < 0) throw err_sys(_("Error the column.")); } else if(!appMode) { // Check column parameters switch(e_cfg.fld().type()) { case TFld::String: if(fld_rec->tip_fild == 'C' && e_cfg.fld().len() == fld_rec->len_fild) continue; break; case TFld::Integer: if(fld_rec->tip_fild == 'N' && e_cfg.fld().len() == fld_rec->len_fild) continue; break; case TFld::Real: if(fld_rec->tip_fild == 'N' && e_cfg.fld().len() == fld_rec->len_fild && e_cfg.fld().dec() == fld_rec->dec_field) continue; break; case TFld::Boolean: if(fld_rec->tip_fild == 'L') continue; break; default: break; } db_str_rec n_rec; fieldPrmSet(e_cfg, n_rec); if(basa->setField(i_clm,&n_rec) < 0) throw err_sys(_("Error the column.")); } } //Del no used column db_str_rec *fld_rec; for(i_clm = 0; !appMode && (fld_rec=basa->getField(i_clm)) != NULL; i_clm++) { unsigned i_cf; for(i_cf = 0; i_cf < cf_el.size(); i_cf++) if(cf_el[i_cf].compare(0,10,fld_rec->name) == 0) break; if(i_cf >= cf_el.size() && basa->DelField(i_clm) < 0) throw err_sys(_("Error deleting the field.")); } //Write to all records //Get key line bool isEnd = false; for(i_ln = 0; !isEnd; i_ln++) { if((i_ln=findKeyLine(cfg,0,false,i_ln)) < 0) { if(forceUpdt) return; i_ln = basa->CreateItems(-1); } //Write data to bd for(unsigned i_cf = 0; i_cf < cf_el.size(); i_cf++) { TCfg &e_cfg = cfg.cfg(cf_el[i_cf]); if(!e_cfg.view()) continue; // Find column db_str_rec *fld_rec; for(i_clm = 0; (fld_rec=basa->getField(i_clm)) != NULL; i_clm++) if(cf_el[i_cf].compare(0,10,fld_rec->name) == 0) break; if(fld_rec == NULL) continue; // Set table volume if(basa->ModifiFieldIt(i_ln,i_clm,getVal(e_cfg,fld_rec).c_str()) < 0) throw err_sys(_("Error the cell.")); } if(!forceUpdt) isEnd = true; } mModify = SYS->sysTm(); } void MTable::fieldDel( TConfig &cfg ) { //Alloc resource ResAlloc res(mRes, true); //Get key line bool i_ok = false; int i_ln; while((i_ln = findKeyLine(cfg,0,true)) >= 0) { if(!i_ok && !(access(nTable.c_str(),F_OK|W_OK) == 0 || (access(nTable.c_str(),F_OK) != 0 && mModify && access(owner().addr().c_str(),W_OK) == 0))) throw err_sys(_("Access to the file '%s' is read only."), nTable.c_str()); if(basa->DeleteItems(i_ln,1) < 0) throw err_sys(_("Error the field.")); i_ok = true; mModify = SYS->sysTm(); } } int MTable::findKeyLine( TConfig &cfg, int cnt, bool useKey, int off ) { int i_ln, i_clm, i_cnt = 0; mLstUse = time(NULL); //Get config fields list vector cf_el; cfg.cfgList(cf_el); //Leave only keys in the list for(unsigned i_cf = 0; i_cf < cf_el.size(); ) if(cfg.cfg(cf_el[i_cf]).isKey()) i_cf++; else cf_el.erase(cf_el.begin()+i_cf); //Find want field for(i_ln = off; i_ln < basa->GetCountItems(); i_ln++) { int cnt_key = 0; for(unsigned i_cf = 0; i_cf < cf_el.size(); i_cf++) { if(useKey && !cfg.cfg(cf_el[i_cf]).keyUse()) { cnt_key++; continue; } //Check key // Find column db_str_rec *fld_rec; for(i_clm = 0; (fld_rec = basa->getField(i_clm)) != NULL; i_clm++) if(cf_el[i_cf].compare(0,10,fld_rec->name) == 0) break; if(fld_rec == NULL) throw err_sys(_("The key column '%s' is not present."), cf_el[i_cf].c_str()); // Get table volume string val; if(basa->GetFieldIt(i_ln,i_clm,val) < 0) throw err_sys(_("Error the cell.")); // Remove spaces from end int i; for(i = val.size(); i > 0 && val[i-1] == ' '; i--) ; if(i != (int)val.size()) val.resize(i); // Compare value if(val != cfg.cfg(cf_el[i_cf]).getS(TCfg::ExtValTwo)/*getVal(cfg.cfg(cf_el[i_cf]))*/) { cnt_key = 0; break; } cnt_key++; } if(cnt_key && cnt <= i_cnt++) break; } if(i_ln >= basa->GetCountItems()) return -1; return i_ln; } void MTable::fieldPrmSet( TCfg &e_cfg, db_str_rec &n_rec ) { memset(&n_rec, 0, sizeof(db_str_rec)); strncpy(n_rec.name, e_cfg.name().c_str(), 10); switch(e_cfg.fld().type()) { case TFld::String: n_rec.tip_fild = 'C'; n_rec.len_fild = vmin(255,e_cfg.fld().len()); n_rec.dec_field = 0; break; case TFld::Integer: n_rec.tip_fild = 'N'; n_rec.len_fild = (e_cfg.fld().len() == 0) ? 5 : vmin(255,e_cfg.fld().len()); n_rec.dec_field = 0; break; case TFld::Real: n_rec.tip_fild = 'N'; n_rec.len_fild = (e_cfg.fld().len() == 0) ? 7 : vmin(255,e_cfg.fld().len()); n_rec.dec_field = (e_cfg.fld().dec() == 0) ? 2 : vmin(255,e_cfg.fld().dec()); break; case TFld::Boolean: n_rec.tip_fild = 'L'; n_rec.len_fild = 1; n_rec.dec_field = 0; break; default: break; } } void MTable::save( ) { ResAlloc res(mRes, true); basa->SaveFile((char *)nTable.c_str(), SYS->permCrtFiles()); mModify = 0; } string MTable::getVal( TCfg &cfg, db_str_rec *fld_rec ) { string rez; switch(cfg.fld().type()) { case TFld::Boolean: rez = cfg.getB() ? "T" : "F"; break; case TFld::Integer: rez = i2s(cfg.getI()); break; case TFld::Real: { if(!fld_rec) { rez = r2s(cfg.getR()); break; } char str[200]; snprintf(str, sizeof(str), "%*.*f", fld_rec->len_fild, fld_rec->dec_field, cfg.getR()); rez = str; break; } default: rez = Mess->codeConvOut(codepage, cfg.getS()); break; } return rez; } void MTable::setVal( TCfg &cfg, const string &val ) { switch(cfg.fld().type()) { case TFld::String: { //Remove spaces from end int i; for(i = val.size(); i > 0 && val[i-1] == ' '; i--) ; cfg.setS(Mess->codeConvIn(codepage.c_str(),val.substr(0,i))); break; } case TFld::Integer: cfg.setI(s2i(val)); break; case TFld::Real: cfg.setR(s2r(val)); break; case TFld::Boolean: cfg.setB(val.c_str()[0]=='T'); break; default: break; } }