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#ifdef HAVE_UNISTD_H 12#include <unistd.h> 13#endif /* HAVE_UNISTD_H */ 14#ifdef HAVE_FCNTL_H 15#include <fcntl.h> 16#endif /* HAVE_FCNTL_H */ 17#include <stdio.h> 18#include <stdlib.h> 19#include <errno.h> 20#include <signal.h> 21#include <string.h> 22#ifdef HAVE_SYS_TYPES_H 23#include <sys/types.h> 24#endif /* HAVE_SYS_TYPES_H */ 25#include <sys/param.h> 26#ifdef HAVE_SYS_STAT_H 27#include <sys/stat.h> 28#endif /* HAVE_SYS_STAT_H */ 29#include <time.h> 30#include <sys/file.h> 31 32#include <netatalk/endian.h> 33#include <atalk/cnid_dbd_private.h> 34#include <atalk/logger.h> 35#include <atalk/volinfo.h> 36 37#include "db_param.h" 38#include "dbif.h" 39#include "dbd.h" 40#include "comm.h" 41 42/* 43 Note: DB_INIT_LOCK is here so we can run the db_* utilities while netatalk is running. 44 It's a likey performance hit, but it might we worth it. 45 */ 46#define DBOPTIONS (DB_CREATE | DB_INIT_LOG | DB_INIT_MPOOL | DB_INIT_LOCK | DB_INIT_TXN) 47 48/* Global, needed by pack.c:idxname() */ 49struct volinfo volinfo; 50 51static DBD *dbd; 52static int exit_sig = 0; 53static int db_locked; 54 55static void sig_exit(int signo) 56{ 57 exit_sig = signo; 58 return; 59} 60 61static void block_sigs_onoff(int block) 62{ 63 sigset_t set; 64 65 sigemptyset(&set); 66 sigaddset(&set, SIGINT); 67 sigaddset(&set, SIGTERM); 68 if (block) 69 sigprocmask(SIG_BLOCK, &set, NULL); 70 else 71 sigprocmask(SIG_UNBLOCK, &set, NULL); 72 return; 73} 74 75/* 76 The dbd_XXX and comm_XXX functions all obey the same protocol for return values: 77 78 1: Success, if transactions are used commit. 79 0: Failure, but we continue to serve requests. If transactions are used abort/rollback. 80 -1: Fatal error, either from t 81 he database or from the socket. Abort the transaction if applicable 82 (which might fail as well) and then exit. 83 84 We always try to notify the client process about the outcome, the result field 85 of the cnid_dbd_rply structure contains further details. 86 87*/ 88#ifndef min 89#define min(a,b) ((a)<(b)?(a):(b)) 90#endif 91 92static int loop(struct db_param *dbp) 93{ 94 struct cnid_dbd_rqst rqst; 95 struct cnid_dbd_rply rply; 96 time_t timeout; 97 int ret, cret; 98 int count; 99 time_t now, time_next_flush, time_last_rqst; 100 char timebuf[64]; 101 static char namebuf[MAXPATHLEN + 1]; 102 sigset_t set; 103 104 sigemptyset(&set); 105 sigprocmask(SIG_SETMASK, NULL, &set); 106 sigdelset(&set, SIGINT); 107 sigdelset(&set, SIGTERM); 108 109 count = 0; 110 now = time(NULL); 111 time_next_flush = now + dbp->flush_interval; 112 time_last_rqst = now; 113 114 rqst.name = namebuf; 115 116 strftime(timebuf, 63, "%b %d %H:%M:%S.",localtime(&time_next_flush)); 117 LOG(log_debug, logtype_cnid, "Checkpoint interval: %d seconds. Next checkpoint: %s", 118 dbp->flush_interval, timebuf); 119 120 while (1) { 121 timeout = min(time_next_flush, time_last_rqst +dbp->idle_timeout); 122 if (timeout > now) 123 timeout -= now; 124 else 125 timeout = 1; 126 127 if ((cret = comm_rcv(&rqst, timeout, &set, &now)) < 0) 128 return -1; 129 130 if (cret == 0) { 131 /* comm_rcv returned from select without receiving anything. */ 132 if (exit_sig) { 133 /* Received signal (TERM|INT) */ 134 return 0; 135 } 136 if (now - time_last_rqst >= dbp->idle_timeout && comm_nbe() <= 0) { 137 /* Idle timeout */ 138 return 0; 139 } 140 /* still active connections, reset time_last_rqst */ 141 time_last_rqst = now; 142 } else { 143 /* We got a request */ 144 time_last_rqst = now; 145 146 memset(&rply, 0, sizeof(rply)); 147 switch(rqst.op) { 148 /* ret gets set here */ 149 case CNID_DBD_OP_OPEN: 150 case CNID_DBD_OP_CLOSE: 151 /* open/close are noops for now. */ 152 rply.namelen = 0; 153 ret = 1; 154 break; 155 case CNID_DBD_OP_ADD: 156 ret = dbd_add(dbd, &rqst, &rply, 0); 157 break; 158 case CNID_DBD_OP_GET: 159 ret = dbd_get(dbd, &rqst, &rply); 160 break; 161 case CNID_DBD_OP_RESOLVE: 162 ret = dbd_resolve(dbd, &rqst, &rply); 163 break; 164 case CNID_DBD_OP_LOOKUP: 165 ret = dbd_lookup(dbd, &rqst, &rply, 0); 166 break; 167 case CNID_DBD_OP_UPDATE: 168 ret = dbd_update(dbd, &rqst, &rply); 169 break; 170 case CNID_DBD_OP_DELETE: 171 ret = dbd_delete(dbd, &rqst, &rply, DBIF_CNID); 172 break; 173 case CNID_DBD_OP_GETSTAMP: 174 ret = dbd_getstamp(dbd, &rqst, &rply); 175 break; 176 case CNID_DBD_OP_REBUILD_ADD: 177 ret = dbd_rebuild_add(dbd, &rqst, &rply); 178 break; 179 case CNID_DBD_OP_SEARCH: 180 ret = dbd_search(dbd, &rqst, &rply); 181 break; 182 default: 183 LOG(log_error, logtype_cnid, "loop: unknown op %d", rqst.op); 184 ret = -1; 185 break; 186 } 187 188 if ((cret = comm_snd(&rply)) < 0 || ret < 0) { 189 dbif_txn_abort(dbd); 190 return -1; 191 } 192 193 if (ret == 0 || cret == 0) { 194 if (dbif_txn_abort(dbd) < 0) 195 return -1; 196 } else { 197 ret = dbif_txn_commit(dbd); 198 if ( ret < 0) 199 return -1; 200 else if ( ret > 0 ) 201 /* We had a designated txn because we wrote to the db */ 202 count++; 203 } 204 } /* got a request */ 205 206 /* 207 Shall we checkpoint bdb ? 208 "flush_interval" seconds passed ? 209 */ 210 if (now >= time_next_flush) { 211 LOG(log_info, logtype_cnid, "Checkpointing BerkeleyDB for volume '%s'", dbp->dir); 212 if (dbif_txn_checkpoint(dbd, 0, 0, 0) < 0) 213 return -1; 214 count = 0; 215 time_next_flush = now + dbp->flush_interval; 216 217 strftime(timebuf, 63, "%b %d %H:%M:%S.",localtime(&time_next_flush)); 218 LOG(log_debug, logtype_cnid, "Checkpoint interval: %d seconds. Next checkpoint: %s", 219 dbp->flush_interval, timebuf); 220 } 221 222 /* 223 Shall we checkpoint bdb ? 224 Have we commited "count" more changes than "flush_frequency" ? 225 */ 226 if (count > dbp->flush_frequency) { 227 LOG(log_info, logtype_cnid, "Checkpointing BerkeleyDB after %d writes for volume '%s'", count, dbp->dir); 228 if (dbif_txn_checkpoint(dbd, 0, 0, 0) < 0) 229 return -1; 230 count = 0; 231 } 232 } /* while(1) */ 233} 234 235/* ------------------------ */ 236static void switch_to_user(char *dir) 237{ 238 struct stat st; 239 240 if (chdir(dir) < 0) { 241 LOG(log_error, logtype_cnid, "chdir to %s failed: %s", dir, strerror(errno)); 242 exit(1); 243 } 244 245 if (stat(".", &st) < 0) { 246 LOG(log_error, logtype_cnid, "error in stat for %s: %s", dir, strerror(errno)); 247 exit(1); 248 } 249 if (!getuid()) { 250 LOG(log_info, logtype_cnid, "Setting uid/gid to %i/%i", st.st_uid, st.st_gid); 251 if (setgid(st.st_gid) < 0 || setuid(st.st_uid) < 0) { 252 LOG(log_error, logtype_cnid, "uid/gid: %s", strerror(errno)); 253 exit(1); 254 } 255 } 256} 257 258 259/* ----------------------- */ 260static void set_signal(void) 261{ 262 struct sigaction sv; 263 264 sv.sa_handler = sig_exit; 265 sv.sa_flags = 0; 266 sigemptyset(&sv.sa_mask); 267 sigaddset(&sv.sa_mask, SIGINT); 268 sigaddset(&sv.sa_mask, SIGTERM); 269 if (sigaction(SIGINT, &sv, NULL) < 0 || sigaction(SIGTERM, &sv, NULL) < 0) { 270 LOG(log_error, logtype_cnid, "main: sigaction: %s", strerror(errno)); 271 exit(1); 272 } 273 sv.sa_handler = SIG_IGN; 274 sigemptyset(&sv.sa_mask); 275 if (sigaction(SIGPIPE, &sv, NULL) < 0) { 276 LOG(log_error, logtype_cnid, "main: sigaction: %s", strerror(errno)); 277 exit(1); 278 } 279} 280 281/* ------------------------ */ 282int main(int argc, char *argv[]) 283{ 284 struct db_param *dbp; 285 int err = 0; 286 int ctrlfd, clntfd; 287 char *logconfig; 288 289 set_processname("cnid_dbd"); 290 291 /* FIXME: implement -d from cnid_metad */ 292 if (argc != 5) { 293 LOG(log_error, logtype_cnid, "main: not enough arguments"); 294 exit(1); 295 } 296 297 ctrlfd = atoi(argv[2]); 298 clntfd = atoi(argv[3]); 299 logconfig = strdup(argv[4]); 300 setuplog(logconfig); 301 302 /* Load .volinfo file */ 303 if (loadvolinfo(argv[1], &volinfo) == -1) { 304 LOG(log_error, logtype_cnid, "Cant load volinfo for \"%s\"", argv[1]); 305 exit(EXIT_FAILURE); 306 } 307 /* Put "/.AppleDB" at end of volpath, get path from volinfo file */ 308 char dbpath[MAXPATHLEN+1]; 309 if ((strlen(volinfo.v_dbpath) + strlen("/.AppleDB")) > MAXPATHLEN ) { 310 LOG(log_error, logtype_cnid, "CNID db pathname too long: \"%s\"", volinfo.v_dbpath); 311 exit(EXIT_FAILURE); 312 } 313 strncpy(dbpath, volinfo.v_dbpath, MAXPATHLEN - strlen("/.AppleDB")); 314 strcat(dbpath, "/.AppleDB"); 315 316 if (vol_load_charsets(&volinfo) == -1) { 317 LOG(log_error, logtype_cnid, "Error loading charsets!"); 318 exit(EXIT_FAILURE); 319 } 320 LOG(log_debug, logtype_cnid, "db dir: \"%s\"", dbpath); 321 322 switch_to_user(dbpath); 323 324 /* Get db lock */ 325 if ((db_locked = get_lock(LOCK_EXCL, dbpath)) == -1) { 326 LOG(log_error, logtype_cnid, "main: fatal db lock error"); 327 exit(1); 328 } 329 if (db_locked != LOCK_EXCL) { 330 /* Couldn't get exclusive lock, try shared lock */ 331 if ((db_locked = get_lock(LOCK_SHRD, NULL)) != LOCK_SHRD) { 332 LOG(log_error, logtype_cnid, "main: fatal db lock error"); 333 exit(1); 334 } 335 } 336 337 set_signal(); 338 339 /* SIGINT and SIGTERM are always off, unless we are in pselect */ 340 block_sigs_onoff(1); 341 342 if ((dbp = db_param_read(dbpath)) == NULL) 343 exit(1); 344 LOG(log_maxdebug, logtype_cnid, "Finished parsing db_param config file"); 345 346 if (NULL == (dbd = dbif_init(dbpath, "cnid2.db"))) 347 exit(2); 348 349 /* Only recover if we got the lock */ 350 if (dbif_env_open(dbd, 351 dbp, 352 (db_locked == LOCK_EXCL) ? DBOPTIONS | DB_RECOVER : DBOPTIONS) < 0) 353 exit(2); /* FIXME: same exit code as failure for dbif_open() */ 354 LOG(log_debug, logtype_cnid, "Finished initializing BerkeleyDB environment"); 355 356 if (dbif_open(dbd, dbp, 0) < 0) { 357 dbif_close(dbd); 358 exit(2); 359 } 360 LOG(log_debug, logtype_cnid, "Finished opening BerkeleyDB databases"); 361 362 /* Downgrade db lock */ 363 if (db_locked == LOCK_EXCL) { 364 if (get_lock(LOCK_UNLOCK, NULL) != 0) { 365 dbif_close(dbd); 366 exit(2); 367 } 368 if (get_lock(LOCK_SHRD, NULL) != LOCK_SHRD) { 369 dbif_close(dbd); 370 exit(2); 371 } 372 } 373 374 375 if (comm_init(dbp, ctrlfd, clntfd) < 0) { 376 dbif_close(dbd); 377 exit(3); 378 } 379 380 if (loop(dbp) < 0) 381 err++; 382 383 if (dbif_close(dbd) < 0) 384 err++; 385 386 if (dbif_env_remove(dbpath) < 0) 387 err++; 388 389 if (err) 390 exit(4); 391 else if (exit_sig) 392 LOG(log_info, logtype_cnid, "main: Exiting on signal %i", exit_sig); 393 else 394 LOG(log_info, logtype_cnid, "main: Idle timeout, exiting"); 395 396 return 0; 397} 398