1#include <sys/types.h> 2#include <sys/stat.h> 3 4#include <errno.h> 5#include <pthread.h> 6#include <stdarg.h> 7#include <stdlib.h> 8#include <string.h> 9#include <unistd.h> 10 11#include <db.h> 12 13#define ENV_DIRECTORY "TXNAPP" 14 15void add_cat(DB_ENV *, DB *, char *, ...); 16void add_color(DB_ENV *, DB *, char *, int); 17void add_fruit(DB_ENV *, DB *, char *, char *); 18void *checkpoint_thread(void *); 19void log_archlist(DB_ENV *); 20void *logfile_thread(void *); 21void db_open(DB_ENV *, DB **, char *, int); 22void env_dir_create(void); 23void env_open(DB_ENV **); 24void usage(void); 25 26int 27main(int argc, char *argv[]) 28{ 29 extern int optind; 30 DB *db_cats, *db_color, *db_fruit; 31 DB_ENV *dbenv; 32 pthread_t ptid; 33 int ch, ret; 34 35 while ((ch = getopt(argc, argv, "")) != EOF) 36 switch (ch) { 37 case '?': 38 default: 39 usage(); 40 } 41 argc -= optind; 42 argv += optind; 43 44 env_dir_create(); 45 env_open(&dbenv); 46 47 /* Start a checkpoint thread. */ 48 if ((ret = pthread_create( 49 &ptid, NULL, checkpoint_thread, (void *)dbenv)) != 0) { 50 fprintf(stderr, 51 "txnapp: failed spawning checkpoint thread: %s\n", 52 strerror(ret)); 53 exit (1); 54 } 55 56 /* Start a logfile removal thread. */ 57 if ((ret = pthread_create( 58 &ptid, NULL, logfile_thread, (void *)dbenv)) != 0) { 59 fprintf(stderr, 60 "txnapp: failed spawning log file removal thread: %s\n", 61 strerror(ret)); 62 exit (1); 63 } 64 65 /* Open database: Key is fruit class; Data is specific type. */ 66 db_open(dbenv, &db_fruit, "fruit", 0); 67 68 /* Open database: Key is a color; Data is an integer. */ 69 db_open(dbenv, &db_color, "color", 0); 70 71 /* 72 * Open database: 73 * Key is a name; Data is: company name, address, cat breeds. 74 */ 75 db_open(dbenv, &db_cats, "cats", 1); 76 77 add_fruit(dbenv, db_fruit, "apple", "yellow delicious"); 78 79 add_color(dbenv, db_color, "blue", 0); 80 add_color(dbenv, db_color, "blue", 3); 81 82 add_cat(dbenv, db_cats, 83 "Amy Adams", 84 "Oracle", 85 "394 E. Riding Dr., Carlisle, MA 01741, USA", 86 "abyssinian", 87 "bengal", 88 "chartreaux", 89 NULL); 90 91 return (0); 92} 93 94void 95env_dir_create() 96{ 97 struct stat sb; 98 99 /* 100 * If the directory exists, we're done. We do not further check 101 * the type of the file, DB will fail appropriately if it's the 102 * wrong type. 103 */ 104 if (stat(ENV_DIRECTORY, &sb) == 0) 105 return; 106 107 /* Create the directory, read/write/access owner only. */ 108 if (mkdir(ENV_DIRECTORY, S_IRWXU) != 0) { 109 fprintf(stderr, 110 "txnapp: mkdir: %s: %s\n", ENV_DIRECTORY, strerror(errno)); 111 exit (1); 112 } 113} 114 115void 116env_open(DB_ENV **dbenvp) 117{ 118 DB_ENV *dbenv; 119 int ret; 120 121 /* Create the environment handle. */ 122 if ((ret = db_env_create(&dbenv, 0)) != 0) { 123 fprintf(stderr, 124 "txnapp: db_env_create: %s\n", db_strerror(ret)); 125 exit (1); 126 } 127 128 /* Set up error handling. */ 129 dbenv->set_errpfx(dbenv, "txnapp"); 130 dbenv->set_errfile(dbenv, stderr); 131 132 /* Do deadlock detection internally. */ 133 if ((ret = dbenv->set_lk_detect(dbenv, DB_LOCK_DEFAULT)) != 0) { 134 dbenv->err(dbenv, ret, "set_lk_detect: DB_LOCK_DEFAULT"); 135 exit (1); 136 } 137 138 /* 139 * Open a transactional environment: 140 * create if it doesn't exist 141 * free-threaded handle 142 * run recovery 143 * read/write owner only 144 */ 145 if ((ret = dbenv->open(dbenv, ENV_DIRECTORY, 146 DB_CREATE | DB_INIT_LOCK | DB_INIT_LOG | 147 DB_INIT_MPOOL | DB_INIT_TXN | DB_RECOVER | DB_THREAD, 148 S_IRUSR | S_IWUSR)) != 0) { 149 dbenv->err(dbenv, ret, "dbenv->open: %s", ENV_DIRECTORY); 150 exit (1); 151 } 152 153 *dbenvp = dbenv; 154} 155 156void * 157checkpoint_thread(void *arg) 158{ 159 DB_ENV *dbenv; 160 int ret; 161 162 dbenv = arg; 163 dbenv->errx(dbenv, "Checkpoint thread: %lu", (u_long)pthread_self()); 164 165 /* Checkpoint once a minute. */ 166 for (;; sleep(60)) 167 if ((ret = dbenv->txn_checkpoint(dbenv, 0, 0, 0)) != 0) { 168 dbenv->err(dbenv, ret, "checkpoint thread"); 169 exit (1); 170 } 171 172 /* NOTREACHED */ 173} 174 175void * 176logfile_thread(void *arg) 177{ 178 DB_ENV *dbenv; 179 int ret; 180 char **begin, **list; 181 182 dbenv = arg; 183 dbenv->errx(dbenv, 184 "Log file removal thread: %lu", (u_long)pthread_self()); 185 186 /* Check once every 5 minutes. */ 187 for (;; sleep(300)) { 188 /* Get the list of log files. */ 189 if ((ret = 190 dbenv->log_archive(dbenv, &list, DB_ARCH_ABS)) != 0) { 191 dbenv->err(dbenv, ret, "DB_ENV->log_archive"); 192 exit (1); 193 } 194 195 /* Remove the log files. */ 196 if (list != NULL) { 197 for (begin = list; *list != NULL; ++list) 198 if ((ret = remove(*list)) != 0) { 199 dbenv->err(dbenv, 200 ret, "remove %s", *list); 201 exit (1); 202 } 203 free (begin); 204 } 205 } 206 /* NOTREACHED */ 207} 208 209void 210log_archlist(DB_ENV *dbenv) 211{ 212 int ret; 213 char **begin, **list; 214 215 /* Get the list of database files. */ 216 if ((ret = dbenv->log_archive(dbenv, 217 &list, DB_ARCH_ABS | DB_ARCH_DATA)) != 0) { 218 dbenv->err(dbenv, ret, "DB_ENV->log_archive: DB_ARCH_DATA"); 219 exit (1); 220 } 221 if (list != NULL) { 222 for (begin = list; *list != NULL; ++list) 223 printf("database file: %s\n", *list); 224 free (begin); 225 } 226 227 /* Get the list of log files. */ 228 if ((ret = dbenv->log_archive(dbenv, 229 &list, DB_ARCH_ABS | DB_ARCH_LOG)) != 0) { 230 dbenv->err(dbenv, ret, "DB_ENV->log_archive: DB_ARCH_LOG"); 231 exit (1); 232 } 233 if (list != NULL) { 234 for (begin = list; *list != NULL; ++list) 235 printf("log file: %s\n", *list); 236 free (begin); 237 } 238} 239 240void 241db_open(DB_ENV *dbenv, DB **dbp, char *name, int dups) 242{ 243 DB *db; 244 int ret; 245 246 /* Create the database handle. */ 247 if ((ret = db_create(&db, dbenv, 0)) != 0) { 248 dbenv->err(dbenv, ret, "db_create"); 249 exit (1); 250 } 251 252 /* Optionally, turn on duplicate data items. */ 253 if (dups && (ret = db->set_flags(db, DB_DUP)) != 0) { 254 dbenv->err(dbenv, ret, "db->set_flags: DB_DUP"); 255 exit (1); 256 } 257 258 /* 259 * Open a database in the environment: 260 * create if it doesn't exist 261 * free-threaded handle 262 * read/write owner only 263 */ 264 if ((ret = db->open(db, NULL, name, NULL, DB_BTREE, 265 DB_AUTO_COMMIT | DB_CREATE | DB_THREAD, S_IRUSR | S_IWUSR)) != 0) { 266 (void)db->close(db, 0); 267 dbenv->err(dbenv, ret, "db->open: %s", name); 268 exit (1); 269 } 270 271 *dbp = db; 272} 273 274void 275add_fruit(DB_ENV *dbenv, DB *db, char *fruit, char *name) 276{ 277 DBT key, data; 278 DB_TXN *tid; 279 int ret; 280 281 /* Initialization. */ 282 memset(&key, 0, sizeof(key)); 283 memset(&data, 0, sizeof(data)); 284 key.data = fruit; 285 key.size = strlen(fruit); 286 data.data = name; 287 data.size = strlen(name); 288 289 for (;;) { 290 /* Begin the transaction. */ 291 if ((ret = dbenv->txn_begin(dbenv, NULL, &tid, 0)) != 0) { 292 dbenv->err(dbenv, ret, "DB_ENV->txn_begin"); 293 exit (1); 294 } 295 296 /* Store the value. */ 297 switch (ret = db->put(db, tid, &key, &data, 0)) { 298 case 0: 299 /* Success: commit the change. */ 300 if ((ret = tid->commit(tid, 0)) != 0) { 301 dbenv->err(dbenv, ret, "DB_TXN->commit"); 302 exit (1); 303 } 304 return; 305 case DB_LOCK_DEADLOCK: 306 /* Deadlock: retry the operation. */ 307 if ((ret = tid->abort(tid)) != 0) { 308 dbenv->err(dbenv, ret, "DB_TXN->abort"); 309 exit (1); 310 } 311 break; 312 default: 313 /* Error: run recovery. */ 314 dbenv->err(dbenv, ret, "dbc->put: %s/%s", fruit, name); 315 exit (1); 316 } 317 } 318} 319 320void 321add_color(DB_ENV *dbenv, DB *dbp, char *color, int increment) 322{ 323 DBT key, data; 324 DB_TXN *tid; 325 int original, ret; 326 char buf[64]; 327 328 /* Initialization. */ 329 memset(&key, 0, sizeof(key)); 330 key.data = color; 331 key.size = strlen(color); 332 memset(&data, 0, sizeof(data)); 333 data.flags = DB_DBT_MALLOC; 334 335 for (;;) { 336 /* Begin the transaction. */ 337 if ((ret = dbenv->txn_begin(dbenv, NULL, &tid, 0)) != 0) { 338 dbenv->err(dbenv, ret, "DB_ENV->txn_begin"); 339 exit (1); 340 } 341 342 /* 343 * Get the key. If it exists, we increment the value. If it 344 * doesn't exist, we create it. 345 */ 346 switch (ret = dbp->get(dbp, tid, &key, &data, 0)) { 347 case 0: 348 original = atoi(data.data); 349 break; 350 case DB_LOCK_DEADLOCK: 351 /* Deadlock: retry the operation. */ 352 if ((ret = tid->abort(tid)) != 0) { 353 dbenv->err(dbenv, ret, "DB_TXN->abort"); 354 exit (1); 355 } 356 continue; 357 case DB_NOTFOUND: 358 original = 0; 359 break; 360 default: 361 /* Error: run recovery. */ 362 dbenv->err( 363 dbenv, ret, "dbc->get: %s/%d", color, increment); 364 exit (1); 365 } 366 if (data.data != NULL) 367 free(data.data); 368 369 /* Create the new data item. */ 370 (void)snprintf(buf, sizeof(buf), "%d", original + increment); 371 data.data = buf; 372 data.size = strlen(buf) + 1; 373 374 /* Store the new value. */ 375 switch (ret = dbp->put(dbp, tid, &key, &data, 0)) { 376 case 0: 377 /* Success: commit the change. */ 378 if ((ret = tid->commit(tid, 0)) != 0) { 379 dbenv->err(dbenv, ret, "DB_TXN->commit"); 380 exit (1); 381 } 382 return; 383 case DB_LOCK_DEADLOCK: 384 /* Deadlock: retry the operation. */ 385 if ((ret = tid->abort(tid)) != 0) { 386 dbenv->err(dbenv, ret, "DB_TXN->abort"); 387 exit (1); 388 } 389 break; 390 default: 391 /* Error: run recovery. */ 392 dbenv->err( 393 dbenv, ret, "dbc->put: %s/%d", color, increment); 394 exit (1); 395 } 396 } 397} 398 399void 400add_cat(DB_ENV *dbenv, DB *db, char *name, ...) 401{ 402 va_list ap; 403 DBC *dbc; 404 DBT key, data; 405 DB_TXN *tid; 406 int ret; 407 char *s; 408 409 /* Initialization. */ 410 memset(&key, 0, sizeof(key)); 411 memset(&data, 0, sizeof(data)); 412 key.data = name; 413 key.size = strlen(name); 414 415retry: /* Begin the transaction. */ 416 if ((ret = dbenv->txn_begin(dbenv, NULL, &tid, 0)) != 0) { 417 dbenv->err(dbenv, ret, "DB_ENV->txn_begin"); 418 exit (1); 419 } 420 421 /* Delete any previously existing item. */ 422 switch (ret = db->del(db, tid, &key, 0)) { 423 case 0: 424 case DB_NOTFOUND: 425 break; 426 case DB_LOCK_DEADLOCK: 427 /* Deadlock: retry the operation. */ 428 if ((ret = tid->abort(tid)) != 0) { 429 dbenv->err(dbenv, ret, "DB_TXN->abort"); 430 exit (1); 431 } 432 goto retry; 433 default: 434 dbenv->err(dbenv, ret, "db->del: %s", name); 435 exit (1); 436 } 437 438 /* Create a cursor. */ 439 if ((ret = db->cursor(db, tid, &dbc, 0)) != 0) { 440 dbenv->err(dbenv, ret, "db->cursor"); 441 exit (1); 442 } 443 444 /* Append the items, in order. */ 445 va_start(ap, name); 446 while ((s = va_arg(ap, char *)) != NULL) { 447 data.data = s; 448 data.size = strlen(s); 449 switch (ret = dbc->c_put(dbc, &key, &data, DB_KEYLAST)) { 450 case 0: 451 break; 452 case DB_LOCK_DEADLOCK: 453 va_end(ap); 454 455 /* Deadlock: retry the operation. */ 456 if ((ret = dbc->c_close(dbc)) != 0) { 457 dbenv->err( 458 dbenv, ret, "dbc->c_close"); 459 exit (1); 460 } 461 if ((ret = tid->abort(tid)) != 0) { 462 dbenv->err(dbenv, ret, "DB_TXN->abort"); 463 exit (1); 464 } 465 goto retry; 466 default: 467 /* Error: run recovery. */ 468 dbenv->err(dbenv, ret, "dbc->put: %s/%s", name, s); 469 exit (1); 470 } 471 } 472 va_end(ap); 473 474 /* Success: commit the change. */ 475 if ((ret = dbc->c_close(dbc)) != 0) { 476 dbenv->err(dbenv, ret, "dbc->c_close"); 477 exit (1); 478 } 479 if ((ret = tid->commit(tid, 0)) != 0) { 480 dbenv->err(dbenv, ret, "DB_TXN->commit"); 481 exit (1); 482 } 483} 484 485void 486usage() 487{ 488 (void)fprintf(stderr, "usage: txnapp\n"); 489 exit(1); 490} 491