#pragma implementation #include #include #include #include #include #include #include #include #include "misc.h" #include "popen.h" #include #include "../paths.h" #include "misc.m" #include #include #include #include "context.h" #include static HELP_FILE help_configf ("misc","configf"); static CONFIG_FILE *first = NULL; static CONFIG_FILE_LISTER *first_lister=NULL; // To force the link of some stuff only used by modules void configf_required() { fviews_required(); malloc_err(0); } class CONFIG_FILE_INTERNAL{ public: CONFIG_FILE *next; const char *key; // Path used as a key in the *.files distribution // specific resource files. const char *stdpath; // Original path of the file const char *cnvpath; // Converted by the resource system const char *realpath; // The admin may move file around! // including the LINUXCONF_CONTEXT adjustment HELP_FILE &helpf; int status; // See CONFIGF_XXXXX const char *owner; const char *group; int perm; const char *perm_str; const char *subsys; // This config file belong to this sub-system /*~PROTOBEG~ CONFIG_FILE_INTERNAL */ public: CONFIG_FILE_INTERNAL (HELP_FILE&_helpfile, const char *_path, int _status, const char *_owner, const char *_group, int _perm, const char *_perm_str, const char *_subsys); ~CONFIG_FILE_INTERNAL (void); /*~PROTOEND~ CONFIG_FILE_INTERNAL */ }; /* #Specification: CONFIG_FILE_LISTER / principles Some configuration file are dynamic. For example the various DNS zone file are known only while reading /etc/named.conf. When linuxconf needs to operate on all known configuration file it uses the CONFIG_FILE_LISTER objects to trigger the definition of all dynamic CONFIG_FILE object. A CONFIG_FILE_LISTER is a static object. The constructor is used to register a void (*f)() function pointer. When linuxconf needs wants to enumerate all the CONFIG_FILE, it walks all the CONFIG_FILE_LISTER objects and trigger the function. All those function enumerate all the dynamic CONFIG_FILE they know and create a new CONFIG_FILE object for each. Linuxconf will delete the newly created CONFIG_FILE object later. So the function only care about creating the objects. */ # PUBLIC CONFIG_FILE_LISTER::CONFIG_FILE_LISTER (void (*f)()) { next = first_lister; first_lister = this; fct = f; } /* Ask the lister to "create" the extra CONFIG_FILE objects We keep a pointer on the current "first" pointer. THis will allows us to delete those extra config file when done Return a pointer to the original first member of the link list */ static CONFIG_FILE *configf_calllisters() { CONFIG_FILE *ret = first; CONFIG_FILE_LISTER *pt = first_lister; while (pt != NULL){ pt->fct(); pt = pt->next; } return ret; } /* Forget the realpath of every config file. This function is called after a context switch to make sure the realpath of the config files will be computed with the new basepath. See CONFIG_FILE::fixpath(). */ void configf_forgetpath() { CONFIG_FILE *pt = first; while (pt != NULL){ pt->forgetpath(); pt = pt->getnext(); } } PUBLIC CONFIG_FILE_INTERNAL::CONFIG_FILE_INTERNAL( HELP_FILE &_helpfile, const char *_path, int _status, const char *_owner, const char *_group, int _perm, const char *_perm_str, const char *_subsys) : helpf(_helpfile) { owner = _owner; group = _group; perm = _perm; perm_str = _perm_str; key = strdup(_path); stdpath = NULL; cnvpath = NULL; realpath = NULL; // fixpath() will look it up // later when more is initialised // This also avoid a massive query // to the linuxconf_getval() function // at startup time. status = _status; subsys = NULL; if (_subsys != NULL) subsys = strdup(_subsys); } PRIVATE void CONFIG_FILE::init( HELP_FILE &_helpfile, const char *_path, int _status, const char *_owner, const char *_group, int _perm, const char *_subsys) { intern = new CONFIG_FILE_INTERNAL (_helpfile ,_path,_status,_owner,_group,_perm,NULL,_subsys); intern->next = first; first = this; } PRIVATE void CONFIG_FILE::init( HELP_FILE &_helpfile, const char *_path, int _status, const char *_owner, const char *_group, const char *_perm_str, const char *_subsys) { intern = new CONFIG_FILE_INTERNAL (_helpfile ,_path,_status,_owner,_group,0,_perm_str,_subsys); intern->next = first; first = this; } /* Replace the original path name (the key to lookup the distribution dependant path) of a config file. This function is meant for contructor of derived class which assemble on the fly the path of a config file. See modules/mailconf/generate.cc. */ PUBLIC void CONFIG_FILE::setkey(const char *newkey) { free ((char*)intern->key); free ((char*)intern->stdpath); free ((char*)intern->realpath); intern->stdpath = NULL; intern->realpath = NULL; intern->key = strdup(newkey); } /* Record the spec of a config file that is maintain (or used) by linuxconf */ PUBLIC CONFIG_FILE::CONFIG_FILE( const char *_path, HELP_FILE &_helpfile, int _status) { /* #Specification: configuration files / default permissions Unless explicitly stated, the owner and permissions of every configuration file produced and maintained by linuxconf are # rw-r--r-- root root # */ init (_helpfile,_path,_status,"root","root",0644,"base"); } PUBLIC CONFIG_FILE::CONFIG_FILE( const char *_path, HELP_FILE &_helpfile, int _status, const char *_subsys) { init (_helpfile,_path,_status,"root","root",0644,_subsys); } /* Record the spec of a config file that is maintain (or used) by linuxconf */ PUBLIC CONFIG_FILE::CONFIG_FILE( const char *_path, HELP_FILE &_helpfile, int _status, const char *_owner, const char *_group, int _perm) { init (_helpfile,_path,_status,_owner,_group,_perm,"base"); } /* Record the spec of a config file that is maintain (or used) by linuxconf */ PUBLIC CONFIG_FILE::CONFIG_FILE( const char *_path, HELP_FILE &_helpfile, int _status, const char *_owner, const char *_group, int _perm, const char *_subsys) { init (_helpfile,_path,_status,_owner,_group,_perm,_subsys); } PUBLIC CONFIG_FILE::CONFIG_FILE( const char *_path, HELP_FILE &_helpfile, int _status, const char *_owner, const char *_group, const char *_perm_str) { init (_helpfile,_path,_status,_owner,_group,_perm_str,"base"); } PUBLIC CONFIG_FILE::CONFIG_FILE( const char *_path, HELP_FILE &_helpfile, int _status, const char *_owner, const char *_group, const char *_perm_str, const char *_subsys) { init (_helpfile,_path,_status,_owner,_group,_perm_str,_subsys); } PUBLIC CONFIG_FILE_INTERNAL::~CONFIG_FILE_INTERNAL() { free ((char*)subsys); free ((char*)key); free ((char*)stdpath); free ((char*)realpath); } PUBLIC VIRTUAL CONFIG_FILE::~CONFIG_FILE() { CONFIG_FILE **ptpt = &first; while (*ptpt != NULL){ if (*ptpt == this){ *ptpt = intern->next; break; } ptpt = &(*ptpt)->intern->next; } forgetpath(); delete intern; } PUBLIC void CONFIG_FILE::forgetpath() { free ((char*)intern->realpath); intern->realpath = NULL; } /* All config files are connected in a linked list. Return the next member */ PUBLIC CONFIG_FILE *CONFIG_FILE::getnext() const { return intern->next; } /* Return the name of the subsystem owning that config file */ PUBLIC const char *CONFIG_FILE::getsubsys() const { return intern->subsys; } /* Set the permissions and owner of a file to the same value as the configuration file, if it has such a requirement. Return -1 if any error. */ PUBLIC int CONFIG_FILE::setperm (const char *fpath) const { int ret = 0; if (intern->owner != NULL){ fixpath(); PERMINFO p; int fixperm_int; p.uid = 0; p.gid = 0; char fixuser[100],fixgroup[100],fixperm[100]; if(intern->perm_str){ configf_mapowner (intern->owner,intern->group,intern->perm_str,fixuser,fixgroup,fixperm); sscanf(fixperm, "%o", &fixperm_int); fixperm_readperm (intern->stdpath,p,fixuser,fixgroup ,fixperm_int,true); }else{ configf_mapowner (intern->owner,intern->group,fixuser,fixgroup); fixperm_readperm (intern->stdpath,p,fixuser,fixgroup ,intern->perm,true); } ret = -1; if (p.uid == (uid_t)-1){ xconf_error (MSG_U(E_SETOWNER ,"Can't set ownership of file\n" "%s\n\n" "User %s does not exist\n") ,fpath,intern->owner); }else if (p.gid == (gid_t)-1){ xconf_error (MSG_U(E_SETGROUP ,"Can't set group ownership of file\n" "%s\n\n" "Group %s does not exist\n") ,fpath,intern->group); }else if (chown (fpath,p.uid,p.gid) != -1 && chmod (fpath,p.perm) != -1){ ret = 0; } }else{ fixpath(); ret = chmod (fpath,intern->perm); } return ret; } static SSTRINGS tbkey,tbval; /* Read the various *.paths files. These let us translate standard paths to correct path for a given distribution. This function must be called after the distribution specific module has been loaded. It is called once. */ void configf_readlookup () { char basepath[PATH_MAX],basepath_rev[PATH_MAX],basepath_brev[PATH_MAX]; snprintf (basepath,sizeof(basepath)-1,"%s/%s",USR_LIB_LINUXCONF,linuxconf_getdistdir()); snprintf (basepath_rev,sizeof(basepath_rev)-1,"%s/%s",basepath,distrib_getrelease()); strcpy (basepath_brev,basepath_rev); { char *pt = strchr(basepath_brev,'.'); if (pt != NULL) *pt = '\0'; } SSTRINGS tb; dir_getlist (basepath,".paths",tb); dir_getlist (basepath_rev,".paths",tb); dir_getlist (basepath_brev,".paths",tb); tb.sort(); tb.remove_dups(); for (int i=0; iget(); /* #Specification: *.paths files / location We check first in /usr/lib/linuxconf/distribution/release then in /usr/lib/linuxconf/distribution/base-release then in /usr/lib/linuxconf/distribution. This allows redefinition for a specific release, a major version or any version. For example, when running on a RedHat 6.2, we look in the following directory # /usr/lib/linuxconf/redhat/6.2 /usr/lib/linuxconf/redhat/6 /usr/lib/linuxconf/redhat # This should offer enough flexibility to adapt to newer releases. */ char filepath[PATH_MAX]; snprintf (filepath,sizeof(filepath)-1,"%s/%s.paths",basepath_rev,basename); FILE *fin = fopen (filepath,"r"); if (fin == NULL){ snprintf (filepath,sizeof(filepath)-1,"%s/%s.paths",basepath_brev,basename); fin = fopen (filepath,"r"); if (fin == NULL){ snprintf (filepath,sizeof(filepath)-1,"%s/%s.paths",basepath,basename); fin = fopen (filepath,"r"); } } if (fin != NULL){ char buf[2*PATH_MAX]; while (fgets_strip(buf,sizeof(buf)-1,fin,'\\','#',NULL)!=NULL){ char key[PATH_MAX],val[PATH_MAX]; if (sscanf(buf,"%s %s",key,val)==2){ tbkey.add (new SSTRING (key)); tbval.add (new SSTRING (val)); } } fclose (fin); } } } /* Find the corresponding path for the current distribution. Return the key itself if the lookup has nothing for this key. */ EXPORT const char *configf_lookuppath(const char *key) { #if 0 static bool is_init = false; if (!is_init){ configf_readlookup(); is_init = true; } #endif const char *ret = key; int no = tbkey.lookup (key); if (no != -1) ret = tbval.getitem(no)->get(); return ret; } EXPORT const char *configf_lookup (const char *prefix, const char *key) { char tmp[strlen(prefix)+1+strlen(key)+1]; snprintf (tmp,sizeof(tmp)-1,"%s:%s",prefix,key); const char *ret = configf_lookuppath (tmp); if (ret == tmp){ fprintf (stderr,"Can't resolve generic mapping %s\n",tmp); ret = "nobody"; } return ret; } /* Translate a user and group (if needed) into other user and group from the resource system (.paths files) */ EXPORT void configf_mapowner ( const char *generic_user, const char *generic_group, char user[100], char group[100]) { if (generic_user[0] == '$'){ generic_user = configf_lookup ("user",generic_user+1); } strcpy_cut (user,generic_user,99); if (generic_group[0] == '$'){ generic_group = configf_lookup ("group",generic_group+1); } strcpy_cut (group,generic_group,99); } EXPORT void configf_mapowner ( const char *generic_user, const char *generic_group, const char *generic_perm, char user[100], char group[100], char perm[100]) { configf_mapowner (generic_user, generic_group, user, group); if (generic_perm[0] == '$'){ generic_perm = configf_lookup ("perm",generic_perm+1); } strcpy_cut (perm,generic_perm,99); } static char CONFIG[]="config"; PRIVATE void CONFIG_FILE::fixpath() const { extern CONFIG_FILE f_linuxconf; if (intern->stdpath == NULL){ const char *std = intern->key; if (this != &f_linuxconf){ std = configf_lookuppath(intern->key); } intern->stdpath = strdup(std); } if (intern->realpath == NULL){ /* #Specification: /etc/conf.linuxconf / can't be moved Given that /etc/conf.linuxconf is used to store almost everything which do not have a standard home (configuration file), including the the corrected path of the configuration themselves it is not possible to change the location of /etc/conf.linuxconf. */ const char *selp = intern->stdpath; if (this != &f_linuxconf){ selp = linuxconf_getval (CONFIG,selp); if (selp == NULL) selp = intern->stdpath; } free ((char*)intern->cnvpath); intern->cnvpath = strdup(selp); CONFIG_FILE *pt = (CONFIG_FILE*)this; if (context_isroot() || (intern->status & CONFIGF_FIXEDBASE) != 0){ pt->intern->realpath = strdup(selp); }else{ char tmp[PATH_MAX]; snprintf (tmp,sizeof(tmp)-1,"%s%s",ui_context.basepath,selp); pt->intern->realpath = strdup(tmp); } } } PRIVATE void CONFIG_FILE::sign (FILE_CFG *fout, const char *mode) const { if (fout != NULL && strcmp(mode,"w")==0){ if (intern->status & CONFIGF_SIGNPOUND){ if (intern->status & CONFIGF_MANAGED){ fprintf (fout ,"### Managed by Linuxconf, you may edit by hand.\n" "### Comments may not be fully preserved by linuxconf.\n" "\n"); }else{ fprintf (fout ,"### Generated from scratch by Linuxconf, don't edit\n" "### Your changes will be lost.\n" "\n"); } } } } /* Open the configuration file without permission checking. */ PUBLIC FILE_CFG *CONFIG_FILE::fopen_ok(const char *mode) const { FILE *fret = NULL; fixpath(); const char *realp = intern->realpath; if (strcmp(mode,"r")==0 && (intern->status & CONFIGF_OPTIONAL) != 0){ fret = ::fopen (realp,mode); }else{ fret = xconf_fopen(realp,mode); setperm(realp); } FILE_CFG *ret = filecfg_new (fret,this); sign (ret,mode); return ret; } static bool extracting = false; /* Open the configuration file with permission checking. It may even ask the user for the root password or a user password if priv != NULL. */ PUBLIC FILE_CFG *CONFIG_FILE::fopen(PRIVILEGE *priv, const char *mode) const { FILE_CFG *ret = NULL; fixpath(); if (strchr(mode,'w')!=NULL || strchr(mode,'+')!=NULL || strchr (mode,'a')!=NULL){ if (!extracting && dialog_mode != DIALOG_TREE && confver_getmode()){ if (is_archived()){ PRIVILEGE *oldp = perm_setdefprivi (priv); archive(); perm_setdefprivi (oldp); }else{ /* This is probably a config file which is archived in several sub-system. Lets try to find out sub-file. They hold the same name but with a suffix starting with - */ CONFIG_FILE *pt = first; const char *key = intern->key; int keylen = strlen(key); while (pt != NULL){ const char *ptkey = pt->intern->key; if (strncmp(key,ptkey,keylen)==0 && ptkey[keylen] == '-' && pt->is_virtual()){ pt->archive(); } pt = pt->getnext(); } } } } /* There is an odd interaction between CONFIG_FILE and xconf_fopencfg. This function may call perm_access() to check the authentification of the user. To do so, it creates a dialog. In GUI mode, linuxconf implement user interface threads. To insure some coherency within thread, the uithread_sync() function is indirectly calling configf_forgetpath(), which trash intern->realpath. This is done because one thread may be working with a different context (a different root) because of the netadm module. So we take a copy of realpath before using it. */ fixpath(); char realp[PATH_MAX]; strcpy_cut (realp,intern->realpath,sizeof(realp)-1); if (strcmp(mode,"r")==0){ if ((intern->status & CONFIGF_OPTIONAL) != 0){ FILE *f = ::fopen (realp,mode); ret = filecfg_new (f,this); }else{ FILE *f = xconf_fopencfg(priv,realp,mode); ret = filecfg_new (f,this); } }else{ /* #Specification: CONFIG_FILE / update strategy / .TMP files The flag CONFIGF_TMPLOCK was used to produce a temp file which was renamed in place at fclose time. This was ensuring a consistant state for the configuration file at all time (since rename is atomic). So any competing process would either read the old or the new version, both in a "completed" state. This strategy has other advantages. If Linuxconf has a flaw and fails (crashes) while updating the file, the original is left in place. Also, at fclose time, we have the old and new file side by side. This may be used by modules supporting the "updatefile" message so they can inspect the changes. The updatemon module is doing that and allows the user to preview the change and even reject them. Because of those advantage, the CONFIGF_TMPLOCK is now the default behavior. */ // bool opentmp = (intern->status & CONFIGF_TMPLOCK) != 0; bool opentmp = true; if (strncmp(realp,"/proc/",6)==0) opentmp = false; if (strcmp(mode,"w")==0 && opentmp){ ret = fopen_tmp(priv,mode); }else{ FILE *f = xconf_fopencfg(priv,realp,mode); ret = filecfg_new (f,this); setperm(realp); } } sign (ret,mode); return ret; } /* Open the configuration file with permission checking. It may even ask the user for the root password. */ PUBLIC FILE_CFG *CONFIG_FILE::fopen(const char *mode) const { return fopen ((PRIVILEGE*)NULL,mode); } /* Open the temp configuration file with permission checking. It may even ask the user for the root password. */ PUBLIC FILE_CFG *CONFIG_FILE::fopen(PRIVILEGE *priv, const char *temp, const char *mode) const { FILE *fret = NULL; if (strcmp(mode,"r")==0 && (intern->status & CONFIGF_OPTIONAL) != 0){ fret = ::fopen (temp,mode); }else{ fret = xconf_fopencfg(priv,temp,mode); setperm(temp); } FILE_CFG *ret = filecfg_new (fret,this); sign (ret,mode); return ret; } /* Return the absolute file name of a configuration file. Check if the path is a simlink. If this is the case, return the path pointed by the symlink. */ PRIVATE const char *CONFIG_FILE::getlinkpath(char linkpath[PATH_MAX]) const { /* #Specification: CONFIG_FILE / symlink and path If the administrator move a configuration file and then setup a symbolic link pointing to the new location, Linuxconf will respect this symbolic link. Further, it will produce a backup file (.OLD) in the same directory as the configuration file. */ const char *ret = getpath(); int len = readlink(ret,linkpath,PATH_MAX-1); if (len > 0){ linkpath[len] = '\0'; if (linkpath[0] != '/'){ // The symlink is relative, make it absolute const char *pt = strrchr(ret,'/'); if (pt != NULL){ char tmp[PATH_MAX]; len = (int)(pt-ret)+1; memmove (tmp,ret,len); tmp[len] = '\0'; strcat (tmp,linkpath); strcpy (linkpath,tmp); } } ret = linkpath; } return ret; } /* Open a temporary file using the configuration and adding .TMP to its name. */ PUBLIC FILE_CFG *CONFIG_FILE::fopen_tmp(PRIVILEGE *priv, const char *mode) const { char linkpath[PATH_MAX]; const char *abspath = getlinkpath(linkpath); char path_tmp[PATH_MAX]; snprintf (path_tmp,PATH_MAX-1,"%s.TMP",abspath); FILE_CFG *ret = fopen (priv,path_tmp,mode); filecfg_setrelink (ret); setperm (path_tmp); return ret; } static const char *tbargs[]={"path","pathtmp",NULL}; static MESSAGE_DEF updatefile ("updatefile",tbargs); /* Rename to original to .OLD, rename the tmp to the original (This function assumes that fopen_tmp() has been used to update the configuration file. It is called from filecfg.cc) return -1 if any error. */ PUBLIC int CONFIG_FILE::relink_tmp() const { /* #Specification: saving multi-user config file / strategy Here is the strategy used to update file like /etc/passwd in linuxconf. This strategy is used because those file may be access any time. The strategy provide an atomic file update. Here is a step by step sequence, shown for /etc/passwd. This strategy is used for few other files in linuxconf. # -Create /etc/passwd.TMP with the new contain -unlink /etc/passwd.OLD -link /etc/passwd to /etc/passwd.OLD -rename /etc/passwd.TMP -> /etc/passwd This last link delete the previous /etc/passwd but the file continue to exist as /etc/passwd.OLD # This strategy makes that /etc/passwd is always visible and in a consistant state at any given time. Some provision are made so this works also if /etc/passwd did not exist. */ char path[PATH_MAX], path_old[PATH_MAX], path_tmp[PATH_MAX]; { char linkpath[PATH_MAX]; const char *oripath = getlinkpath(linkpath); strcpy (path,oripath); snprintf (path_tmp,sizeof(path_tmp)-1,"%s.TMP",oripath); snprintf (path_old,sizeof(path_old)-1,"%s.OLD",oripath); } int ret = -1; const char *argv[]={path,path_tmp}; if (module_sendmessage (updatefile,2,argv)!=-1){ bool end_ok = true; if ((intern->status & CONFIGF_NOOLD) == 0){ ::unlink(path_old); // If the original file do not exist, we must still // rename the temporary file in place. int link_ret = link(path, path_old); if (link_ret == -1 && errno != ENOENT){ end_ok = false; } } if (end_ok){ if (rename(path_tmp, path) != -1){ ret = 0; } } } return ret; } /* Open the temp configuration file with permission checking. It may even ask the user for the root password. */ PUBLIC FILE_CFG *CONFIG_FILE::fopen(const char *temp, const char *mode) const { return fopen (NULL,temp,mode); } /* Send the heading line telling that the file exist and the rest is following. This function must be used by all variation of the CONFIG_FILE::archive() function */ void configf_sendexist (SSTREAM &ss, bool do_exist) { if (do_exist){ ss.puts (ARCHIVE_FILEEXIST "\n"); }else{ ss.puts (ARCHIVE_NOFILE "\n"); } } PROTECTED VIRTUAL int CONFIG_FILE::archive(SSTREAM &ss) const { if (exist()){ configf_sendexist (ss,true); FILE_CFG *fin = fopen ("r"); char buf[3000]; while (fgets(buf,sizeof(buf)-1,fin)!=NULL){ ss.puts (buf); } ::fclose (fin); }else{ configf_sendexist (ss,false); } return 0; } static const char *cfg_command=NULL; static const char *cfg_arg=NULL; static bool cfg_verbose=true; static SSTREAM *cfg_ssout = NULL; /* Archive the config file if configured */ PUBLIC VIRTUAL int CONFIG_FILE::archive() const { int ret = -1; if (is_archived()){ const char *arch = confver_getfamily(getsubsys()); if (arch == NULL){ ret = 0; }else{ fixpath(); if (cfg_verbose){ net_prtlog (NETLOG_TITLE,MSG_U(L_ARCHIVING,"Archiving %s, version %s\n") ,intern->realpath,arch); configf_forgetpath(); fixpath(); } char args[2*PATH_MAX]; const char *argpath = intern->realpath; if (!context_isroot()){ argpath = intern->realpath + strlen(ui_context.basepath); } const char *comm = cfg_command == NULL ? "cfgarchive" : cfg_command; const char *carg = cfg_arg == NULL ? "--arch" : cfg_arg; snprintf (args,sizeof(args)-1,"%s %s \"%s\"",carg,argpath,arch); POPEN pop (comm,args); if (pop.isok()){ /* #Specification: config file / archiving / strategy Linuxconf is calling a command (user changeable) to archive various config file (or sometime parts of). It is opening a pipe to this command and sends the content of the file to it. The command receive the full path of the file as it is accessed on the file system. This allows the archiving mecanism to create an archive tree similar to the normal tree. For example, the supplied archiving mecanism of linuxconf create a sub-tree in /etc/linuxconf/archive where you will find the various RCS files in the etc subdirectory for example. The archiving command does not read directly the original. There are various reasons for that # The file may not exist at all. In that case, linuxconf is archiving that fact by sending a special content. When linuxconf is extracting the file, it finds out that the file did not exist at that time and can reproduce the same state. The archiving is fine grain. Some aspect of some files may be archived independently. Linuxconf creates "virtual files" which represent limited views of a physical file. So the actual name used for archiving is "virtual". # */ SSTREAM_POPEN ss(pop); ret = archive(ss); if (ret == 0) ret = pop.close(); if (cfg_ssout != NULL){ char line[1000]; while (pop.readout(line,sizeof(line)-1)==0){ cfg_ssout->puts (line); } } } } }else{ ret = 0; } return ret; } /* Compute the md5 checksum of the current configuration file */ PUBLIC VIRTUAL int CONFIG_FILE::md5sum(char *sum) { int ret = -1; fixpath(); POPEN pop ("md5sum",""); sum[0] = '\0'; if (pop.isok()){ SSTREAM_POPEN ss (pop); ret = archive(ss); if (ret == 0){ pop.close(); char line[100]; if (pop.readout(line,sizeof(line)-1)==0){ str_copyword (sum,line); ret = 0; } } } return ret; } static const char *extract_todir = "/"; PUBLIC VIRTUAL int CONFIG_FILE::extract(SSTREAM &ss) { int ret = -1; SSTRING tmp; if (strcmp(extract_todir,"/")==0){ tmp.setfrom (getpath()); }else{ tmp.setfromf ("%s/%s",extract_todir,getpath()); } FILE_CFG *fout = fopen(NULL,tmp.get(),"w"); if (fout != NULL){ char line[1000]; while (ss.gets(line,sizeof(line)-1) != NULL){ fputs (line,fout); } ret = fclose (fout); } return ret; } /* Replace the current config file with the one in the archive */ PUBLIC VIRTUAL int CONFIG_FILE::extract() { int ret = -1; fixpath(); if (is_archived()){ const char *arch = confver_getfamily(getsubsys()); if (arch != NULL){ net_prtlog (NETLOG_TITLE,MSG_U(L_UNARCHIVING,"Un-Archiving %s, version %s\n") ,intern->realpath,arch); char args[2*PATH_MAX]; snprintf (args,sizeof(args)-1,"%s %s %s",cfg_arg,intern->realpath,arch); POPEN pop (cfg_command,args); if (pop.isok()){ pop.closepipe(); if (pop.wait(10)>=0){ char line[300]; if (pop.readout(line,sizeof(line)-1) != -1){ if (strcmp(line,ARCHIVE_NOFILE "\n")==0){ net_prtlog (NETLOG_VERB ,MSG_U(I_ERASING,"File did not exist, erasing %s\n") ,intern->realpath); unlink(); ret = 0; }else if (strcmp(line,ARCHIVE_NOARCH "\n")==0){ net_prtlog (NETLOG_VERB ,MSG_U(I_KEEPINGOLD ,"File %s was never archived, keeping current copy\n") ,intern->realpath); ret = 0; }else if (strcmp(line,ARCHIVE_FILEEXIST "\n")==0){ SSTREAM_POPEN ss(pop); ret = extract (ss); }else{ xconf_error (MSG_U(E_EXTRFORM ,"File %s can't be extract properly.\n" "The first line of the archive is not\n" "a status line.") ,intern->realpath); net_prtlog (NETLOG_ERR,MSG_U(E_EXTRFORMLOG ,"File %s can't be extracted properly\n") ,intern->realpath); } }else{ net_prtlog (NETLOG_ERR,MSG_U(E_NOSTATUS,"Command produced no status: %s %s\n"),cfg_command,args); } }else{ net_prtlog (NETLOG_ERR,MSG_U(E_SHORTEXEC,"Command produced no output: %s %s\n"),cfg_command,args); } char line[300]; while (pop.readerr(line,sizeof(line)-1) != -1){ net_prtlog (NETLOG_ERR,"%s",line); } }else{ net_prtlog (NETLOG_ERR,MSG_U(E_CANTEXEC,"Can't execute %s %s"),cfg_command,args); } } }else{ ret = 0; } return ret; } PUBLIC int CONFIG_FILE::fclose (FILE_CFG *fout) { return ::fclose(fout); } /* Return the path of the configuration file including relocation by LINUXCONF_CONTEXT */ PUBLIC const char *CONFIG_FILE::getpath() const { fixpath(); return intern->realpath; } /* Return the localised (distribution dependent) path of the configuration file */ PUBLIC const char *CONFIG_FILE::getcnvpath() const { fixpath(); return intern->cnvpath; } /* Return the canonical path of the configuration file */ PUBLIC const char *CONFIG_FILE::getstdpath() const { fixpath(); return intern->stdpath; } /* Return the path of the help for that configuration file */ PUBLIC const char *CONFIG_FILE::gethelp() const { return intern->helpf.getpath(); } /* Return != 0 if the configuration file is managed by linuxconf. Some configuration file are only read (expect to be there and probably never edited by the user. */ PUBLIC int CONFIG_FILE::is_managed() const { return intern->status & CONFIGF_MANAGED; } /* Return != 0 if the configuration file is optional. */ PUBLIC int CONFIG_FILE::is_optional() const { return intern->status & CONFIGF_OPTIONAL; } /* Return != 0 if the configuration file is generated by linuxconf. */ PUBLIC int CONFIG_FILE::is_generated() const { return intern->status & CONFIGF_GENERATED; } /* Return != 0 if the configuration file is erased at boot time */ PUBLIC int CONFIG_FILE::is_erased() const { return intern->status & CONFIGF_ERASED; } /* Return != 0 if the configuration file is virtual */ PUBLIC int CONFIG_FILE::is_virtual() const { return intern->status & CONFIGF_VIRTUAL; } /* Return != 0 if the configuration file is archived (or may be archived) */ PUBLIC int CONFIG_FILE::is_archived() const { /* #Specification: config files / archiving / exception All config file which do not carry the CONFIGF_NOARCH are candidate for archiving. There are some exceptions. Any file in the following subdirectory are never archived # /proc /usr/lib/linuxconf /var/run /var/log # */ fixpath(); const char *realp = intern->realpath; return (intern->status & CONFIGF_NOARCH)==0 && strncmp(realp,"/proc/",6)!=0 && strncmp(realp,USR_LIB_LINUXCONF,strlen(USR_LIB_LINUXCONF))!=0 && strncmp(realp,"/var/run/",9)!=0 && strncmp(realp,"/var/log/",9)!=0 && !is_erased(); } /* Return != 0 if the configuration file is probed by linuxconf. probing is simply to check its modification time. */ PUBLIC int CONFIG_FILE::is_probed() const { return intern->status & CONFIGF_PROBED; } static int file_type (struct stat &st) { int ret = -1; if (S_ISREG(st.st_mode)){ ret = 0; }else if (S_ISDIR(st.st_mode)){ ret = 1; }else if (S_ISCHR(st.st_mode) || S_ISBLK(st.st_mode)){ ret = 2; }else if (S_ISLNK(st.st_mode)){ ret = 3; }else if (S_ISFIFO(st.st_mode)){ ret = 4; } return ret; } /* Check the type of a file or directory. If follow is 1, follow symbolic links. Return -1 if the path does not exist. Return 0 if this is a file 1 if this is a directory 2 if this is a device 3 if this is a symbolic link 4 if this is a fifo */ int file_type (const char *path, bool follow) { struct stat st; int ret = -1; if (path[0] == '\0' || strcmp(path,"/")==0){ ret = 1; }else if ((follow || lstat(path,&st)!=-1) && (!follow || stat(path,&st)!=-1)) { ret = file_type (st); } return ret; } int file_type (const char *path) { return file_type(path,false); } /* Check the type of a file or directory. Return -1 if the path does not exist. Return 0 if this is a file 1 if this is a directory 2 if this is a device 3 if this is a symbolic link 4 if this is a fifo This function uses stat instead of lstat */ int file_rtype (const char *path) { struct stat st; int ret = -1; if (path[0] == '\0' || strcmp(path,"/")==0){ ret = 1; }else if (stat(path,&st)!=-1){ ret = file_type (st); } return ret; } /* Check if the file or directory exist. Return true if yes. */ bool file_exist (const char *path) { return file_type (path)!=-1; } /* Check if the configuration file do exist. Return != 0 if yes. */ PUBLIC int CONFIG_FILE::exist() const { fixpath(); return file_exist (intern->realpath); } /* Return the modification date of a file */ long file_date (const char *path) { struct stat buf; long ret = -1; if (stat(path,&buf)!=-1){ ret = buf.st_mtime; } return ret; } /* Create a file with proper permission set. Request root privilege to do this */ int file_create ( const char *path, const char *owner, const char *group, int perm, PRIVILEGE *priv) { int ret = 0; if (!file_exist(path)){ CONFIG_FILE file (path,help_nil ,CONFIGF_MANAGED|CONFIGF_OPTIONAL ,owner,group,perm); FILE_CFG *fin = file.fopen (priv,"w"); if (fin != NULL){ ret = fclose (fin); }else{ ret = -1; } } return ret; } int file_create ( const char *path, const char *owner, const char *group, int perm) { return file_create (path,owner,group,perm,NULL); } /* Create the file empty with proper permission and ownership. Return -1 if any error */ PUBLIC int CONFIG_FILE::create(PRIVILEGE *priv) const { fixpath(); return file_create (intern->realpath,intern->owner,intern->group ,intern->perm,priv); } PUBLIC int CONFIG_FILE::create() const { return create (NULL); } /* Get the modification time of a configuration file. */ PUBLIC long CONFIG_FILE::getdate() const { fixpath(); return file_date (intern->realpath); } /* Erase a configuration file */ PUBLIC int CONFIG_FILE::unlink() const { fixpath(); return ::unlink (intern->realpath); } PUBLIC int CONFIG_FILE::editpath() { int ret = -1; SSTRING p(getpath()); DIALOG dia; dia.newf_str (MSG_U(F_CORRPATH,"Correct path"),p); int nof = 0; while (1){ MENU_STATUS code = dia.edit ( MSG_U(T_MODCONFPATH,"Modifying a config file path") ,MSG_U(I_MODCONFPATH ,"You can redefined the path of a\n" "configuration file. If you do so\n" "Linuxconf will use the new path from now on.\n" "\n" "Be advise that other utilities are on their\n" "own and may forget to notice. Unless you\n" "really knows what you are doing, don't play\n" "here.\n") ,intern->helpf ,nof ,MENUBUT_RESET|MENUBUT_ACCEPT|MENUBUT_CANCEL); if (code == MENU_ESCAPE || code == MENU_CANCEL){ break; }else if (code == MENU_RESET){ p.setfrom (intern->stdpath); dia.reload(); }else if (code == MENU_ACCEPT){ const char *stdp = getstdpath(); if (p.is_empty()) p.setfrom (stdp); forgetpath(); if (p.cmp(intern->stdpath)==0){ linuxconf_removeall (CONFIG,stdp); }else{ linuxconf_replace (CONFIG,stdp,p); } linuxconf_save(); ret = 0; break; } } return ret; } static int cmp_config(const void *pt1, const void *pt2) { CONFIG_FILE *p1 = *(CONFIG_FILE**)pt1; CONFIG_FILE *p2 = *(CONFIG_FILE**)pt2; return strcmp(p1->getstdpath(),p2->getstdpath()); } static int config_getsortedlist(CONFIG_FILE *tb[]) { CONFIG_FILE *f = first; int no = 0; while (f != NULL){ if (tb != NULL) tb[no] = f; no++; f = f->getnext(); } if (tb != NULL) qsort (tb,no,sizeof(CONFIG_FILE*),cmp_config); return no; } static void config_setflags (CONFIG_FILE *f, char type[10]) { strcpy (type," "); if (f->is_archived()) type[0] = 'A'; if (f->is_erased()) type[1] = 'E'; if (f->is_generated()) type[2] = 'G'; if (f->is_managed()) type[3] = 'M'; if (f->is_optional()) type[4] = 'O'; if (f->is_probed()) type[5] = 'P'; if (f->is_virtual()) type[6] = 'V'; } /* List all config file managed by this system */ void configf_show() { int nbconfig = config_getsortedlist(NULL); CONFIG_FILE *tb[nbconfig]; config_getsortedlist(tb); int choice = 0; DIALOG_RECORDS dia; dia.newf_head ("",MSG_U(H_CONFIGP,"Path\tStatus\tSubsys")); while (1){ for (int i=0; igetsubsys(); if (!f->is_archived()) subsys = ""; const char *path = f->getpath(); const char *stdp = f->getstdpath(); if (strcmp(path,stdp)!=0) type[7] = '*'; char buf[2*PATH_MAX]; snprintf (buf,sizeof(buf)-1,"%s\t%s",type,subsys); dia.set_menuitem (i,stdp,buf); } MENU_STATUS code = dia.editmenu ( MSG_U(T_LISTCONF,"List of configuration files") ,MSG_U(I_LISTCONF ,"This is the list of all file managed\n" "by linuxconf. For each file, you can access\n" "directly a help file describing its purpose.\n" "The letters preceding the file name indicate\n" "how this file is managed by Linuxconf. Press help\n" "for more info") ,help_configf ,choice,0); if (code == MENU_QUIT || code == MENU_ESCAPE){ break; }else{ CONFIG_FILE *cfgf = tb[choice]; if (cfgf->is_virtual()){ xconf_error (MSG_U(E_CFGVIRTUAL ,"You can't edit a virtual config file")); }else if (perm_rootaccess ( MSG_U(P_MODCONF,"modify a configuration file path"))){ cfgf->editpath(); } } } } /* Output on stdout the list of config file known to linuxconf with flags, sub-system and original path if the admin changed it. */ void configf_list() { // Setup the list of all config file, including dynamic one CONFIG_FILE *original_first = configf_calllisters(); int nbconfig = config_getsortedlist(NULL); CONFIG_FILE *tb[nbconfig]; config_getsortedlist(tb); for (int i=0; igetsubsys(); if (!f->is_archived()) subsys = ""; const char *path = f->getpath(); const char *stdp = f->getstdpath(); printf ("%s\t%s\t%s\t%s\n",path,type,subsys ,strcmp(path,stdp)== 0 ? "" : stdp); } // The CONFIG_FILE object are responsible from removing themselves // from the list while (first != original_first) delete first; } /* Obtain the list of all subsystems associated with configuration files The list is added to the tb object. Return the number of subsystem added to tb. */ int configf_getsubsyslist (SSTRINGS &tb) { int start = tb.getnb(); SSTRINGS tmp; { CONFIG_FILE *original = configf_calllisters(); CONFIG_FILE *f = first; while (f != NULL){ tmp.add (new SSTRING (f->getsubsys())); f = f->getnext(); } tmp.sort(); tmp.remove_dups(); while (first != original) delete first; } for (int i=0; iget())); } return tb.getnb()-start; } /* Get the list of configuration files member of a sub-system */ int configf_getsubsysmembers(const char *subsys, SSTRINGS &tb) { CONFIG_FILE *original = configf_calllisters(); CONFIG_FILE *f = first; while (f != NULL){ if (f->is_archived() && strcmp(f->getsubsys(),subsys)==0){ tb.add (new SSTRING (f->getstdpath())); } f = f->getnext(); } while (first != original) delete first; return tb.getnb(); } /* Erase some config file at boot time. */ void configf_booterase() { CONFIG_FILE *f = first; while (f != NULL){ if (f->is_erased()) f->unlink(); f = f->getnext(); } } /* Archive all config file member of a subsystem */ static int configf_archiveone ( const char *name) // Sub-system or file if it starts with a slash { int ret = 0; CONFIG_FILE *f = first; bool isfile = name[0] == '/'; while (f != NULL){ if (isfile){ if (strcmp(name,f->getpath())==0){ ret |= f->archive(); break; } }else if (strcmp(f->getsubsys(),name)==0){ ret |= f->archive(); } f = f->getnext(); } return ret; } int configf_archive( const SSTRINGS &tb, // Sub-system or files to archive // (A file starts with a /) const char *command, // Archiving command (just the name) // normally cfgarchive const char *arg, // First argument of the command // normally --arch SSTREAM &ssout, bool verbose) { cfg_command = command; cfg_arg = arg; cfg_verbose = verbose; cfg_ssout = &ssout; int ret = 0; if (verbose){ net_prtlog (NETLOG_SECTION,MSG_U(I_ARCHCONF,"Archiving %s\n") ,confver_getcur()); } CONFIG_FILE *original_first = configf_calllisters(); int nbsys = tb.getnb(); for (int i=0; iget(); ret |= configf_archiveone (sub); if (sub[0] != '/') linuxconf_archive (sub); } // The CONFIG_FILE object are responsible from removing themselves // from the list while (first != original_first) delete first; cfg_command = NULL; cfg_arg = NULL; cfg_ssout = NULL; return ret; } /* Extract all config file member of a subsystem */ static int configf_extractone ( const char *name, // Subsystem name or file path CONFIG_FILE *end, // Stop when we reach this CONFIG_FILE bool &done) // Was it extracted { int ret = 0; CONFIG_FILE *f = first; bool isfile = name[0] == '/'; while (f != end){ if (isfile){ if (strcmp(name,f->getcnvpath())==0){ done = true; ret |= f->extract(); break; } }else if (strcmp(f->getsubsys(),name)==0){ done = true; ret |= f->extract(); } f = f->getnext(); } return ret; } /* Extract all the file of the current configuration */ int configf_extract ( const char *todir, const SSTRINGS &tb, // Sub-systems or files (starts with a /) const char *command, // Archiving command (just the name) // normally cfgarchive const char *arg) // First argument of the command // normally --extr { extracting = true; cfg_command = command; cfg_arg = arg; extract_todir = todir; net_prtlog (NETLOG_SECTION,MSG_U(I_EXTRCONF,"Extracting %s\n") ,confver_getcur()); /* #Specification: config versionning / extraction / principle Extracting a configuration is difficult because ultimatly we have no clue what must be extracted. For example, we may archive a lot of .dconf file (PPP/Slip dialout config) but we have no clue which one to extract. The same apply to the DNS configuration. We know how to extract the /etc/named.conf but without its content, we have no clue about the various zone file. Extraction is done by iteration. First we extract the well known config file. As a side effect of the extraction, the various CONFIG_FILE are free to define new CONFIG_FILE object as they learn about them. New CONFIG_FILE are recorded at the beginning of the linked list. So by running the process until all file have been extracted and no new CONFIG_FILE has been added, we end with a working solution. */ bool tbdone[tb.getnb()]; memset (tbdone,0,sizeof(tbdone)); int ret = 0; CONFIG_FILE *original_first = first; CONFIG_FILE *start = NULL; int nbsys = tb.getnb(); while (first != start){ CONFIG_FILE *end = start; start = first; for (int i=0; iget() ,end,tbdone[i]); } } } /* #Specification: config versionning / extraction / principle part 2 After doing some iteration, we may have extracts all needed sub-systems and files. But we may have missed some. Here is a case. We can to extract /var/named/solucorp.qc.ca. This is a DNS zone file. We only learn about this zone file if we extract /etc/named.conf. Now, if we do not extract the dnsserv sub-system (which own /etc/named.conf) or /etc/named.conf, we will never learn about the zone file. So it won't be extracted. After doing the iteration explain above, where we extract files and sub-systems, checking if new config file appears, we may still have some sub-system and files not extracted. THis time, we trigger the config file listers and we try again to perform the extraction. */ for (int i=0; iget() ,end,tbdone[i]); } } break; } } for (int i=0; iget(); bool is_subsys = name[0] != '/'; if (!tbdone[i]){ if (is_subsys){ xconf_error (MSG_U(E_HOWEXTRACTSUB ,"Don't know how to extract %s: unknown sub-system") ,name); }else{ xconf_error (MSG_U(E_HOWEXTRACTFILE ,"Don't know how to extract %s:" " not member of any sub-system") ,name); } }else if (is_subsys){ ret |= linuxconf_extract (name); } } linuxconf_save(); while (first != original_first) delete first; cfg_command = NULL; cfg_arg = NULL; extract_todir = "/"; extracting = false; return ret; } /* Do the md5 checksum of all config file member of a subsystem */ static int configf_md5sumone (const char *subsys, SSTREAM &ss) { int ret = 0; CONFIG_FILE *f = first; while (f != NULL){ if (strcmp(f->getsubsys(),subsys)==0 && f->is_archived()){ char sum[100]; ret |= f->md5sum(sum); ss.printf ("%s\t%s\n",f->getpath(),sum); } f = f->getnext(); } return ret; } /* Do the md5 check sum all the file of the current configuration */ int configf_md5sum (const SSTRINGS &tb, SSTREAM &ss) { int ret = 0; CONFIG_FILE *original_first = configf_calllisters(); int nbsys = tb.getnb(); for (int i=0; iget(); ret |= configf_md5sumone (sub,ss); linuxconf_md5sum (sub,ss); } // The CONFIG_FILE object are responsible from removing themselves // from the list while (first != original_first) delete first; return ret; } /* Locate a CONFIG_FILE based on its real (fixed) path Return NULL if it does not exist */ CONFIG_FILE *configf_locate (const char *path) { CONFIG_FILE *ret = NULL; CONFIG_FILE *pt = first; while (pt != NULL){ if (strcmp(path,pt->getpath())==0){ ret = pt; break; } pt = pt->getnext(); } return ret; } PUBLIC CONFIG_FILE *CONFIG_FILES::getitem (int no) const { return (CONFIG_FILE*)ARRAY::getitem(no); }