/* #Specification: VIEWITEMS / principle The VIEWITEMS object is used to parse a configuration file with comments and continuation line. The special character starting comments is configurable. The default is the # character. All comments before statements and at the end of the statements are preserved. Application using that object will generally scan the VIEWITEM (VIEWITEMS is derived from ARRAY) and extract various configuration statement they can handle. While doing so, they generally remember the VIEWITEM holding that statement. This allows application to make correction to the file and preserve comment as much as possible. This preserve also the original ordering of the file. New statement are generally append at the end though. This could be used for simple module. The first client for this code is the wuftpd module. */ #pragma implementation #include #include #include #include #include "fviews.h" // To force this at link time (called by misc.cc) void fviews_required(){} class VIEWITEMS_PRIVATE{ public: bool nocase; // Case insensitive ? bool owner; // Are we owning the vip object ? VIEWITEMS_PARSER *vip; VIEWITEMS_PRIVATE(VIEWITEMS_PARSER *_vip, bool _owner) { owner = _owner; vip = _vip; nocase = false; } ~VIEWITEMS_PRIVATE(){ if (owner) delete vip; } int strcmp(const char *s1, const char *s2) { return nocase ? strcasecmp(s1,s2) : ::strcmp(s1,s2); } int strncmp(const char *s1, const char *s2,int len) { return nocase ? strncasecmp(s1,s2,len) : ::strncmp(s1,s2,len); } }; PUBLIC VIEWITEMS_PARSER::VIEWITEMS_PARSER () { comstrs.add(new SSTRING("#")); sepchar = '='; quotchar = '"'; linecont = true; } PUBLIC VIRTUAL void VIEWITEMS_PARSER::init (VIEWITEMS &_vi) { vi = &_vi; } /* Return true if this is a comment */ PUBLIC VIRTUAL bool VIEWITEMS_PARSER::is_comment (const char *line) { bool ret = false; int nb = comstrs.getnb(); line = str_skip(line); for(int i=0;incmp(line,s->getlen())==0){ ret = true; break; } } return ret; } PUBLIC VIRTUAL const char *VIEWITEMS_PARSER::skip_comment (const char *line) { int nb = comstrs.getnb(); line = str_skip(line); for(int i=0;incmp(line,s->getlen())==0){ return str_skip(line+s->getlen()); } } return line; } PUBLIC VIRTUAL void VIEWITEMS_PARSER::addline (const char *line) { int type; if (is_comment(line)){ type = VIEWITEM_COMMENT; }else if (*(str_skip(line)) == 0){ type = VIEWITEM_EMPTY; }else{ type = VIEWITEM_VARIABLE; } vi->add(new VIEWITEM(line,type)); } /* Iterator returning each line of the file. reset is used to position to the start of the file. Return NULL at the end. */ PUBLIC VIRTUAL const char *VIEWITEMS_PARSER::getline ( bool reset, const char *&comment) { const char *ret = NULL; comment = ""; if (reset) cvi = 0; if (cvi < vi->getnb(-1)){ VIEWITEM *it = vi->getitem(cvi++,-1); ret = it->line.get(); comment = it->comment.get(); } return ret; } PUBLIC VIEWITEM::VIEWITEM (const char *_line, int _type) : type(_type) { line.setfrom (_line); } PUBLIC VIEWITEM::VIEWITEM (const char *_line) { type = VIEWITEM_VARIABLE; line.setfrom (_line); } PUBLIC int VIEWITEMS::realpos(int no, int type) const { if (no < 0 || type < 0) return no; int n = -1; int nb = ARRAY::getnb(); for (int i=0;itype == type) n++; if (n == no) return i; } return -1; } /* Convert a "real" position into a virtual one. A real position is simply the index of a VIEWITEM in a VIEWITEMS. A virtual position is the index of the VIEWITEM if we are only counting the items of the same type. */ PUBLIC int VIEWITEMS::virtpos(int no) const { if (no < 0) return no; VIEWITEM *v1 = (VIEWITEM*) ARRAY::getitem(no); if (v1 == NULL) return -1; int type = v1->type; int n = -1; int nb = ARRAY::getnb(); for (int i=0;itype == type) n++; if (i == no) return n; } return -1; } PUBLIC VIEWITEM *VIEWITEMS::getitem(int no, int type) const { if (type != -1) no = realpos(no,type); return (VIEWITEM*) ARRAY::getitem(no); } PUBLIC VIEWITEM *VIEWITEMS::getitem (int no) const { return getitem(no,VIEWITEM_VARIABLE); } PUBLIC int VIEWITEMS::getnb(int type) const { if (type == -1){ return ARRAY::getnb(); }else{ int ret = 0; int nb = ARRAY::getnb(); for (int i=0;itype == type){ ret++; } } return ret; } } PUBLIC int VIEWITEMS::getnb () const { return getnb(VIEWITEM_VARIABLE); } PUBLIC void VIEWITEMS::insert (int pos, VIEWITEM *pt, int type) { pos = realpos(pos,type); if (pos == -1){ pos = realpos(getnb(type)-1,type)+1; } ARRAY::insert(pos,pt); } PUBLIC void VIEWITEMS::insert (int pos, VIEWITEM *pt) { insert (pos,pt,VIEWITEM_VARIABLE); } PUBLIC void VIEWITEMS::add (VIEWITEM *pt, int type) { int pos = realpos(getnb(type)-1,type)+1; ARRAY::insert(pos,pt); } PUBLIC void VIEWITEMS::add (VIEWITEM *pt) { ARRAY::add(pt); } PUBLIC int VIEWITEMS::lookup (VIEWITEM *o, bool real) const { int pos = ARRAY::lookup(o); if (!real) pos = virtpos(pos); return pos; } PUBLIC int VIEWITEMS::lookup (VIEWITEM *o) const { return lookup(o,false); } PUBLIC void VIEWITEMS::moveto (VIEWITEM *o, int newpos, int type) { newpos = realpos(newpos,type); if (newpos == -1){ newpos = realpos(getnb(type)-1,type)+1; } ARRAY::moveto(o,newpos); } PUBLIC void VIEWITEMS::moveto (VIEWITEM *o, int newpos) { moveto (o,newpos,VIEWITEM_VARIABLE); } PUBLIC int VIEWITEMS::remove_del (int no, int type) { no = realpos(no,type); return ARRAY::remove_del(no); } PUBLIC int VIEWITEMS::remove_del (int no) { return remove_del(no,VIEWITEM_VARIABLE); } PUBLIC int VIEWITEMS::remove_del (VIEWITEM *it) { return ARRAY::remove_del(it); } /* Locate a VIEWITEM starting with the keyword key. Search in a range starting with start and ending before end. */ PUBLIC VIEWITEM* VIEWITEMS::locate (const char *key, int start, int end, int type) { VIEWITEM *ret = NULL; for (int i=start; iline.get(),sizeof(word)); if (priv->strcmp(word,key)==0){ ret = it; break; } } return ret; } PUBLIC VIEWITEM* VIEWITEMS::locate (const char *key, int start, int end) { return locate(key,start,end,VIEWITEM_VARIABLE); } /* Locate a VIEWITEM starting with the keyword key */ PUBLIC VIEWITEM* VIEWITEMS::locate (const char *key, int type) { return locate (key,0,getnb(type),type); } PUBLIC VIEWITEM* VIEWITEMS::locate (const char *key) { return locate (key,VIEWITEM_VARIABLE); } /* Locate a VIEWITEM starting with the keyword key1 and key2 Seach in a range starting with start and ending before end. */ PUBLIC VIEWITEM* VIEWITEMS::locate ( const char *key1, const char *key2, int start, int end, int type) { VIEWITEM *ret = NULL; for (int i=start; iline.get(),sizeof(word1)); str_copyword (word2,pt,sizeof(word2)); if (priv->strcmp(word1,key1)==0 && priv->strcmp(word2,key2)==0){ ret = it; break; } } return ret; } PUBLIC VIEWITEM* VIEWITEMS::locate ( const char *key1, const char *key2, int start, int end) { return locate(key1,key2,start,end,VIEWITEM_VARIABLE); } /* Locate a VIEWITEM starting with the keyword key1 and key2 */ PUBLIC VIEWITEM* VIEWITEMS::locate (const char *key1, const char *key2, int type) { return locate (key1,key2,0,getnb(type),type); } PUBLIC VIEWITEM* VIEWITEMS::locate (const char *key1, const char *key2) { return locate (key1,key2,VIEWITEM_VARIABLE); } /* Locate a VIEWITEM which is an assignment of the variable var (var=value) */ PUBLIC VIEWITEM* VIEWITEMS::locateassign (const char *var) { /* #Specification: VIEWITEMS / various file format VIEWITEMS may be used to managed two types of configuration files. You have normal keyed configuration file of the form: # keyword value ... # and you have the shell type ones of the form: # #!/bin/sh VAR1=value VAR2=value # Functions like VIEWITEMS::locate() and VIEWITEMS::locateassign() are used to dig into the VIEWITEMS object. */ int lenvar = strlen(var); VIEWITEM *ret = NULL; int n = getnb(); char sepchar = priv->vip->sepchar; for (int i=0; iline.get(); pt = str_skip(pt); if (strncmp(pt,var,lenvar)==0){ pt += lenvar; if (sepchar != ' '){ pt = str_skip(pt); } if (*pt == sepchar){ ret = it; break; } } } return ret; } /* Locate a VIEWITEM which is an assignment of the variable var (var=value) and is commented */ PUBLIC VIEWITEM* VIEWITEMS::locatecommented (const char *var) { int lenvar = strlen(var); VIEWITEM *ret = NULL; int n = getnb(VIEWITEM_COMMENT); VIEWITEMS_PARSER *vip = priv->vip; char sepchar = vip->sepchar; for (int i=0; iline.get(); pt = vip->skip_comment(pt); if (strncmp(pt,var,lenvar)==0){ pt += lenvar; if (sepchar != ' '){ pt = str_skip(pt); } if (*pt == sepchar){ ret = it; break; } } } return ret; } /* Locate all VIEWITEMs starting with the keyword key (full or partial match) */ PRIVATE int VIEWITEMS::locate_gen ( const char *key, VIEWITEMS &items, bool fullmatch) // key match completly the first word on the // line or not { int ret = 0; int n = getnb(); items.neverdelete(); int len = strlen (key); for (int i=0; iline.get(),sizeof(word)); if (fullmatch){ if (priv->strcmp(word,key)==0){ ret++; items.add (it); } }else{ if (priv->strncmp(word,key,len)==0){ ret++; items.add (it); } } } return ret; } /* Locate all VIEWITEMs starting with the keyword key (full match) */ PUBLIC int VIEWITEMS::locate (const char *key, VIEWITEMS &items) { return locate_gen (key,items,true); } /* Locate all VIEWITEMs starting with the keyword key (partial match) */ PUBLIC int VIEWITEMS::locate_prefix (const char *key, VIEWITEMS &items) { return locate_gen (key,items,false); } /* Locate all VIEWITEMs starting with the keyword key */ PUBLIC int VIEWITEMS::locate ( const char *key1, const char *key2, VIEWITEMS &items) { int ret = 0; int n = getnb(); items.neverdelete(); for (int i=0; iline.get(),sizeof(word1)); str_copyword (word2,pt,sizeof(word2)); if (strcmp(word1,key1)==0 && priv->strcmp(word2,key2)==0){ ret++; items.add (it); } } return ret; } PUBLIC VIEWITEMS::VIEWITEMS () { VIEWITEMS_PARSER *v = new VIEWITEMS_PARSER; v->init(*this); priv = new VIEWITEMS_PRIVATE (v,true); } PUBLIC VIEWITEMS::VIEWITEMS (VIEWITEMS_PARSER &_vip) { priv = new VIEWITEMS_PRIVATE (&_vip,false); _vip.init(*this); } /* Define the comment character used in the config file. This is the # sign by default */ PUBLIC void VIEWITEMS::setcomcar ( char com) { char comstr[] = {com,0}; priv->vip->comstrs.remove_all(); priv->vip->comstrs.add(new SSTRING(comstr)); } /* Read a configuration file in memory */ PUBLIC int VIEWITEMS::read (CONFIG_FILE &fconf) { int ret = -1; FILE_CFG *fin = fconf.fopen ("r"); if (fin != NULL){ char buf[2000]; VIEWITEMS_PARSER *vip = priv->vip; char linecont = vip->linecont; while (fgets_cont (buf,sizeof(buf)-1,fin,linecont)!=-1){ vip->addline(buf); } ret = fclose (fin); } return ret; } /* Write back a configuration file (overwrite). Return -1 if any error */ PUBLIC int VIEWITEMS::write (CONFIG_FILE &fconf, PRIVILEGE *privi) { int ret = -1; FILE_CFG *fout = fconf.fopen (privi,"w"); if (fout != NULL){ const char *comment; VIEWITEMS_PARSER *vip = priv->vip; const char *curline = vip->getline(true,comment); while (curline != NULL){ fprintf (fout,"%s%s\n",curline,comment); curline = vip->getline(false,comment); } ret = fclose (fout); } return ret; } /* Locate the value of a variable. Return NULL if the variable is not defined The quote surrounding the value are removed. */ PUBLIC const char *VIEWITEMS::locateval ( const char *var, char tmp[1000]) // Work buffer, ret might point to it { const char *ret = NULL; VIEWITEM *it = locateassign(var); if (it != NULL){ ret = it->line.strstr(var); if (ret != NULL){ VIEWITEMS_PARSER *vip = priv->vip; ret = str_skip(ret+strlen(var)); if (vip->sepchar != ' '){ ret++; ret = str_skip(ret); } if (vip->quotchar != 0 && ret[0] == vip->quotchar){ ret++; strcpy (tmp,ret); strip_end (tmp); int len = strlen(tmp)-1; if (len >= 0 && tmp[len] == vip->quotchar) tmp[len] = '\0'; ret = tmp; } } } return ret; } /* Locate a boolean (yes/no) value Return 0 if false, 1 if true. Return defval if the variable is not there. Depending on defval, the parsing logic is done differently. If defval != 0, the result will be true unless the value is either no or false. If defval == 0, then the result will be false unless the value is either yes or true. */ PUBLIC int VIEWITEMS::locatebval ( const char *var, int defval) { int ret = defval; char tmp[1000]; const char *val = locateval (var,tmp); if (val != NULL){ if (defval != 0){ ret = stricmp(val,"no")!=0 && stricmp(val,"false")!=0; }else{ ret = stricmp(val,"yes")==0 || stricmp(val,"true")==0; } } return ret; } /* Locate a boolean (yes/no) value Return 0 if false, 1 if true. Return 0 if the variable is not there. */ PUBLIC int VIEWITEMS::locatebval ( const char *var) { return locatebval (var,0); } /* Locate an hexadecimal value */ PUBLIC int VIEWITEMS::locatehval ( const char *var) { int ret = 0; char tmp[1000]; const char *val = locateval (var,tmp); if (val != NULL && isxdigit(val[0])){ sscanf (val,"%x",&ret); } return ret; } /* Locate a numeric value */ PUBLIC int VIEWITEMS::locatenval ( const char *var) { int ret = 0; char tmp[1000]; const char *val = locateval (var,tmp); if (val != NULL){ ret = atoi (val); } return ret; } /* Update or add an assignement of the form var="val" Return -1 if the update is rejected (variable name too long, by far). Resulting lines are limited to 1000 characters. */ PUBLIC int VIEWITEMS::update ( const char *var, const char *val) { /* #Specification: VIEWITEMS / update strategy When updating a config file using the VIEWITEMS object, the code generally goes like this. # CONFIG_FILE cf (...); VIEWITEMS items; items.read (cf); . items.update (var,value); . items.write(cf,(PRIVILEGE*)NULL); # There are several VIEWITEMS::update() function to support various data type. For some project, you may be better to write your own update function. An update function always looks like the following pseudo-code # // Used one of the VIEWITEMS::locate or write your own VIEWITEM *it = items.locate (key); if (it == NULL){ // No record found, add a new empty one // It will go at the end of the config file it = new VIEWITEM; items.add (it); } // formats the updated value of the record and stores it // in the VIEWITEM object. char line[1000]; snprintf (line,sizeof(line)-1,"....",....); it->line.setfrom (line); # */ int ret = -1; char buf[1000]; int len; VIEWITEMS_PARSER *vip = priv->vip; if (vip->quotchar != 0){ len = snprintf (buf,sizeof(buf)-1,"%s%c%c%s%c",var,vip->sepchar,vip->quotchar,val,vip->quotchar); }else{ len = snprintf (buf,sizeof(buf)-1,"%s%c%s",var,vip->sepchar,val); } if (len != -1 && len < (int)(sizeof(buf)-1)){ VIEWITEM *it = locateassign(var); if (it == NULL){ it = new VIEWITEM(""); add (it); } it->line.setfrom (buf); ret = 0; }else{ fprintf (stderr,"VIEWITEMS::update rejected: %30.30s...\n",buf); } return ret; } /* Update or add an assignement of the form var="val" */ PUBLIC void VIEWITEMS::update ( const char *var, const SSTRING &val) { update (var,val.get()); } /* Update or add an assignement of the form var="val" */ PUBLIC void VIEWITEMS::updatehval ( const char *var, int val) { char buf[20]; sprintf (buf,"%x",val); update (var,buf); } /* Update or add an assignement of the form var="val" */ PUBLIC void VIEWITEMS::update ( const char *var, int val) { char buf[20]; sprintf (buf,"%d",val); update (var,buf); } /* Update or add an assignement of the form var="yes" or var="no" */ PUBLIC void VIEWITEMS::updatebval ( const char *var, bool val) { update (var,val ? "yes" : "no"); } PUBLIC void VIEWITEMS::uncomment (VIEWITEM *vi) { vi->line.setfrom(priv->vip->skip_comment(vi->line.get())); vi->type = VIEWITEM_VARIABLE; } PUBLIC bool VIEWITEMS::uncomment (const char *var) { VIEWITEM *it = locatecommented(var); if (it == NULL){ return false; } uncomment(it); return true; } PUBLIC void VIEWITEMS::comment (VIEWITEM *vi, const char *comstr) { SSTRING line; line.setfromf ("%s %s",comstr,vi->line.get()); vi->line.setfrom (line.get()); vi->type = VIEWITEM_COMMENT; } PUBLIC void VIEWITEMS::comment (VIEWITEM *vi) { comment(vi,priv->vip->comstrs.getitem(0)->get()); } PUBLIC bool VIEWITEMS::comment (const char *var, const char *comstr) { VIEWITEM *it = locateassign(var); if (it == NULL){ return false; } comment(it, comstr); return true; } PUBLIC bool VIEWITEMS::comment (const char *var) { return comment(var,priv->vip->comstrs.getitem(0)->get()); } /* Register if variable are matched case insensitive or not. The object use case sensitive by default. */ PUBLIC void VIEWITEMS::setcasevar (bool nocase) { priv->nocase = nocase; }