1 /*
2    Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved.
3 
4    This program is free software; you can redistribute it and/or modify
5    it under the terms of the GNU General Public License, version 2.0,
6    as published by the Free Software Foundation.
7 
8    This program is also distributed with certain software (including
9    but not limited to OpenSSL) that is licensed under separate terms,
10    as designated in a particular file or component or in included license
11    documentation.  The authors of MySQL hereby grant you an additional
12    permission to link the program and your derivative works with the
13    separately licensed software that they have included with MySQL.
14 
15    This program is distributed in the hope that it will be useful,
16    but WITHOUT ANY WARRANTY; without even the implied warranty of
17    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18    GNU General Public License, version 2.0, for more details.
19 
20    You should have received a copy of the GNU General Public License
21    along with this program; if not, write to the Free Software
22    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
23 */
24 
25 
26 #include <ndb_global.h>
27 #include <NdbOut.hpp>
28 
29 #include "APIService.hpp"
30 #include "CPCD.hpp"
31 #include <NdbMutex.h>
32 
33 #include "common.hpp"
34 #ifdef _WIN32
35 #include <sys/types.h>
36 #include <direct.h>
37 #endif
38 
39 extern const ParserRow<CPCDAPISession> commands[];
40 
41 
CPCD()42 CPCD::CPCD() {
43   loadingProcessList = false;
44   m_processes.clear();
45   m_monitor = NULL;
46   m_monitor = new Monitor(this);
47   m_procfile = "ndb_cpcd.db";
48 }
49 
~CPCD()50 CPCD::~CPCD() {
51   if(m_monitor != NULL) {
52     delete m_monitor;
53     m_monitor = NULL;
54   }
55 }
56 
57 int
findUniqueId()58 CPCD::findUniqueId() {
59   int id;
60   bool ok = false;
61   m_processes.lock();
62 
63   while(!ok) {
64     ok = true;
65     id = rand() % 8192; /* Don't want so big numbers */
66 
67     if(id == 0)
68       ok = false;
69 
70     for(size_t i = 0; i<m_processes.size(); i++) {
71       if(m_processes[i]->m_id == id)
72 	ok = false;
73     }
74   }
75   m_processes.unlock();
76   return id;
77 }
78 
79 bool
defineProcess(RequestStatus * rs,Process * arg)80 CPCD::defineProcess(RequestStatus * rs, Process * arg){
81   if(arg->m_id == -1)
82     arg->m_id = findUniqueId();
83 
84   Guard tmp(m_processes);
85 
86   for(size_t i = 0; i<m_processes.size(); i++) {
87     Process * proc = m_processes[i];
88 
89     if((strcmp(arg->m_name.c_str(), proc->m_name.c_str()) == 0) &&
90        (strcmp(arg->m_group.c_str(), proc->m_group.c_str()) == 0)) {
91       /* Identical names in the same group */
92       rs->err(AlreadyExists, "Name already exists");
93       return false;
94     }
95 
96     if(arg->m_id == proc->m_id) {
97       /* Identical ID numbers */
98       rs->err(AlreadyExists, "Id already exists");
99       return false;
100     }
101   }
102 
103   m_processes.push_back(arg, false);
104 
105   notifyChanges();
106 
107   return true;
108 }
109 
110 bool
undefineProcess(CPCD::RequestStatus * rs,int id)111 CPCD::undefineProcess(CPCD::RequestStatus *rs, int id) {
112 
113   Guard tmp(m_processes);
114 
115   Process * proc = 0;
116   size_t i;
117   for(i = 0; i < m_processes.size(); i++) {
118     if(m_processes[i]->m_id == id) {
119       proc = m_processes[i];
120       break;
121     }
122   }
123 
124   if(proc == 0){
125     rs->err(NotExists, "No such process");
126     return false;
127   }
128 
129   switch(proc->m_status){
130   case RUNNING:
131   case STOPPED:
132   case STOPPING:
133   case STARTING:
134     proc->stop();
135     m_processes.erase(i, false /* Already locked */);
136   }
137 
138 
139   notifyChanges();
140 
141   return true;
142 }
143 
144 bool
startProcess(CPCD::RequestStatus * rs,int id)145 CPCD::startProcess(CPCD::RequestStatus *rs, int id) {
146 
147   Process * proc = 0;
148   {
149 
150     Guard tmp(m_processes);
151 
152     for(size_t i = 0; i < m_processes.size(); i++) {
153       if(m_processes[i]->m_id == id) {
154 	proc = m_processes[i];
155 	break;
156       }
157     }
158 
159     if(proc == 0){
160       rs->err(NotExists, "No such process");
161       return false;
162     }
163 
164     switch(proc->m_status){
165     case STOPPED:
166       proc->m_status = STARTING;
167       if(proc->start() != 0){
168 	rs->err(Error, "Failed to start");
169 	return false;
170       }
171       break;
172     case STARTING:
173       rs->err(Error, "Already starting");
174       return false;
175     case RUNNING:
176       rs->err(Error, "Already started");
177       return false;
178     case STOPPING:
179       rs->err(Error, "Currently stopping");
180       return false;
181     }
182 
183     notifyChanges();
184   }
185 
186   return true;
187 }
188 
189 bool
stopProcess(CPCD::RequestStatus * rs,int id)190 CPCD::stopProcess(CPCD::RequestStatus *rs, int id) {
191 
192   Guard tmp(m_processes);
193 
194   Process * proc = 0;
195   for(size_t i = 0; i < m_processes.size(); i++) {
196     if(m_processes[i]->m_id == id) {
197       proc = m_processes[i];
198       break;
199     }
200   }
201 
202   if(proc == 0){
203     rs->err(NotExists, "No such process");
204     return false;
205   }
206 
207   switch(proc->m_status){
208   case STARTING:
209   case RUNNING:
210     proc->stop();
211     break;
212   case STOPPED:
213     rs->err(AlreadyStopped, "Already stopped");
214     return false;
215     break;
216   case STOPPING:
217     rs->err(Error, "Already stopping");
218     return false;
219   }
220 
221   notifyChanges();
222 
223   return true;
224 }
225 
226 bool
notifyChanges()227 CPCD::notifyChanges() {
228   bool ret = true;
229   if(!loadingProcessList)
230     ret = saveProcessList();
231 
232   m_monitor->signal();
233 
234   return ret;
235 }
236 
237 
238 #ifdef _WIN32
link(const char * from_file,const char * to_file)239 static int link(const char* from_file, const char* to_file)
240 {
241   BOOL fail_if_exists = TRUE;
242   if (CopyFile(from_file, to_file, fail_if_exists) == 0)
243   {
244     /* "On error, -1 is returned" */
245     return -1;
246   }
247   /* "On success, zero is returned" */
248   return 0;
249 }
250 #endif
251 
252 
253 /* Must be called with m_processlist locked */
254 bool
saveProcessList()255 CPCD::saveProcessList(){
256   char newfile[PATH_MAX+4];
257   char oldfile[PATH_MAX+4];
258   char curfile[PATH_MAX];
259   FILE *f;
260 
261   /* Create the filenames that we will use later */
262   BaseString::snprintf(newfile, sizeof(newfile), "%s.new", m_procfile.c_str());
263   BaseString::snprintf(oldfile, sizeof(oldfile), "%s.old", m_procfile.c_str());
264   BaseString::snprintf(curfile, sizeof(curfile), "%s", m_procfile.c_str());
265 
266   f = fopen(newfile, "w");
267 
268   if(f == NULL) {
269     /* XXX What should be done here? */
270     logger.critical("Cannot open `%s': %s\n", newfile, strerror(errno));
271     return false;
272   }
273 
274   for(size_t i = 0; i<m_processes.size(); i++){
275     m_processes[i]->print(f);
276     fprintf(f, "\n");
277 
278     if(m_processes[i]->m_processType == TEMPORARY){
279       /**
280        * Interactive process should never be "restarted" on cpcd restart
281        */
282       continue;
283     }
284 
285     if(m_processes[i]->m_status == RUNNING ||
286        m_processes[i]->m_status == STARTING){
287       fprintf(f, "start process\nid: %d\n\n", m_processes[i]->m_id);
288     }
289   }
290 
291   fclose(f);
292   f = NULL;
293 
294   /* This will probably only work on reasonably Unix-like systems. You have
295    * been warned...
296    *
297    * The motivation behind all this link()ing is that the daemon might
298    * crash right in the middle of updating the configuration file, and in
299    * that case we want to be sure that the old file is around until we are
300    * guaranteed that there is always at least one copy of either the old or
301    * the new configuration file left.
302    */
303 
304   /* Remove an old config file if it exists */
305   unlink(oldfile);
306 
307   if(link(curfile, oldfile) != 0) /* make a backup of the running config */
308     logger.error("Cannot rename '%s' -> '%s'", curfile, oldfile);
309   else {
310     if(unlink(curfile) != 0) { /* remove the running config file */
311       logger.critical("Cannot remove file '%s'", curfile);
312       return false;
313     }
314   }
315 
316   if(link(newfile, curfile) != 0) { /* put the new config file in place */
317     printf("-->%d\n", __LINE__);
318 
319     logger.critical("Cannot rename '%s' -> '%s': %s",
320 		    curfile, newfile, strerror(errno));
321     return false;
322   }
323 
324   /* XXX Ideally we would fsync() the directory here, but I'm not sure if
325    * that actually works.
326    */
327 
328   unlink(newfile); /* remove the temporary file */
329   unlink(oldfile); /* remove the old file */
330 
331   logger.info("Process list saved as '%s'", curfile);
332 
333   return true;
334 }
335 
336 bool
loadProcessList()337 CPCD::loadProcessList(){
338   BaseString secondfile;
339   FILE *f;
340 
341   loadingProcessList = true;
342 
343   secondfile.assfmt("%s.new", m_procfile.c_str());
344 
345   /* Try to open the config file */
346   f = fopen(m_procfile.c_str(), "r");
347 
348   /* If it did not exist, try to open the backup. See the saveProcessList()
349    * method for an explanation why it is done this way.
350    */
351   if(f == NULL) {
352     f = fopen(secondfile.c_str(), "r");
353 
354     if(f == NULL) {
355       /* XXX What to do here? */
356       logger.info("Configuration file `%s' not found",
357 		  m_procfile.c_str());
358       logger.info("Starting with empty configuration");
359       loadingProcessList = false;
360       return false;
361     } else {
362       logger.info("Configuration file `%s' missing",
363 		  m_procfile.c_str());
364       logger.info("Backup configuration file `%s' is used",
365 		  secondfile.c_str());
366       /* XXX Maybe we should just rename the backup file to the official
367        * name, and be done with it?
368        */
369     }
370   }
371 
372   CPCDAPISession sess(f, *this);
373   fclose(f);
374   sess.loadFile();
375   loadingProcessList = false;
376 
377   size_t i;
378   Vector<int> temporary;
379   for(i = 0; i<m_processes.size(); i++){
380     Process * proc = m_processes[i];
381     proc->readPid();
382     if(proc->m_processType == TEMPORARY){
383       temporary.push_back(proc->m_id);
384     }
385   }
386 
387   for(i = 0; i<temporary.size(); i++){
388     RequestStatus rs;
389     undefineProcess(&rs, temporary[i]);
390   }
391 
392   /* Don't call notifyChanges here, as that would save the file we just
393      loaded */
394   m_monitor->signal();
395   return true;
396 }
397 
398 MutexVector<CPCD::Process *> *
getProcessList()399 CPCD::getProcessList() {
400   return &m_processes;
401 }
402 
403 void
err(enum RequestStatusCode status,const char * msg)404 CPCD::RequestStatus::err(enum RequestStatusCode status, const char *msg) {
405   m_status = status;
406   BaseString::snprintf(m_errorstring, sizeof(m_errorstring), "%s", msg);
407 }
408