2 * Copyright (C) Joerg Lenneis 2003
3 * Copyright (C) Frank Lahm 2009, 2010
5 * All Rights Reserved. See COPYING.
9 cnid_dbd metadaemon to start up cnid_dbd upon request from afpd.
13 1. afpd -------> cnid_metad
15 via UNIX domain socket
16 2. cnid_metad -------> cnid_dbd
19 3. cnid_metad -------> cnid_dbd
23 4. afpd -------> cnid_dbd
25 cnid_metad and cnid_dbd have been converted to non-blocking IO in 2010.
31 #endif /* HAVE_CONFIG_H */
37 #include <sys/param.h>
41 #include <sys/types.h>
43 #include <sys/resource.h>
48 #include <sys/socket.h>
53 #define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8)
54 #endif /* ! WEXITSTATUS */
56 #define WIFEXITED(stat_val) (((stat_val) & 255) == 0)
57 #endif /* ! WIFEXITED */
59 #define WIFSTOPPED(status) (((status) & 0xff) == 0x7f)
63 #define WIFSIGNALED(status) (!WIFSTOPPED(status) && !WIFEXITED(status))
66 #define WTERMSIG(status) ((status) & 0x7f)
69 /* functions for username and group */
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 */
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 */
88 #include <atalk/util.h>
89 #include <atalk/logger.h>
90 #include <atalk/cnid_bdb_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>
101 #define DBHOME ".AppleDB"
106 static volatile sig_atomic_t sigchild = 0;
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 */
113 #define DEFAULTHOST "localhost"
114 #define DEFAULTPORT "4700"
119 time_t tm; /* When respawned last */
120 unsigned int count; /* Times respawned in the last TESTTIME secondes */
121 int control_fd; /* file descriptor to child cnid_dbd process */
124 static struct server srv[MAXVOLS];
126 static void daemon_exit(int i)
131 /* ------------------ */
132 static void sig_handler(int sig)
137 LOG(log_note, logtype_afpd, "shutting down on %s",
138 sig == SIGTERM ? "SIGTERM" : "SIGQUIT");
141 LOG(log_error, logtype_afpd, "unexpected signal: %d", sig);
146 static struct server *test_usockfn(const char *path)
150 for (i = 0; i < maxvol; i++) {
151 if (srv[i].v_path && STRCMP(path, ==, srv[i].v_path))
159 * Pass connection request to existing cnid_dbd process or start a new one
161 * @param[in] obj handle
162 * @param[in] dbdpn Path to cnid_dbd binary
163 * @param[in] volpath Path of AFP volume
164 * @param[in] username Optional username, may be NULL
166 * @return 0 on success, -1 on error
168 int maybe_start_dbd(const AFPObj *obj, char *dbdpn, const char *volpath, const char *username)
178 LOG(log_debug, logtype_cnid, "maybe_start_dbd(\"%s\"): BEGIN", volpath);
180 up = test_usockfn(volpath);
182 /* we already have a process, send our fd */
183 LOG(log_debug, logtype_cnid, "maybe_start_dbd: cnid_dbd[%d] already serving", up->pid);
184 if (send_fd(up->control_fd, rqstfd) < 0) {
191 LOG(log_debug, logtype_cnid, "maybe_start_dbd: no cnid_dbd serving yet");
195 /* find an empty slot (i < maxvol) or the first free slot (i == maxvol)*/
196 for (i = 0; i <= maxvol && i < MAXVOLS; i++) {
197 if (srv[i].v_path == NULL) {
199 if ((up->v_path = strdup(volpath)) == NULL)
209 LOG(log_error, logtype_cnid, "no free slot for cnid_dbd child. Configured maximum: %d. Do you have so many volumes?", MAXVOLS);
213 /* we have a slot but no process */
215 /* check for respawn too fast */
216 if (t < (up->tm + TESTTIME)) {
217 /* We're in the respawn time window */
218 if (up->count > MAXSPAWN) {
219 /* ...and already tried to fork too often */
220 LOG(log_maxdebug, logtype_cnid, "maybe_start_dbd: respawning too fast");
221 return -1; /* just exit, dont sleep, because we might have work to do for another client */
224 /* out of respawn too fast windows reset the count */
225 LOG(log_info, logtype_cnid, "maybe_start_dbd: respawn window ended");
231 LOG(log_maxdebug, logtype_cnid, "maybe_start_dbd: respawn count: %u", up->count);
232 if (up->count > MAXSPAWN) {
233 /* We spawned too fast. From now until the first time we tried + TESTTIME seconds
234 we will just return -1 above */
235 LOG(log_info, logtype_cnid, "maybe_start_dbd: reached MAXSPAWN threshhold");
240 Create socketpair for comm between parent and child.
241 We use it to pass fds from connecting afpd processes to our
242 cnid_dbd child via fd passing.
244 if (socketpair(PF_UNIX, SOCK_STREAM, 0, sv) < 0) {
245 LOG(log_error, logtype_cnid, "error in socketpair: %s", strerror(errno));
249 if ((pid = fork()) < 0) {
250 LOG(log_error, logtype_cnid, "error in fork: %s", strerror(errno));
256 * Child. Close descriptors and start the daemon. If it fails
257 * just log it. The client process will fail connecting
264 for (i = 0; i < MAXVOLS; i++) {
265 if (srv[i].pid && up != &srv[i]) {
266 close(srv[i].control_fd);
270 sprintf(buf1, "%i", sv[1]);
271 sprintf(buf2, "%i", rqstfd);
273 if (up->count == MAXSPAWN) {
274 /* there's a pb with the db inform child, it will delete the db */
275 LOG(log_warning, logtype_cnid,
276 "Multiple attempts to start CNID db daemon for \"%s\" failed, wiping the slate clean...",
278 ret = execlp(dbdpn, dbdpn,
279 "-F", obj->options.configfile,
286 ret = execlp(dbdpn, dbdpn,
287 "-F", obj->options.configfile,
294 /* Yikes! We're still here, so exec failed... */
295 LOG(log_error, logtype_cnid, "Fatal error in exec: %s", strerror(errno));
303 up->control_fd = sv[0];
307 /* ------------------ */
308 static int set_dbdir(const char *dbdir, const char *vpath)
312 bstring oldpath, newpath;
315 LOG(log_debug, logtype_cnid, "set_dbdir: volume: %s, db path: %s", vpath, dbdir);
317 EC_NULL_LOG( oldpath = bformat("%s/%s/", vpath, DBHOME) );
318 EC_NULL_LOG( newpath = bformat("%s/%s/", dbdir, DBHOME) );
320 if (lstat(dbdir, &st) < 0 && mkdir(dbdir, 0755) < 0) {
321 LOG(log_error, logtype_cnid, "set_dbdir: mkdir failed for %s", dbdir);
325 if (lstat(cfrombstr(oldpath), &st) == 0 && lstat(cfrombstr(newpath), &st) != 0 && errno == ENOENT) {
326 /* There's an .AppleDB in the volume root, we move it */
328 cmd_argv[1] = bdata(oldpath);
329 cmd_argv[2] = (char *)dbdir;
331 if (run_cmd("mv", cmd_argv) != 0) {
332 LOG(log_error, logtype_cnid, "set_dbdir: moving CNID db from \"%s\" to \"%s\" failed",
333 bdata(oldpath), dbdir);
339 if (lstat(cfrombstr(newpath), &st) < 0 && mkdir(cfrombstr(newpath), 0755 ) < 0) {
340 LOG(log_error, logtype_cnid, "set_dbdir: mkdir failed for %s", bdata(newpath));
350 /* ------------------ */
351 static void catch_child(int sig _U_)
356 /* ----------------------- */
357 static void set_signal(void)
362 memset(&sv, 0, sizeof(sv));
365 sv.sa_handler = catch_child;
366 sv.sa_flags = SA_NOCLDSTOP;
367 sigemptyset(&sv.sa_mask);
368 if (sigaction(SIGCHLD, &sv, NULL) < 0) {
369 LOG(log_error, logtype_cnid, "cnid_metad: sigaction: %s", strerror(errno));
370 daemon_exit(EXITERR_SYS);
373 /* Catch SIGTERM and SIGQUIT */
374 sv.sa_handler = sig_handler;
375 sigfillset(&sv.sa_mask );
376 if (sigaction(SIGTERM, &sv, NULL ) < 0 ) {
377 LOG(log_error, logtype_afpd, "sigaction: %s", strerror(errno) );
378 daemon_exit(EXITERR_SYS);
380 if (sigaction(SIGQUIT, &sv, NULL ) < 0 ) {
381 LOG(log_error, logtype_afpd, "sigaction: %s", strerror(errno) );
382 daemon_exit(EXITERR_SYS);
385 /* Ignore the rest */
386 sv.sa_handler = SIG_IGN;
387 sigemptyset(&sv.sa_mask );
388 if (sigaction(SIGALRM, &sv, NULL ) < 0 ) {
389 LOG(log_error, logtype_afpd, "sigaction: %s", strerror(errno) );
390 daemon_exit(EXITERR_SYS);
392 sv.sa_handler = SIG_IGN;
393 sigemptyset(&sv.sa_mask );
394 if (sigaction(SIGHUP, &sv, NULL ) < 0 ) {
395 LOG(log_error, logtype_afpd, "sigaction: %s", strerror(errno) );
396 daemon_exit(EXITERR_SYS);
398 sv.sa_handler = SIG_IGN;
399 sigemptyset(&sv.sa_mask );
400 if (sigaction(SIGUSR1, &sv, NULL ) < 0 ) {
401 LOG(log_error, logtype_afpd, "sigaction: %s", strerror(errno) );
402 daemon_exit(EXITERR_SYS);
404 sv.sa_handler = SIG_IGN;
405 sigemptyset(&sv.sa_mask );
406 if (sigaction(SIGUSR2, &sv, NULL ) < 0 ) {
407 LOG(log_error, logtype_afpd, "sigaction: %s", strerror(errno) );
408 daemon_exit(EXITERR_SYS);
410 sv.sa_handler = SIG_IGN;
411 sigemptyset(&sv.sa_mask );
412 if (sigaction(SIGPIPE, &sv, NULL ) < 0 ) {
413 LOG(log_error, logtype_afpd, "sigaction: %s", strerror(errno) );
414 daemon_exit(EXITERR_SYS);
417 /* block everywhere but in pselect */
419 sigaddset(&set, SIGCHLD);
420 sigprocmask(SIG_SETMASK, &set, NULL);
423 static int setlimits(void)
427 if (getrlimit(RLIMIT_NOFILE, &rlim) != 0) {
428 LOG(log_error, logtype_afpd, "setlimits: %s", strerror(errno));
431 if (rlim.rlim_cur != RLIM_INFINITY && rlim.rlim_cur < 65535) {
432 rlim.rlim_cur = 65535;
433 if (rlim.rlim_max != RLIM_INFINITY && rlim.rlim_max < 65535)
434 rlim.rlim_max = 65535;
435 if (setrlimit(RLIMIT_NOFILE, &rlim) != 0) {
436 LOG(log_error, logtype_afpd, "setlimits: %s", strerror(errno));
443 static uid_t uid_from_name(const char *name)
447 pwd = getpwnam(name);
453 /* ------------------ */
454 int main(int argc, char *argv[])
456 char *volname = NULL;
457 char *volpath = NULL;
458 char *username = NULL;
459 int len[DBD_NUM_OPEN_ARGS], actual_len;
462 char *dbdpn = _PATH_CNID_DBD;
475 while (( cc = getopt( argc, argv, "dF:vV")) != -1 ) {
481 obj.cmdlineconfigfile = strdup(optarg);
485 printf("cnid_metad (Netatalk %s)\n", VERSION);
488 printf("cnid_metad [-dvV] [-F alternate configfile ]\n");
493 if (!debug && daemonize(0, 0) != 0)
496 if (afp_config_parse(&obj, "cnid_metad") != 0)
501 host = atalk_iniparser_getstrdup(obj.iniconfig, INISEC_GLOBAL, "cnid listen", "localhost:4700");
502 if ((port = strrchr(host, ':')))
506 if ((srvfd = tsockfd_create(host, port, 10)) < 0)
509 LOG(log_note, logtype_afpd, "CNID Server listening on %s:%s", host, port);
513 LOG(log_debug, logtype_cnid, "Setting uid/gid to %i/%i", uid, gid);
515 if (SWITCH_TO_GID(gid) < 0) {
516 LOG(log_info, logtype_cnid, "unable to switch to group %d", gid);
521 if (SWITCH_TO_UID(uid) < 0) {
522 LOG(log_info, logtype_cnid, "unable to switch to user %d", uid);
531 sigprocmask(SIG_SETMASK, NULL, &set);
532 sigdelset(&set, SIGCHLD);
535 rqstfd = usockfd_check(srvfd, &set);
536 /* Collect zombie processes and log what happened to them */
537 if (sigchild) while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
538 for (i = 0; i < maxvol; i++) {
539 if (srv[i].pid == pid) {
541 close(srv[i].control_fd);
545 if (WIFEXITED(status)) {
546 LOG(log_info, logtype_cnid, "cnid_dbd[%i] exited with exit code %i",
547 pid, WEXITSTATUS(status));
549 /* cnid_dbd did a clean exit probably on idle timeout, reset bookkeeping */
553 if (WIFSIGNALED(status)) {
554 LOG(log_info, logtype_cnid, "cnid_dbd[%i] got signal %i",
555 pid, WTERMSIG(status));
562 ret = readt(rqstfd, &len[0], sizeof(int) * DBD_NUM_OPEN_ARGS, 1, 4);
569 LOG(log_severe, logtype_cnid, "error read: %s", strerror(errno));
572 else if (ret != DBD_NUM_OPEN_ARGS * sizeof(int)) {
573 LOG(log_error, logtype_cnid, "short read: got %d", ret);
578 * checks for buffer overruns. The client libatalk side does it too
579 * before handing the dir path over but who trusts clients?
581 if (!len[0] || !len[1]) {
582 LOG(log_error, logtype_cnid, "wrong len parameter: len[0]: %d, len[1]: %d", len[0], len[1]);
586 volname = malloc(len[0]);
587 volpath = malloc(len[1]);
589 username = malloc(len[2]);
591 if (!volname || !volpath || (len[2] && !username)) {
592 LOG(log_severe, logtype_cnid, "malloc: %s", strerror(errno));
596 actual_len = readt(rqstfd, volname, len[0], 1, 5);
597 if (actual_len != len[0]) {
598 LOG(log_severe, logtype_cnid, "readt: %s", strerror(errno));
602 actual_len = readt(rqstfd, volpath, len[1], 1, 5);
603 if (actual_len != len[1]) {
604 LOG(log_severe, logtype_cnid, "readt: %s", strerror(errno));
609 actual_len = readt(rqstfd, username, len[2], 1, 5);
610 if (actual_len != len[2]) {
611 LOG(log_severe, logtype_cnid, "readt: %s", strerror(errno));
614 strlcpy(obj.username, username, MAXUSERLEN);
615 obj.uid = uid_from_name(username);
622 LOG(log_debug, logtype_cnid, "user: %s, volume %s, path %s",
623 username ? username : "-", volname, volpath);
625 if (load_volumes(&obj, lv_all) != 0) {
626 LOG(log_severe, logtype_cnid, "main: error reloading config");
630 if ((vol = getvolbypath(&obj, volpath)) == NULL) {
631 LOG(log_severe, logtype_cnid, "main: no volume for path \"%s\"", volpath);
635 LOG(log_maxdebug, logtype_cnid, "main: dbpath: %s", vol->v_dbpath);
637 if (set_dbdir(vol->v_dbpath, vol->v_path) < 0) {
641 maybe_start_dbd(&obj, dbdpn, vol->v_path, username);
645 unload_volumes(&obj);