1/* 2 * Copyright (C) Joerg Lenneis 2003 3 * Copyright (C) Frank Lahm 2009, 2010 4 * 5 * All Rights Reserved. See COPYING. 6 */ 7 8/* 9 cnid_dbd metadaemon to start up cnid_dbd upon request from afpd. 10 Here is how it works: 11 12 via TCP socket 13 1. afpd -------> cnid_metad 14 15 via UNIX domain socket 16 2. cnid_metad -------> cnid_dbd 17 18 passes afpd client fd 19 3. cnid_metad -------> cnid_dbd 20 21 Result: 22 via TCP socket 23 4. afpd -------> cnid_dbd 24 25 cnid_metad and cnid_dbd have been converted to non-blocking IO in 2010. 26 */ 27 28 29#ifdef HAVE_CONFIG_H 30#include "config.h" 31#endif /* HAVE_CONFIG_H */ 32 33#include <unistd.h> 34#undef __USE_GNU 35 36#include <stdlib.h> 37#include <sys/param.h> 38#include <errno.h> 39#include <string.h> 40#include <signal.h> 41#include <sys/types.h> 42#include <sys/time.h> 43#include <sys/resource.h> 44#include <sys/wait.h> 45#include <sys/uio.h> 46#include <sys/un.h> 47// #define _XPG4_2 1 48#include <sys/socket.h> 49#include <stdio.h> 50#include <time.h> 51 52#ifndef WEXITSTATUS 53#define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8) 54#endif /* ! WEXITSTATUS */ 55#ifndef WIFEXITED 56#define WIFEXITED(stat_val) (((stat_val) & 255) == 0) 57#endif /* ! WIFEXITED */ 58#ifndef WIFSTOPPED 59#define WIFSTOPPED(status) (((status) & 0xff) == 0x7f) 60#endif 61 62#ifndef WIFSIGNALED 63#define WIFSIGNALED(status) (!WIFSTOPPED(status) && !WIFEXITED(status)) 64#endif 65#ifndef WTERMSIG 66#define WTERMSIG(status) ((status) & 0x7f) 67#endif 68 69/* functions for username and group */ 70#include <pwd.h> 71#include <grp.h> 72 73/* FIXME */ 74#ifdef linux 75#ifndef USE_SETRESUID 76#define USE_SETRESUID 1 77#define SWITCH_TO_GID(gid) ((setresgid(gid,gid,gid) < 0 || setgid(gid) < 0) ? -1 : 0) 78#define SWITCH_TO_UID(uid) ((setresuid(uid,uid,uid) < 0 || setuid(uid) < 0) ? -1 : 0) 79#endif /* USE_SETRESUID */ 80#else /* ! linux */ 81#ifndef USE_SETEUID 82#define USE_SETEUID 1 83#define SWITCH_TO_GID(gid) ((setegid(gid) < 0 || setgid(gid) < 0) ? -1 : 0) 84#define SWITCH_TO_UID(uid) ((setuid(uid) < 0 || seteuid(uid) < 0 || setuid(uid) < 0) ? -1 : 0) 85#endif /* USE_SETEUID */ 86#endif /* linux */ 87 88#include <atalk/util.h> 89#include <atalk/logger.h> 90#include <atalk/cnid_dbd_private.h> 91#include <atalk/paths.h> 92#include <atalk/compat.h> 93#include <atalk/errchk.h> 94#include <atalk/bstrlib.h> 95#include <atalk/bstradd.h> 96#include <atalk/netatalk_conf.h> 97#include <atalk/volume.h> 98 99#include "usockfd.h" 100 101#define DBHOME ".AppleDB" 102#define DBHOMELEN 8 103 104static int srvfd; 105static int rqstfd; 106static volatile sig_atomic_t sigchild = 0; 107static uint maxvol; 108 109#define MAXSPAWN 3 /* Max times respawned in.. */ 110#define TESTTIME 10 /* this much seconds apfd client tries to * 111 * to reconnect every 5 secondes, catch it */ 112#define MAXVOLS 4096 113#define DEFAULTHOST "localhost" 114#define DEFAULTPORT "4700" 115 116//Change path by Edison in 20130923 117#define T_PATH_CNID_DBD "/usr/sbin/cnid_dbd" 118 119struct server { 120 char *v_path; 121 pid_t pid; 122 time_t tm; /* When respawned last */ 123 unsigned int count; /* Times respawned in the last TESTTIME secondes */ 124 int control_fd; /* file descriptor to child cnid_dbd process */ 125}; 126 127static struct server srv[MAXVOLS]; 128 129static void daemon_exit(int i) 130{ 131 exit(i); 132} 133 134/* ------------------ */ 135static void sig_handler(int sig) 136{ 137 switch( sig ) { 138 case SIGTERM: 139 case SIGQUIT: 140 LOG(log_note, logtype_afpd, "shutting down on %s", 141 sig == SIGTERM ? "SIGTERM" : "SIGQUIT"); 142 break; 143 default : 144 LOG(log_error, logtype_afpd, "unexpected signal: %d", sig); 145 } 146 daemon_exit(0); 147} 148 149static struct server *test_usockfn(const char *path) 150{ 151 int i; 152 153 for (i = 0; i < maxvol; i++) { 154 if (srv[i].v_path && STRCMP(path, ==, srv[i].v_path)) 155 return &srv[i]; 156 } 157 158 return NULL; 159} 160 161/* -------------------- */ 162static int maybe_start_dbd(const AFPObj *obj, char *dbdpn, const char *volpath) 163{ 164 pid_t pid; 165 struct server *up; 166 int sv[2]; 167 int i; 168 time_t t; 169 char buf1[8]; 170 char buf2[8]; 171 172 LOG(log_debug, logtype_cnid, "maybe_start_dbd(\"%s\"): BEGIN", volpath); 173 174 up = test_usockfn(volpath); 175 if (up && up->pid) { 176 /* we already have a process, send our fd */ 177 LOG(log_debug, logtype_cnid, "maybe_start_dbd: cnid_dbd[%d] already serving", up->pid); 178 if (send_fd(up->control_fd, rqstfd) < 0) { 179 /* FIXME */ 180 return -1; 181 } 182 return 0; 183 } 184 185 LOG(log_debug, logtype_cnid, "maybe_start_dbd: no cnid_dbd serving yet"); 186 187 time(&t); 188 if (!up) { 189 /* find an empty slot (i < maxvol) or the first free slot (i == maxvol)*/ 190 for (i = 0; i <= maxvol && i < MAXVOLS; i++) { 191 if (srv[i].v_path == NULL) { 192 up = &srv[i]; 193 if ((up->v_path = strdup(volpath)) == NULL) 194 return -1; 195 up->tm = t; 196 up->count = 0; 197 if (i == maxvol) 198 maxvol++; 199 break; 200 } 201 } 202 if (!up) { 203 LOG(log_error, logtype_cnid, "no free slot for cnid_dbd child. Configured maximum: %d. Do you have so many volumes?", MAXVOLS); 204 return -1; 205 } 206 } else { 207 /* we have a slot but no process */ 208 if (up->count > 0) { 209 /* check for respawn too fast */ 210 if (t < (up->tm + TESTTIME)) { 211 /* We're in the respawn time window */ 212 if (up->count > MAXSPAWN) { 213 /* ...and already tried to fork too often */ 214 LOG(log_maxdebug, logtype_cnid, "maybe_start_dbd: respawning too fast"); 215 return -1; /* just exit, dont sleep, because we might have work to do for another client */ 216 } 217 } else { 218 /* out of respawn too fast windows reset the count */ 219 LOG(log_info, logtype_cnid, "maybe_start_dbd: respawn window ended"); 220 up->count = 0; 221 } 222 } 223 up->count++; 224 up->tm = t; 225 LOG(log_maxdebug, logtype_cnid, "maybe_start_dbd: respawn count: %u", up->count); 226 if (up->count > MAXSPAWN) { 227 /* We spawned too fast. From now until the first time we tried + TESTTIME seconds 228 we will just return -1 above */ 229 LOG(log_info, logtype_cnid, "maybe_start_dbd: reached MAXSPAWN threshhold"); 230 } 231 } 232 233 /* 234 Create socketpair for comm between parent and child. 235 We use it to pass fds from connecting afpd processes to our 236 cnid_dbd child via fd passing. 237 */ 238 if (socketpair(PF_UNIX, SOCK_STREAM, 0, sv) < 0) { 239 LOG(log_error, logtype_cnid, "error in socketpair: %s", strerror(errno)); 240 return -1; 241 } 242 243 if ((pid = fork()) < 0) { 244 LOG(log_error, logtype_cnid, "error in fork: %s", strerror(errno)); 245 return -1; 246 } 247 if (pid == 0) { 248 int ret; 249 /* 250 * Child. Close descriptors and start the daemon. If it fails 251 * just log it. The client process will fail connecting 252 * afterwards anyway. 253 */ 254 255 close(srvfd); 256 close(sv[0]); 257 258 for (i = 0; i < MAXVOLS; i++) { 259 if (srv[i].pid && up != &srv[i]) { 260 close(srv[i].control_fd); 261 } 262 } 263 264 sprintf(buf1, "%i", sv[1]); 265 sprintf(buf2, "%i", rqstfd); 266 267 if (up->count == MAXSPAWN) { 268 /* there's a pb with the db inform child, it will delete the db */ 269 LOG(log_warning, logtype_cnid, 270 "Multiple attempts to start CNID db daemon for \"%s\" failed, wiping the slate clean...", 271 up->v_path); 272 ret = execlp(dbdpn, dbdpn, "-F", obj->options.configfile, "-p", volpath, "-t", buf1, "-l", buf2, "-d", NULL); 273 } else { 274 ret = execlp(dbdpn, dbdpn, "-F", obj->options.configfile, "-p", volpath, "-t", buf1, "-l", buf2, NULL); 275 } 276 /* Yikes! We're still here, so exec failed... */ 277 LOG(log_error, logtype_cnid, "Fatal error in exec: %s", strerror(errno)); 278 daemon_exit(0); 279 } 280 /* 281 * Parent. 282 */ 283 up->pid = pid; 284 close(sv[1]); 285 up->control_fd = sv[0]; 286 return 0; 287} 288 289/* ------------------ */ 290static int set_dbdir(const char *dbdir, const char *vpath) 291{ 292 EC_INIT; 293 struct stat st; 294 bstring oldpath, newpath; 295 char *cmd_argv[4]; 296 297 LOG(log_debug, logtype_cnid, "set_dbdir: volume: %s, db path: %s", vpath, dbdir); 298 299 EC_NULL_LOG( oldpath = bformat("%s/%s/", vpath, DBHOME) ); 300 EC_NULL_LOG( newpath = bformat("%s/%s/", dbdir, DBHOME) ); 301 302 if (lstat(dbdir, &st) < 0 && mkdir(dbdir, 0755) < 0) { 303 LOG(log_error, logtype_cnid, "set_dbdir: mkdir failed for %s", dbdir); 304 EC_FAIL; 305 } 306 307 if (lstat(cfrombstr(oldpath), &st) == 0 && lstat(cfrombstr(newpath), &st) != 0 && errno == ENOENT) { 308 /* There's an .AppleDB in the volume root, we move it */ 309 cmd_argv[0] = "mv"; 310 cmd_argv[1] = bdata(oldpath); 311 cmd_argv[2] = (char *)dbdir; 312 cmd_argv[3] = NULL; 313 if (run_cmd("mv", cmd_argv) != 0) { 314 LOG(log_error, logtype_cnid, "set_dbdir: moving CNID db from \"%s\" to \"%s\" failed", 315 bdata(oldpath), dbdir); 316 EC_FAIL; 317 } 318 319 } 320 321 if (lstat(cfrombstr(newpath), &st) < 0 && mkdir(cfrombstr(newpath), 0755 ) < 0) { 322 LOG(log_error, logtype_cnid, "set_dbdir: mkdir failed for %s", bdata(newpath)); 323 EC_FAIL; 324 } 325 326EC_CLEANUP: 327 bdestroy(oldpath); 328 bdestroy(newpath); 329 EC_EXIT; 330} 331 332/* ------------------ */ 333static void catch_child(int sig _U_) 334{ 335 sigchild = 1; 336} 337 338/* ----------------------- */ 339static void set_signal(void) 340{ 341 struct sigaction sv; 342 sigset_t set; 343 344 memset(&sv, 0, sizeof(sv)); 345 346 /* Catch SIGCHLD */ 347 sv.sa_handler = catch_child; 348 sv.sa_flags = SA_NOCLDSTOP; 349 sigemptyset(&sv.sa_mask); 350 if (sigaction(SIGCHLD, &sv, NULL) < 0) { 351 LOG(log_error, logtype_cnid, "cnid_metad: sigaction: %s", strerror(errno)); 352 daemon_exit(EXITERR_SYS); 353 } 354 355 /* Catch SIGTERM and SIGQUIT */ 356 sv.sa_handler = sig_handler; 357 sigfillset(&sv.sa_mask ); 358 if (sigaction(SIGTERM, &sv, NULL ) < 0 ) { 359 LOG(log_error, logtype_afpd, "sigaction: %s", strerror(errno) ); 360 daemon_exit(EXITERR_SYS); 361 } 362 if (sigaction(SIGQUIT, &sv, NULL ) < 0 ) { 363 LOG(log_error, logtype_afpd, "sigaction: %s", strerror(errno) ); 364 daemon_exit(EXITERR_SYS); 365 } 366 367 /* Ignore the rest */ 368 sv.sa_handler = SIG_IGN; 369 sigemptyset(&sv.sa_mask ); 370 if (sigaction(SIGALRM, &sv, NULL ) < 0 ) { 371 LOG(log_error, logtype_afpd, "sigaction: %s", strerror(errno) ); 372 daemon_exit(EXITERR_SYS); 373 } 374 sv.sa_handler = SIG_IGN; 375 sigemptyset(&sv.sa_mask ); 376 if (sigaction(SIGHUP, &sv, NULL ) < 0 ) { 377 LOG(log_error, logtype_afpd, "sigaction: %s", strerror(errno) ); 378 daemon_exit(EXITERR_SYS); 379 } 380 sv.sa_handler = SIG_IGN; 381 sigemptyset(&sv.sa_mask ); 382 if (sigaction(SIGUSR1, &sv, NULL ) < 0 ) { 383 LOG(log_error, logtype_afpd, "sigaction: %s", strerror(errno) ); 384 daemon_exit(EXITERR_SYS); 385 } 386 sv.sa_handler = SIG_IGN; 387 sigemptyset(&sv.sa_mask ); 388 if (sigaction(SIGUSR2, &sv, NULL ) < 0 ) { 389 LOG(log_error, logtype_afpd, "sigaction: %s", strerror(errno) ); 390 daemon_exit(EXITERR_SYS); 391 } 392 sv.sa_handler = SIG_IGN; 393 sigemptyset(&sv.sa_mask ); 394 if (sigaction(SIGPIPE, &sv, NULL ) < 0 ) { 395 LOG(log_error, logtype_afpd, "sigaction: %s", strerror(errno) ); 396 daemon_exit(EXITERR_SYS); 397 } 398 399 /* block everywhere but in pselect */ 400 sigemptyset(&set); 401 sigaddset(&set, SIGCHLD); 402 sigprocmask(SIG_SETMASK, &set, NULL); 403} 404 405static int setlimits(void) 406{ 407 struct rlimit rlim; 408 409 if (getrlimit(RLIMIT_NOFILE, &rlim) != 0) { 410 LOG(log_error, logtype_afpd, "setlimits: %s", strerror(errno)); 411 exit(1); 412 } 413 if (rlim.rlim_cur != RLIM_INFINITY && rlim.rlim_cur < 65535) { 414 rlim.rlim_cur = 65535; 415 if (rlim.rlim_max != RLIM_INFINITY && rlim.rlim_max < 65535) 416 rlim.rlim_max = 65535; 417 if (setrlimit(RLIMIT_NOFILE, &rlim) != 0) { 418 LOG(log_error, logtype_afpd, "setlimits: %s", strerror(errno)); 419 exit(1); 420 } 421 } 422 return 0; 423} 424 425/* ------------------ */ 426int main(int argc, char *argv[]) 427{ 428 char volpath[MAXPATHLEN + 1]; 429 int len, actual_len; 430 pid_t pid; 431 int status; 432// char *dbdpn = _PATH_CNID_DBD; //Edison marked 20130923 433 char *dbdpn = T_PATH_CNID_DBD; 434 char *host; 435 char *port; 436 int i; 437 int cc; 438 uid_t uid = 0; 439 gid_t gid = 0; 440 int debug = 0; 441 int ret; 442 sigset_t set; 443 AFPObj obj = { 0 }; 444 struct vol *vol; 445 446 while (( cc = getopt( argc, argv, "dF:vV")) != -1 ) { 447 switch (cc) { 448 case 'd': 449 debug = 1; 450 break; 451 case 'F': 452 obj.cmdlineconfigfile = strdup(optarg); 453 break; 454 case 'v': 455 case 'V': 456 printf("cnid_metad (Netatalk %s)\n", VERSION); 457 return -1; 458 default: 459 printf("cnid_metad [-dvV] [-F alternate configfile ]\n"); 460 return -1; 461 } 462 } 463 464 if (!debug && daemonize(0, 0) != 0) 465 exit(EXITERR_SYS); 466 467 if (afp_config_parse(&obj, "cnid_metad") != 0) 468 daemon_exit(1); 469 470 if (load_volumes(&obj) != 0) 471 daemon_exit(1); 472 473 (void)setlimits(); 474 475 host = atalk_iniparser_getstrdup(obj.iniconfig, INISEC_GLOBAL, "cnid listen", "localhost:4700"); 476 if ((port = strrchr(host, ':'))) 477 *port++ = 0; 478 else 479 port = DEFAULTPORT; 480 if ((srvfd = tsockfd_create(host, port, 10)) < 0) 481 daemon_exit(1); 482 483 LOG(log_note, logtype_afpd, "CNID Server listening on %s:%s", host, port); 484 485 /* switch uid/gid */ 486 if (uid || gid) { 487 LOG(log_debug, logtype_cnid, "Setting uid/gid to %i/%i", uid, gid); 488 if (gid) { 489 if (SWITCH_TO_GID(gid) < 0) { 490 LOG(log_info, logtype_cnid, "unable to switch to group %d", gid); 491 daemon_exit(1); 492 } 493 } 494 if (uid) { 495 if (SWITCH_TO_UID(uid) < 0) { 496 LOG(log_info, logtype_cnid, "unable to switch to user %d", uid); 497 daemon_exit(1); 498 } 499 } 500 } 501 502 set_signal(); 503 504 sigemptyset(&set); 505 sigprocmask(SIG_SETMASK, NULL, &set); 506 sigdelset(&set, SIGCHLD); 507 508 while (1) { 509 rqstfd = usockfd_check(srvfd, &set); 510 /* Collect zombie processes and log what happened to them */ 511 if (sigchild) while ((pid = waitpid(-1, &status, WNOHANG)) > 0) { 512 for (i = 0; i < maxvol; i++) { 513 if (srv[i].pid == pid) { 514 srv[i].pid = 0; 515 close(srv[i].control_fd); 516 break; 517 } 518 } 519 if (WIFEXITED(status)) { 520 LOG(log_info, logtype_cnid, "cnid_dbd[%i] exited with exit code %i", 521 pid, WEXITSTATUS(status)); 522 } else { 523 /* cnid_dbd did a clean exit probably on idle timeout, reset bookkeeping */ 524 srv[i].tm = 0; 525 srv[i].count = 0; 526 } 527 if (WIFSIGNALED(status)) { 528 LOG(log_info, logtype_cnid, "cnid_dbd[%i] got signal %i", 529 pid, WTERMSIG(status)); 530 } 531 sigchild = 0; 532 } 533 if (rqstfd <= 0) 534 continue; 535 536 ret = readt(rqstfd, &len, sizeof(int), 1, 4); 537 538 if (!ret) { 539 /* already close */ 540 goto loop_end; 541 } 542 else if (ret < 0) { 543 LOG(log_severe, logtype_cnid, "error read: %s", strerror(errno)); 544 goto loop_end; 545 } 546 else if (ret != sizeof(int)) { 547 LOG(log_error, logtype_cnid, "short read: got %d", ret); 548 goto loop_end; 549 } 550 /* 551 * checks for buffer overruns. The client libatalk side does it too 552 * before handing the dir path over but who trusts clients? 553 */ 554 if (!len || len +DBHOMELEN +2 > MAXPATHLEN) { 555 LOG(log_error, logtype_cnid, "wrong len parameter: %d", len); 556 goto loop_end; 557 } 558 559 actual_len = readt(rqstfd, volpath, len, 1, 5); 560 if (actual_len < 0) { 561 LOG(log_severe, logtype_cnid, "Read(2) error : %s", strerror(errno)); 562 goto loop_end; 563 } 564 if (actual_len != len) { 565 LOG(log_error, logtype_cnid, "error/short read (dir): %s", strerror(errno)); 566 goto loop_end; 567 } 568 volpath[len] = '\0'; 569 570 LOG(log_debug, logtype_cnid, "main: request for volume: %s", volpath); 571 572 if (load_volumes(&obj) != 0) { 573 LOG(log_severe, logtype_cnid, "main: error reloading config"); 574 goto loop_end; 575 } 576 577 if ((vol = getvolbypath(&obj, volpath)) == NULL) { 578 LOG(log_severe, logtype_cnid, "main: no volume for path \"%s\"", volpath); 579 goto loop_end; 580 } 581 582 LOG(log_maxdebug, logtype_cnid, "main: dbpath: %s", vol->v_dbpath); 583 584 if (set_dbdir(vol->v_dbpath, volpath) < 0) { 585 goto loop_end; 586 } 587 588 maybe_start_dbd(&obj, dbdpn, vol->v_path); 589 loop_end: 590 close(rqstfd); 591 } 592} 593