1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 * 21 * Copyright 2009 Sun Microsystems, Inc. All rights reserved. 22 * Use is subject to license terms. 23 */ 24 25 26 #include <stdio.h> 27 #include <string.h> 28 #include <stdlib.h> 29 #include <time.h> 30 #include <errno.h> 31 #include <sys/types.h> 32 #include <ctype.h> 33 #include <syslog.h> 34 #include <unistd.h> 35 #include <libpq-fe.h> 36 #include <mms_list.h> 37 #include <mms_parser.h> 38 #include <msg_sub.h> 39 #include <mms_trace.h> 40 #include <mms_strapp.h> 41 #include "mm_commands.h" 42 #include "mm_db.h" 43 #include "mm.h" 44 #include "mm_util.h" 45 #include "mm_db_version.h" 46 #include <mms_cfg.h> 47 #include <net_cfg_service.h> 48 #include <fcntl.h> 49 50 static char *_SrcFile = __FILE__; 51 static mm_db_rval_t db_connect(mm_db_t *db, char *database_name); 52 static mm_db_rval_t mm_db_oid_init(mm_db_t *db); 53 static void notice_receiver(void *, const PGresult *); 54 static char *mm_db_get_sql_cmd(char *fmt, va_list args); 55 static mm_db_rval_t mm_db_do_exec(char *file, int line, mm_db_t *db, int flag, 56 char *fmt, va_list args); 57 58 /* 59 * Initialize database, called once at startup. 60 * 61 * The MMS installation process must up-grade or down-grade the 62 * postgres database to the same version as mm is expecting. 63 */ 64 mm_db_rval_t 65 mm_db_init(mm_db_t *db) 66 { 67 int exists; 68 int rows; 69 int row; 70 int try = 0; 71 const int retrys = 10; 72 char *initialized; 73 int db_version; 74 75 76 mms_trace(MMS_DEVP, "inspecting database"); 77 78 try_again: 79 if (mm_is_exiting()) { 80 mms_trace(MMS_ERR, "MM Exiting"); 81 exit(SMF_EXIT_OK); 82 } 83 if (try >= retrys) { 84 mms_trace(MMS_ERR, "unable to initialize, retries exceed"); 85 mms_trace(MMS_INFO, 86 "HINT: make sure mms:db is configured and running"); 87 return (MM_DB_ERROR); 88 } else if (try > 1) { 89 mms_trace(MMS_DEVP, "trying to initialize again %d", try); 90 } 91 try++; 92 93 if (db_connect(db, "template1") != MM_DB_OK) { 94 mms_trace(MMS_INFO, "can't connect to built-in database"); 95 sleep(2); 96 goto try_again; 97 } 98 if (mm_db_exec(HERE, db, "SELECT datname FROM pg_database " 99 "WHERE datname = '%s';", db->mm_db_cfg->mm_db_name) != MM_DB_DATA) { 100 mm_db_disconnect(db); 101 mms_trace(MMS_ERR, "can't query for database"); 102 return (MM_DB_ERROR); 103 } 104 rows = PQntuples(db->mm_db_results); 105 for (row = 0, exists = 0; row < rows && exists == 0; row++) { 106 if (strcmp(PQgetvalue(db->mm_db_results, row, 0), 107 db->mm_db_cfg->mm_db_name) == 0) { 108 mms_trace(MMS_DEVP, "found database %s", 109 db->mm_db_cfg->mm_db_name); 110 exists = 1; 111 break; 112 } 113 } 114 mm_clear_db(&db->mm_db_results); 115 if (exists == 0) { 116 mms_trace(MMS_ERR, "database %s not found", 117 db->mm_db_cfg->mm_db_name); 118 mms_trace(MMS_INFO, "HINT: run `mms.ksh data`"); 119 return (MM_DB_ERROR); 120 } 121 mm_db_disconnect(db); 122 123 if (db_connect(db, db->mm_db_cfg->mm_db_name) != MM_DB_OK) { 124 mms_trace(MMS_ERR, "can't connect to database %s", 125 db->mm_db_cfg->mm_db_name); 126 mms_trace(MMS_INFO, "HINT: run `mms.ksh start`"); 127 return (MM_DB_ERROR); 128 } 129 if (mm_db_exec(HERE, db, "SELECT \"DBInitialized\",\"DBVersion\" " 130 "FROM \"MM\";") != MM_DB_DATA) { 131 mms_trace(MMS_ERR, "can't query internal mm object"); 132 mm_clear_db(&db->mm_db_results); 133 return (MM_DB_ERROR); 134 } 135 if (PQntuples(db->mm_db_results) != 1) { 136 mm_clear_db(&db->mm_db_results); 137 mms_trace(MMS_ERR, "database not completely initialized"); 138 mms_trace(MMS_INFO, "HINT: run `mms.ksh clean; mms.ksh data`"); 139 return (MM_DB_ERROR); 140 } 141 initialized = PQgetvalue(db->mm_db_results, 0, 0); 142 if (strcmp(initialized, "true") != 0 && 143 strcmp(initialized, "t") != 0) { 144 mms_trace(MMS_ERR, "database not initialized"); 145 mms_trace(MMS_INFO, "HINT: run `mms.ksh clean; mms.ksh data`"); 146 mm_clear_db(&db->mm_db_results); 147 return (MM_DB_ERROR); 148 } 149 db_version = atoi(PQgetvalue(db->mm_db_results, 0, 1)); 150 if (db_version != MM_DB_VERSION) { 151 if (db_version > MM_DB_VERSION) { 152 mms_trace(MMS_INFO, 153 "down-grade database version from %d to %d", 154 db_version, MM_DB_VERSION); 155 if (mm_db_downgrade(db, db_version, MM_DB_VERSION) 156 != MM_DB_OK) { 157 mms_trace(MMS_ERR, 158 "down-grade database failed"); 159 return (MM_DB_ERROR); 160 } 161 } else { 162 mms_trace(MMS_INFO, 163 "up-grade database version from %d to %d", 164 db_version, MM_DB_VERSION); 165 if (mm_db_upgrade(db, db_version, MM_DB_VERSION) 166 != MM_DB_OK) { 167 mms_trace(MMS_ERR, "up-grade database failed"); 168 return (MM_DB_ERROR); 169 } 170 } 171 if (mm_db_exec(HERE, db, "UPDATE \"MM\" SET " 172 "\"DBVersion\" = '%d';", MM_DB_VERSION) != MM_DB_OK) { 173 mms_trace(MMS_ERR, "Error upgrade DBVersion"); 174 } 175 mm_clear_db(&db->mm_db_results); 176 mms_trace(MMS_DEVP, "database mods done"); 177 } 178 mms_trace(MMS_INFO, "mm and database version %d", MM_DB_VERSION); 179 mm_clear_db(&db->mm_db_results); 180 181 if (mm_db_oid_init(db) != MM_DB_OK) { 182 mms_trace(MMS_ERR, "failed to read database OIDs"); 183 return (MM_DB_ERROR); 184 } 185 186 mm_db_disconnect(db); 187 mms_trace(MMS_DEVP, "Initialization status - ok"); 188 return (MM_DB_OK); 189 } 190 191 /* Connect to the database. */ 192 static mm_db_rval_t 193 db_connect(mm_db_t *db, char *database_name) 194 { 195 mm_db_rval_t rval; 196 char *sql_cmd; 197 mm_db_cfg_t *db_cfg = db->mm_db_cfg; 198 199 sql_cmd = mms_strnew("host=%s port=%d dbname=%s user=%s", 200 db->mm_db_cfg->mm_db_host, 201 db->mm_db_cfg->mm_db_port, 202 database_name, 203 db->mm_db_cfg->mm_db_user); 204 mms_trace(MMS_DEBUG, "connect command - %s", sql_cmd); 205 if (db_cfg->mm_db_passwd) { 206 mms_trace(MMS_DEBUG, "adding password"); 207 sql_cmd = mms_strapp(sql_cmd, " password=%s", 208 db_cfg->mm_db_passwd); 209 } 210 if (sql_cmd == NULL) { 211 mms_trace(MMS_ERR, "Connect - unable to build command"); 212 return (MM_DB_ERROR); 213 } 214 db->mm_db_rval = PQstatus(db->mm_db_conn = PQconnectdb(sql_cmd)); 215 switch (db->mm_db_rval) { 216 case CONNECTION_OK: 217 if ((db->mm_db_fd = PQsocket(db->mm_db_conn)) < 0) { 218 (void) mm_db_disconnect(db); 219 rval = MM_DB_ERROR; 220 mms_trace(MMS_DEVP, 221 "connect command status, no sockfd"); 222 } else { 223 rval = MM_DB_OK; 224 PQsetNoticeReceiver(db->mm_db_conn, notice_receiver, 225 NULL); 226 mms_trace(MMS_DEVP, "connect command status - ok"); 227 } 228 break; 229 default: 230 rval = MM_DB_ERROR; 231 mms_trace(MMS_DEVP, "connect command status - %d, %s", 232 db->mm_db_rval, PQerrorMessage(db->mm_db_conn)); 233 } 234 free(sql_cmd); 235 return (rval); 236 } 237 238 /* Disconnect from the database. */ 239 void 240 mm_db_disconnect(mm_db_t *db) 241 { 242 if (db != NULL && db->mm_db_conn != NULL) { 243 PQfinish(db->mm_db_conn); 244 db->mm_db_conn = NULL; 245 db->mm_db_fd = -1; 246 mms_trace(MMS_DEVP, "disconnect"); 247 } 248 } 249 250 mm_db_rval_t 251 mm_db_resend(mm_db_t *db, char *sql_cmd) { 252 int num_retry = 0; 253 int rc; 254 255 mm_char_list_t *node; 256 mm_char_list_t *next; 257 258 int max_retry = 10; 259 int timeout = 3; 260 261 char *buf = NULL; 262 263 /* MM has disconnected from the db */ 264 /* attempt to reconnect and resend all commands */ 265 /* in the currect transaction block for this db connection */ 266 267 if ((buf = mms_cfg_alloc_getvar(MMS_CFG_DB_RETRY, NULL)) == NULL) { 268 /* report service configuration repoistory scf_error() */ 269 mms_trace(MMS_ERR, "using default-path, ssi path cfg error"); 270 max_retry = 50; 271 } else { 272 max_retry = atoi(buf); 273 free(buf); 274 buf = NULL; 275 } 276 if ((buf = mms_cfg_alloc_getvar(MMS_CFG_DB_TIMEOUT, NULL)) == NULL) { 277 /* report service configuration repoistory scf_error() */ 278 mms_trace(MMS_ERR, "using default-path, ssi path cfg error"); 279 timeout = 3; 280 } else { 281 timeout = atoi(buf); 282 free(buf); 283 buf = NULL; 284 } 285 286 mms_trace(MMS_DEVP, 287 "mm_db_resend: " 288 "connection to db lost fd - %d, " 289 "attempt to reconnect, max retry = %d, timeout = %d", 290 db->mm_db_fd, 291 max_retry, 292 timeout); 293 294 while (mm_db_reconnect(db) == MM_DB_ERROR) { 295 296 if (mm_is_exiting()) { 297 mms_trace(MMS_INFO, "MM Exiting"); 298 exit(SMF_EXIT_OK); 299 } 300 301 num_retry ++; 302 if (num_retry > max_retry) { 303 mms_trace(MMS_ERR, 304 "mm_db_resend: " 305 "reconnect for db fd - %d failed " 306 "to many times %d, MM exiting", 307 db->mm_db_fd, 308 max_retry); 309 exit(SMF_EXIT_ERR_FATAL); 310 } 311 mms_trace(MMS_ERR, 312 "mm_db_resend: " 313 "error reconnecting to db fd - %d" 314 ", retry %d", 315 db->mm_db_fd, 316 num_retry); 317 sleep(timeout); 318 } 319 mms_trace(MMS_DEVP, 320 "mm_db_resend: " 321 "reconnect successful db fd -%d, " 322 "try to resend commands", 323 db->mm_db_fd); 324 db->mm_db_resending = 1; 325 if ((db->mm_db_txn_blk == 1) && 326 (db->mm_db_has_list)) { 327 mms_trace(MMS_ERR, 328 "mm_db_resend: " 329 "commands in list"); 330 /* have commands in list */ 331 for (node = mms_list_head(&db->mm_db_cmds); 332 node != NULL; 333 node = next) { 334 next = mms_list_next(&db->mm_db_cmds, node); 335 rc = mm_db_exec(HERE, db, node->text); 336 } 337 338 } else { 339 mms_trace(MMS_ERR, 340 "mm_db_resend: " 341 "single command"); 342 /* don't have commands in list */ 343 /* resend sql_cmd */ 344 rc = mm_db_exec(HERE, db, sql_cmd); 345 } 346 mms_trace(MMS_DEVP, 347 "mm_db_resend: " 348 "reconnect successful db fd - %d, " 349 "commands sent", 350 db->mm_db_fd); 351 db->mm_db_resending = 0; 352 return (rc); 353 354 } 355 356 /* 357 * Execute sql command with sensitive information. 358 */ 359 mm_db_rval_t 360 mm_db_exec_si(char *file, int line, mm_db_t *db, char *fmt, ...) 361 { 362 va_list args; 363 int rval; 364 365 va_start(args, fmt); 366 rval = mm_db_do_exec(file, line, db, 0, fmt, args); 367 va_end(args); 368 369 return (rval); 370 } 371 372 /* 373 * Execute sql command. 374 */ 375 mm_db_rval_t 376 mm_db_exec(char *file, int line, mm_db_t *db, char *fmt, ...) 377 { 378 va_list args; 379 int rval; 380 381 va_start(args, fmt); 382 rval = mm_db_do_exec(file, line, db, 1, fmt, args); 383 va_end(args); 384 385 return (rval); 386 } 387 388 /* 389 * Add sql command terminator if needed. 390 */ 391 static char * 392 mm_db_get_sql_cmd(char *fmt, va_list args) 393 { 394 int i; 395 char *sql_cmd; 396 397 if ((sql_cmd = mms_vstrapp(NULL, fmt, args)) != NULL) { 398 /* add sql command terminator if needed */ 399 for (i = strlen(sql_cmd) - 1; i >= 0; i--) { 400 if (sql_cmd[i] == ';') { 401 break; 402 } 403 if (isspace(sql_cmd[i]) == 0) { 404 sql_cmd = mms_strapp(sql_cmd, ";"); 405 break; 406 } 407 } 408 } 409 return (sql_cmd); 410 } 411 412 /* 413 * Execute synchronous sql command in auto-commit mode, no txn needed. 414 * Multiple commands can be wrapped in transaction block. 415 */ 416 static mm_db_rval_t 417 mm_db_do_exec(char *file, int line, mm_db_t *db, int flag, 418 char *fmt, va_list args) 419 { 420 int rval; 421 char *count; 422 char *sql_cmd; 423 424 if ((sql_cmd = mm_db_get_sql_cmd(fmt, args)) == NULL) { 425 mms_trace(MMS_ERR, "Exec %s:%d fd %d - unable to build command", 426 file, line, db->mm_db_fd); 427 return (MM_DB_ERROR); 428 } 429 /* add this cmd to this db's cmd list */ 430 if (db->mm_db_has_list && 431 db->mm_db_txn_blk && 432 (db->mm_db_resending == 0)) { 433 (void) mm_add_char(sql_cmd, &db->mm_db_cmds); 434 } 435 db->mm_db_results = PQexec(db->mm_db_conn, sql_cmd); 436 if (flag) { 437 mms_trace(MMS_DEVP, "Exec %s:%d fd %d - \n\n%s\n", file, line, 438 db->mm_db_fd, sql_cmd); 439 } else { 440 mms_trace(MMS_DEVP, "Exec %s:%d fd %d", file, line, 441 db->mm_db_fd); 442 } 443 db->mm_db_rval = PQresultStatus(db->mm_db_results); 444 db->mm_db_oid = PQoidValue(db->mm_db_results); 445 count = PQcmdTuples(db->mm_db_results); 446 db->mm_db_count = count ? atoi(count) : 0; 447 switch (db->mm_db_rval) { 448 case PGRES_COMMAND_OK: 449 rval = MM_DB_OK; 450 mms_trace(MMS_DEVP, "Exec %s:%d fd %d status - ok (%d %d)", 451 file, line, db->mm_db_fd, db->mm_db_oid, 452 db->mm_db_count); 453 mm_clear_db(&db->mm_db_results); 454 break; 455 case PGRES_TUPLES_OK: 456 rval = MM_DB_DATA; 457 mms_trace(MMS_DEVP, 458 "Exec %s:%d fd %d status - ok, data (%d %d)", 459 file, line, db->mm_db_fd, db->mm_db_oid, 460 db->mm_db_count); 461 break; 462 default: 463 rval = MM_DB_ERROR; 464 mms_trace(MMS_ERR, "Exec %s:%d fd %d status - %d, %s", 465 file, line, db->mm_db_fd, db->mm_db_rval, 466 PQerrorMessage(db->mm_db_conn)); 467 468 if ((db->mm_db_rval == 7) && 469 ((strstr(PQerrorMessage(db->mm_db_conn), 470 "no connection to the server") != NULL) || 471 (strstr(PQerrorMessage(db->mm_db_conn), 472 "terminating connection due " 473 "to administrator command") != NULL) || 474 (strstr(PQerrorMessage(db->mm_db_conn), 475 "server closed the connection unexpectedly") != NULL))) { 476 /* DB has disconnected, attempt to reconnect */ 477 /* and resend the entire transaction block */ 478 /* associated with this db fd */ 479 mm_clear_db(&db->mm_db_results); 480 rval = mm_db_resend(db, sql_cmd); 481 free(sql_cmd); 482 return (rval); 483 484 } 485 } 486 free(sql_cmd); 487 return (rval); 488 } 489 490 /* Trace query execution notice and warning messages. */ 491 static void 492 /* LINTED: void *arg is required arg in PQsetNoticeReceiver */ 493 notice_receiver(void *arg, const PGresult *res) 494 { 495 int rval; 496 497 rval = PQresultStatus(res); 498 if (rval != 6) { 499 mms_trace(MMS_DEVP, "Notice Receiver - %d, %s", 500 rval, PQresultErrorMessage(res)); 501 } 502 } 503 504 505 /* Get database oids for mms string conversions. */ 506 static mm_db_rval_t 507 mm_db_oid_init(mm_db_t *db) 508 { 509 char *value; 510 511 if (mm_db_exec(HERE, db, "SELECT OID FROM pg_type " 512 "WHERE typname = 'bool';") != MM_DB_DATA) { 513 mms_trace(MMS_ERR, "boolean oid not found"); 514 return (MM_DB_ERROR); 515 } 516 if (PQntuples(db->mm_db_results) != 1) { 517 mms_trace(MMS_ERR, "missing boolean oid"); 518 mm_clear_db(&db->mm_db_results); 519 return (MM_DB_ERROR); 520 } 521 value = PQgetvalue(db->mm_db_results, 0, 0); 522 db->mm_db_cfg->mm_db_bool_oid = atoi(value); 523 mm_clear_db(&db->mm_db_results); 524 525 if (mm_db_exec(HERE, db, "SELECT OID FROM pg_type " 526 "WHERE typname = 'timestamp';") != MM_DB_DATA) { 527 mms_trace(MMS_ERR, "timestamp oid not found"); 528 return (MM_DB_ERROR); 529 } 530 if (PQntuples(db->mm_db_results) != 1) { 531 mms_trace(MMS_ERR, "missing timestamp oid"); 532 mm_clear_db(&db->mm_db_results); 533 return (MM_DB_ERROR); 534 } 535 value = PQgetvalue(db->mm_db_results, 0, 0); 536 db->mm_db_cfg->mm_db_timestamp_oid = atoi(value); 537 mm_clear_db(&db->mm_db_results); 538 return (MM_DB_OK); 539 } 540 541 542 /* Connect to database. */ 543 mm_db_rval_t 544 mm_db_connect(mm_db_t *db) 545 { 546 mms_trace(MMS_DEVP, "Connection"); 547 if (db_connect(db, db->mm_db_cfg->mm_db_name) != MM_DB_OK) { 548 mms_trace(MMS_DEVP, "Connection status - failed to connect"); 549 return (MM_DB_ERROR); 550 } 551 mms_trace(MMS_DEVP, "Connection status - ok"); 552 553 return (MM_DB_OK); 554 } 555 556 /* Reconnect to the database. */ 557 mm_db_rval_t 558 mm_db_reconnect(mm_db_t *db) 559 { 560 mm_db_cfg_t *db_cfg = db->mm_db_cfg; 561 562 /* Re-read password for db */ 563 db_cfg->mm_db_passwd = mms_net_cfg_read_pass_file(MMS_NET_CFG_DB_FILE); 564 565 mm_db_disconnect(db); 566 if (mm_db_connect(db) != MM_DB_OK) { 567 mms_trace(MMS_DEVP, "reconnect init failed"); 568 return (MM_DB_ERROR); 569 } 570 if (mm_db_connected(db) == B_TRUE) { 571 return (MM_DB_OK); 572 } 573 mms_trace(MMS_DEVP, "reconnect failed"); 574 return (MM_DB_ERROR); 575 } 576 577 /* Determine if database connection exists. */ 578 boolean_t 579 mm_db_connected(mm_db_t *db) 580 { 581 if (db != NULL && 582 db->mm_db_conn != NULL && 583 PQstatus(db->mm_db_conn) == CONNECTION_OK) { 584 return (B_TRUE); 585 } 586 return (B_FALSE); 587 } 588 589 /* Database transaction begin. */ 590 mm_db_rval_t 591 mm_db_txn_begin(mm_db_t *db) 592 { 593 int rc; 594 595 mms_trace(MMS_DEVP, "TXN begin"); 596 /* clear this db's cmd list */ 597 if (db->mm_db_has_list) 598 mm_free_list(&db->mm_db_cmds); 599 if ((rc = mm_db_exec(HERE, db, "BEGIN;")) == MM_DB_OK) { 600 db->mm_db_txn_blk = 1; 601 (void) mm_add_char("BEGIN;", 602 &db->mm_db_cmds); 603 } else { 604 db->mm_db_txn_blk = 0; 605 } 606 return (rc); 607 } 608 609 /* Database transaction rollback. */ 610 mm_db_rval_t 611 mm_db_txn_rollback(mm_db_t *db) 612 { 613 int rc; 614 mms_trace(MMS_DEVP, "TXN rollback"); 615 db->mm_db_txn_blk = 0; 616 rc = mm_db_exec(HERE, db, "ROLLBACK;"); 617 /* clear this db's cmd list */ 618 if (db->mm_db_has_list) 619 mm_free_list(&db->mm_db_cmds); 620 return (rc); 621 } 622 623 /* Database transaction savepoint rollback. */ 624 mm_db_rval_t 625 mm_db_txn_savepoint_rollback(mm_db_t *db, char *savepoint) 626 { 627 if (db->mm_db_txn_blk) { 628 mms_trace(MMS_DEVP, "TXN rollback to %s", savepoint); 629 return (mm_db_exec(HERE, db, "ROLLBACK TO %s;", savepoint)); 630 } 631 return (MM_DB_OK); 632 } 633 634 /* Database transaction savepoint. */ 635 mm_db_rval_t 636 mm_db_txn_savepoint(mm_db_t *db, char *savepoint) 637 { 638 if (db->mm_db_txn_blk) { 639 mms_trace(MMS_DEVP, "TXN savepoint %s", savepoint); 640 return (mm_db_exec(HERE, db, "SAVEPOINT %s;", savepoint)); 641 } 642 return (MM_DB_OK); 643 } 644 645 /* Database transaction release savepoint. */ 646 mm_db_rval_t 647 mm_db_txn_release_savepoint(mm_db_t *db, char *savepoint) 648 { 649 int rc; 650 651 if (db->mm_db_txn_blk) { 652 mms_trace(MMS_DEVP, "TXN release savepoint %s", savepoint); 653 rc = mm_db_exec(HERE, db, "RELEASE SAVEPOINT %s;", savepoint); 654 return (rc); 655 } 656 return (MM_DB_OK); 657 } 658 659 /* Database transaction commit. */ 660 mm_db_rval_t 661 mm_db_txn_commit(mm_db_t *db) 662 { 663 int rc; 664 mms_trace(MMS_DEVP, "TXN commit"); 665 db->mm_db_txn_blk = 0; 666 rc = mm_db_exec(HERE, db, "COMMIT;"); 667 /* clear this db's cmd list */ 668 if (db->mm_db_has_list) 669 mm_free_list(&db->mm_db_cmds); 670 return (rc); 671 } 672 673 /* Add attribute (column) to object (table). */ 674 mm_db_rval_t 675 mm_db_create_attribute(mm_db_t *db, char *objname, char *attribute) 676 { 677 int col; 678 int cols; 679 int rows; 680 int found; 681 682 mms_trace(MMS_DEBUG, "mm_db_create_attribute"); 683 684 /* does this object allow user-defined attributes? */ 685 if (mm_db_exec(HERE, db, "SELECT * FROM \"SYSTEM_DEFINED\" " 686 "WHERE objname = '%s'", objname) != MM_DB_DATA) { 687 mm_clear_db(&db->mm_db_results); 688 return (MM_DB_ERROR); 689 } 690 rows = PQntuples(db->mm_db_results); 691 mm_clear_db(&db->mm_db_results); 692 if (rows == 0) { 693 /* user-defined attributes not allowed */ 694 return (MM_DB_OK); 695 } 696 /* does attribute already exist? */ 697 if (mm_db_exec(HERE, db, 698 "SELECT * FROM \"%s\"", objname) != MM_DB_DATA) { 699 return (MM_DB_ERROR); 700 } 701 found = 0; 702 cols = PQnfields(db->mm_db_results); 703 for (col = 0; !found && col < cols; col++) { 704 if (strcmp(PQfname(db->mm_db_results, col), attribute) == 0) { 705 found = 1; 706 } 707 } 708 mm_clear_db(&db->mm_db_results); 709 if (found) { 710 /* user-defined attributes already exists */ 711 return (MM_DB_OK); 712 } 713 /* add user-defined table column */ 714 if (mm_db_exec(HERE, db, "ALTER TABLE \"%s\" ADD \"%s\" text", 715 objname, attribute) != MM_DB_OK) { 716 return (MM_DB_ERROR); 717 } 718 /* user-defined table column added */ 719 return (MM_DB_OK); 720 } 721 722 mm_db_rval_t 723 mm_db_create_attribute2(mm_db_t *db, char *objname, 724 char *attribute, char **send_buf) 725 { 726 int col; 727 int cols; 728 int rows; 729 int found; 730 731 /* does this object allow user-defined attributes? */ 732 if (mm_db_exec(HERE, db, "SELECT * FROM \"SYSTEM_DEFINED\" " 733 "WHERE objname = '%s'", objname) != MM_DB_DATA) { 734 mm_clear_db(&db->mm_db_results); 735 return (MM_DB_ERROR); 736 } 737 rows = PQntuples(db->mm_db_results); 738 mm_clear_db(&db->mm_db_results); 739 if (rows == 0) { 740 /* user-defined attributes not allowed */ 741 return (MM_DB_OK); 742 } 743 /* does attribute already exist? */ 744 if (mm_db_exec(HERE, db, 745 "SELECT * FROM \"%s\"", objname) != MM_DB_DATA) { 746 return (MM_DB_ERROR); 747 } 748 found = 0; 749 cols = PQnfields(db->mm_db_results); 750 for (col = 0; !found && col < cols; col++) { 751 if (strcmp(PQfname(db->mm_db_results, col), attribute) == 0) { 752 found = 1; 753 } 754 } 755 mm_clear_db(&db->mm_db_results); 756 if (found) { 757 /* user-defined attributes already exists */ 758 return (MM_DB_OK); 759 } 760 /* add user-defined table column */ 761 *send_buf = mms_strapp(*send_buf, "ALTER TABLE \"%s\" ADD \"%s\" text;", 762 objname, attribute); 763 764 /* user-defined table column added */ 765 return (MM_DB_OK); 766 } 767 768 /* Delete attribute (column) from object (table). */ 769 mm_db_rval_t 770 mm_db_delete_attribute(mm_db_t *db, char *objname, char *attribute) 771 { 772 int row; 773 int rows; 774 int col; 775 int cols; 776 int found; 777 778 779 mms_trace(MMS_INFO, "mm_db_delete_attribute"); 780 781 /* does object allow user-defined attributes? */ 782 if (mm_db_exec(HERE, db, "SELECT * FROM \"SYSTEM_DEFINED\" " 783 "WHERE objname = '%s'", objname) != MM_DB_DATA) { 784 mm_clear_db(&db->mm_db_results); 785 return (MM_DB_ERROR); 786 } 787 rows = PQntuples(db->mm_db_results); 788 mm_clear_db(&db->mm_db_results); 789 if (rows == 0) { 790 /* user-defined attributes not allowed */ 791 return (MM_DB_OK); 792 } 793 /* does object attribute exists? */ 794 if (mm_db_exec(HERE, db, 795 "SELECT * FROM \"%s\"", objname) != MM_DB_DATA) { 796 return (MM_DB_ERROR); 797 } 798 found = 0; 799 cols = PQnfields(db->mm_db_results); 800 for (col = 0; !found && col < cols; col++) { 801 if (strcmp(PQfname(db->mm_db_results, col), attribute) == 0) { 802 found = 1; 803 } 804 } 805 mm_clear_db(&db->mm_db_results); 806 if (!found) { 807 /* user-defined attribute not found */ 808 return (MM_DB_OK); 809 } 810 /* is attribute part of base data model? */ 811 if (mm_db_exec(HERE, db, "SELECT attribute FROM \"SYSTEM_DEFINED\" " 812 "WHERE attribute = '%s'", attribute) != MM_DB_DATA) { 813 return (MM_DB_ERROR); 814 } 815 found = 0; 816 rows = PQntuples(db->mm_db_results); 817 for (row = 0; !found && row < rows; row++) { 818 if (strcmp(PQgetvalue(db->mm_db_results, row, 0), 819 attribute) == 0) { 820 found = 1; 821 } 822 } 823 mm_clear_db(&db->mm_db_results); 824 if (found) { 825 /* attribute is part of base data model */ 826 return (MM_DB_OK); 827 } 828 /* remove user-defined object attribute */ 829 if (mm_db_exec(HERE, db, "ALTER TABLE \"%s\" DROP \"%s\"", 830 objname, attribute) != MM_DB_OK) { 831 return (MM_DB_ERROR); 832 } 833 /* user-defined object attribute removed */ 834 return (MM_DB_DROPPED); 835 } 836 837 /* 838 * This is hard coded without database access because we are probably in a 839 * database transaction block that has failed and thus an additional database 840 * message catalog lookup would fail. 841 */ 842 char * 843 mm_db_sql_err_rsp(int mm_db_status, char *mm_db_error_message, 844 char *lang, char *taskid) 845 { 846 char *dberrmsg; 847 char *response; 848 char args[100]; 849 char *p; 850 851 852 /* 853 * Replace ', ", ... with multi-character mms_escape sequences not 854 * processed by the parser. 855 */ 856 if ((dberrmsg = mms_strpar_escape_sequence(mm_db_error_message)) 857 == NULL) { 858 return (NULL); 859 } 860 /* 861 * Remove last new line from database error message(s). Leave new 862 * lines in-between multiline database error messages. 863 */ 864 if (p = strrchr(dberrmsg, '\n')) { 865 *p = '\0'; 866 } 867 /* 868 * Build command error response with loctext. 869 */ 870 snprintf(args, sizeof (args), "\"status\" \"%d\"", mm_db_status); 871 if (taskid && lang) { 872 response = mms_strnew(RESPONSE_ERROR_TEXT, taskid, 873 ECLASS_INTERNAL, 874 EDATABASE, MESS_MANUFACTURER, MESS_MODEL, 5001, 875 args, lang, dberrmsg); 876 } else { 877 response = mms_strnew(RESPONSE_UNACCEPTABLE); 878 } 879 free(dberrmsg); 880 881 return (response); 882 } 883 884 char * 885 mm_db_escape_string(char *from) 886 { 887 char *to; 888 int len; 889 890 len = strlen(from); 891 if ((to = (char *)malloc((len * 2) + 1)) == NULL) { 892 mms_trace(MMS_ERR, "mms_escape string out of memory"); 893 return (NULL); 894 } 895 PQescapeString(to, from, len); 896 if (strcmp(from, to) != 0) { 897 mms_trace(MMS_DEVP, 898 "mms_escape string\nfrom - %s\nto - %s", from, to); 899 } 900 return (to); 901 } 902 903 mm_db_rval_t 904 mm_db_upgrade(mm_db_t *db, int dbcurver, int dbnewver) 905 { 906 char linebuf[100]; 907 char *cmdbuf = NULL; 908 FILE *dbfp; 909 char *verpos; 910 char *sqlcmd = NULL; 911 char *dbpath = NULL; 912 char filebuf[4096]; 913 char cfgvar[2048]; 914 ptrdiff_t verlen; 915 char verbuf[10]; 916 int vernum; 917 int multicmd = 0; 918 int fileIn; 919 int fileOut; 920 int bytes; 921 int st = 0; 922 923 if (db_version_check("/etc/mms/db/mms_db") != dbnewver) { 924 mms_trace(MMS_ERR, "Error, db file found in " 925 "/etc/mms/db/mms_db does not match new version %d", 926 dbnewver); 927 return (MM_DB_ERROR); 928 } 929 930 if ((dbfp = fopen("/etc/mms/db/mms_db", "r")) == NULL) { 931 printf("Error, mms_db not found\n"); 932 return (-1); 933 } 934 935 /* Begin transaction block */ 936 if (mm_db_exec(HERE, db, "BEGIN;") != MM_DB_OK) { 937 mms_trace(MMS_ERR, "Upgrade cmd" 938 " failed for BEGIN"); 939 return (MM_DB_ERROR); 940 } 941 942 /* 943 * Read in a SQL command 944 * Check the database version 945 */ 946 while (fgets(linebuf, sizeof (linebuf), dbfp) != NULL) { 947 if (isdigit(linebuf[0])) { 948 verpos = strchr(linebuf, ' '); 949 verlen = verpos - linebuf; 950 strncpy(verbuf, linebuf, verlen); 951 vernum = atoi(verbuf); 952 if (vernum > dbcurver && linebuf[verlen - 1] == 'u') { 953 /* Start of a database command, read it in */ 954 if (strchr(linebuf, ';') != NULL) { 955 cmdbuf = mms_strapp(cmdbuf, "%s", 956 linebuf); 957 sqlcmd = strchr(cmdbuf, ' ') + 1; 958 /* Run SQL command on database */ 959 if (mm_db_exec(HERE, db, "%s", 960 sqlcmd) != MM_DB_OK) { 961 mms_trace(MMS_ERR, "Upgrade cmd" 962 " failed for %s", sqlcmd); 963 mm_clear_db(&db->mm_db_results); 964 (void) mm_db_exec(HERE, db, 965 "ROLLBACK;"); 966 return (MM_DB_ERROR); 967 } 968 free(cmdbuf); 969 cmdbuf = NULL; 970 sqlcmd = NULL; 971 } else { 972 cmdbuf = mms_strapp(cmdbuf, "%s", 973 linebuf); 974 multicmd = 1; 975 } 976 } 977 } else if (multicmd == 1) { 978 cmdbuf = mms_strapp(cmdbuf, "%s", 979 linebuf); 980 if (strchr(linebuf, ';') != NULL) { 981 multicmd = 0; 982 sqlcmd = strchr(cmdbuf, ' ') + 1; 983 /* Run SQL command on database */ 984 if (mm_db_exec(HERE, db, "%s", sqlcmd) 985 != MM_DB_OK) { 986 mms_trace(MMS_ERR, "Upgrade cmd" 987 " failed for %s", sqlcmd); 988 mm_clear_db(&db->mm_db_results); 989 (void) mm_db_exec(HERE, db, 990 "ROLLBACK;"); 991 return (MM_DB_ERROR); 992 } 993 free(cmdbuf); 994 cmdbuf = NULL; 995 sqlcmd = NULL; 996 } 997 } 998 } 999 1000 /* 1001 * End transaction block 1002 * Commit changes to db, db does rollback on error 1003 */ 1004 if (mm_db_exec(HERE, db, "COMMIT;") != MM_DB_OK) { 1005 mms_trace(MMS_ERR, "Upgrade cmd" 1006 " failed for COMMIT"); 1007 return (MM_DB_ERROR); 1008 } 1009 1010 fclose(dbfp); 1011 1012 /* Get path to mmsdb schema file */ 1013 st = mms_cfg_getvar(MMS_CFG_DB_DATA, cfgvar); 1014 if (st == 0) { 1015 dbpath = mms_strapp(dbpath, "%s/../mmsdb", cfgvar); 1016 } 1017 1018 if (st != 0) { 1019 mms_trace(MMS_ERR, "mms_cfg_getvar error," 1020 "mmsdb not known"); 1021 return (MM_DB_ERROR); 1022 } 1023 1024 /* Make a copy of the newer db schema */ 1025 if ((fileIn = open("/etc/mms/db/mms_db", O_RDONLY)) == -1) { 1026 mms_trace(MMS_ERR, "Failed to open db schema for read"); 1027 } 1028 1029 if ((fileOut = open(dbpath, O_WRONLY, O_CREAT)) == -1) { 1030 mms_trace(MMS_ERR, "Failed to open db schema for write"); 1031 } 1032 1033 while ((bytes = read(fileIn, filebuf, sizeof (filebuf))) > 0) 1034 write(fileOut, filebuf, bytes); 1035 1036 close(fileIn); 1037 close(fileOut); 1038 1039 return (MM_DB_OK); 1040 } 1041 1042 mm_db_rval_t 1043 mm_db_downgrade(mm_db_t *db, int dbcurver, int dbnewver) 1044 { 1045 char linebuf[100]; 1046 char *cmdbuf = NULL; 1047 FILE *dbfp; 1048 char *verpos; 1049 int sqlcmdsize = 50; 1050 char **sqlcmds = (char **)malloc(sqlcmdsize * 1051 sizeof (char *)); 1052 char **tmpbuf; 1053 char *sqlptr; 1054 char *dbpath = NULL; 1055 char cfgvar[2048]; 1056 int cmdcount = -1; 1057 ptrdiff_t verlen; 1058 char verbuf[10]; 1059 int vernum; 1060 int multicmd = 0; 1061 int i; 1062 int st = 0; 1063 1064 /* Get path to mmsdb schema file */ 1065 st = mms_cfg_getvar(MMS_CFG_DB_DATA, cfgvar); 1066 if (st == 0) { 1067 dbpath = mms_strapp(dbpath, "%s/../mmsdb", cfgvar); 1068 } 1069 1070 if (st != 0) { 1071 mms_trace(MMS_ERR, "mms_cfg_getvar error," 1072 "mmsdb not known"); 1073 free(sqlcmds); 1074 free(dbpath); 1075 return (MM_DB_ERROR); 1076 } 1077 1078 if (db_version_check(dbpath) != dbcurver) { 1079 mms_trace(MMS_ERR, "Error, db file found in %s" 1080 "does not match current version %d", 1081 dbpath, dbcurver); 1082 free(dbpath); 1083 free(sqlcmds); 1084 return (MM_DB_ERROR); 1085 } 1086 1087 if ((dbfp = fopen(dbpath, "r")) == NULL) { 1088 mms_trace(MMS_ERR, "Error, mmsdb not found for downgrade\n"); 1089 free(dbpath); 1090 free(sqlcmds); 1091 return (MM_DB_ERROR); 1092 } 1093 1094 /* Begin transaction block */ 1095 if (mm_db_exec(HERE, db, "BEGIN;") != MM_DB_OK) { 1096 mms_trace(MMS_ERR, "Downgrade cmd" 1097 " failed for BEGIN"); 1098 free(dbpath); 1099 free(sqlcmds); 1100 return (MM_DB_ERROR); 1101 } 1102 1103 /* 1104 * Read in a SQL command 1105 * Check the database version 1106 */ 1107 while (fgets(linebuf, sizeof (linebuf), dbfp) != NULL) { 1108 if (isdigit(linebuf[0])) { 1109 verpos = strchr(linebuf, ' '); 1110 verlen = verpos - linebuf; 1111 strncpy(verbuf, linebuf, verlen); 1112 vernum = atoi(verbuf); 1113 if (vernum > dbnewver && linebuf[verlen - 1] == 'd') { 1114 /* Check SQL cmd buffer size */ 1115 if (cmdcount+1 > sqlcmdsize) { 1116 sqlcmdsize = sqlcmdsize * 2; 1117 tmpbuf = (char **) 1118 realloc(sqlcmds, sqlcmdsize); 1119 if (tmpbuf != NULL) 1120 sqlcmds = tmpbuf; 1121 else { 1122 mms_trace(MMS_ERR, 1123 "realloc failed in " 1124 "mm_db_downgrade"); 1125 free(dbpath); 1126 for (i = 0; i < cmdcount - 1; 1127 i++) 1128 free(sqlcmds[i]); 1129 free(sqlcmds); 1130 return (MM_DB_ERROR); 1131 } 1132 } 1133 1134 /* Start of a database command, read it in */ 1135 if (strchr(linebuf, ';') != NULL) { 1136 cmdbuf = mms_strapp(cmdbuf, "%s", 1137 linebuf); 1138 sqlptr = strchr(cmdbuf, ' ') + 1; 1139 cmdcount++; 1140 sqlcmds[cmdcount] = strdup(sqlptr); 1141 free(cmdbuf); 1142 cmdbuf = NULL; 1143 sqlptr = NULL; 1144 } else { 1145 cmdbuf = mms_strapp(cmdbuf, "%s", 1146 linebuf); 1147 multicmd = 1; 1148 } 1149 } 1150 } else if (multicmd == 1) { 1151 cmdbuf = mms_strapp(cmdbuf, "%s", 1152 linebuf); 1153 if (strchr(linebuf, ';') != NULL) { 1154 multicmd = 0; 1155 sqlptr = strchr(cmdbuf, ' ') + 1; 1156 cmdcount++; 1157 sqlcmds[cmdcount] = strdup(sqlptr); 1158 free(cmdbuf); 1159 cmdbuf = NULL; 1160 sqlptr = NULL; 1161 } 1162 } 1163 } 1164 for (i = cmdcount; i > -1; i--) { 1165 /* Run SQL command on database */ 1166 if (mm_db_exec(HERE, db, "%s", sqlcmds[i]) != MM_DB_OK) { 1167 mms_trace(MMS_ERR, "Downgrade cmd" 1168 " failed for %s", sqlcmds[i]); 1169 mm_clear_db(&db->mm_db_results); 1170 free(dbpath); 1171 (void) mm_db_exec(HERE, db, "ROLLBACK;"); 1172 return (MM_DB_ERROR); 1173 } 1174 free(sqlcmds[i]); 1175 } 1176 1177 free(sqlcmds); 1178 1179 /* 1180 * End transaction block 1181 * Commit changes to db, db does rollback on error 1182 */ 1183 if (mm_db_exec(HERE, db, "COMMIT;") != MM_DB_OK) { 1184 mms_trace(MMS_ERR, "Downgrade cmd" 1185 " failed for COMMIT"); 1186 free(dbpath); 1187 return (MM_DB_ERROR); 1188 } 1189 1190 fclose(dbfp); 1191 1192 free(dbpath); 1193 return (MM_DB_OK); 1194 } 1195 1196 int 1197 db_version_check(char *dbfile) 1198 { 1199 char linebuf[100]; 1200 char verbuf[10]; 1201 char *verpos; 1202 ptrdiff_t verlen; 1203 FILE *dbfp; 1204 int vernum; 1205 int version = 0; 1206 1207 if ((dbfp = fopen(dbfile, "r")) == NULL) { 1208 mms_trace(MMS_ERR, "db_version_check " 1209 "database file not found\n"); 1210 return (-1); 1211 } 1212 1213 while (fgets(linebuf, sizeof (linebuf), dbfp) != NULL) { 1214 if (isdigit(linebuf[0])) { 1215 verpos = strchr(linebuf, ' '); 1216 verlen = verpos - linebuf - 1; 1217 strncpy(verbuf, linebuf, verlen); 1218 vernum = atoi(verbuf); 1219 if (vernum > version) 1220 version = vernum; 1221 memset(verbuf, '\0', sizeof (verbuf)); 1222 } 1223 } 1224 1225 fclose(dbfp); 1226 1227 return (version); 1228 } 1229