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/volinfo.h> 93 94#include "usockfd.h" 95 96#define DBHOME ".AppleDB" 97#define DBHOMELEN 8 98 99static int srvfd; 100static int rqstfd; 101static volatile sig_atomic_t sigchild = 0; 102static uint maxvol; 103 104#define MAXSPAWN 3 /* Max times respawned in.. */ 105#define TESTTIME 10 /* this much seconds apfd client tries to * 106 * to reconnect every 5 secondes, catch it */ 107#define MAXVOLS 4096 108#define DEFAULTHOST "localhost" 109#define DEFAULTPORT "4700" 110 111struct server { 112 struct volinfo *volinfo; 113 pid_t pid; 114 time_t tm; /* When respawned last */ 115 unsigned int count; /* Times respawned in the last TESTTIME secondes */ 116 int control_fd; /* file descriptor to child cnid_dbd process */ 117}; 118 119static struct server srv[MAXVOLS]; 120 121/* Default logging config: log to syslog with level log_note */ 122static char logconfig[MAXPATHLEN + 21 + 1] = "default log_note"; 123 124static void daemon_exit(int i) 125{ 126 server_unlock(_PATH_CNID_METAD_LOCK); 127 exit(i); 128} 129 130/* ------------------ */ 131static void sigterm_handler(int sig) 132{ 133 switch( sig ) { 134 case SIGTERM : 135 LOG(log_info, logtype_afpd, "shutting down on signal %d", sig ); 136 break; 137 default : 138 LOG(log_error, logtype_afpd, "unexpected signal: %d", sig); 139 } 140 daemon_exit(0); 141} 142 143static struct server *test_usockfn(struct volinfo *volinfo) 144{ 145 int i; 146 for (i = 0; i < maxvol; i++) { 147 if ((srv[i].volinfo) && (strcmp(srv[i].volinfo->v_path, volinfo->v_path) == 0)) { 148 return &srv[i]; 149 } 150 } 151 return NULL; 152} 153 154/* -------------------- */ 155static int maybe_start_dbd(char *dbdpn, struct volinfo *volinfo) 156{ 157 pid_t pid; 158 struct server *up; 159 int sv[2]; 160 int i; 161 time_t t; 162 char buf1[8]; 163 char buf2[8]; 164 char *volpath = volinfo->v_path; 165 166 LOG(log_debug, logtype_cnid, "maybe_start_dbd: Volume: \"%s\"", volpath); 167 168 up = test_usockfn(volinfo); 169 if (up && up->pid) { 170 /* we already have a process, send our fd */ 171 if (send_fd(up->control_fd, rqstfd) < 0) { 172 /* FIXME */ 173 return -1; 174 } 175 return 0; 176 } 177 178 LOG(log_maxdebug, logtype_cnid, "maybe_start_dbd: no cnid_dbd for that volume yet"); 179 180 time(&t); 181 if (!up) { 182 /* find an empty slot (i < maxvol) or the first free slot (i == maxvol)*/ 183 for (i = 0; i <= maxvol; i++) { 184 if (srv[i].volinfo == NULL && i < MAXVOLS) { 185 up = &srv[i]; 186 up->volinfo = volinfo; 187 retainvolinfo(volinfo); 188 up->tm = t; 189 up->count = 0; 190 if (i == maxvol) 191 maxvol++; 192 break; 193 } 194 } 195 if (!up) { 196 LOG(log_error, logtype_cnid, "no free slot for cnid_dbd child. Configured maximum: %d. Do you have so many volumes?", MAXVOLS); 197 return -1; 198 } 199 } else { 200 /* we have a slot but no process */ 201 if (up->count > 0) { 202 /* check for respawn too fast */ 203 if (t < (up->tm + TESTTIME)) { 204 /* We're in the respawn time window */ 205 if (up->count > MAXSPAWN) { 206 /* ...and already tried to fork too often */ 207 LOG(log_maxdebug, logtype_cnid, "maybe_start_dbd: respawning too fast"); 208 return -1; /* just exit, dont sleep, because we might have work to do for another client */ 209 } 210 } else { 211 /* out of respawn too fast windows reset the count */ 212 LOG(log_info, logtype_cnid, "maybe_start_dbd: respawn window ended"); 213 up->count = 0; 214 } 215 } 216 up->count++; 217 up->tm = t; 218 LOG(log_maxdebug, logtype_cnid, "maybe_start_dbd: respawn count: %u", up->count); 219 if (up->count > MAXSPAWN) { 220 /* We spawned too fast. From now until the first time we tried + TESTTIME seconds 221 we will just return -1 above */ 222 LOG(log_info, logtype_cnid, "maybe_start_dbd: reached MAXSPAWN threshhold"); 223 } 224 } 225 226 /* 227 Create socketpair for comm between parent and child. 228 We use it to pass fds from connecting afpd processes to our 229 cnid_dbd child via fd passing. 230 */ 231 if (socketpair(PF_UNIX, SOCK_STREAM, 0, sv) < 0) { 232 LOG(log_error, logtype_cnid, "error in socketpair: %s", strerror(errno)); 233 return -1; 234 } 235 236 if ((pid = fork()) < 0) { 237 LOG(log_error, logtype_cnid, "error in fork: %s", strerror(errno)); 238 return -1; 239 } 240 if (pid == 0) { 241 int ret; 242 /* 243 * Child. Close descriptors and start the daemon. If it fails 244 * just log it. The client process will fail connecting 245 * afterwards anyway. 246 */ 247 248 close(srvfd); 249 close(sv[0]); 250 251 for (i = 0; i < MAXVOLS; i++) { 252 if (srv[i].pid && up != &srv[i]) { 253 close(srv[i].control_fd); 254 } 255 } 256 257 sprintf(buf1, "%i", sv[1]); 258 sprintf(buf2, "%i", rqstfd); 259 260 if (up->count == MAXSPAWN) { 261 /* there's a pb with the db inform child, it will delete the db */ 262 LOG(log_warning, logtype_cnid, 263 "Multiple attempts to start CNID db daemon for \"%s\" failed, wiping the slate clean...", 264 up->volinfo->v_path); 265 ret = execlp(dbdpn, dbdpn, "-d", volpath, buf1, buf2, logconfig, NULL); 266 } else { 267 ret = execlp(dbdpn, dbdpn, volpath, buf1, buf2, logconfig, NULL); 268 } 269 /* Yikes! We're still here, so exec failed... */ 270 LOG(log_error, logtype_cnid, "Fatal error in exec: %s", strerror(errno)); 271 daemon_exit(0); 272 } 273 /* 274 * Parent. 275 */ 276 up->pid = pid; 277 close(sv[1]); 278 up->control_fd = sv[0]; 279 return 0; 280} 281 282/* ------------------ */ 283static int set_dbdir(char *dbdir) 284{ 285 int len; 286 struct stat st; 287 288 len = strlen(dbdir); 289 290 if (stat(dbdir, &st) < 0 && mkdir(dbdir, 0755) < 0) { 291 LOG(log_error, logtype_cnid, "set_dbdir: mkdir failed for %s", dbdir); 292 return -1; 293 } 294 295 if (dbdir[len - 1] != '/') { 296 strcat(dbdir, "/"); 297 len++; 298 } 299 strcpy(dbdir + len, DBHOME); 300 if (stat(dbdir, &st) < 0 && mkdir(dbdir, 0755 ) < 0) { 301 LOG(log_error, logtype_cnid, "set_dbdir: mkdir failed for %s", dbdir); 302 return -1; 303 } 304 return 0; 305} 306 307/* ------------------ */ 308static uid_t user_to_uid (char *username) 309{ 310 struct passwd *this_passwd; 311 312 /* check for anything */ 313 if ( !username || strlen ( username ) < 1 ) return 0; 314 315 /* grab the /etc/passwd record relating to username */ 316 this_passwd = getpwnam ( username ); 317 318 /* return false if there is no structure returned */ 319 if (this_passwd == NULL) return 0; 320 321 /* return proper uid */ 322 return this_passwd->pw_uid; 323 324} 325 326/* ------------------ */ 327static gid_t group_to_gid ( char *group) 328{ 329 struct group *this_group; 330 331 /* check for anything */ 332 if ( !group || strlen ( group ) < 1 ) return 0; 333 334 /* grab the /etc/groups record relating to group */ 335 this_group = getgrnam ( group ); 336 337 /* return false if there is no structure returned */ 338 if (this_group == NULL) return 0; 339 340 /* return proper gid */ 341 return this_group->gr_gid; 342 343} 344 345/* ------------------ */ 346static void catch_child(int sig _U_) 347{ 348 sigchild = 1; 349} 350 351/* ----------------------- */ 352static void set_signal(void) 353{ 354 struct sigaction sv; 355 sigset_t set; 356 357 memset(&sv, 0, sizeof(sv)); 358 359 /* Catch SIGCHLD */ 360 sv.sa_handler = catch_child; 361 sv.sa_flags = SA_NOCLDSTOP; 362 sigemptyset(&sv.sa_mask); 363 if (sigaction(SIGCHLD, &sv, NULL) < 0) { 364 LOG(log_error, logtype_cnid, "cnid_metad: sigaction: %s", strerror(errno)); 365 daemon_exit(EXITERR_SYS); 366 } 367 368 /* Catch SIGTERM */ 369 sv.sa_handler = sigterm_handler; 370 sigfillset(&sv.sa_mask ); 371 if (sigaction(SIGTERM, &sv, NULL ) < 0 ) { 372 LOG(log_error, logtype_afpd, "sigaction: %s", strerror(errno) ); 373 daemon_exit(EXITERR_SYS); 374 } 375 376 /* Ignore the rest */ 377 sv.sa_handler = SIG_IGN; 378 sigemptyset(&sv.sa_mask ); 379 if (sigaction(SIGALRM, &sv, NULL ) < 0 ) { 380 LOG(log_error, logtype_afpd, "sigaction: %s", strerror(errno) ); 381 daemon_exit(EXITERR_SYS); 382 } 383 sv.sa_handler = SIG_IGN; 384 sigemptyset(&sv.sa_mask ); 385 if (sigaction(SIGHUP, &sv, NULL ) < 0 ) { 386 LOG(log_error, logtype_afpd, "sigaction: %s", strerror(errno) ); 387 daemon_exit(EXITERR_SYS); 388 } 389 sv.sa_handler = SIG_IGN; 390 sigemptyset(&sv.sa_mask ); 391 if (sigaction(SIGUSR1, &sv, NULL ) < 0 ) { 392 LOG(log_error, logtype_afpd, "sigaction: %s", strerror(errno) ); 393 daemon_exit(EXITERR_SYS); 394 } 395 sv.sa_handler = SIG_IGN; 396 sigemptyset(&sv.sa_mask ); 397 if (sigaction(SIGUSR2, &sv, NULL ) < 0 ) { 398 LOG(log_error, logtype_afpd, "sigaction: %s", strerror(errno) ); 399 daemon_exit(EXITERR_SYS); 400 } 401 sv.sa_handler = SIG_IGN; 402 sigemptyset(&sv.sa_mask ); 403 if (sigaction(SIGPIPE, &sv, NULL ) < 0 ) { 404 LOG(log_error, logtype_afpd, "sigaction: %s", strerror(errno) ); 405 daemon_exit(EXITERR_SYS); 406 } 407 408 /* block everywhere but in pselect */ 409 sigemptyset(&set); 410 sigaddset(&set, SIGCHLD); 411 sigprocmask(SIG_BLOCK, &set, NULL); 412} 413 414static int setlimits(void) 415{ 416 struct rlimit rlim; 417 418 if (getrlimit(RLIMIT_NOFILE, &rlim) != 0) { 419 LOG(log_error, logtype_afpd, "setlimits: %s", strerror(errno)); 420 exit(1); 421 } 422 if (rlim.rlim_cur != RLIM_INFINITY && rlim.rlim_cur < 65535) { 423 rlim.rlim_cur = 65535; 424 if (rlim.rlim_max != RLIM_INFINITY && rlim.rlim_max < 65535) 425 rlim.rlim_max = 65535; 426 if (setrlimit(RLIMIT_NOFILE, &rlim) != 0) { 427 LOG(log_error, logtype_afpd, "setlimits: %s", strerror(errno)); 428 exit(1); 429 } 430 } 431 return 0; 432} 433 434/* ------------------ */ 435int main(int argc, char *argv[]) 436{ 437 char volpath[MAXPATHLEN + 1]; 438 int len, actual_len; 439 pid_t pid; 440 int status; 441 char *dbdpn = _PATH_CNID_DBD; 442 char *host = DEFAULTHOST; 443 char *port = DEFAULTPORT; 444 int i; 445 int cc; 446 uid_t uid = 0; 447 gid_t gid = 0; 448 int err = 0; 449 int debug = 0; 450 int ret; 451 char *loglevel = NULL; 452 char *logfile = NULL; 453 sigset_t set; 454 struct volinfo *volinfo; 455 456 set_processname("cnid_metad"); 457 458 while (( cc = getopt( argc, argv, "vVds:p:h:u:g:l:f:")) != -1 ) { 459 switch (cc) { 460 case 'v': 461 case 'V': 462 printf("cnid_metad (Netatalk %s)\n", VERSION); 463 return -1; 464 case 'd': 465 debug = 1; 466 break; 467 case 'h': 468 host = strdup(optarg); 469 break; 470 case 'u': 471 uid = user_to_uid (optarg); 472 if (!uid) { 473 LOG(log_error, logtype_cnid, "main: bad user %s", optarg); 474 err++; 475 } 476 break; 477 case 'g': 478 gid =group_to_gid (optarg); 479 if (!gid) { 480 LOG(log_error, logtype_cnid, "main: bad group %s", optarg); 481 err++; 482 } 483 break; 484 case 'p': 485 port = strdup(optarg); 486 break; 487 case 's': 488 dbdpn = strdup(optarg); 489 break; 490 case 'l': 491 loglevel = strdup(optarg); 492 break; 493 case 'f': 494 logfile = strdup(optarg); 495 break; 496 default: 497 err++; 498 break; 499 } 500 } 501 502 /* Check for PID lockfile */ 503 if (check_lockfile("cnid_metad", _PATH_CNID_METAD_LOCK)) 504 return -1; 505 506 if (!debug && daemonize(0, 0) != 0) 507 exit(EXITERR_SYS); 508 509 /* Create PID lockfile */ 510 if (create_lockfile("cnid_metad", _PATH_CNID_METAD_LOCK)) 511 return -1; 512 513 if (loglevel) { 514 strlcpy(logconfig + 8, loglevel, 13); 515 free(loglevel); 516 strcat(logconfig, " "); 517 } 518 if (logfile) { 519 strlcat(logconfig, logfile, MAXPATHLEN); 520 free(logfile); 521 } 522 setuplog(logconfig); 523 524 if (err) { 525 LOG(log_error, logtype_cnid, "main: bad arguments"); 526 daemon_exit(1); 527 } 528 529 (void)setlimits(); 530 531 if ((srvfd = tsockfd_create(host, port, 10)) < 0) 532 daemon_exit(1); 533 534 /* switch uid/gid */ 535 if (uid || gid) { 536 LOG(log_debug, logtype_cnid, "Setting uid/gid to %i/%i", uid, gid); 537 if (gid) { 538 if (SWITCH_TO_GID(gid) < 0) { 539 LOG(log_info, logtype_cnid, "unable to switch to group %d", gid); 540 daemon_exit(1); 541 } 542 } 543 if (uid) { 544 if (SWITCH_TO_UID(uid) < 0) { 545 LOG(log_info, logtype_cnid, "unable to switch to user %d", uid); 546 daemon_exit(1); 547 } 548 } 549 } 550 551 set_signal(); 552 553 sigemptyset(&set); 554 sigprocmask(SIG_SETMASK, NULL, &set); 555 sigdelset(&set, SIGCHLD); 556 557 while (1) { 558 rqstfd = usockfd_check(srvfd, &set); 559 /* Collect zombie processes and log what happened to them */ 560 if (sigchild) while ((pid = waitpid(-1, &status, WNOHANG)) > 0) { 561 for (i = 0; i < maxvol; i++) { 562 if (srv[i].pid == pid) { 563 srv[i].pid = 0; 564 close(srv[i].control_fd); 565 break; 566 } 567 } 568 if (WIFEXITED(status)) { 569 LOG(log_info, logtype_cnid, "cnid_dbd[%i] exited with exit code %i", 570 pid, WEXITSTATUS(status)); 571 } else { 572 /* cnid_dbd did a clean exit probably on idle timeout, reset bookkeeping */ 573 srv[i].tm = 0; 574 srv[i].count = 0; 575 } 576 if (WIFSIGNALED(status)) { 577 LOG(log_info, logtype_cnid, "cnid_dbd[%i] received signal %i", 578 pid, WTERMSIG(status)); 579 } 580 sigchild = 0; 581 } 582 if (rqstfd <= 0) 583 continue; 584 585 ret = readt(rqstfd, &len, sizeof(int), 1, 4); 586 587 if (!ret) { 588 /* already close */ 589 goto loop_end; 590 } 591 else if (ret < 0) { 592 LOG(log_severe, logtype_cnid, "error read: %s", strerror(errno)); 593 goto loop_end; 594 } 595 else if (ret != sizeof(int)) { 596 LOG(log_error, logtype_cnid, "short read: got %d", ret); 597 goto loop_end; 598 } 599 /* 600 * checks for buffer overruns. The client libatalk side does it too 601 * before handing the dir path over but who trusts clients? 602 */ 603 if (!len || len +DBHOMELEN +2 > MAXPATHLEN) { 604 LOG(log_error, logtype_cnid, "wrong len parameter: %d", len); 605 goto loop_end; 606 } 607 608 actual_len = readt(rqstfd, volpath, len, 1, 5); 609 if (actual_len < 0) { 610 LOG(log_severe, logtype_cnid, "Read(2) error : %s", strerror(errno)); 611 goto loop_end; 612 } 613 if (actual_len != len) { 614 LOG(log_error, logtype_cnid, "error/short read (dir): %s", strerror(errno)); 615 goto loop_end; 616 } 617 volpath[len] = '\0'; 618 619 /* Load .volinfo file */ 620 if ((volinfo = allocvolinfo(volpath)) == NULL) { 621 LOG(log_severe, logtype_cnid, "allocvolinfo(\"%s\"): %s", 622 volpath, strerror(errno)); 623 goto loop_end; 624 } 625 626 if (set_dbdir(volinfo->v_dbpath) < 0) { 627 goto loop_end; 628 } 629 630 maybe_start_dbd(dbdpn, volinfo); 631 632 (void)closevolinfo(volinfo); 633 634 loop_end: 635 close(rqstfd); 636 } 637} 638