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