]> arthur.barton.de Git - netatalk.git/blob - etc/cnid_dbd/cnid_metad.c
42b398f792c30707ad3c44db9d4e11353696256c
[netatalk.git] / etc / cnid_dbd / cnid_metad.c
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_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>
98
99 #include "usockfd.h"
100
101 #define DBHOME        ".AppleDB"
102 #define DBHOMELEN    8
103
104 static int srvfd;
105 static int rqstfd;
106 static volatile sig_atomic_t sigchild = 0;
107 static 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 struct server {
117     char *v_path;
118     pid_t pid;
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 */
122 };
123
124 static struct server srv[MAXVOLS];
125
126 static void daemon_exit(int i)
127 {
128     exit(i);
129 }
130
131 /* ------------------ */
132 static void sig_handler(int sig)
133 {
134     switch( sig ) {
135     case SIGTERM:
136     case SIGQUIT:
137         LOG(log_note, logtype_afpd, "shutting down on %s",
138             sig == SIGTERM ? "SIGTERM" : "SIGQUIT");
139         break;
140     default :
141         LOG(log_error, logtype_afpd, "unexpected signal: %d", sig);
142     }
143     daemon_exit(0);
144 }
145
146 static struct server *test_usockfn(const char *path)
147 {
148     int i;
149
150     for (i = 0; i < maxvol; i++) {
151         if (srv[i].v_path && STRCMP(path, ==, srv[i].v_path))
152             return &srv[i];
153     }
154
155     return NULL;
156 }
157
158 /* -------------------- */
159 static int maybe_start_dbd(const AFPObj *obj, char *dbdpn, const char *volpath)
160 {
161     pid_t pid;
162     struct server *up;
163     int sv[2];
164     int i;
165     time_t t;
166     char buf1[8];
167     char buf2[8];
168
169     LOG(log_debug, logtype_cnid, "maybe_start_dbd(\"%s\"): BEGIN", volpath);
170
171     up = test_usockfn(volpath);
172     if (up && up->pid) {
173         /* we already have a process, send our fd */
174         LOG(log_debug, logtype_cnid, "maybe_start_dbd: cnid_dbd[%d] already serving", up->pid);
175         if (send_fd(up->control_fd, rqstfd) < 0) {
176             /* FIXME */
177             return -1;
178         }
179         return 0;
180     }
181
182     LOG(log_debug, logtype_cnid, "maybe_start_dbd: no cnid_dbd serving yet");
183
184     time(&t);
185     if (!up) {
186         /* find an empty slot (i < maxvol) or the first free slot (i == maxvol)*/
187         for (i = 0; i <= maxvol && i < MAXVOLS; i++) {
188             if (srv[i].v_path == NULL) {
189                 up = &srv[i];
190                 if ((up->v_path = strdup(volpath)) == NULL)
191                     return -1;
192                 up->tm = t;
193                 up->count = 0;
194                 if (i == maxvol)
195                     maxvol++;
196                 break;
197             }
198         }
199         if (!up) {
200             LOG(log_error, logtype_cnid, "no free slot for cnid_dbd child. Configured maximum: %d. Do you have so many volumes?", MAXVOLS);
201             return -1;
202         }
203     } else {
204         /* we have a slot but no process */
205         if (up->count > 0) {
206             /* check for respawn too fast */
207             if (t < (up->tm + TESTTIME)) {
208                 /* We're in the respawn time window */
209                 if (up->count > MAXSPAWN) {
210                     /* ...and already tried to fork too often */
211                     LOG(log_maxdebug, logtype_cnid, "maybe_start_dbd: respawning too fast");
212                     return -1; /* just exit, dont sleep, because we might have work to do for another client  */
213                 }
214             } else {
215                 /* out of respawn too fast windows reset the count */
216                 LOG(log_info, logtype_cnid, "maybe_start_dbd: respawn window ended");
217                 up->count = 0;
218             }
219         }
220         up->count++;
221         up->tm = t;
222         LOG(log_maxdebug, logtype_cnid, "maybe_start_dbd: respawn count: %u", up->count);
223         if (up->count > MAXSPAWN) {
224             /* We spawned too fast. From now until the first time we tried + TESTTIME seconds
225                we will just return -1 above */
226             LOG(log_info, logtype_cnid, "maybe_start_dbd: reached MAXSPAWN threshhold");
227        }
228     }
229
230     /* 
231        Create socketpair for comm between parent and child.
232        We use it to pass fds from connecting afpd processes to our
233        cnid_dbd child via fd passing.
234     */
235     if (socketpair(PF_UNIX, SOCK_STREAM, 0, sv) < 0) {
236         LOG(log_error, logtype_cnid, "error in socketpair: %s", strerror(errno));
237         return -1;
238     }
239
240     if ((pid = fork()) < 0) {
241         LOG(log_error, logtype_cnid, "error in fork: %s", strerror(errno));
242         return -1;
243     }
244     if (pid == 0) {
245         int ret;
246         /*
247          *  Child. Close descriptors and start the daemon. If it fails
248          *  just log it. The client process will fail connecting
249          *  afterwards anyway.
250          */
251
252         close(srvfd);
253         close(sv[0]);
254
255         for (i = 0; i < MAXVOLS; i++) {
256             if (srv[i].pid && up != &srv[i]) {
257                 close(srv[i].control_fd);
258             }
259         }
260
261         sprintf(buf1, "%i", sv[1]);
262         sprintf(buf2, "%i", rqstfd);
263
264         if (up->count == MAXSPAWN) {
265             /* there's a pb with the db inform child, it will delete the db */
266             LOG(log_warning, logtype_cnid,
267                 "Multiple attempts to start CNID db daemon for \"%s\" failed, wiping the slate clean...",
268                 up->v_path);
269             ret = execlp(dbdpn, dbdpn, "-F", obj->options.configfile, "-p", volpath, "-t", buf1, "-l", buf2, "-d", NULL);
270         } else {
271             ret = execlp(dbdpn, dbdpn, "-F", obj->options.configfile, "-p", volpath, "-t", buf1, "-l", buf2, NULL);
272         }
273         /* Yikes! We're still here, so exec failed... */
274         LOG(log_error, logtype_cnid, "Fatal error in exec: %s", strerror(errno));
275         daemon_exit(0);
276     }
277     /*
278      *  Parent.
279      */
280     up->pid = pid;
281     close(sv[1]);
282     up->control_fd = sv[0];
283     return 0;
284 }
285
286 /* ------------------ */
287 static int set_dbdir(const char *dbdir, const char *vpath)
288 {
289     EC_INIT;
290     struct stat st;
291     bstring oldpath, newpath;
292     char *cmd_argv[4];
293
294     LOG(log_debug, logtype_cnid, "set_dbdir: volume: %s, db path: %s", vpath, dbdir);
295
296     EC_NULL_LOG( oldpath = bformat("%s/%s/", vpath, DBHOME) );
297     EC_NULL_LOG( newpath = bformat("%s/%s/", dbdir, DBHOME) );
298
299     if (lstat(dbdir, &st) < 0 && mkdir(dbdir, 0755) < 0) {
300         LOG(log_error, logtype_cnid, "set_dbdir: mkdir failed for %s", dbdir);
301         EC_FAIL;
302     }
303
304     if (lstat(cfrombstr(oldpath), &st) == 0 && lstat(cfrombstr(newpath), &st) != 0 && errno == ENOENT) {
305         /* There's an .AppleDB in the volume root, we move it */
306         cmd_argv[0] = "mv";
307         cmd_argv[1] = bdata(oldpath);
308         cmd_argv[2] = (char *)dbdir;
309         cmd_argv[3] = NULL;
310         if (run_cmd("mv", cmd_argv) != 0) {
311             LOG(log_error, logtype_cnid, "set_dbdir: moving CNID db from \"%s\" to \"%s\" failed",
312                 bdata(oldpath), dbdir);
313             EC_FAIL;
314         }
315
316     }
317
318     if (lstat(cfrombstr(newpath), &st) < 0 && mkdir(cfrombstr(newpath), 0755 ) < 0) {
319         LOG(log_error, logtype_cnid, "set_dbdir: mkdir failed for %s", bdata(newpath));
320         EC_FAIL;
321     }
322
323 EC_CLEANUP:
324     bdestroy(oldpath);
325     bdestroy(newpath);
326     EC_EXIT;
327 }
328
329 /* ------------------ */
330 static void catch_child(int sig _U_) 
331 {
332     sigchild = 1;
333 }
334
335 /* ----------------------- */
336 static void set_signal(void)
337 {
338     struct sigaction sv;
339     sigset_t set;
340
341     memset(&sv, 0, sizeof(sv));
342
343     /* Catch SIGCHLD */
344     sv.sa_handler = catch_child;
345     sv.sa_flags = SA_NOCLDSTOP;
346     sigemptyset(&sv.sa_mask);
347     if (sigaction(SIGCHLD, &sv, NULL) < 0) {
348         LOG(log_error, logtype_cnid, "cnid_metad: sigaction: %s", strerror(errno));
349         daemon_exit(EXITERR_SYS);
350     }
351
352     /* Catch SIGTERM and SIGQUIT */
353     sv.sa_handler = sig_handler;
354     sigfillset(&sv.sa_mask );
355     if (sigaction(SIGTERM, &sv, NULL ) < 0 ) {
356         LOG(log_error, logtype_afpd, "sigaction: %s", strerror(errno) );
357         daemon_exit(EXITERR_SYS);
358     }
359     if (sigaction(SIGQUIT, &sv, NULL ) < 0 ) {
360         LOG(log_error, logtype_afpd, "sigaction: %s", strerror(errno) );
361         daemon_exit(EXITERR_SYS);
362     }
363
364     /* Ignore the rest */
365     sv.sa_handler = SIG_IGN;
366     sigemptyset(&sv.sa_mask );
367     if (sigaction(SIGALRM, &sv, NULL ) < 0 ) {
368         LOG(log_error, logtype_afpd, "sigaction: %s", strerror(errno) );
369         daemon_exit(EXITERR_SYS);
370     }
371     sv.sa_handler = SIG_IGN;
372     sigemptyset(&sv.sa_mask );
373     if (sigaction(SIGHUP, &sv, NULL ) < 0 ) {
374         LOG(log_error, logtype_afpd, "sigaction: %s", strerror(errno) );
375         daemon_exit(EXITERR_SYS);
376     }
377     sv.sa_handler = SIG_IGN;
378     sigemptyset(&sv.sa_mask );
379     if (sigaction(SIGUSR1, &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(SIGUSR2, &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(SIGPIPE, &sv, NULL ) < 0 ) {
392         LOG(log_error, logtype_afpd, "sigaction: %s", strerror(errno) );
393         daemon_exit(EXITERR_SYS);
394     }
395
396     /* block everywhere but in pselect */
397     sigemptyset(&set);
398     sigaddset(&set, SIGCHLD);
399     sigprocmask(SIG_SETMASK, &set, NULL);
400 }
401
402 static int setlimits(void)
403 {
404     struct rlimit rlim;
405
406     if (getrlimit(RLIMIT_NOFILE, &rlim) != 0) {
407         LOG(log_error, logtype_afpd, "setlimits: %s", strerror(errno));
408         exit(1);
409     }
410     if (rlim.rlim_cur != RLIM_INFINITY && rlim.rlim_cur < 65535) {
411         rlim.rlim_cur = 65535;
412         if (rlim.rlim_max != RLIM_INFINITY && rlim.rlim_max < 65535)
413             rlim.rlim_max = 65535;
414         if (setrlimit(RLIMIT_NOFILE, &rlim) != 0) {
415             LOG(log_error, logtype_afpd, "setlimits: %s", strerror(errno));
416             exit(1);
417         }
418     }
419     return 0;
420 }
421
422 /* ------------------ */
423 int main(int argc, char *argv[])
424 {
425     char  volpath[MAXPATHLEN + 1];
426     int   len, actual_len;
427     pid_t pid;
428     int   status;
429     char  *dbdpn = _PATH_CNID_DBD;
430     char  *host;
431     char  *port;
432     int    i;
433     int    cc;
434     uid_t  uid = 0;
435     gid_t  gid = 0;
436     int    debug = 0;
437     int    ret;
438     sigset_t set;
439     AFPObj obj = { 0 };
440     struct vol *vol;
441
442     while (( cc = getopt( argc, argv, "dF:vV")) != -1 ) {
443         switch (cc) {
444         case 'd':
445             debug = 1;
446             break;
447         case 'F':
448             obj.cmdlineconfigfile = strdup(optarg);
449             break;
450         case 'v':
451         case 'V':
452             printf("cnid_metad (Netatalk %s)\n", VERSION);
453             return -1;
454         default:
455             printf("cnid_metad [-dvV] [-F alternate configfile ]\n");
456             return -1;
457         }
458     }
459
460     if (!debug && daemonize(0, 0) != 0)
461         exit(EXITERR_SYS);
462
463     if (afp_config_parse(&obj, "cnid_metad") != 0)
464         daemon_exit(1);
465
466     if (load_volumes(&obj) != 0)
467         daemon_exit(1);
468
469     (void)setlimits();
470
471     host = atalk_iniparser_getstrdup(obj.iniconfig, INISEC_GLOBAL, "cnid listen", "localhost:4700");
472     if ((port = strrchr(host, ':')))
473         *port++ = 0;
474     else
475         port = DEFAULTPORT;
476     if ((srvfd = tsockfd_create(host, port, 10)) < 0)
477         daemon_exit(1);
478
479     LOG(log_note, logtype_afpd, "CNID Server listening on %s:%s", host, port);
480
481     /* switch uid/gid */
482     if (uid || gid) {
483         LOG(log_debug, logtype_cnid, "Setting uid/gid to %i/%i", uid, gid);
484         if (gid) {
485             if (SWITCH_TO_GID(gid) < 0) {
486                 LOG(log_info, logtype_cnid, "unable to switch to group %d", gid);
487                 daemon_exit(1);
488             }
489         }
490         if (uid) {
491             if (SWITCH_TO_UID(uid) < 0) {
492                 LOG(log_info, logtype_cnid, "unable to switch to user %d", uid);
493                 daemon_exit(1);
494             }
495         }
496     }
497
498     set_signal();
499
500     sigemptyset(&set);
501     sigprocmask(SIG_SETMASK, NULL, &set);
502     sigdelset(&set, SIGCHLD);
503
504     while (1) {
505         rqstfd = usockfd_check(srvfd, &set);
506         /* Collect zombie processes and log what happened to them */
507         if (sigchild) while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
508             for (i = 0; i < maxvol; i++) {
509                 if (srv[i].pid == pid) {
510                     srv[i].pid = 0;
511                     close(srv[i].control_fd);
512                     break;
513                 }
514             }
515             if (WIFEXITED(status)) {
516                 LOG(log_info, logtype_cnid, "cnid_dbd[%i] exited with exit code %i",
517                     pid, WEXITSTATUS(status));
518             } else {
519                 /* cnid_dbd did a clean exit probably on idle timeout, reset bookkeeping */
520                 srv[i].tm = 0;
521                 srv[i].count = 0;
522             }
523             if (WIFSIGNALED(status)) {
524                 LOG(log_info, logtype_cnid, "cnid_dbd[%i] got signal %i",
525                     pid, WTERMSIG(status));
526             }
527             sigchild = 0;
528         }
529         if (rqstfd <= 0)
530             continue;
531
532         ret = readt(rqstfd, &len, sizeof(int), 1, 4);
533
534         if (!ret) {
535             /* already close */
536             goto loop_end;
537         }
538         else if (ret < 0) {
539             LOG(log_severe, logtype_cnid, "error read: %s", strerror(errno));
540             goto loop_end;
541         }
542         else if (ret != sizeof(int)) {
543             LOG(log_error, logtype_cnid, "short read: got %d", ret);
544             goto loop_end;
545         }
546         /*
547          *  checks for buffer overruns. The client libatalk side does it too
548          *  before handing the dir path over but who trusts clients?
549          */
550         if (!len || len +DBHOMELEN +2 > MAXPATHLEN) {
551             LOG(log_error, logtype_cnid, "wrong len parameter: %d", len);
552             goto loop_end;
553         }
554
555         actual_len = readt(rqstfd, volpath, len, 1, 5);
556         if (actual_len < 0) {
557             LOG(log_severe, logtype_cnid, "Read(2) error : %s", strerror(errno));
558             goto loop_end;
559         }
560         if (actual_len != len) {
561             LOG(log_error, logtype_cnid, "error/short read (dir): %s", strerror(errno));
562             goto loop_end;
563         }
564         volpath[len] = '\0';
565
566         LOG(log_debug, logtype_cnid, "main: request for volume: %s", volpath);
567
568         if (load_volumes(&obj) != 0) {
569             LOG(log_severe, logtype_cnid, "main: error reloading config");
570             goto loop_end;
571         }
572
573         if ((vol = getvolbypath(&obj, volpath)) == NULL) {
574             LOG(log_severe, logtype_cnid, "main: no volume for path \"%s\"", volpath);
575             goto loop_end;
576         }
577
578         LOG(log_maxdebug, logtype_cnid, "main: dbpath: %s", vol->v_dbpath);
579
580         if (set_dbdir(vol->v_dbpath, volpath) < 0) {
581             goto loop_end;
582         }
583
584         maybe_start_dbd(&obj, dbdpn, vol->v_path);
585     loop_end:
586         close(rqstfd);
587     }
588 }