]> arthur.barton.de Git - netatalk.git/blob - etc/cnid_dbd/cnid_metad.c
cnid_metad cnid_dbd option -V
[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   42                  /* 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     struct vol *vol;
117     pid_t pid;
118     time_t tm;                    /* When respawned last */
119     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     server_unlock(_PATH_CNID_METAD_LOCK);
128     exit(i);
129 }
130
131 /* ------------------ */
132 static void sigterm_handler(int sig)
133 {
134     switch( sig ) {
135     case SIGTERM :
136         LOG(log_info, logtype_afpd, "shutting down on signal %d", sig );
137         break;
138     default :
139         LOG(log_error, logtype_afpd, "unexpected signal: %d", sig);
140     }
141     daemon_exit(0);
142 }
143
144 static struct server *test_usockfn(const struct vol *vol)
145 {
146     int i;
147
148     if (!(vol->v_flags & AFPVOL_OPEN))
149         return NULL;
150
151     for (i = 0; i < maxvol; i++) {
152         if (vol->v_vid == srv[i].vol->v_vid)
153             return &srv[i];
154     }
155
156     return NULL;
157 }
158
159 /* -------------------- */
160 static int maybe_start_dbd(const AFPObj *obj, char *dbdpn, struct vol *vol)
161 {
162     pid_t pid;
163     struct server *up;
164     int sv[2];
165     int i;
166     time_t t;
167     char buf1[8];
168     char buf2[8];
169     char *volpath = vol->v_path;
170
171     LOG(log_debug, logtype_cnid, "maybe_start_dbd: Volume: \"%s\"", volpath);
172
173     up = test_usockfn(vol);
174     if (up && up->pid) {
175         /* we already have a process, send our fd */
176         if (send_fd(up->control_fd, rqstfd) < 0) {
177             /* FIXME */
178             return -1;
179         }
180         return 0;
181     }
182
183     LOG(log_maxdebug, logtype_cnid, "maybe_start_dbd: no cnid_dbd for that volume yet");
184
185     time(&t);
186     if (!up) {
187         /* find an empty slot (i < maxvol) or the first free slot (i == maxvol)*/
188         for (i = 0; i <= maxvol; i++) {
189             if (srv[i].vol == NULL && i < MAXVOLS) {
190                 up = &srv[i];
191                 up->vol = vol;
192                 vol->v_flags |= AFPVOL_OPEN;
193                 up->tm = t;
194                 up->count = 0;
195                 if (i == maxvol)
196                     maxvol++;
197                 break;
198             }
199         }
200         if (!up) {
201             LOG(log_error, logtype_cnid, "no free slot for cnid_dbd child. Configured maximum: %d. Do you have so many volumes?", MAXVOLS);
202             return -1;
203         }
204     } else {
205         /* we have a slot but no process, check for respawn too fast */
206         if ( (t < (up->tm + TESTTIME)) /* We're in the respawn time window */
207              &&
208              (up->count > MAXSPAWN) ) { /* ...and already tried to fork too often */
209             LOG(log_maxdebug, logtype_cnid, "maybe_start_dbd: respawn too fast just exiting");
210             return -1; /* just exit, dont sleep, because we might have work to do for another client  */
211         }
212         if ( t >= (up->tm + TESTTIME) ) { /* out of respawn too fast windows reset the count */
213             LOG(log_maxdebug, logtype_cnid, "maybe_start_dbd: respawn window ended");
214             up->tm = t;
215             up->count = 0;
216         }
217         up->count++;
218         LOG(log_maxdebug, logtype_cnid, "maybe_start_dbd: respawn count now is: %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_maxdebug, 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->vol->v_path);
265             ret = execlp(dbdpn, dbdpn, "-F", obj->options.configfile, "-p", volpath, "-t", buf1, "-l", buf2, "-d", NULL);
266         } else {
267             ret = execlp(dbdpn, dbdpn, "-F", obj->options.configfile, "-p", volpath, "-t", buf1, "-l", buf2, 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 /* ------------------ */
283 static 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 /* ------------------ */
308 static 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 /* ------------------ */
327 static 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 /* ------------------ */
346 static void catch_child(int sig _U_) 
347 {
348     sigchild = 1;
349 }
350
351 /* ----------------------- */
352 static 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
414 static 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 /* ------------------ */
435 int 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     sigset_t set;
452     AFPObj obj = { 0 };
453     struct vol *vol;
454
455     while (( cc = getopt( argc, argv, "dF:vV")) != -1 ) {
456         switch (cc) {
457         case 'd':
458             debug = 1;
459             break;
460         case 'F':
461             obj.cmdlineconfigfile = strdup(optarg);
462             break;
463         case 'v':
464         case 'V':
465             printf("cnid_metad (Netatalk %s)\n", VERSION);
466             return -1;
467         default:
468             printf("cnid_metad [-dvV] [-F alternate configfile ]\n");
469             return -1;
470         }
471     }
472
473     /* Check for PID lockfile */
474     if (check_lockfile("cnid_metad", _PATH_CNID_METAD_LOCK))
475         return -1;
476
477     if (!debug && daemonize(0, 0) != 0)
478         exit(EXITERR_SYS);
479
480     /* Create PID lockfile */
481     if (create_lockfile("cnid_metad", _PATH_CNID_METAD_LOCK))
482         return -1;
483
484     if (afp_config_parse(&obj) != 0)
485         daemon_exit(1);
486
487     set_processname("cnid_metad");
488     setuplog(obj.options.logconfig, obj.options.logfile);
489
490     if (load_volumes(&obj, NULL) != 0)
491         daemon_exit(1);
492
493     (void)setlimits();
494
495     if ((srvfd = tsockfd_create(host, port, 10)) < 0)
496         daemon_exit(1);
497
498     /* switch uid/gid */
499     if (uid || gid) {
500         LOG(log_debug, logtype_cnid, "Setting uid/gid to %i/%i", uid, gid);
501         if (gid) {
502             if (SWITCH_TO_GID(gid) < 0) {
503                 LOG(log_info, logtype_cnid, "unable to switch to group %d", gid);
504                 daemon_exit(1);
505             }
506         }
507         if (uid) {
508             if (SWITCH_TO_UID(uid) < 0) {
509                 LOG(log_info, logtype_cnid, "unable to switch to user %d", uid);
510                 daemon_exit(1);
511             }
512         }
513     }
514
515     set_signal();
516
517     sigemptyset(&set);
518     sigprocmask(SIG_SETMASK, NULL, &set);
519     sigdelset(&set, SIGCHLD);
520
521     while (1) {
522         rqstfd = usockfd_check(srvfd, &set);
523         /* Collect zombie processes and log what happened to them */
524         if (sigchild) while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
525             for (i = 0; i < maxvol; i++) {
526                 if (srv[i].pid == pid) {
527                     srv[i].pid = 0;
528                     close(srv[i].control_fd);
529                     srv[i].vol->v_flags &= ~AFPVOL_OPEN;
530                     break;
531                 }
532             }
533             if (WIFEXITED(status)) {
534                 LOG(log_info, logtype_cnid, "cnid_dbd[%i] exited with exit code %i",
535                     pid, WEXITSTATUS(status));
536             }
537             else if (WIFSIGNALED(status)) {
538                 LOG(log_info, logtype_cnid, "cnid_dbd[%i] got signal %i",
539                     pid, WTERMSIG(status));
540             }
541             sigchild = 0;
542         }
543         if (rqstfd <= 0)
544             continue;
545
546         ret = readt(rqstfd, &len, sizeof(int), 1, 4);
547
548         if (!ret) {
549             /* already close */
550             goto loop_end;
551         }
552         else if (ret < 0) {
553             LOG(log_severe, logtype_cnid, "error read: %s", strerror(errno));
554             goto loop_end;
555         }
556         else if (ret != sizeof(int)) {
557             LOG(log_error, logtype_cnid, "short read: got %d", ret);
558             goto loop_end;
559         }
560         /*
561          *  checks for buffer overruns. The client libatalk side does it too
562          *  before handing the dir path over but who trusts clients?
563          */
564         if (!len || len +DBHOMELEN +2 > MAXPATHLEN) {
565             LOG(log_error, logtype_cnid, "wrong len parameter: %d", len);
566             goto loop_end;
567         }
568
569         actual_len = readt(rqstfd, volpath, len, 1, 5);
570         if (actual_len < 0) {
571             LOG(log_severe, logtype_cnid, "Read(2) error : %s", strerror(errno));
572             goto loop_end;
573         }
574         if (actual_len != len) {
575             LOG(log_error, logtype_cnid, "error/short read (dir): %s", strerror(errno));
576             goto loop_end;
577         }
578         volpath[len] = '\0';
579
580
581         if ((vol = getvolbypath(volpath)) == NULL) {
582             LOG(log_severe, logtype_cnid, "getvolbypath(\"%s\"): %s", volpath, strerror(errno));
583             goto loop_end;
584         }
585
586         if (set_dbdir(vol->v_dbpath) < 0) {
587             goto loop_end;
588         }
589
590         maybe_start_dbd(&obj, dbdpn, vol);
591     loop_end:
592         close(rqstfd);
593     }
594 }