/* #Specification: subsystems / principle All services managed by linuxconf and its modules are organised in subsystems. Subsystems are used for configuration versioning and cluster administration (where a cluster is a group of machine sharing some configuration files). Each config file (or even part of a config file) belong to a sub-system. Subsystems may be archived independantly. A configuration version tells linuxconf how to archive the various subsystems it holds. Two different version may archive all subsystem in the same "family" except for few sub-systems. This mean one can switch between two version and share various configuration file between the two: Changes made to them while working in one version appear in the other. Only the sub-system which are archived differently will change when linuxconf do a version switch. */ /* #Specification: subsystems / fine grain / some idea At some point, it might be useful to break sub-systems. For example the apache subsystem is composed of 4 files. One may think that this could be split into 4 sub-systems, which may be archived (and co-administered) separatly. If we do that now, the specification of a version might become endless. One day, we may think of a sub-system as a hierarchic organisation, where the behavior of one file (or sub-file) may be controlled separatly. So far this is not done. */ #pragma implementation #include #include #include "subsys.h" #include "misc.m" #include "sstream.h" #include #include #include static HELP_FILE help_confver ("misc","confver"); static HELP_FILE help_switchver ("misc","switchver"); const char subsys_base[] = "base"; const char subsys_stationid[] = "stationid"; const char subsys_netclient[] = "netclient"; const char subsys_hardware[] = "hardware"; const char subsys_netaccess[] = "netaccess"; const char subsys_subsys[] = "subsys"; // Special sub-system never archived const char subsys_noarch[] = "noarch"; const char subsys_useraccounts[] = "useraccounts"; const char subsys_userpriv[]="userpriv"; static LINUXCONF_SUBSYS *first = NULL; PRIVATE void LINUXCONF_SUBSYS::init (const char *key) { strcpy (name,key); title = NULL; titlestr = NULL; next = first; first = this; } PUBLIC LINUXCONF_SUBSYS::LINUXCONF_SUBSYS( const char *key, TRANS_NOTLOAD *_title) // Title shown in the subsys dialogs { init (key); title = _title; } PUBLIC LINUXCONF_SUBSYS::LINUXCONF_SUBSYS( const char *key, const char *_title) // Title shown in the subsys dialogs { init (key); titlestr = strdup(_title); } PUBLIC const char *LINUXCONF_SUBSYS::gettitle() { const char *ret = titlestr; if (title != NULL) ret = title->get(); return ret; } PUBLIC LINUXCONF_SUBSYS::~LINUXCONF_SUBSYS() { LINUXCONF_SUBSYS **ptpt = &first; while (*ptpt != NULL){ if (*ptpt == this){ *ptpt = next; break; } ptpt = &(*ptpt)->next; } free ((char*)titlestr); } PUBLIC LINUXCONF_SUBSYS *LINUXCONF_SUBSYSS::getitem (int no) const { return (LINUXCONF_SUBSYS*)ARRAY::getitem(no); } static LINUXCONF_SUBSYS suba (subsys_base,P_MSG_U(M_SUBBASE,"Base/Station specific")); static LINUXCONF_SUBSYS subb (subsys_netclient ,P_MSG_U(M_NETCLIENT,"Network connectivity")); static LINUXCONF_SUBSYS subc (subsys_hardware ,P_MSG_U(M_HARDWARE,"Hardware setup")); static LINUXCONF_SUBSYS subd (subsys_stationid ,P_MSG_U(M_STATIONID,"Station identity")); static LINUXCONF_SUBSYS sube (subsys_netaccess ,P_MSG_U(M_NETACCESS,"Network access")); static LINUXCONF_SUBSYS subf (subsys_subsys ,P_MSG_U(M_PROFILEDEF,"System profile definitions")); static LINUXCONF_SUBSYS subg (subsys_useraccounts ,P_MSG_U(M_USERACCOUNTS,"User accounts")); static LINUXCONF_SUBSYS subh (subsys_userpriv ,P_MSG_U(M_USERPRIV,"User privileges")); /* Get the list of all sub-systems defined */ int subsys_getallsubsys(SSTRINGS &tb) { configf_getsubsyslist (tb); LINUXCONF_SUBSYS *pt = first; while (pt != NULL){ if (tb.lookup(pt->name)==-1){ tb.add (new SSTRING (pt->name)); } pt = pt->next; } tb.sort(); int lk = tb.lookup (subsys_noarch); if (lk != -1) tb.remove_del (lk); lk = tb.lookup (subsys_subsys); if (lk != -1) tb.remove_del (lk); return tb.getnb(); } /* Get the list of all sub-systems defined along with their title */ int subsys_getallsubsys(SSTRINGS &tb, SSTRINGS &titles) { int nb = subsys_getallsubsys (tb); // Put the title for each subsystem for (int i=0; iget(); SSTRING *ti = new SSTRING; titles.add (ti); ti->setfrom (sub); // Better to use the sub-system name // than nothing in case the LINUXCONF_SUBSYS // object is not defined LINUXCONF_SUBSYS *p = first; while (p != NULL){ if (strcmp(p->name,sub)==0){ ti->setfrom (p->gettitle()); break; } p = p->next; } if (p == NULL){ fprintf (stderr,"Missing sub-system title: %s\n",sub); } } return nb; } class ONECONF{ public: SSTRING name; SSTRING title; SSTRINGS subsyss; // All the various sub-systems SSTRINGS families; // and the corresponding archiving families // one for each sub-system SSTRINGS titles; // Associated titles SSTRING deffam; // Default family for archiving /*~PROTOBEG~ ONECONF */ public: ONECONF (const char *_name); const char *getfamily (const char *subsys); void remove (void); void setkey (char *key); void write (void); /*~PROTOEND~ ONECONF */ }; static const char K_CONFVER[]="confver"; static const char K_INDEX[]="index"; static const char K_TITLE[]="title"; static const char K_CUR[]="current"; static const char K_MODE[]="mode"; static const char K_CONF[]="conf"; static const char K_DEFFAM[]="deffam"; static const char K_NONE[]="none"; static const char K_LAST[]="last"; static ONECONF *curver; PUBLIC void ONECONF::setkey(char *key) { sprintf (key,"%s-%s",K_CONFVER,name.get()); } PUBLIC ONECONF::ONECONF(const char *_name) { name.setfrom (_name); char key[100]; setkey (key); title.setfrom (linuxconf_getval(key,K_TITLE)); deffam.setfrom (linuxconf_getval(key,K_DEFFAM)); subsys_getallsubsys(subsyss,titles); SSTRINGS tb; linuxconf_getall (key,K_CONF,tb,false); { // Create an edit field for each sub-system for (int i=0; i0){ for (int i=0; iget(); // Find the current family associated with this subsystem SSTRING *s = families.getitem(i); for (int j=0; jget(); char namesub[SUBSYS_NAMELEN+1]; const char *pt = str_copyword (namesub,val,sizeof(namesub)); if (strcmp(namesub,sub)==0){ s->setfrom (str_skip(pt)); break; } } } }else{ bool is_office = strcmp(_name,MSG_U(T_OFFICE,"Office"))==0; bool is_home = strcmp(_name,MSG_U(T_HOME,"Home"))==0; if (is_home || is_office){ // Put some default values for special version deffam.setfrom (MSG_U(T_HOMEOFFICE,"Home-Office")); if (title.is_empty()){ title.setfrom (is_home ? MSG_U(T_HOMECONFIG,"Home configuration") : MSG_U(T_OFFICECONFIG,"Office configuration")); } } for (int i=0; iget(); SSTRING *s = families.getitem(i); if (strcmp(sub,subsys_stationid)==0 || strcmp(sub,subsys_netclient)==0){ if (is_home || is_office){ s->setfrom (is_home ? MSG_R(T_HOME) : MSG_R(T_OFFICE)); } }else if (strcmp(sub,subsys_useraccounts)==0 || strcmp(sub,subsys_userpriv)==0){ s->setfrom (MSG_U(T_COMMON,"Common")); } } } } /* Save the configuration in conf.linuxconf */ PUBLIC void ONECONF::write () { char key[100]; setkey (key); linuxconf_replace (key,K_TITLE,title.get()); linuxconf_replace (key,K_DEFFAM,deffam.get()); linuxconf_removeall (key,K_CONF); for (int i=0; iget(); const char *fam = families.getitem(i)->get(); if (fam[0] != '\0'){ char line[100]; snprintf (line,sizeof(line)-1,"%s %s",sub,fam); linuxconf_add (key,K_CONF,line); } } } PUBLIC void ONECONF::remove () { char key[100]; setkey (key); linuxconf_removeall (key,K_TITLE); linuxconf_removeall (key,K_DEFFAM); linuxconf_removeall (key,K_CONF); } /* Get the archiving family for a sub-system */ PUBLIC const char *ONECONF::getfamily(const char *subsys) { const char *ret = deffam.get(); int l = subsyss.lookup(subsys); if (l != -1){ ret = families.getitem(l)->get(); if (ret[0] == '\0'){ ret = deffam.get(); } } if (ret != NULL && (ret[0] == '\0' || strcmp(ret,K_NONE)==0)) ret = NULL; return ret; } /* Get the current version name of all the configuration files */ const char *confver_getcur () { return linuxconf_getval(K_CONFVER,K_CUR,MSG_R(T_OFFICE)); } static const char *confver_loadcur () { if (curver == NULL) curver = new ONECONF(confver_getcur()); return curver->name.get(); } /* Record the new configuration name of all the configuration files */ void confver_setcur (const char *newcur) { delete curver; curver = new ONECONF (newcur); linuxconf_setcursys(subsys_noarch); linuxconf_replace (K_CONFVER,K_CUR,newcur); linuxconf_save(); } /* Return the archiving family associated with a subsystem. Return NULL if none (no archiving needed for this system). */ const char *confver_getfamily (const char *subsys) { const char *ret = NULL; confver_loadcur(); if (curver != NULL){ ret = curver->getfamily (subsys); } return ret; } /* Return the archiving mode. True means that archiving is done each time linuxconf modify the file (before modifying it in fact). */ bool confver_getmode () { return linuxconf_getvalnum (K_CONFVER,K_MODE,1); } static void subsys_addif (SSTRINGS &tb, const char *str) { if (tb.lookup(str)==-1) tb.add (new SSTRING(str)); } static void subsys_showmembers(const char *subsys) { SSTRINGS tb; configf_getsubsysmembers (subsys,tb); SSTRING title; title.setfromf(MSG_U(T_SUBSYSNAME,"Sus-system %s"),subsys); dialog_textbox (title.get() ,MSG_U(I_SUBSYSNAME,"Here is the list of configuration file\n" "member of the sub-system") ,help_nil,tb); } /* Edit the definition of a profile */ static int confver_editone (const char *name) { ONECONF conf (name); DIALOG dia; dia.newf_str (MSG_U(F_TITLE,"Title/comment"),conf.title); FIELD_COMBO *comb = dia.newf_combo (MSG_U(F_DEFFAM ,"Default archiving family"),conf.deffam); { SSTRINGS tb; dir_getlist (ETC_LINUXCONF_ARCHIVE,tb); subsys_addif (tb,MSG_R(T_OFFICE)); subsys_addif (tb,MSG_R(T_HOME)); subsys_addif (tb,MSG_R(T_COMMON)); subsys_addif (tb,MSG_R(T_HOMEOFFICE)); tb.sort(); for (int i=0; iaddopt (tb.getitem(i)->get()); } } dia.newf_title ("",MSG_U(T_SUBSYS,"Archiving families")); int nbsys = conf.subsyss.getnb(); PRIVATE_MESSAGE tbmsg[nbsys]; for (int i=0; iget() ,*conf.families.getitem(i)); dia.set_helpdia (tbmsg[i]); } int ret = -1; int nof = 0; while (1){ char title[80]; snprintf (title,sizeof(title)-1,MSG_U(T_ONECONF,"Configuration %s"),name); MENU_STATUS code = dia.edit (title ,MSG_U(I_ONECONF,"You must associate each subsystem with\n" "a configuration family. If you leave one field blank\n" "this subsystem won't be archived") ,help_confver ,nof,MENUBUT_DEL|MENUBUT_CANCEL|MENUBUT_ACCEPT); if (code == MENU_CANCEL || code == MENU_ESCAPE){ break; }else if (code == MENU_DEL){ if (xconf_delok()){ conf.remove(); ret = 1; break; } }else if (code == MENU_MESSAGE){ for (int i=0; iget()); break; } } }else{ conf.write(); // Flush the cached values delete curver; curver = NULL; ret = 0; break; } } return ret; } /* Get the list of all available versions Create defaults one if non exist */ static void confver_getconfs(SSTRINGS &tbconf) { linuxconf_getall (K_CONFVER,K_INDEX,tbconf,true); if (tbconf.getnb()==0){ tbconf.add (new SSTRING(MSG_R(T_OFFICE))); tbconf.add (new SSTRING(MSG_R(T_HOME))); } } void confver_edit() { SSTRINGS tbconf; confver_getconfs(tbconf); char mode = confver_getmode(); DIALOG dia; int nof = 0; int start_list = 0; while (1){ if (dia.getnb() == 0){ dia.newf_chk (MSG_U(F_CONFVERMODE,"Archiving mode"),mode ,MSG_U(I_AUTO,"Automatic")); dia.newf_title ("",MSG_U(T_CONFIGS,"Configurations")); start_list = dia.getnb(); for (int i=0; iget(); ONECONF one (conf); dia.new_menuitem (conf,one.title.get()); } dia.addwhat (MSG_U(I_ADDVER,"Select [Add] to create a new configuration tree")); } MENU_STATUS code = dia.edit ( MSG_U(T_CONFVER,"Configuration versioning") ,MSG_U(I_CONFVER ,"You can save/archive all the configuration of this station\n" "keep multiple versions around and switch at will\n" "between them") ,help_confver ,nof,MENUBUT_ADD|MENUBUT_CANCEL|MENUBUT_ACCEPT|MENUBUT_OK); bool must_delete = false; if (code == MENU_CANCEL || code == MENU_ESCAPE){ break; }else if (code == MENU_ADD){ char conf[MAX_LEN+1]; conf[0] = '\0'; while (dialog_inputbox (MSG_U(T_NEWCONF,"New configuration name") ,"" ,help_confver,conf)==MENU_ACCEPT){ if (tbconf.lookup (conf)!=-1){ xconf_error (MSG_U(E_CONFEXIST ,"This configuration name already exist\n" "pick another one")); }else if (strchr(conf,' ')!=NULL){ xconf_error (MSG_U(E_NOSPACE ,"No space allowed in the configuration name")); }else{ linuxconf_setcursys (subsys_subsys); int ok = confver_editone (conf); if (ok==0){ linuxconf_setcursys (subsys_subsys); tbconf.add (new SSTRING(conf)); linuxconf_replace (K_CONFVER,K_INDEX,tbconf); linuxconf_save(); must_delete = true; } break; } } }else if (code == MENU_OK){ if (nof >= start_list){ int noconf = nof-start_list; linuxconf_setcursys (subsys_subsys); int ok = confver_editone (tbconf.getitem(noconf)->get()); if (ok != -1){ if (ok == 1){ tbconf.remove_del (noconf); } linuxconf_replace (K_CONFVER,K_INDEX,tbconf); linuxconf_save(); must_delete = true; } } }else{ linuxconf_setcursys (subsys_subsys); linuxconf_replace (K_CONFVER,K_MODE,mode); linuxconf_save(); break; } if (must_delete){ dia.remove_all(); } } } static int subsys_archive (SSTRINGS &t) { SSTREAM_FILE ss (stdout); return configf_archive (t,"cfgarchive","--arch",ss,true); } static int subsys_extract (const char *todir, SSTRINGS &t) { return configf_extract (todir,t,"cfgarchive","--extr"); } /* Switch to a new profile, archive the current one Return -1 if any error */ int confver_selectprofile (const char *newver_name) { int ret = -1; SSTRINGS tbconf; confver_getconfs (tbconf); if (tbconf.lookup(newver_name)==-1){ xconf_error (MSG_U(E_NOCONF,"Profile %s does not exist"),newver_name); }else if (strcmp(confver_loadcur(),newver_name)==0){ net_prtlog (NETLOG_VERB,MSG_U(I_ALREADYSEL ,"Profile %s is already selected\n"),newver_name); ret = 0; }else{ SSTRINGS tbsub; int nbsys = subsys_getallsubsys(tbsub); /* #Specification: subsystem / switching version When we switch from one configuration version to another, we only work with subsystem that are archived in different family. Subsystems archived in the same family are simply not touched. This means that most of the time, switching from one version to another affects a limit amount of files. */ SSTRINGS tbsw; tbsw.neverdelete(); ONECONF newver (newver_name); for (int i=0; iget(); const char *oldfam = curver->getfamily(sub); const char *newfam = newver.getfamily(sub); if (newfam != NULL){ if (oldfam == NULL || strcmp(oldfam,newfam)!=0){ tbsw.add (ssub); } } } net_introlog (NETINTRO_SWITCHCONF); if (subsys_archive (tbsw)==0){ linuxconf_setcursys(subsys_noarch); linuxconf_replace(K_CONFVER,K_LAST ,linuxconf_getval(K_CONFVER,K_CUR,"")); confver_setcur(newver_name); ret = subsys_extract ("/",tbsw); }else{ xconf_error (MSG_U(E_ARCHFAILED ,"Archiving failed\n" "Aborting extraction of the selected profile")); } } return ret; } void confver_selnewver() { SSTRINGS tbconf; confver_getconfs(tbconf); DIALOG_RECORDS dia; dia.newf_head ("",MSG_U(H_SELPROF,"Profile\tTitle")); const char *cur = confver_loadcur(); int lookup[tbconf.getnb()]; for (int i=0; iget(); if (strcmp(s,cur)!=0){ ONECONF one (s); lookup[dia.getnb()-1] = i; dia.new_menuitem (s,one.title.get()); } } int nof = 0; while (1){ const char *intro1 = MSG_U(I_PICKVER ,"Pick a new version to activate.\n" "All configuration files of the current version\n" "will be archived and replaced by configuration files\n" "of the new version."); const char *intro2 = MSG_U(I_CURVERIS,"Current version"); const char *intro3 = MSG_U(I_LASTVERIS,"Last version"); char intro[1000]; const char *cur_ver = linuxconf_getval(K_CONFVER,K_CUR,""); const char *last_ver = linuxconf_getval(K_CONFVER,K_LAST,""); snprintf (intro,sizeof(intro)-1,"%s\n\n%s: %s\n%s: %s" ,intro1 ,intro2,cur_ver ,intro3,last_ver); MENU_STATUS code = dia.editmenu(MSG_U(T_PICKVER,"Pick a version") ,intro ,help_switchver ,nof,0); if (code == MENU_QUIT || code == MENU_ESCAPE){ break; }else{ const char *newver_name = tbconf.getitem(lookup[nof])->get(); confver_selectprofile (newver_name); break; } } } /* Archive all configuration file for the current profile version Some sort of backup */ int subsys_archive () { net_introlog (NETINTRO_ARCHIVE); SSTRINGS tb; subsys_getallsubsys(tb); return subsys_archive (tb); } /* Archive some or all subsystem */ int subsys_archive (int nb, const char *tb[]) { int ret = -1; if (nb == 0){ ret = subsys_archive(); }else{ SSTRINGS t; for (int i=0; i= 1 && strcmp(tb[0],"--to")==0){ if (nb < 2){ }else{ ret = subsys_extract (tb[1],nb-2,tb+2); } }else{ ret = subsys_extract ("/",nb,tb); } return ret; } static void subsys_setuplst (SSTRINGS &t, int nb, const char *tb[]) { if (nb == 0){ subsys_getallsubsys(t); }else{ for (int i=0; i