1/* 2 * Copyright (C) Joerg Lenneis 2003 3 * Copyright (c) Frank Lahm 2009 4 * All Rights Reserved. See COPYING. 5 */ 6 7#ifdef HAVE_CONFIG_H 8#include "config.h" 9#endif /* HAVE_CONFIG_H */ 10 11#include <unistd.h> 12#include <fcntl.h> 13#include <stdio.h> 14#include <stdlib.h> 15#include <errno.h> 16#include <signal.h> 17#include <string.h> 18#include <sys/types.h> 19#include <sys/param.h> 20#include <sys/stat.h> 21#include <time.h> 22#include <sys/file.h> 23#include <arpa/inet.h> 24 25#include <atalk/cnid_dbd_private.h> 26#include <atalk/logger.h> 27#include <atalk/errchk.h> 28#include <atalk/bstrlib.h> 29#include <atalk/bstradd.h> 30#include <atalk/netatalk_conf.h> 31#include <atalk/util.h> 32 33#include "db_param.h" 34#include "dbif.h" 35#include "dbd.h" 36#include "comm.h" 37#include "pack.h" 38 39/* 40 Note: DB_INIT_LOCK is here so we can run the db_* utilities while netatalk is running. 41 It's a likey performance hit, but it might we worth it. 42 */ 43#define DBOPTIONS (DB_CREATE | DB_INIT_LOG | DB_INIT_MPOOL | DB_INIT_LOCK | DB_INIT_TXN) 44 45static DBD *dbd; 46static int exit_sig = 0; 47static int db_locked; 48static bstring dbpath; 49static struct db_param *dbp; 50static struct vol *vol; 51 52static void sig_exit(int signo) 53{ 54 exit_sig = signo; 55 return; 56} 57 58static void block_sigs_onoff(int block) 59{ 60 sigset_t set; 61 62 sigemptyset(&set); 63 sigaddset(&set, SIGINT); 64 sigaddset(&set, SIGTERM); 65 if (block) 66 sigprocmask(SIG_BLOCK, &set, NULL); 67 else 68 sigprocmask(SIG_UNBLOCK, &set, NULL); 69 return; 70} 71 72/* 73 The dbd_XXX and comm_XXX functions all obey the same protocol for return values: 74 75 1: Success, if transactions are used commit. 76 0: Failure, but we continue to serve requests. If transactions are used abort/rollback. 77 -1: Fatal error, either from t 78 he database or from the socket. Abort the transaction if applicable 79 (which might fail as well) and then exit. 80 81 We always try to notify the client process about the outcome, the result field 82 of the cnid_dbd_rply structure contains further details. 83 84*/ 85 86/*! 87 * Get lock on db lock file 88 * 89 * @args cmd (r) lock command: 90 * LOCK_FREE: close lockfd 91 * LOCK_UNLOCK: unlock lockm keep lockfd open 92 * LOCK_EXCL: F_WRLCK on lockfd 93 * LOCK_SHRD: F_RDLCK on lockfd 94 * @args dbpath (r) path to lockfile, only used on first call, 95 * later the stored fd is used 96 * @returns LOCK_FREE/LOCK_UNLOCK return 0 on success, -1 on error 97 * LOCK_EXCL/LOCK_SHRD return LOCK_EXCL or LOCK_SHRD respectively on 98 * success, 0 if the lock couldn't be acquired, -1 on other errors 99 */ 100static int get_lock(int cmd, const char *dbpath) 101{ 102 static int lockfd = -1; 103 int ret; 104 char lockpath[PATH_MAX]; 105 struct stat st; 106 107 LOG(log_debug, logtype_cnid, "get_lock(%s, \"%s\")", 108 cmd == LOCK_EXCL ? "LOCK_EXCL" : 109 cmd == LOCK_SHRD ? "LOCK_SHRD" : 110 cmd == LOCK_FREE ? "LOCK_FREE" : 111 cmd == LOCK_UNLOCK ? "LOCK_UNLOCK" : "UNKNOWN", 112 dbpath ? dbpath : ""); 113 114 switch (cmd) { 115 case LOCK_FREE: 116 if (lockfd == -1) 117 return -1; 118 close(lockfd); 119 lockfd = -1; 120 return 0; 121 122 case LOCK_UNLOCK: 123 if (lockfd == -1) 124 return -1; 125 return unlock(lockfd, 0, SEEK_SET, 0); 126 127 case LOCK_EXCL: 128 case LOCK_SHRD: 129 if (lockfd == -1) { 130 if ( (strlen(dbpath) + strlen(LOCKFILENAME+1)) > (PATH_MAX - 1) ) { 131 LOG(log_error, logtype_cnid, ".AppleDB pathname too long"); 132 return -1; 133 } 134 strncpy(lockpath, dbpath, PATH_MAX - 1); 135 strcat(lockpath, "/"); 136 strcat(lockpath, LOCKFILENAME); 137 138 if ((lockfd = open(lockpath, O_RDWR | O_CREAT, 0644)) < 0) { 139 LOG(log_error, logtype_cnid, "Error opening lockfile: %s", strerror(errno)); 140 return -1; 141 } 142 143 if ((stat(dbpath, &st)) != 0) { 144 LOG(log_error, logtype_cnid, "Error statting lockfile: %s", strerror(errno)); 145 return -1; 146 } 147 148 if ((chown(lockpath, st.st_uid, st.st_gid)) != 0) { 149 LOG(log_error, logtype_cnid, "Error inheriting lockfile permissions: %s", 150 strerror(errno)); 151 return -1; 152 } 153 } 154 155 if (cmd == LOCK_EXCL) 156 ret = write_lock(lockfd, 0, SEEK_SET, 0); 157 else 158 ret = read_lock(lockfd, 0, SEEK_SET, 0); 159 160 if (ret != 0) { 161 if (cmd == LOCK_SHRD) 162 LOG(log_error, logtype_cnid, "Volume CNID db is locked, try again..."); 163 return 0; 164 } 165 166 LOG(log_debug, logtype_cnid, "get_lock: got %s lock", 167 cmd == LOCK_EXCL ? "LOCK_EXCL" : "LOCK_SHRD"); 168 return cmd; 169 170 default: 171 return -1; 172 } /* switch(cmd) */ 173 174 /* deadc0de, never get here */ 175 return -1; 176} 177 178static int open_db(void) 179{ 180 EC_INIT; 181 182 /* Get db lock */ 183 if ((db_locked = get_lock(LOCK_EXCL, bdata(dbpath))) != LOCK_EXCL) { 184 LOG(log_error, logtype_cnid, "main: fatal db lock error"); 185 EC_FAIL; 186 } 187 188 if (NULL == (dbd = dbif_init(bdata(dbpath), "cnid2.db"))) 189 EC_FAIL; 190 191 /* Only recover if we got the lock */ 192 if (dbif_env_open(dbd, dbp, DBOPTIONS | DB_RECOVER) < 0) 193 EC_FAIL; 194 195 LOG(log_debug, logtype_cnid, "Finished initializing BerkeleyDB environment"); 196 197 if (dbif_open(dbd, dbp, 0) < 0) 198 EC_FAIL; 199 200 LOG(log_debug, logtype_cnid, "Finished opening BerkeleyDB databases"); 201 202EC_CLEANUP: 203 if (ret != 0) { 204 if (dbd) { 205 (void)dbif_close(dbd); 206 dbd = NULL; 207 } 208 } 209 210 EC_EXIT; 211} 212 213static int delete_db(void) 214{ 215 EC_INIT; 216 int cwd = -1; 217 218 EC_ZERO( get_lock(LOCK_FREE, bdata(dbpath)) ); 219 EC_NEG1( cwd = open(".", O_RDONLY) ); 220 chdir(cfrombstr(dbpath)); 221 system("rm -f cnid2.db lock log.* __db.*"); 222 223 if ((db_locked = get_lock(LOCK_EXCL, bdata(dbpath))) != LOCK_EXCL) { 224 LOG(log_error, logtype_cnid, "main: fatal db lock error"); 225 EC_FAIL; 226 } 227 228 LOG(log_warning, logtype_cnid, "Recreated CNID BerkeleyDB databases of volume \"%s\"", vol->v_localname); 229 230EC_CLEANUP: 231 if (cwd != -1) { 232 fchdir(cwd); 233 close(cwd); 234 } 235 EC_EXIT; 236} 237 238 239/** 240 * Close dbd if open, delete it, reopen 241 * 242 * Also tries to copy the rootinfo key, that would allow for keeping the db stamp 243 * and last used CNID 244 **/ 245static int reinit_db(void) 246{ 247 EC_INIT; 248 DBT key, data; 249 bool copyRootInfo = false; 250 251 if (dbd) { 252 memset(&key, 0, sizeof(key)); 253 memset(&data, 0, sizeof(data)); 254 255 key.data = ROOTINFO_KEY; 256 key.size = ROOTINFO_KEYLEN; 257 258 if (dbif_get(dbd, DBIF_CNID, &key, &data, 0) <= 0) { 259 LOG(log_error, logtype_cnid, "dbif_copy_rootinfokey: Error getting rootinfo record"); 260 copyRootInfo = false; 261 } else { 262 copyRootInfo = true; 263 } 264 (void)dbif_close(dbd); 265 } 266 267 EC_ZERO_LOG( delete_db() ); 268 EC_ZERO_LOG( open_db() ); 269 270 if (copyRootInfo == true) { 271 memset(&key, 0, sizeof(key)); 272 key.data = ROOTINFO_KEY; 273 key.size = ROOTINFO_KEYLEN; 274 275 if (dbif_put(dbd, DBIF_CNID, &key, &data, 0) != 0) { 276 LOG(log_error, logtype_cnid, "dbif_copy_rootinfokey: Error writing rootinfo key"); 277 EC_FAIL; 278 } 279 } 280 281EC_CLEANUP: 282 EC_EXIT; 283} 284 285static int loop(struct db_param *dbp) 286{ 287 struct cnid_dbd_rqst rqst; 288 struct cnid_dbd_rply rply; 289 time_t timeout; 290 int ret, cret; 291 int count; 292 time_t now, time_next_flush, time_last_rqst; 293 char timebuf[64]; 294 static char namebuf[MAXPATHLEN + 1]; 295 sigset_t set; 296 297 sigemptyset(&set); 298 sigprocmask(SIG_SETMASK, NULL, &set); 299 sigdelset(&set, SIGINT); 300 sigdelset(&set, SIGTERM); 301 302 count = 0; 303 now = time(NULL); 304 time_next_flush = now + dbp->flush_interval; 305 time_last_rqst = now; 306 307 rqst.name = namebuf; 308 309 strftime(timebuf, 63, "%b %d %H:%M:%S.",localtime(&time_next_flush)); 310 LOG(log_debug, logtype_cnid, "Checkpoint interval: %d seconds. Next checkpoint: %s", 311 dbp->flush_interval, timebuf); 312 313 while (1) { 314 timeout = MIN(time_next_flush, time_last_rqst + dbp->idle_timeout); 315 if (timeout > now) 316 timeout -= now; 317 else 318 timeout = 1; 319 320 if ((cret = comm_rcv(&rqst, timeout, &set, &now)) < 0) 321 return -1; 322 323 if (cret == 0) { 324 /* comm_rcv returned from select without receiving anything. */ 325 if (exit_sig) { 326 /* Received signal (TERM|INT) */ 327 return 0; 328 } 329 if (now - time_last_rqst >= dbp->idle_timeout && comm_nbe() <= 0) { 330 /* Idle timeout */ 331 return 0; 332 } 333 /* still active connections, reset time_last_rqst */ 334 time_last_rqst = now; 335 } else { 336 /* We got a request */ 337 time_last_rqst = now; 338 339 memset(&rply, 0, sizeof(rply)); 340 switch(rqst.op) { 341 /* ret gets set here */ 342 case CNID_DBD_OP_OPEN: 343 case CNID_DBD_OP_CLOSE: 344 /* open/close are noops for now. */ 345 rply.namelen = 0; 346 ret = 1; 347 break; 348 case CNID_DBD_OP_ADD: 349 ret = dbd_add(dbd, &rqst, &rply); 350 break; 351 case CNID_DBD_OP_GET: 352 ret = dbd_get(dbd, &rqst, &rply); 353 break; 354 case CNID_DBD_OP_RESOLVE: 355 ret = dbd_resolve(dbd, &rqst, &rply); 356 break; 357 case CNID_DBD_OP_LOOKUP: 358 ret = dbd_lookup(dbd, &rqst, &rply); 359 break; 360 case CNID_DBD_OP_UPDATE: 361 ret = dbd_update(dbd, &rqst, &rply); 362 break; 363 case CNID_DBD_OP_DELETE: 364 ret = dbd_delete(dbd, &rqst, &rply, DBIF_CNID); 365 break; 366 case CNID_DBD_OP_GETSTAMP: 367 ret = dbd_getstamp(dbd, &rqst, &rply); 368 break; 369 case CNID_DBD_OP_REBUILD_ADD: 370 ret = dbd_rebuild_add(dbd, &rqst, &rply); 371 break; 372 case CNID_DBD_OP_SEARCH: 373 ret = dbd_search(dbd, &rqst, &rply); 374 break; 375 case CNID_DBD_OP_WIPE: 376 ret = reinit_db(); 377 break; 378 default: 379 LOG(log_error, logtype_cnid, "loop: unknown op %d", rqst.op); 380 ret = -1; 381 break; 382 } 383 384 if ((cret = comm_snd(&rply)) < 0 || ret < 0) { 385 dbif_txn_abort(dbd); 386 return -1; 387 } 388 389 if (ret == 0 || cret == 0) { 390 if (dbif_txn_abort(dbd) < 0) 391 return -1; 392 } else { 393 ret = dbif_txn_commit(dbd); 394 if ( ret < 0) 395 return -1; 396 else if ( ret > 0 ) 397 /* We had a designated txn because we wrote to the db */ 398 count++; 399 } 400 } /* got a request */ 401 402 /* 403 Shall we checkpoint bdb ? 404 "flush_interval" seconds passed ? 405 */ 406 if (now >= time_next_flush) { 407 LOG(log_info, logtype_cnid, "Checkpointing BerkeleyDB for volume '%s'", dbp->dir); 408 if (dbif_txn_checkpoint(dbd, 0, 0, 0) < 0) 409 return -1; 410 count = 0; 411 time_next_flush = now + dbp->flush_interval; 412 413 strftime(timebuf, 63, "%b %d %H:%M:%S.",localtime(&time_next_flush)); 414 LOG(log_debug, logtype_cnid, "Checkpoint interval: %d seconds. Next checkpoint: %s", 415 dbp->flush_interval, timebuf); 416 } 417 418 /* 419 Shall we checkpoint bdb ? 420 Have we commited "count" more changes than "flush_frequency" ? 421 */ 422 if (count > dbp->flush_frequency) { 423 LOG(log_info, logtype_cnid, "Checkpointing BerkeleyDB after %d writes for volume '%s'", count, dbp->dir); 424 if (dbif_txn_checkpoint(dbd, 0, 0, 0) < 0) 425 return -1; 426 count = 0; 427 } 428 } /* while(1) */ 429} 430 431/* ------------------------ */ 432static void switch_to_user(char *dir) 433{ 434 struct stat st; 435 436 if (chdir(dir) < 0) { 437 LOG(log_error, logtype_cnid, "chdir to %s failed: %s", dir, strerror(errno)); 438 exit(1); 439 } 440 441 if (stat(".", &st) < 0) { 442 LOG(log_error, logtype_cnid, "error in stat for %s: %s", dir, strerror(errno)); 443 exit(1); 444 } 445 if (!getuid()) { 446 LOG(log_debug, logtype_cnid, "Setting uid/gid to %i/%i", st.st_uid, st.st_gid); 447 if (setgid(st.st_gid) < 0 || setuid(st.st_uid) < 0) { 448 LOG(log_error, logtype_cnid, "uid/gid: %s", strerror(errno)); 449 exit(1); 450 } 451 } 452} 453 454 455/* ----------------------- */ 456static void set_signal(void) 457{ 458 struct sigaction sv; 459 460 sv.sa_handler = sig_exit; 461 sv.sa_flags = 0; 462 sigemptyset(&sv.sa_mask); 463 sigaddset(&sv.sa_mask, SIGINT); 464 sigaddset(&sv.sa_mask, SIGTERM); 465 if (sigaction(SIGINT, &sv, NULL) < 0 || sigaction(SIGTERM, &sv, NULL) < 0) { 466 LOG(log_error, logtype_cnid, "main: sigaction: %s", strerror(errno)); 467 exit(1); 468 } 469 sv.sa_handler = SIG_IGN; 470 sigemptyset(&sv.sa_mask); 471 if (sigaction(SIGPIPE, &sv, NULL) < 0) { 472 LOG(log_error, logtype_cnid, "main: sigaction: %s", strerror(errno)); 473 exit(1); 474 } 475} 476 477/* ------------------------ */ 478int main(int argc, char *argv[]) 479{ 480 EC_INIT; 481 int delete_bdb = 0; 482 int ctrlfd = -1, clntfd = -1; 483 AFPObj obj = { 0 }; 484 char *volpath = NULL; 485 486 while (( ret = getopt( argc, argv, "dF:l:p:t:vV")) != -1 ) { 487 switch (ret) { 488 case 'd': 489 /* this is now just ignored, as we do it automatically anyway */ 490 delete_bdb = 1; 491 break; 492 case 'F': 493 obj.cmdlineconfigfile = strdup(optarg); 494 break; 495 case 'p': 496 volpath = strdup(optarg); 497 break; 498 case 'l': 499 clntfd = atoi(optarg); 500 break; 501 case 't': 502 ctrlfd = atoi(optarg); 503 break; 504 case 'v': 505 case 'V': 506 printf("cnid_dbd (Netatalk %s)\n", VERSION); 507 return -1; 508 } 509 } 510 511 if (ctrlfd == -1 || clntfd == -1 || !volpath) { 512 LOG(log_error, logtype_cnid, "main: bad IPC fds"); 513 exit(EXIT_FAILURE); 514 } 515 516 EC_ZERO( afp_config_parse(&obj, "cnid_dbd") ); 517 518 EC_ZERO( load_volumes(&obj) ); 519 EC_NULL( vol = getvolbypath(&obj, volpath) ); 520 EC_ZERO( load_charset(vol) ); 521 pack_setvol(vol); 522 523 EC_NULL( dbpath = bfromcstr(vol->v_dbpath) ); 524 EC_ZERO( bcatcstr(dbpath, "/.AppleDB") ); 525 526 LOG(log_debug, logtype_cnid, "db dir: \"%s\"", bdata(dbpath)); 527 528 switch_to_user(bdata(dbpath)); 529 530 set_signal(); 531 532 /* SIGINT and SIGTERM are always off, unless we are in pselect */ 533 block_sigs_onoff(1); 534 535 if ((dbp = db_param_read(bdata(dbpath))) == NULL) 536 EC_FAIL; 537 LOG(log_maxdebug, logtype_cnid, "Finished parsing db_param config file"); 538 539 if (open_db() != 0) { 540 LOG(log_error, logtype_cnid, "Failed to open CNID database for volume \"%s\"", vol->v_localname); 541 EC_ZERO_LOG( reinit_db() ); 542 } 543 544 if (comm_init(dbp, ctrlfd, clntfd) < 0) { 545 ret = -1; 546 goto close_db; 547 } 548 549 if (loop(dbp) < 0) { 550 ret = -1; 551 goto close_db; 552 } 553 554close_db: 555 if (dbif_close(dbd) < 0) 556 ret = -1; 557 558 if (dbif_env_remove(bdata(dbpath)) < 0) 559 ret = -1; 560 561EC_CLEANUP: 562 if (ret != 0) 563 exit(1); 564 565 if (exit_sig) 566 LOG(log_info, logtype_cnid, "main: Exiting on signal %i", exit_sig); 567 else 568 LOG(log_info, logtype_cnid, "main: Idle timeout, exiting"); 569 570 EC_EXIT; 571} 572