1 /*****************************************************************************\
2  *  mysql_common.c - common functions for the mysql storage plugin.
3  *****************************************************************************
4  *  Copyright (C) 2004-2007 The Regents of the University of California.
5  *  Produced at Lawrence Livermore National Laboratory (cf, DISCLAIMER).
6  *  Written by Danny Auble <da@llnl.gov>
7  *
8  *  This file is part of Slurm, a resource management program.
9  *  For details, see <https://slurm.schedmd.com/>.
10  *  Please also read the included file: DISCLAIMER.
11  *
12  *  Slurm is free software; you can redistribute it and/or modify it under
13  *  the terms of the GNU General Public License as published by the Free
14  *  Software Foundation; either version 2 of the License, or (at your option)
15  *  any later version.
16  *
17  *  In addition, as a special exception, the copyright holders give permission
18  *  to link the code of portions of this program with the OpenSSL library under
19  *  certain conditions as described in each individual source file, and
20  *  distribute linked combinations including the two. You must obey the GNU
21  *  General Public License in all respects for all of the code used other than
22  *  OpenSSL. If you modify file(s) with this exception, you may extend this
23  *  exception to your version of the file(s), but you are not obligated to do
24  *  so. If you do not wish to do so, delete this exception statement from your
25  *  version.  If you delete this exception statement from all source files in
26  *  the program, then also delete it here.
27  *
28  *  Slurm is distributed in the hope that it will be useful, but WITHOUT ANY
29  *  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
30  *  FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
31  *  details.
32  *
33  *  You should have received a copy of the GNU General Public License along
34  *  with Slurm; if not, write to the Free Software Foundation, Inc.,
35  *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA.
36  *
37  *  This file is patterned after jobcomp_linux.c, written by Morris Jette and
38  *  Copyright (C) 2002 The Regents of the University of California.
39 \*****************************************************************************/
40 
41 #include "config.h"
42 
43 #include "mysql_common.h"
44 #include "src/common/log.h"
45 #include "src/common/xstring.h"
46 #include "src/common/xmalloc.h"
47 #include "src/common/timers.h"
48 #include "src/common/slurm_protocol_api.h"
49 #include "src/common/read_config.h"
50 
51 #define MAX_DEADLOCK_ATTEMPTS 10
52 
53 static char *table_defs_table = "table_defs_table";
54 
55 typedef struct {
56 	char *name;
57 	char *columns;
58 } db_key_t;
59 
_destroy_db_key(void * arg)60 static void _destroy_db_key(void *arg)
61 {
62 	db_key_t *db_key = (db_key_t *)arg;
63 
64 	if (db_key) {
65 		xfree(db_key->name);
66 		xfree(db_key->columns);
67 		xfree(db_key);
68 	}
69 }
70 
71 /* NOTE: Ensure that mysql_conn->lock is set on function entry */
_clear_results(MYSQL * db_conn)72 static int _clear_results(MYSQL *db_conn)
73 {
74 	MYSQL_RES *result = NULL;
75 	int rc = 0;
76 
77 	do {
78 		/* did current statement return data? */
79 		if ((result = mysql_store_result(db_conn)))
80 			mysql_free_result(result);
81 
82 		/* more results? -1 = no, >0 = error, 0 = yes (keep looping) */
83 		if ((rc = mysql_next_result(db_conn)) > 0)
84 			error("Could not execute statement %d %s",
85 			      mysql_errno(db_conn),
86 			      mysql_error(db_conn));
87 	} while (rc == 0);
88 
89 	if (rc > 0) {
90 		errno = rc;
91 		return SLURM_ERROR;
92 	}
93 	return SLURM_SUCCESS;
94 }
95 
96 /* NOTE: Ensure that mysql_conn->lock is set on function entry */
_get_first_result(MYSQL * db_conn)97 static MYSQL_RES *_get_first_result(MYSQL *db_conn)
98 {
99 	MYSQL_RES *result = NULL;
100 	int rc = 0;
101 	do {
102 		/* did current statement return data? */
103 		if ((result = mysql_store_result(db_conn)))
104 			return result;
105 
106 		/* more results? -1 = no, >0 = error, 0 = yes (keep looping) */
107 		if ((rc = mysql_next_result(db_conn)) > 0)
108 			debug3("error: Could not execute statement %d", rc);
109 
110 	} while (rc == 0);
111 
112 	return NULL;
113 }
114 
115 /* NOTE: Ensure that mysql_conn->lock is set on function entry */
_get_last_result(MYSQL * db_conn)116 static MYSQL_RES *_get_last_result(MYSQL *db_conn)
117 {
118 	MYSQL_RES *result = NULL;
119 	MYSQL_RES *last_result = NULL;
120 	int rc = 0;
121 	do {
122 		/* did current statement return data? */
123 		if ((result = mysql_store_result(db_conn))) {
124 			if (last_result)
125 				mysql_free_result(last_result);
126 			last_result = result;
127 		}
128 		/* more results? -1 = no, >0 = error, 0 = yes (keep looping) */
129 		if ((rc = mysql_next_result(db_conn)) > 0)
130 			debug3("error: Could not execute statement %d", rc);
131 	} while (rc == 0);
132 
133 	return last_result;
134 }
135 
136 /* NOTE: Ensure that mysql_conn->lock is set on function entry */
_mysql_query_internal(MYSQL * db_conn,char * query)137 static int _mysql_query_internal(MYSQL *db_conn, char *query)
138 {
139 	int rc = SLURM_SUCCESS;
140 	int deadlock_attempt = 0;
141 
142 try_again:
143 	if (!db_conn)
144 		fatal("You haven't inited this storage yet.");
145 
146 	/* clear out the old results so we don't get a 2014 error */
147 	_clear_results(db_conn);
148 	if (mysql_query(db_conn, query)) {
149 		const char *err_str = mysql_error(db_conn);
150 		errno = mysql_errno(db_conn);
151 		if (errno == ER_NO_SUCH_TABLE) {
152 			debug4("This could happen often and is expected.\n"
153 			       "mysql_query failed: %d %s\n%s",
154 			       errno, err_str, query);
155 			errno = 0;
156 			goto end_it;
157 		}
158 		error("mysql_query failed: %d %s\n%s", errno, err_str, query);
159 		if (errno == ER_LOCK_DEADLOCK) {
160 			/*
161 			 * Mysql detected a deadlock and we should retry
162 			 * a few times since this is mainly a race condition
163 			 */
164 			deadlock_attempt++;
165 
166 			if (deadlock_attempt < MAX_DEADLOCK_ATTEMPTS) {
167 				error("%s: deadlock detected attempt %u/%u: %d %s",
168 				      __func__, deadlock_attempt,
169 				      MAX_DEADLOCK_ATTEMPTS, errno, err_str);
170 				goto try_again;
171 			} else {
172 				fatal("%s: unable to resolve deadlock with attempts %u/%u: %d %s\nPlease call 'show engine innodb status;' in MySQL/MariaDB and open a bug report with SchedMD.",
173 				      __func__, deadlock_attempt,
174 				      MAX_DEADLOCK_ATTEMPTS, errno, err_str);
175 			}
176 		} else if (errno == ER_LOCK_WAIT_TIMEOUT) {
177 			/* FIXME: If we get ER_LOCK_WAIT_TIMEOUT here we need
178 			 * to restart the connections, but it appears restarting
179 			 * the calling program is the only way to handle this.
180 			 * If anyone in the future figures out a way to handle
181 			 * this, super.  Until then we will need to restart the
182 			 * calling program if you ever get this error.
183 			 */
184 			fatal("mysql gave ER_LOCK_WAIT_TIMEOUT as an error. "
185 			      "The only way to fix this is restart the "
186 			      "calling program");
187 		} else if (errno == ER_HOST_IS_BLOCKED) {
188 			fatal("MySQL gave ER_HOST_IS_BLOCKED as an error. "
189 			      "You will need to call 'mysqladmin flush-hosts' "
190 			      "to regain connectivity.");
191 		}
192 		rc = SLURM_ERROR;
193 	}
194 end_it:
195 	/*
196 	 * Starting in MariaDB 10.2 many of the api commands started
197 	 * setting errno erroneously.
198 	 */
199 	if (!rc)
200 		errno = 0;
201 
202 	return rc;
203 }
204 
205 /* NOTE: Ensure that mysql_conn->lock is NOT set on function entry */
_mysql_make_table_current(mysql_conn_t * mysql_conn,char * table_name,storage_field_t * fields,char * ending)206 static int _mysql_make_table_current(mysql_conn_t *mysql_conn, char *table_name,
207 				     storage_field_t *fields, char *ending)
208 {
209 	char *query = NULL;
210 	char *correct_query = NULL;
211 	MYSQL_RES *result = NULL;
212 	MYSQL_ROW row;
213 	int i = 0;
214 	List columns = NULL;
215 	ListIterator itr = NULL;
216 	char *col = NULL;
217 	int adding = 0;
218 	int run_update = 0;
219 	char *primary_key = NULL;
220 	char *unique_index = NULL;
221 	int old_primary = 0;
222 	char *old_index = NULL;
223 	char *temp = NULL, *temp2 = NULL;
224 	List keys_list = NULL;
225 	db_key_t *db_key = NULL;
226 
227 	DEF_TIMERS;
228 
229 	/* figure out the unique keys in the table */
230 	query = xstrdup_printf("show index from %s where non_unique=0",
231 			       table_name);
232 	if (!(result = mysql_db_query_ret(mysql_conn, query, 0))) {
233 		xfree(query);
234 		return SLURM_ERROR;
235 	}
236 	xfree(query);
237 	while ((row = mysql_fetch_row(result))) {
238 		// row[2] is the key name
239 		if (!xstrcasecmp(row[2], "PRIMARY"))
240 			old_primary = 1;
241 		else if (!old_index)
242 			old_index = xstrdup(row[2]);
243 	}
244 	mysql_free_result(result);
245 
246 	/* figure out the non-unique keys in the table */
247 	query = xstrdup_printf("show index from %s where non_unique=1",
248 			       table_name);
249 	if (!(result = mysql_db_query_ret(mysql_conn, query, 0))) {
250 		xfree(query);
251 		xfree(old_index);
252 		return SLURM_ERROR;
253 	}
254 	xfree(query);
255 
256 	itr = NULL;
257 	keys_list = list_create(_destroy_db_key);
258 	while ((row = mysql_fetch_row(result))) {
259 		if (!itr)
260 			itr = list_iterator_create(keys_list);
261 		else
262 			list_iterator_reset(itr);
263 		while ((db_key = list_next(itr))) {
264 			if (!xstrcmp(db_key->name, row[2]))
265 				break;
266 		}
267 
268 		if (db_key) {
269 			xstrfmtcat(db_key->columns, ", %s", row[4]);
270 		} else {
271 			db_key = xmalloc(sizeof(db_key_t));
272 			db_key->name = xstrdup(row[2]); // name
273 			db_key->columns = xstrdup(row[4]); // column name
274 			list_append(keys_list, db_key); // don't use list_push
275 		}
276 	}
277 	mysql_free_result(result);
278 
279 	if (itr) {
280 		list_iterator_destroy(itr);
281 		itr = NULL;
282 	}
283 
284 	/* figure out the existing columns in the table */
285 	query = xstrdup_printf("show columns from %s", table_name);
286 	if (!(result = mysql_db_query_ret(mysql_conn, query, 0))) {
287 		xfree(query);
288 		xfree(old_index);
289 		FREE_NULL_LIST(keys_list);
290 		return SLURM_ERROR;
291 	}
292 	xfree(query);
293 	columns = list_create(xfree_ptr);
294 	while ((row = mysql_fetch_row(result))) {
295 		col = xstrdup(row[0]); //Field
296 		list_append(columns, col);
297 	}
298 	mysql_free_result(result);
299 
300 
301 	itr = list_iterator_create(columns);
302 	/* In MySQL 5.7.4 we lost the ability to run 'alter ignore'.  This was
303 	 * needed when converting old tables to new schemas.  If people convert
304 	 * in the future from an older version of Slurm that needed the ignore
305 	 * to work they will have to downgrade mysql to <= 5.7.3 to make things
306 	 * work correctly or manually edit the database to get things to work.
307 	 */
308 	/*
309 	 * `query` is compared against the current table_defs_table.definition
310 	 * and run if they are different. `correct_query` is inserted into the
311 	 * table, so it must be what future `query` schemas will be.
312 	 * In other words, `query` transitions the table to the new schema,
313 	 * `correct_query` represents the new schema
314 	 */
315 	query = xstrdup_printf("alter table %s", table_name);
316 	correct_query = xstrdup(query);
317 	START_TIMER;
318 	while (fields[i].name) {
319 		int found = 0;
320 
321 		list_iterator_reset(itr);
322 		while ((col = list_next(itr))) {
323 			if (!xstrcmp(col, fields[i].name)) {
324 				xstrfmtcat(query, " modify `%s` %s,",
325 					   fields[i].name,
326 					   fields[i].options);
327 				xstrfmtcat(correct_query, " modify `%s` %s,",
328 					   fields[i].name,
329 					   fields[i].options);
330 				list_delete_item(itr);
331 				found = 1;
332 				break;
333 			}
334 		}
335 		if (!found) {
336 			if (i) {
337 				info("adding column %s after %s in table %s",
338 				     fields[i].name,
339 				     fields[i-1].name,
340 				     table_name);
341 				xstrfmtcat(query, " add `%s` %s after %s,",
342 					   fields[i].name,
343 					   fields[i].options,
344 					   fields[i-1].name);
345 				xstrfmtcat(correct_query, " modify `%s` %s,",
346 					   fields[i].name,
347 					   fields[i].options);
348 			} else {
349 				info("adding column %s at the beginning "
350 				     "of table %s",
351 				     fields[i].name,
352 				     table_name);
353 				xstrfmtcat(query, " add `%s` %s first,",
354 					   fields[i].name,
355 					   fields[i].options);
356 				xstrfmtcat(correct_query, " modify `%s` %s,",
357 					   fields[i].name,
358 					   fields[i].options);
359 			}
360 			adding = 1;
361 		}
362 
363 		i++;
364 	}
365 
366 	list_iterator_reset(itr);
367 	while ((col = list_next(itr))) {
368 		adding = 1;
369 		info("dropping column %s from table %s", col, table_name);
370 		xstrfmtcat(query, " drop %s,", col);
371 	}
372 
373 	list_iterator_destroy(itr);
374 	FREE_NULL_LIST(columns);
375 
376 	if ((temp = strstr(ending, "primary key ("))) {
377 		int open = 0, close =0;
378 		int end = 0;
379 		while (temp[end++]) {
380 			if (temp[end] == '(')
381 				open++;
382 			else if (temp[end] == ')')
383 				close++;
384 			else
385 				continue;
386 			if (open == close)
387 				break;
388 		}
389 		if (temp[end]) {
390 			end++;
391 			primary_key = xstrndup(temp, end);
392 			if (old_primary)
393 				xstrcat(query, " drop primary key,");
394 			xstrcat(correct_query, " drop primary key,");
395 			xstrfmtcat(query, " add %s,",  primary_key);
396 			xstrfmtcat(correct_query, " add %s,",  primary_key);
397 
398 			xfree(primary_key);
399 		}
400 	}
401 
402 	if ((temp = strstr(ending, "unique index"))) {
403 		int open = 0, close = 0;
404 		/* sizeof includes NULL, and end should start 1 back */
405 		int end = sizeof("unique index") - 2;
406 		char *udex_name = NULL, *name_marker = NULL;
407 		while (temp[end++]) {
408 			/*
409 			 * Extracts the index name, which is given explicitly
410 			 * or is the name of the first field included in the
411 			 * index.
412 			 * "unique index indexname (field1, field2)"
413 			 * "unique index (indexname, field2)"
414 			 * indexname is started by the first non '(' or ' '
415 			 *     after "unique index"
416 			 * indexname is terminated by '(' ')' ' ' or ','
417 			 */
418 			if (name_marker) {
419 				if (!udex_name && (temp[end] == '(' ||
420 						   temp[end] == ')' ||
421 						   temp[end] == ' ' ||
422 						   temp[end] == ','))
423 					udex_name = xstrndup(name_marker,
424 						temp + end - name_marker);
425 			} else if (temp[end] != '(' && temp[end] != ' ') {
426 				name_marker = temp + end;
427 			}
428 
429 			/* find the end of the parenthetical expression */
430 			if (temp[end] == '(')
431 				open++;
432 			else if (temp[end] == ')')
433 				close++;
434 			else
435 				continue;
436 			if (open == close)
437 				break;
438 		}
439 		if (temp[end]) {
440 			end++;
441 			unique_index = xstrndup(temp, end);
442 			if (old_index)
443 				xstrfmtcat(query, " drop index %s,", old_index);
444 			xstrfmtcat(correct_query, " drop index %s,", udex_name);
445 			xstrfmtcat(query, " add %s,", unique_index);
446 			xstrfmtcat(correct_query, " add %s,", unique_index);
447 			xfree(unique_index);
448 		}
449 		xfree(udex_name);
450 	}
451 	xfree(old_index);
452 
453 	temp2 = ending;
454 	itr = list_iterator_create(keys_list);
455 	while ((temp = strstr(temp2, ", key "))) {
456 		int open = 0, close = 0, name_end = 0;
457 		int end = 5;
458 		char *new_key_name = NULL, *new_key = NULL;
459 		while (temp[end++]) {
460 			if (!name_end && (temp[end] == ' ')) {
461 				name_end = end;
462 				continue;
463 			} else if (temp[end] == '(') {
464 				open++;
465 				if (!name_end)
466 					name_end = end;
467 			} else if (temp[end] == ')')
468 				close++;
469 			else
470 				continue;
471 			if (open == close)
472 				break;
473 		}
474 		if (temp[end]) {
475 			end++;
476 			new_key_name = xstrndup(temp+6, name_end-6);
477 			new_key = xstrndup(temp+2, end-2); // skip ', '
478 			while ((db_key = list_next(itr))) {
479 				if (!xstrcmp(db_key->name, new_key_name)) {
480 					list_remove(itr);
481 					break;
482 				}
483 			}
484 			list_iterator_reset(itr);
485 			if (db_key) {
486 				xstrfmtcat(query,
487 					   " drop key %s,", db_key->name);
488 				_destroy_db_key(db_key);
489 			} else
490 				info("adding %s to table %s",
491 				     new_key, table_name);
492 			xstrfmtcat(correct_query,
493 				   " drop key %s,", new_key_name);
494 
495 			xstrfmtcat(query, " add %s,",  new_key);
496 			xstrfmtcat(correct_query, " add %s,",  new_key);
497 
498 			xfree(new_key);
499 			xfree(new_key_name);
500 		}
501 		temp2 = temp + end;
502 	}
503 
504 	/* flush extra (old) keys */
505 	while ((db_key = list_next(itr))) {
506 		info("dropping key %s from table %s", db_key->name, table_name);
507 		xstrfmtcat(query, " drop key %s,", db_key->name);
508 	}
509 	list_iterator_destroy(itr);
510 
511 	FREE_NULL_LIST(keys_list);
512 
513 	query[strlen(query)-1] = ';';
514 	correct_query[strlen(correct_query)-1] = ';';
515 	//info("%d query\n%s", __LINE__, query);
516 
517 	/* see if we have already done this definition */
518 	if (!adding) {
519 		char *quoted = slurm_add_slash_to_quotes(query);
520 		char *query2 = xstrdup_printf("select table_name from "
521 					      "%s where definition='%s'",
522 					      table_defs_table, quoted);
523 		MYSQL_RES *result = NULL;
524 		MYSQL_ROW row;
525 
526 		xfree(quoted);
527 		run_update = 1;
528 		if ((result = mysql_db_query_ret(mysql_conn, query2, 0))) {
529 			if ((row = mysql_fetch_row(result)))
530 				run_update = 0;
531 			mysql_free_result(result);
532 		}
533 		xfree(query2);
534 		if (run_update) {
535 			run_update = 2;
536 			query2 = xstrdup_printf("select table_name from "
537 						"%s where table_name='%s'",
538 						table_defs_table, table_name);
539 			if ((result = mysql_db_query_ret(
540 				     mysql_conn, query2, 0))) {
541 				if ((row = mysql_fetch_row(result)))
542 					run_update = 1;
543 				mysql_free_result(result);
544 			}
545 			xfree(query2);
546 		}
547 	}
548 
549 	/* if something has changed run the alter line */
550 	if (run_update || adding) {
551 		time_t now = time(NULL);
552 		char *query2 = NULL;
553 		char *quoted = NULL;
554 
555 		if (run_update == 2)
556 			debug4("Table %s doesn't exist, adding", table_name);
557 		else
558 			debug("Table %s has changed.  Updating...", table_name);
559 		if (mysql_db_query(mysql_conn, query)) {
560 			xfree(query);
561 			return SLURM_ERROR;
562 		}
563 		quoted = slurm_add_slash_to_quotes(correct_query);
564 		query2 = xstrdup_printf("insert into %s (creation_time, "
565 					"mod_time, table_name, definition) "
566 					"values (%ld, %ld, '%s', '%s') "
567 					"on duplicate key update "
568 					"definition='%s', mod_time=%ld;",
569 					table_defs_table, now, now,
570 					table_name, quoted,
571 					quoted, now);
572 		xfree(quoted);
573 		if (mysql_db_query(mysql_conn, query2)) {
574 			xfree(query2);
575 			return SLURM_ERROR;
576 		}
577 		xfree(query2);
578 	}
579 
580 	xfree(query);
581 	xfree(correct_query);
582 	query = xstrdup_printf("make table current %s", table_name);
583 	END_TIMER2(query);
584 	xfree(query);
585 	return SLURM_SUCCESS;
586 }
587 
588 /* NOTE: Ensure that mysql_conn->lock is set on function entry */
_create_db(char * db_name,mysql_db_info_t * db_info)589 static int _create_db(char *db_name, mysql_db_info_t *db_info)
590 {
591 	MYSQL *mysql_db = NULL;
592 	int rc = SLURM_ERROR;
593 
594 	MYSQL *db_ptr = NULL;
595 	char *db_host = NULL;
596 
597 	while (rc == SLURM_ERROR) {
598 		rc = SLURM_SUCCESS;
599 		if (!(mysql_db = mysql_init(mysql_db)))
600 			fatal("mysql_init failed: %s", mysql_error(mysql_db));
601 
602 		db_host = db_info->host;
603 		db_ptr = mysql_real_connect(mysql_db,
604 					    db_host, db_info->user,
605 					    db_info->pass, NULL,
606 					    db_info->port, NULL, 0);
607 
608 		if (!db_ptr && db_info->backup) {
609 			info("Connection failed to host = %s "
610 			     "user = %s port = %u",
611 			     db_host, db_info->user,
612 			     db_info->port);
613 			db_host = db_info->backup;
614 			db_ptr = mysql_real_connect(mysql_db, db_host,
615 						    db_info->user,
616 						    db_info->pass, NULL,
617 						    db_info->port, NULL, 0);
618 		}
619 
620 		if (db_ptr) {
621 			char *create_line = NULL;
622 			xstrfmtcat(create_line, "create database %s", db_name);
623 			if (mysql_query(mysql_db, create_line)) {
624 				fatal("mysql_query failed: %d %s\n%s",
625 				      mysql_errno(mysql_db),
626 				      mysql_error(mysql_db), create_line);
627 			}
628 			xfree(create_line);
629 			if (mysql_thread_safe())
630 				mysql_thread_end();
631 			mysql_close(mysql_db);
632 		} else {
633 			info("Connection failed to host = %s "
634 			     "user = %s port = %u",
635 			     db_host, db_info->user,
636 			     db_info->port);
637 			error("mysql_real_connect failed: %d %s",
638 			      mysql_errno(mysql_db),
639 			      mysql_error(mysql_db));
640 			rc = SLURM_ERROR;
641 		}
642 		if (rc == SLURM_ERROR)
643 			sleep(3);
644 	}
645 	return rc;
646 }
647 
create_mysql_conn(int conn_num,bool rollback,char * cluster_name)648 extern mysql_conn_t *create_mysql_conn(int conn_num, bool rollback,
649 				       char *cluster_name)
650 {
651 	mysql_conn_t *mysql_conn = xmalloc(sizeof(mysql_conn_t));
652 
653 	mysql_conn->rollback = rollback;
654 	mysql_conn->conn = conn_num;
655 	mysql_conn->cluster_name = xstrdup(cluster_name);
656 	slurm_mutex_init(&mysql_conn->lock);
657 	mysql_conn->update_list = list_create(slurmdb_destroy_update_object);
658 
659 	return mysql_conn;
660 }
661 
destroy_mysql_conn(mysql_conn_t * mysql_conn)662 extern int destroy_mysql_conn(mysql_conn_t *mysql_conn)
663 {
664 	if (mysql_conn) {
665 		mysql_db_close_db_connection(mysql_conn);
666 		xfree(mysql_conn->pre_commit_query);
667 		xfree(mysql_conn->cluster_name);
668 		slurm_mutex_destroy(&mysql_conn->lock);
669 		FREE_NULL_LIST(mysql_conn->update_list);
670 		xfree(mysql_conn);
671 	}
672 
673 	return SLURM_SUCCESS;
674 }
675 
create_mysql_db_info(slurm_mysql_plugin_type_t type)676 extern mysql_db_info_t *create_mysql_db_info(slurm_mysql_plugin_type_t type)
677 {
678 	mysql_db_info_t *db_info = xmalloc(sizeof(mysql_db_info_t));
679 
680 	switch (type) {
681 	case SLURM_MYSQL_PLUGIN_AS:
682 		db_info->port = slurm_get_accounting_storage_port();
683 		if (!db_info->port) {
684 			db_info->port = DEFAULT_MYSQL_PORT;
685 			slurm_set_accounting_storage_port(db_info->port);
686 		}
687 		db_info->host = slurm_get_accounting_storage_host();
688 		db_info->backup = slurm_get_accounting_storage_backup_host();
689 		db_info->user = slurm_get_accounting_storage_user();
690 		db_info->pass = slurm_get_accounting_storage_pass();
691 		break;
692 	case SLURM_MYSQL_PLUGIN_JC:
693 		db_info->port = slurm_get_jobcomp_port();
694 		if (!db_info->port) {
695 			db_info->port = DEFAULT_MYSQL_PORT;
696 			slurm_set_jobcomp_port(db_info->port);
697 		}
698 		db_info->host = slurm_get_jobcomp_host();
699 		db_info->user = slurm_get_jobcomp_user();
700 		db_info->pass = slurm_get_jobcomp_pass();
701 		break;
702 	default:
703 		xfree(db_info);
704 		fatal("Unknown mysql_db_info %d", type);
705 	}
706 	return db_info;
707 }
708 
destroy_mysql_db_info(mysql_db_info_t * db_info)709 extern int destroy_mysql_db_info(mysql_db_info_t *db_info)
710 {
711 	if (db_info) {
712 		xfree(db_info->backup);
713 		xfree(db_info->host);
714 		xfree(db_info->user);
715 		xfree(db_info->pass);
716 		xfree(db_info);
717 	}
718 	return SLURM_SUCCESS;
719 }
720 
mysql_db_get_db_connection(mysql_conn_t * mysql_conn,char * db_name,mysql_db_info_t * db_info)721 extern int mysql_db_get_db_connection(mysql_conn_t *mysql_conn, char *db_name,
722 				      mysql_db_info_t *db_info)
723 {
724 	int rc = SLURM_SUCCESS;
725 	bool storage_init = false;
726 	char *db_host = db_info->host;
727 	unsigned int my_timeout = 30;
728 #ifdef MYSQL_OPT_RECONNECT
729 	my_bool reconnect = 1;
730 #endif
731 	xassert(mysql_conn);
732 
733 	slurm_mutex_lock(&mysql_conn->lock);
734 
735 	if (!(mysql_conn->db_conn = mysql_init(mysql_conn->db_conn))) {
736 		slurm_mutex_unlock(&mysql_conn->lock);
737 		fatal("mysql_init failed: %s",
738 		      mysql_error(mysql_conn->db_conn));
739 	}
740 
741 	/* If this ever changes you will need to alter
742 	 * src/common/slurmdbd_defs.c function _send_init_msg to
743 	 * handle a different timeout when polling for the
744 	 * response.
745 	 */
746 #ifdef MYSQL_OPT_RECONNECT
747 	/* make sure reconnect is on */
748 	mysql_options(mysql_conn->db_conn, MYSQL_OPT_RECONNECT,
749 		      &reconnect);
750 #endif
751 	mysql_options(mysql_conn->db_conn, MYSQL_OPT_CONNECT_TIMEOUT,
752 		      (char *)&my_timeout);
753 	while (!storage_init) {
754 		debug2("Attempting to connect to %s:%d", db_host,
755 		       db_info->port);
756 		if (!mysql_real_connect(mysql_conn->db_conn, db_host,
757 					db_info->user, db_info->pass,
758 					db_name, db_info->port, NULL,
759 					CLIENT_MULTI_STATEMENTS)) {
760 			const char *err_str = NULL;
761 			int err = mysql_errno(mysql_conn->db_conn);
762 
763 			if (err == ER_BAD_DB_ERROR) {
764 				debug("Database %s not created.  Creating",
765 				      db_name);
766 				rc = _create_db(db_name, db_info);
767 				continue;
768 			}
769 
770 			err_str = mysql_error(mysql_conn->db_conn);
771 
772 			if ((db_host == db_info->host) && db_info->backup) {
773 				debug2("mysql_real_connect failed: %d %s",
774 				       err, err_str);
775 				db_host = db_info->backup;
776 				continue;
777 			}
778 
779 			error("mysql_real_connect failed: %d %s",
780 			      err, err_str);
781 			rc = ESLURM_DB_CONNECTION;
782 			mysql_close(mysql_conn->db_conn);
783 			mysql_conn->db_conn = NULL;
784 			break;
785 		}
786 
787 		storage_init = true;
788 		if (mysql_conn->rollback)
789 			mysql_autocommit(mysql_conn->db_conn, 0);
790 		rc = _mysql_query_internal(mysql_conn->db_conn,
791 					   "SET session sql_mode='ANSI_QUOTES,"
792 					   "NO_ENGINE_SUBSTITUTION';");
793 	}
794 	slurm_mutex_unlock(&mysql_conn->lock);
795 	errno = rc;
796 	return rc;
797 }
798 
mysql_db_close_db_connection(mysql_conn_t * mysql_conn)799 extern int mysql_db_close_db_connection(mysql_conn_t *mysql_conn)
800 {
801 	slurm_mutex_lock(&mysql_conn->lock);
802 	if (mysql_conn && mysql_conn->db_conn) {
803 		if (mysql_thread_safe())
804 			mysql_thread_end();
805 		mysql_close(mysql_conn->db_conn);
806 		mysql_conn->db_conn = NULL;
807 	}
808 	slurm_mutex_unlock(&mysql_conn->lock);
809 	return SLURM_SUCCESS;
810 }
811 
mysql_db_cleanup()812 extern int mysql_db_cleanup()
813 {
814 	debug3("starting mysql cleaning up");
815 
816 #ifdef mysql_library_end
817 	mysql_library_end();
818 #else
819 	mysql_server_end();
820 #endif
821 	debug3("finished mysql cleaning up");
822 	return SLURM_SUCCESS;
823 }
824 
mysql_db_query(mysql_conn_t * mysql_conn,char * query)825 extern int mysql_db_query(mysql_conn_t *mysql_conn, char *query)
826 {
827 	int rc = SLURM_SUCCESS;
828 
829 	if (!mysql_conn || !mysql_conn->db_conn) {
830 		fatal("You haven't inited this storage yet.");
831 		return 0;	/* For CLANG false positive */
832 	}
833 	slurm_mutex_lock(&mysql_conn->lock);
834 	rc = _mysql_query_internal(mysql_conn->db_conn, query);
835 	slurm_mutex_unlock(&mysql_conn->lock);
836 	return rc;
837 }
838 
839 /*
840  * Executes a single delete sql query.
841  * Returns the number of deleted rows, <0 for failure.
842  */
mysql_db_delete_affected_rows(mysql_conn_t * mysql_conn,char * query)843 extern int mysql_db_delete_affected_rows(mysql_conn_t *mysql_conn, char *query)
844 {
845 	int rc = SLURM_SUCCESS;
846 
847 	if (!mysql_conn || !mysql_conn->db_conn) {
848 		fatal("You haven't inited this storage yet.");
849 		return 0;	/* For CLANG false positive */
850 	}
851 	slurm_mutex_lock(&mysql_conn->lock);
852 	if (!(rc = _mysql_query_internal(mysql_conn->db_conn, query)))
853 		rc = mysql_affected_rows(mysql_conn->db_conn);
854 	slurm_mutex_unlock(&mysql_conn->lock);
855 	return rc;
856 }
857 
mysql_db_ping(mysql_conn_t * mysql_conn)858 extern int mysql_db_ping(mysql_conn_t *mysql_conn)
859 {
860 	int rc;
861 
862 	if (!mysql_conn->db_conn)
863 		return -1;
864 
865 	/* clear out the old results so we don't get a 2014 error */
866 	slurm_mutex_lock(&mysql_conn->lock);
867 	_clear_results(mysql_conn->db_conn);
868 	rc = mysql_ping(mysql_conn->db_conn);
869 	/*
870 	 * Starting in MariaDB 10.2 many of the api commands started
871 	 * setting errno erroneously.
872 	 */
873 	if (!rc)
874 		errno = 0;
875 	slurm_mutex_unlock(&mysql_conn->lock);
876 	return rc;
877 }
878 
mysql_db_commit(mysql_conn_t * mysql_conn)879 extern int mysql_db_commit(mysql_conn_t *mysql_conn)
880 {
881 	int rc = SLURM_SUCCESS;
882 
883 	if (!mysql_conn->db_conn)
884 		return SLURM_ERROR;
885 
886 	slurm_mutex_lock(&mysql_conn->lock);
887 	/* clear out the old results so we don't get a 2014 error */
888 	_clear_results(mysql_conn->db_conn);
889 	if (mysql_commit(mysql_conn->db_conn)) {
890 		error("mysql_commit failed: %d %s",
891 		      mysql_errno(mysql_conn->db_conn),
892 		      mysql_error(mysql_conn->db_conn));
893 		errno = mysql_errno(mysql_conn->db_conn);
894 		rc = SLURM_ERROR;
895 	}
896 	slurm_mutex_unlock(&mysql_conn->lock);
897 	return rc;
898 }
899 
mysql_db_rollback(mysql_conn_t * mysql_conn)900 extern int mysql_db_rollback(mysql_conn_t *mysql_conn)
901 {
902 	int rc = SLURM_SUCCESS;
903 
904 	if (!mysql_conn->db_conn)
905 		return SLURM_ERROR;
906 
907 	slurm_mutex_lock(&mysql_conn->lock);
908 	/* clear out the old results so we don't get a 2014 error */
909 	_clear_results(mysql_conn->db_conn);
910 	if (mysql_rollback(mysql_conn->db_conn)) {
911 		error("mysql_commit failed: %d %s",
912 		      mysql_errno(mysql_conn->db_conn),
913 		      mysql_error(mysql_conn->db_conn));
914 		errno = mysql_errno(mysql_conn->db_conn);
915 		rc = SLURM_ERROR;
916 	} else {
917 		/*
918 		 * Starting in MariaDB 10.2 many of the api commands started
919 		 * setting errno erroneously.
920 		 */
921 		errno = 0;
922 	}
923 	slurm_mutex_unlock(&mysql_conn->lock);
924 	return rc;
925 
926 }
927 
mysql_db_query_ret(mysql_conn_t * mysql_conn,char * query,bool last)928 extern MYSQL_RES *mysql_db_query_ret(mysql_conn_t *mysql_conn,
929 				     char *query, bool last)
930 {
931 	MYSQL_RES *result = NULL;
932 
933 	slurm_mutex_lock(&mysql_conn->lock);
934 	if (_mysql_query_internal(mysql_conn->db_conn, query) != SLURM_ERROR)  {
935 		if (mysql_errno(mysql_conn->db_conn) == ER_NO_SUCH_TABLE)
936 			goto fini;
937 		else if (last)
938 			result = _get_last_result(mysql_conn->db_conn);
939 		else
940 			result = _get_first_result(mysql_conn->db_conn);
941 		/*
942 		 * Starting in MariaDB 10.2 many of the api commands started
943 		 * setting errno erroneously.
944 		 */
945 		errno = 0;
946 		if (!result && mysql_field_count(mysql_conn->db_conn)) {
947 			/* should have returned data */
948 			error("We should have gotten a result: '%m' '%s'",
949 			      mysql_error(mysql_conn->db_conn));
950 		}
951 	}
952 
953 fini:
954 	slurm_mutex_unlock(&mysql_conn->lock);
955 	return result;
956 }
957 
mysql_db_query_check_after(mysql_conn_t * mysql_conn,char * query)958 extern int mysql_db_query_check_after(mysql_conn_t *mysql_conn, char *query)
959 {
960 	int rc = SLURM_SUCCESS;
961 
962 	slurm_mutex_lock(&mysql_conn->lock);
963 	if ((rc = _mysql_query_internal(
964 		     mysql_conn->db_conn, query)) != SLURM_ERROR)
965 		rc = _clear_results(mysql_conn->db_conn);
966 	slurm_mutex_unlock(&mysql_conn->lock);
967 	return rc;
968 }
969 
mysql_db_insert_ret_id(mysql_conn_t * mysql_conn,char * query)970 extern uint64_t mysql_db_insert_ret_id(mysql_conn_t *mysql_conn, char *query)
971 {
972 	uint64_t new_id = 0;
973 
974 	slurm_mutex_lock(&mysql_conn->lock);
975 	if (_mysql_query_internal(mysql_conn->db_conn, query) != SLURM_ERROR)  {
976 		new_id = mysql_insert_id(mysql_conn->db_conn);
977 		if (!new_id) {
978 			/* should have new id */
979 			error("We should have gotten a new id: %s",
980 			      mysql_error(mysql_conn->db_conn));
981 		}
982 	}
983 	slurm_mutex_unlock(&mysql_conn->lock);
984 	return new_id;
985 
986 }
987 
mysql_db_create_table(mysql_conn_t * mysql_conn,char * table_name,storage_field_t * fields,char * ending)988 extern int mysql_db_create_table(mysql_conn_t *mysql_conn, char *table_name,
989 				 storage_field_t *fields, char *ending)
990 {
991 	char *query = NULL;
992 	int i = 0, rc;
993 	storage_field_t *first_field = fields;
994 
995 	if (!fields || !fields->name) {
996 		error("Not creating an empty table");
997 		return SLURM_ERROR;
998 	}
999 
1000 	/* We have an internal table called table_defs_table which
1001 	 * contains the definition of each table in the database.  To
1002 	 * speed things up we just check against that to see if
1003 	 * anything has changed.
1004 	 */
1005 	query = xstrdup_printf("create table if not exists %s "
1006 			       "(creation_time int unsigned not null, "
1007 			       "mod_time int unsigned default 0 not null, "
1008 			       "table_name text not null, "
1009 			       "definition text not null, "
1010 			       "primary key (table_name(50))) engine='innodb'",
1011 			       table_defs_table);
1012 	if (mysql_db_query(mysql_conn, query) == SLURM_ERROR) {
1013 		xfree(query);
1014 		return SLURM_ERROR;
1015 	}
1016 	xfree(query);
1017 
1018 	query = xstrdup_printf("create table if not exists %s (`%s` %s",
1019 			       table_name, fields->name, fields->options);
1020 	i = 1;
1021 	fields++;
1022 
1023 	while (fields && fields->name) {
1024 		xstrfmtcat(query, ", `%s` %s", fields->name, fields->options);
1025 		fields++;
1026 		i++;
1027 	}
1028 	xstrcat(query, ending);
1029 
1030 	/* make sure we can do a rollback */
1031 	xstrcat(query, " engine='innodb'");
1032 
1033 	if (mysql_db_query(mysql_conn, query) == SLURM_ERROR) {
1034 		xfree(query);
1035 		return SLURM_ERROR;
1036 	}
1037 	xfree(query);
1038 
1039 	rc = _mysql_make_table_current(
1040 		mysql_conn, table_name, first_field, ending);
1041 	return rc;
1042 }
1043