/* #Specification: boot process / sysv init scripts / strategy Linuxconf does not have a "boot" command. Well, did not have one. The strategy is that at any time, the command (or when you quit from linuxconf) "linuxconf --update" can compute the proper set of action to bring the current configuration active. For example, if you do "linuxconf --update" twice, it will do something the first time and do nothing the next. This is a major concept. Rebooting should never be needed. This strategy rely on two technologies used by linuxconf: The builtin services and the dropins. Both technologies provide enough information to linuxconf allowing it to compare the state of a service and its current configuration. If there is a mismatch, linuxconf knows enough to do the proper thing (kill, restart, signal, whatever). So at boot time, a single "linuxconf --update" is needed in the proper RC script and linuxconf will bring all the required service up. There is a problem with this strategy: It is new and different from anything else in the industry (Not only linux, not only Unix). Many distribution are now supporting the SysV init script (Unix system V). Those scripts are organised in several directories: One per init runlevel. Each directory contains a bunch of script which defines what have to be started and what have to be stopped in that specific runlevel. The sysv strategy is handy for package developpers. When you install a package, it adds a new file in the proper runlevel directories and the system will know how to start/initialise this new package at the next boot. This concept is similar to the dropins of linuxconf except that those scripts do not carry much information about the processes/systems they configure. The end result is that a monitor like linuxconf has no way to associate one of those scripts with configuration files and processes states. Those scripts are there mainly to start the systems at boot time and that's about it. Yet, this is the technology out there. So linuxconf must be able to provide its enhancements where possible and support those scripts when it does not know better. Here is the strategy. Linuxconf now support a new command line option # netconf --bootrc path_of_the_rc_directory [ path_of_previous_rc_dir ] # This is almost the same as "netconf --update", but with a twist. Each service start by linuxconf fall in one of three category # -builtin linuxconf service -dropins -sysv init script # Using a special configuration file (one supplied per supported distribution), linuxconf is able to associate a sysv init script to the builtin service. dropin will have the same name as the sysv init script they are enhancing (dropin will often be using the sysv init script they are enhancing as the start/stop/restart method btw). So the strategy goes like this. Linuxconf reads the directory content and collect all file in it. There are the start script (The S scripts) and the stop scripts (K scripts). The strategy to start the S scripts in the proper order so they intermixed with linuxconf builtin and dropins goes like this. For each service linuxconf start (a builtin or a dropin), it walk the list of RC script to find the equivalent. It then starts all the scripts which follows that one and do not have a linuxconf equivalent. At the end of the linuxconf's processing, all scripts that were nore processed are done. There should not be any, yet the ordering of the SysV scripts is not exact nor the one of linuxconf. Also before processing its own rules and dropins, linuxconf walk the list of scripts from the beginning and start each script until it hits one with a linuxconf's equivalence. When running a script, linuxconf write to its log and collect all error message and return code. Plus it will use (as with other linuxconf commands) a timeout allowing the admin to recover control when a script take to much time to start. */ /* #Specification: boot process / sysv init scripts / variation Most Linux distribution are using the Sysv init script strategy. Yet they do it slightly differently. Linuxconf supports all known variation. Here they are: The redhat way: Each runlevel directory (/etc/rc.d/rc$RUNLEVEL.d) contain a list of things that should run and things that should not run. So most runlevel directory is somewhat selfcontain. The scripts in redhat knows if they have been activated or not so there is no problem calling a Kscript is the service was not already active. So we run all the Kscripts and then all the Sscripts The SuSE way: SuSE use probably a more elegant strategy. A runlevel tells us how to start the services and how to stop them. Ultimatly, for each Sscript in a runlevel directory, there is a corresponding Kscript. To effectivly move from one runlevel to the other in SuSE, we need to know the previous runlevel and the target runlevel. By comparing the two directories, we run all Kscripts which are in the previous runlevel directory and for which there is no corresponding Sscripts in the target runlevel (no need to stop and restart for nothing). Then we run all the Script in the target runlevel for which there are no Script in the previous runlevel. Linuxconf differentiate the two strategies using a simple trick. If the --bootrc option gets one argument, this is the redhat way. If it gets two, this is the SuSE way. */ #include #include #include #include #include #include #include #include #include "netconf.h" #include "netconf.m" #include "internal.h" static NETCONF_HELP_FILE helpf ("sysv"); static CONFIG_FILE f_equiv (USR_LIB_CONF_SCRIPTS,helpf ,CONFIGF_OPTIONAL); CONFIG_FILE f_rcd (VAR_RUN_RUNLEVEL_DIR,help_nil ,CONFIGF_OPTIONAL|CONFIGF_NOARCH|CONFIGF_MANAGED); PUBLIC CONFIG_SYSV::CONFIG_SYSV (const char *str, int _autoreload) { autoreload = _autoreload != 0; path.setfrom (str); } PUBLIC CONFIG_SYSV *CONFIG_SYSVS::getitem(int no) const { return (CONFIG_SYSV*)ARRAY::getitem(no); } PUBLIC void CONFIG_SYSVS::remove_empty() { for (int i=0; ipath.is_empty()){ remove_del (s); i--; } } } PRIVATE void BOOTRC::init(const char *name, const char *equiv) { this->name.setfrom (name); this->equiv.setfrom (equiv); ctrl.name.setfrom (name); ctrl.desc.setfrom (name); was_processed = false; start_cmd = true; pipes.in = pipes.out = pipes.err = -1; } PUBLIC BOOTRC::BOOTRC(const char *name, const char *equiv) { init(name,equiv); } static bool bootrc_parsebool (const char *line) { line = str_skip(line); return stricmp(line,"true")==0; } static void bootrc_addstr (SSTRINGS &tb, const char *line) { line = str_skip(line); if (line[0] != '\0') tb.add (new SSTRING(line)); } PRIVATE void BOOTRC::parseenh (char *line) { line = str_skip(line+1); strip_end (line); if (strncmp(line,"autoreload:",11)==0){ ctrl.no_reload = bootrc_parsebool (line+11); }else if (strncmp(line,"processname:",12)==0){ bootrc_addstr(ctrl.processes,line+12); }else if (strncmp(line,"pidfile:",8)==0){ bootrc_addstr(ctrl.pidfiles,line+8); }else if (strncmp(line,"config:",7)==0){ ctrl.addmonitor (line+7); }else if (strncmp(line,"probe:",6)==0){ if (bootrc_parsebool (line+6)){ ctrl.cmd.probe.setfrom (path); } }else if (strncmp(line,"description:",12)==0){ description.setfrom (line+12); }else if (strncmp(line,"override:",9)==0){ // This one only happen in override sysv file located // in /usr/lib/linuxconf/DIST/sysv. This tells that the file // completly supplement the normal script. Linuxconf will execute // that one instead ctrl.override = bootrc_parsebool (line+9); } } PRIVATE void BOOTRC::parseintro (FILE *fin) { char accum[10000],buf[1000]; accum[0] = '\0'; while (fgets(buf,sizeof(buf)-1,fin)!=NULL){ if (buf[0] != '#') break; strip_end (buf); int last = strlen (buf) - 1; bool cont = false; if (buf[last] == '\\'){ buf[last] = '\0'; cont = true; } if (accum[0] != '\0'){ char *pt = str_skip (buf + 1); strcat (accum,pt); }else{ strcpy (accum,buf); } if (!cont){ parseenh(accum); accum[0] = '\0'; } } if (accum[0] != '\0'){ parseenh (accum); } } PUBLIC BOOTRC::BOOTRC( bool start_cmd, const char *path, const char *name, const char *equiv) { init(name,equiv); this->start_cmd = start_cmd; this->path.setfrom (path); if (start_cmd){ // This serves also for non enhanced sysv script, but barely char buf[PATH_MAX+20]; snprintf (buf,sizeof(buf)-1,"%s start",path); ctrl.cmd.start.setfrom (buf); /* #Specification: sysv script support / enhancements Sysv init scripts may be enhanced to provide more information to linuxconf. This information is telling linuxconf # -process names which are started (daemons) by this script -related PID files -description -config file to monitor -Various information about the behavior of the package (no need to restart it, ...) # This information is encoded using special comments at the beginning of the script. The first non comment line is ending the "enhanced" section. The general presentation is # variable: value See the document http://www.solucorp.qc.ca/linuxconf/tech/sysv */ FILE *fin = fopen (path,"r"); if (fin != NULL){ parseintro (fin); fclose (fin); /* #Specification: sysv scripts support / extension file Most distribution out there do not support the enhanced sysv script yet. This does not help linuxconf. Further, the distribution which support those scripts sometime deliver a buggy one. For each sysv, there is optionally one file with the same name in the directory /usr/lib/linuxconf/DIST/sysv. If this file exist, it may add information to the sysv script and potentially overrides it completly. */ char altern1[PATH_MAX],altern2[PATH_MAX]; snprintf (altern1,sizeof(altern1)-1,"/usr/lib/linuxconf/DIST/sysv/%s",name); linuxconf_fixdistdir (altern1,altern2); fin = fopen (altern2,"r"); if (fin != NULL){ parseintro (fin); fclose (fin); if (ctrl.override){ this->path.setfrom (altern2); path = altern2; snprintf (buf,sizeof(buf)-1,"%s start",path); ctrl.cmd.start.setfrom (buf); } } snprintf (buf,sizeof(buf)-1,"%s stop",path); ctrl.cmd.stop.setfrom (buf); if (distrib_getvalnum("sysvrestart",0)){ snprintf (buf,sizeof(buf)-1,"%s restart",path); ctrl.cmd.reload.setfrom (buf); } ctrl.init_command(); } } } PUBLIC bool BOOTRC::isenhanced() { return ctrl.processes.getnb() > 0 || ctrl.pidfiles.getnb() > 0 || !ctrl.cmd.probe.is_empty(); } PUBLIC int BOOTRC::run(bool probe) { int ret = 0; if (!was_processed && start_cmd){ was_processed = true; if (isenhanced()){ // This is an enhanced sysv script ret = ctrl.startif(); }else if (!probe){ /* #Specification: sysv init script / weirdness Sysv init script as discussed earlier are an inexpensive way of starting a service at boot time. Creating init script is easy: Too easy. Many scripts are brain dead: They go immediatly in background allowing another script to start, but continue to write stuff on standard i/o, potentially useful error message which get all mixed up with the messages from the following scripts. Under linuxconf, things are even worse. Linuxconf start the service and collect all message sent until the service terminate (and leave a daemon in background). That's fine. Then Linuxconf close the various pipes it uses to collect those messages. And then the broken init script try to talk again on this closed pipe. It receives a SIGPIPE and kill itself :-( The only way to cure that is not to close the pipes immediatly but wait a few seconds. This will delay the startup for no very good reason (it will be annoying). Instead we delay the closing of the pipes, but start the following init script immediatly. The pipes are closed later after checking if they contain further messages. */ ret = netconf_system (120,ctrl.cmd.start.get()); #if 0 ,&pipes.in,&pipes.out,&pipes.err); pipes.date = time(NULL); #endif } } return ret; } #if 0 /* Checks if other message were sent by the "broken" init script while in background */ PUBLIC void BOOTRC::getlastmsgs() { if (pipes.out != -1){ int waittime = 5 - (time(NULL) - pipes.date); if (waittime > 0) sleep (waittime); close (pipes.in); bool title_done = false; const char *nam = name.get(); netconf_readpipe (NETLOG_OUT,pipes.out,nam,title_done); netconf_readpipe (NETLOG_ERR,pipes.err,nam,title_done); close (pipes.out); close (pipes.err); pipes.in = pipes.out = pipes.err = -1; } } #endif PUBLIC void BOOTRC::stop() { if (!was_processed && !start_cmd){ was_processed = true; char cmd[PATH_MAX + 20]; snprintf (cmd,sizeof(cmd)-1,"%s stop",path.get()); netconf_system (8,cmd); } } PUBLIC BOOTRCS::BOOTRCS(bool _probe) { probe = _probe; lastsysv = 0; } PUBLIC BOOTRC *BOOTRCS::getitem(int no) { return (BOOTRC*)ARRAY::getitem(no); } /* Locate a command by name */ PUBLIC BOOTRC *BOOTRCS::getitem (const char *name) { BOOTRC *ret = NULL; int n = getnb(); for (int i=0; iname.cmp(name)==0){ ret = b; break; } } return ret; } /* Locate a command by name. Locate either a start command or stop command */ PUBLIC BOOTRC *BOOTRCS::getitem (bool start_cmd, const char *name) { BOOTRC *ret = NULL; int n = getnb(); for (int i=0; iname.cmp(name)==0 && b->start_cmd == start_cmd){ ret = b; break; } } return ret; } /* Read the scripts-equiv for the distribution This file tells which sysv script are equivalent to a builtin service of linuxconf. Builtin services takes precedance. Note that on enhanced distribution (linuxconf aware), the scripts-equiv file is not used since the builtin services are disabled (generally turned to --hint) */ PRIVATE void BOOTRCS::readequiv() { if (!distrib_isenhanced()){ char path[PATH_MAX]; linuxconf_fixdistdir (USR_LIB_CONF_SCRIPTS,path); FILE_CFG *fin = f_equiv.fopen(path,"r"); if (fin != NULL){ char buf[300]; while (fgets(buf,sizeof(buf)-1,fin)!=NULL){ strip_end(buf); char script[PATH_MAX],equiv[PATH_MAX]; if (sscanf (buf,"%s %s",script,equiv)==2){ add (new BOOTRC (script,equiv)); } } fclose (fin); } dropin_addbootequiv (*this); } } PUBLIC void BOOTRCS::readdir(const char *dirpath, const char *prevpath) { BOOTRCS conf(false); conf.readequiv(); for (int d=0; d<2 && dirpath != NULL; d++, dirpath=prevpath){ // We collect the Kscript if // - there is only one directory specified (RedHat way) // - there are two (SuSE way) and we are processing // the second (the previous runlevel) bool collect_k = d == 1 || prevpath == NULL; SSTRINGS tb; int n = dir_getlist (dirpath,tb); tb.sort(); for (int i=0; iget(); if (strstr(s,".rpmsave")==NULL && strstr(s,".rpmorig")==NULL){ const char *name = s + 3; char path[PATH_MAX]; snprintf (path,sizeof(path)-1,"%s/%s",dirpath,s); bool start_cmd = s[0] == 'S'; if (start_cmd || (s[0] == 'K' && collect_k)){ BOOTRC *eq = conf.getitem (name); const char *equiv = NULL; if (eq != NULL){ equiv = eq->equiv.get(); } if (d == 0){ add (new BOOTRC(start_cmd,path,name,equiv)); }else{ // Check if there is already a corresponding // Sscript in the table BOOTRC *b = getitem (true,name); if (start_cmd){ // Ok, there is a start script and the previous // runlevel do contain the same, so no need // to start it remove_del (b); // b may be NULL, remove_del is ok }else if (b != NULL){ // Ok, this is a Kscript, but there a Sscript // in the target runlevel // So we do nothing }else{ // Ok, a Kscript in the previous runlevel // and no Script in the target runlevel add (new BOOTRC(start_cmd,path,name,equiv)); } } } } } } /* #Specification: sysv init script / the last stretch Linuxconf executes the sysv init script in the proper order. It mix with them the various initialisation steps of the internal services and dropins of linuxconf. The distribution specific file scripts-equiv provide an equivalence list of sysv script vs linuxconf services. So each sysv init script has an equivalent linuxconf counterpart or not. So mostly the sysv init script list looks like # script1 linuxconf-equivalence script2 no equiv script3 no equiv script4 linuxconf-equivalence script5 linuxconf-equivalence script6 no equiv script7 no equiv script8 no equiv # So linuxconf starts his own services and for each one, lookup the equivalent sysv init script. If it finds one, it executes all the following init scripts which have no linuxconf equivalence. In the list above, linuxconf would execute in that order # linuxconf services equivalent to script1 script2 script3 linuxconf services equivalent to script4 linuxconf services equivalent to script5 script6 script7 script8 rest of linuxconf services # There is a problem with this strategy. The last scripts will be executed right after the equivalent of script5. While the ordering of the sysv script is ok, some of them may rely on some initialisation done by linuxconf but which is not done yet. The problem is even worse on system where the service 4 and 5 have not been installed. The means that script 2,3,6,7,8 will be executed much too soon. The strategy has been enhanced to avoid this problem. Mostly the last trunk of sysv init scripts without linuxconf equivalence are exexecuted after the last linuxconf services. This means that the example above would become. # linuxconf services equivalent to script1 script2 script3 linuxconf services equivalent to script4 linuxconf services equivalent to script5 rest of linuxconf services script6 script7 script8 # */ lastsysv = getnb(); for (int i=lastsysv-1; i>=0; i--){ BOOTRC *rc = getitem(i); if (!rc->equiv.is_empty()){ lastsysv = i+1; break; } } } /* Read the name of all the sysv script in a directory */ PUBLIC void BOOTRCS::readall(const char *dirpath) { SSTRINGS tb; int n = dir_getlist (dirpath,tb); tb.sort(); for (int i=0; iget(); if (strstr(name,".rpmsave")==NULL){ char path[PATH_MAX]; snprintf (path,sizeof(path)-1,"%s/%s",dirpath,name); if (file_type(path)==0){ add (new BOOTRC(true,path,name,"")); } } } } /* Run all the sysv script which follow this "equivalent" service */ PUBLIC int BOOTRCS::startsome (const char *from) { int ret = 0; bool title_done = false; for (int i=0; iname.cmp(from)==0){ // Start all the scripts up the next one which has an // equivalent builtin or dropin // If the equivalent script is enhanced, this means that // the internal hability of linuxconf has not been used // so the script is processed also. if (!b->isenhanced()){ b->was_processed = true; i++; } for (int j=i; jequiv.is_empty() && !b->isenhanced()) break; if (!title_done){ net_title (MSG_U(T_EXECSYSV ,"Executing some Sysv init scripts")); title_done = true; } ret |= b->run(probe); } break; } } return ret; } #if 0 /* Is there an equivalent sysv init script, enhanced Linuxconf is calling this function to know if a builtin service must let a sysv init script handle the task. If there is an equivalent sysv init script, but not enhanced, we are better to let linuxconf fully handle the task since old sysv script can't "probe" (They are only useful at boot time) *** This was removed. A distribution is either enhanced or not. Mixing has proven not to work to well */ PUBLIC bool BOOTRCS::hasequiv(const char *name) { bool ret = false; int n = getnb(); for (int i=0; iequiv.cmp(name)==0){ ret = b->isenhanced(); break; } } return ret; } #endif /* Run all the script which have not been so far. */ PUBLIC void BOOTRCS::startrest () { bool title_done = false; int n=getnb(); int i; for (i=0; iwas_processed && b->equiv.is_empty()){ if (!title_done){ net_title (MSG_R(T_EXECSYSV)); title_done = true; } b->run(probe); } } } /* Execute the K Sysv stop scripts for the runlevel */ PUBLIC void BOOTRCS::dostopcmds() { int n=getnb(); for (int i=0; istart_cmd && b->equiv.is_empty()) b->stop(); } } int bootrc_do ( const char *dirpath, // Directory for the target runlevel const char *prevpath, // Directory for the previous runlevel (or NULL) bool booting) { BOOTRCS rcs(false); rcs.readdir (dirpath,prevpath); if (!booting) rcs.dostopcmds(); netconf_runlevel (-1,rcs,true); /* #Specification: current runlevel / remembering the path At boot time and at runlevel change time, linuxconf is called with the path of the new runlevel directory holding all the Sysv init script. It keeps a copy of this path in /var/run/runlevel.dir, so it can check enhanced Sysv script at probe time (checking for configuration changes requiring actions). */ FILE_CFG *fout = f_rcd.fopen ("w"); if (fout != NULL){ fprintf (fout,"%s\n",dirpath); fclose (fout); } return 0; } static void bootrc_lister_fct() { /* #Specification: sysv init scripts / file archiving / principle (the same spec apply to dropins) All config file specified in a sysv script participate in the configuration versionning. Quite often, a module define itself few CONFIG_FILE object which are the same one defined in the sysv script itself. The module may have a special way to archive these config file. So the file defined in the sysv script are defined as CONFIG_FILE only if they are not already by some modules. For each sysv script, a subsystem is defined with the name of the script. A 'subsys' tag could be added in the sysv script to associate several package in the same sub-system. Probably not useful. Future will tell. */ const char *sysvdir = configf_lookuppath("/etc/rc.d/init.d"); if (file_type(sysvdir)==1){ BOOTRCS rcs (false); rcs.readall (sysvdir); static DROPIN_SUBSYSS subs; int n = rcs.getnb(); subs.alloc (n); if (subs.tb != NULL){ for (int i=0; ictrl.list_config(subs); } } } } static CONFIG_FILE_LISTER bootrc_lister(bootrc_lister_fct);