Home | History | Annotate | Download | only in common
      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