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