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