#include #include #include #include #include #include #include #include #include #include #include #include #include "netconf.h" #include "netconf.m" #include #include "../main/main.h" #include #include "internal.h" static NETCONF_HELP_FILE help_toolong ("toolong"); /* Return != 0 if a file is missing or do not contain any configuration line. A file containing only comments is considered empty. configuration file use \ for continuation and # for comments. */ int file_empty (const char *fpath) { int ret = 1; FILE *fin = fopen (fpath,"r"); if (fin != NULL){ char buf[2000]; while (fgets_strip(buf,sizeof(buf)-1,fin,NULL)!=NULL){ char *pt = buf; while (isspace(*pt)) pt++; if (*pt != '\0'){ ret = 0; break; } } fclose (fin); } return ret; } /* Information on how to manage and start a daemon (generally network) To avoid repetition in subclasses, the destructor received no argument and the init function complete the setup, so the object must always be created and init() immediatly after. This is all done in daemon_new(). */ PUBLIC DAEMON::DAEMON () { restart_signal = -1; } static void daemon_getpomsg(POPEN &po) { while (1){ char buf[5000]; if (po.readout (buf,sizeof(buf)-1) != -1){ strip_end (buf); net_prtlog (NETLOG_OUT,"%s\n",buf); }else if (po.readerr (buf,sizeof(buf)-1) != -1){ strip_end (buf); net_prtlog (NETLOG_ERR,"%s\n",buf); }else{ break; } } } static char session_mode = 0; static char was_config = 0; /* Record the operation mode -Are we doing many config things in a row or a single one. */ void daemon_setsession(char mode) { session_mode = mode; was_config = 0; } /* Was there a config done during the activation session */ int daemon_wasconfig () { return was_config; } /* Execute a command using the shell with loging in /tmp/netconf.log if grabhandles[] is not NULL, then the PIPEs used by the POPEN object will be grabed (so won't be close immediatly). See comment in bootrc.c near the BOOTRC::run function. */ int netconf_system ( int timeout, const char *cmd, int *grabin, // Grab the stdin of the POPEN object (or NULL) int *grabout, // Grab the stdout int *graberr) // { /* #Specification: netconf / starting daemons / log The file /var/adm/netconf.log is updated each time netconf start or stop a daemon. */ net_prtlog (NETLOG_CMD,MSG_U(X_EXECUTING,"Executing: %s\n"),cmd); int ret = 0; if (!simul_ison()){ int report_status = 1; POPEN po (cmd); int code=0; int count = 0; int logonce = false; bool done = false; while (!done && (code=po.wait(1))!=-1){ if (code == 0){ count++; if (count == timeout){ if (!logonce){ net_prtlog (NETLOG_ERR ,MSG_U(E_TOOLONG ,"The command takes more than %d seconds to execute, odd\n") ,timeout); logonce = true; } count = 0; DIALOG dia; dia.settype (DIATYPE_POPUP); char action=0; if (session_mode){ dia.newf_radio (MSG_U(F_SKIP,"Skip"),action,0 ,MSG_U(I_SKIP,"this command")); dia.newf_radio (MSG_U(F_ABORT,"Abort"),action,1 ,MSG_U(I_ABORT,"this session")); } char killcmd=0; dia.newf_chk (MSG_U(F_KILLCMD,"Kill"),killcmd ,MSG_U(I_KILLCMD,"this command")); char buf[2000]; // Truncate the command so it fits the dialog char tmpcmd[70]; strncpy (tmpcmd,cmd,sizeof(tmpcmd)-1); tmpcmd[sizeof(tmpcmd)-1] = '\0'; snprintf (buf,sizeof(buf)-1,MSG_U(I_CMDTIMEOUT ,"The command\n\n%s\n\n" "is taking longer than expected to complete\n" "Please take action"),tmpcmd); dia.setbutinfo (MENU_USR1,MSG_U(B_CONFIG,"Config") ,MSG_U(X_CONFIG,"Config")); int nof = 0; while (1){ MENU_STATUS code = dia.edit (MSG_U(T_CMDTIMEOUT,"Too long") ,buf,help_toolong ,nof ,MENUBUT_ACCEPT|MENUBUT_CANCEL|MENUBUT_USR1); if (code == MENU_ACCEPT){ report_status = 0; if (session_mode && action == 1){ ret = -1; simul_setdisable (1); } if (killcmd){ po.kill(); net_prtlog (NETLOG_ERR,"%s\n" ,MSG_U(E_KILLED,"killed by operator")); }else{ po.forget(); net_prtlog (NETLOG_ERR,"%s\n" ,MSG_U(E_LEFTBG,"Left running in the background")); } done = true; break; }else if (code == MENU_USR1){ if (perm_checkpass()){ linuxconf_main(true); if (session_mode){ simul_setdisable(1); po.kill(); net_prtlog (NETLOG_ERR,"%s\n" ,MSG_R(E_KILLED)); was_config = 1; done = true; break; } } }else{ po.forget(); net_prtlog (NETLOG_ERR,"%s\n",MSG_R(E_LEFTBG)); done = true; break; } } } }else{ daemon_getpomsg(po); } } daemon_getpomsg(po); ret = po.getstatus(); if (report_status && ret != 0){ net_prtlog (NETLOG_ERR,MSG_U(S_RETURN,"return %d\n"),ret); } if (grabin != NULL) po.grabhandles(*grabin,*grabout,*graberr); } return ret; } class PIPEINFO: public ARRAY_OBJ{ int in,out,err; // stdin,out,err used to start the init script long date; // time at which the command was activated SSTRING command; /*~PROTOBEG~ PIPEINFO */ public: PIPEINFO (const char *cmd, int _in, int _out, int _err); void getlastmsg (bool dowait); /*~PROTOEND~ PIPEINFO */ }; PUBLIC PIPEINFO::PIPEINFO( const char *cmd, int _in, int _out, int _err) { command.setfrom (cmd); in = _in; out = _out; err = _err; date = time(NULL); } void netconf_readpipe ( int msglevel, // NETLOG_XXX int fd, // Pipe to read const char *name, // name of the sysv init script or command bool &title_done) // Was the title printed { fcntl (fd,F_SETFL,O_NDELAY); FILE *fin = fdopen (fd,"r"); if (fin != NULL){ char buf[1000]; while (fgets(buf,sizeof(buf)-1,fin)!=NULL){ strip_end (buf); if (!title_done){ title_done = true; net_prtlog (NETLOG_TITLE,MSG_U(T_LATEOUTPUT ,"Grabbing some late messages from command %s\n") ,name); } net_prtlog (msglevel,"%s\n",buf); } fclose (fin); } } PUBLIC void PIPEINFO::getlastmsg(bool dowait) { if (dowait){ int waittime = 2 - (time(NULL) - date); if (waittime > 0) sleep (waittime); } close (in); bool title_done = false; const char *cmd = command.get(); netconf_readpipe (NETLOG_OUT,out,cmd,title_done); netconf_readpipe (NETLOG_ERR,err,cmd,title_done); close (out); close (err); } class PIPEINFOS: public ARRAY{ /*~PROTOBEG~ PIPEINFOS */ public: PIPEINFO *getitem (int no)const; /*~PROTOEND~ PIPEINFOS */ }; PUBLIC PIPEINFO *PIPEINFOS::getitem(int no) const { return (PIPEINFO*)ARRAY::getitem(no); } static PIPEINFOS infos; EXPORT int netconf_system (int timeout, const char *cmd) { int in=-1,out=-1,err=-1; int ret = netconf_system (timeout,cmd,&in,&out,&err); if (out != -1){ // Avoid using too many file handles // This situation only happen when setting tons of IP aliases if (infos.getnb() >= 30) netconf_getlastmsgs(); infos.add (new PIPEINFO (cmd,in,out,err)); } return ret; } /* Close all pipes (for the last netconf_system_if) and grab late messages but wait at least 2 seconds from the command startup time to make sure the command has completed. See the message in bootrc::run. */ void netconf_getlastmsgs() { for (int i=0; igetlastmsg(true); } infos.remove_all(); } /* Close all pipes (for the last netconf_system_if) and grab late messages */ void netconf_closepipes() { for (int i=0; igetlastmsg(false); } infos.remove_all(); } /* start the daemon without much questionning. Return -1 if any error. */ PUBLIC VIRTUAL int DAEMON::start() { return -1; } /* Stop a daemon if it is running. The default is to use signal SIGTERM. This function is virtual. Return -1 if any error. */ PUBLIC VIRTUAL int DAEMON::stop() { return -1; } /* Record the signal number used to reinitialise a server By default, a server is stopped then started. */ PUBLIC void DAEMON::set_restartsignal(int sig) { restart_signal = sig; } /* A daemon may be restart normally by killing it and starting it. It may be different for some daemon... Return -1 if any error. */ PUBLIC VIRTUAL int DAEMON::restart() { int ret = -1; bool problem; PROC *prc = findprocess(problem); if (prc != NULL && restart_signal != -1){ net_prtlog (NETLOG_VERB ,MSG_U(I_SENDINGSIG,"Sending signal %d to process %s\n") ,restart_signal,name.get()); kill (prc->getpid(),restart_signal); }else{ ret = stop(); if (ret != -1) ret = start(); } return ret; } /* Get the modification date of a config file. Return -1 if the file is missing. Signal if the date is in the future */ static long daemon_file_date(const char *fname) { long date = file_date (fname); #if 0 if (date != -1){ int empty = file_empty(fname); if (empty) date = -1; #endif if (date > time(NULL)){ /* #Specification: file in the future When linuxconf finds a file with a modification time in the future, it sends a warning. This will explain the admin why linuxconf is trying to start and restart and restart the same service everytime */ xconf_notice (MSG_U(N_FILEFUTURE ,"The file %s has a revision date in the future\n" "This probably means that you system time is wrong\n" "or as been wrong at some point."),fname); } return date; } /* Start or stop a daemon depending of the content of a configuration file. If the file is younger than the process, the process is restarted. */ PUBLIC int DAEMON::startif_file (const char *fname) { long date = daemon_file_date(fname); long dates[1]; dates[0] = date; const char *tb[] = {fname}; return startif_date (date,tb,dates,1); } /* Start or stop a daemon depending of the content of many configuration file. If one of the file is younger than the process, the process is restarted. */ PUBLIC int DAEMON::startif_file (const SSTRINGS &files) { long date = -1; int n = files.getnb(); long dates[n]; const char *tbfiles[n]; if (n == 0){ // If there is no configuration file defined for a daemon // then it will start if it is not running // date = 1 is a valid date for a file, but very old. This // force startif_date() to start the daemon if not running // already but not much since the process start date is // newer the 1970 + 1 second. date = 1; }else{ for (int i=0; iget(); long fdate = daemon_file_date (fname); dates[i] = fdate; tbfiles[i] = fname; if (fdate > date) date = fdate; } } return startif_date (date,tbfiles,dates,n); } /* Start or stop a daemon depending of the content of a configuration file. If the file is younger than the process, the process is restarted. */ PUBLIC int DAEMON::startif_file (const CONFIG_FILE &cfile) { return startif_file (cfile.getpath()); } /* Locate the running process associated with a system Return NULL if not running. */ PUBLIC VIRTUAL PROC *DAEMON::findprocess(bool &problem) { problem = false; return NULL; } static NETCONF_HELP_FILE help_restartdb ("restartdb"); static CONFIG_FILE f_restart (VAR_RUN_LINUXCONF_RESTART ,help_restartdb ,CONFIGF_MANAGED|CONFIGF_NOARCH|CONFIGF_OPTIONAL); static CONFDB *restartdb=NULL; static const char K_RESTART[]="restart"; static const char K_ARGS[]="args"; void daemon_loadrestartdb() { if (restartdb == NULL){ restartdb = new CONFDB (f_restart); } } void daemon_setrestarttime (const char *process_name) { restartdb->replace (K_RESTART,process_name,time(NULL)); } void daemon_saverestartdb() { restartdb->save(); } /* Return the timestamp of the last restart for this task or 0 */ PUBLIC long DAEMON::getrestarttime() { daemon_loadrestartdb(); return restartdb->getvalnum (K_RESTART,name.get(),0); } PUBLIC void DAEMON::setrestarttime() { daemon_loadrestartdb(); if (!simul_ison()){ daemon_setrestarttime (name.get()); daemon_saverestartdb(); } } PUBLIC void DAEMON::delrestarttime() { if (!simul_ison()){ if (getrestarttime()!=0){ restartdb->removeall (K_RESTART,name.get()); restartdb->save(); } } } /* Return the last arguments used to start a process */ PUBLIC const char *DAEMON::getlastargs() { daemon_loadrestartdb(); return restartdb->getval (K_ARGS,name.get(),""); } /* Record the last argument used to start a process */ PUBLIC void DAEMON::recordargs(const char *args) { daemon_loadrestartdb(); if (!simul_ison()){ restartdb->replace (K_ARGS,name.get(),args); restartdb->save(); } } /* Restart, start or stop a daemon if he is older than a date. If the date is <= 0, it means that the corresponding configuration file do not exist or is empty, so the daemon must be stopped. */ PRIVATE int DAEMON::startif_date ( long date, const char *tbf[], const long dates[], int nbfile) { bool problem; PROC *prc = findprocess(problem); int ret = 0; if (problem){ /* #Specification: probing a package / missing processes For dropins and sysv script which are starting multiple daemons (processes), if linuxconf finds out that one is missing (not all), it will trigger a stop and then a start, not a restart. For many package, a restart means some simple signaling and not a proper reinitialisation. A message is recorded in the log about the missing processes */ net_prtlog (NETLOG_WHY,MSG_U(E_MISSINGPRC ,"Some process missing, full restart of service %s required\n") ,name.get()); ret = stop(); if (ret == 0) ret = start(); if (ret == 0) delrestarttime(); }else if (prc == NULL){ if (date > 0){ net_prtlog (NETLOG_WHY,MSG_U(I_NOTRUNNING ,"Service %s is not running\n"),name.get()); ret = start(); if (ret == 0) delrestarttime(); } }else if (date <= 0){ for (int i=0; igetstarttime(); long rdate = getrestarttime(); if (rdate > pdate) pdate = rdate; if (date > pdate){ for (int i=0; i pdate){ net_prtlog (NETLOG_WHY,MSG_U(I_CHANGEDCONFIG ,"Configuration file %s have changed for service %s\n") ,tbf[i],name.get()); } } ret = restart(); if (ret == 0) setrestarttime(); } } return ret; } /* Restart, start or stop a daemon if he is older than a date. If the date is <= 0, it means that the corresponding configuration file do not exist or is empty, so the daemon must be stopped. */ PUBLIC int DAEMON::startif_date (long date) { return startif_date (date,NULL,NULL,0); } /* start the daemon if not active or restart it if needed. The daemon won't be started if not configured (Missing configuration file). The default behavior (virtual function) is to start it if it not already active. Return 0 = Was already active, 1 = started, 2 = not needed and -1 = some error. */ PUBLIC VIRTUAL int DAEMON::startif() { bool problem; PROC *prc = findprocess (problem); int ret = 0; if (prc == NULL){ ret = start() != -1 ? 1 : -1; } return ret; } /* Return the name (not the path) of a daemon */ PUBLIC const char *DAEMON::getname() { return name.get(); }