]> arthur.barton.de Git - netatalk.git/blob - etc/cnid_dbd/main.c
use min(512, number of FD in a select set) for per/db default number of connections
[netatalk.git] / etc / cnid_dbd / main.c
1 /*
2  * $Id: main.c,v 1.12 2009-10-18 17:50:13 didg Exp $
3  *
4  * Copyright (C) Joerg Lenneis 2003
5  * Copyright (c) Frank Lahm 2009
6  * All Rights Reserved.  See COPYING.
7  */
8
9 #ifdef HAVE_CONFIG_H
10 #include "config.h"
11 #endif /* HAVE_CONFIG_H */
12
13 #ifdef HAVE_UNISTD_H
14 #include <unistd.h>
15 #endif /* HAVE_UNISTD_H */
16 #ifdef HAVE_FCNTL_H
17 #include <fcntl.h>
18 #endif /* HAVE_FCNTL_H */
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <errno.h>
22 #include <signal.h>
23 #include <string.h>
24 #ifdef HAVE_SYS_TYPES_H
25 #include <sys/types.h>
26 #endif /* HAVE_SYS_TYPES_H */
27 #include <sys/param.h>
28 #ifdef HAVE_SYS_STAT_H
29 #include <sys/stat.h>
30 #endif /* HAVE_SYS_STAT_H */
31 #include <time.h>
32 #include <sys/file.h>
33
34 #include <netatalk/endian.h>
35 #include <atalk/cnid_dbd_private.h>
36 #include <atalk/logger.h>
37
38 #include "db_param.h"
39 #include "dbif.h"
40 #include "dbd.h"
41 #include "comm.h"
42
43 #define LOCKFILENAME  "lock"
44
45 /* 
46    Note: DB_INIT_LOCK is here so we can run the db_* utilities while netatalk is running.
47    It's a likey performance hit, but it might we worth it.
48  */
49 #define DBOPTIONS (DB_CREATE | DB_INIT_LOG | DB_INIT_MPOOL | DB_INIT_LOCK | DB_INIT_TXN | DB_RECOVER)
50
51 static DBD *dbd;
52
53 static int exit_sig = 0;
54
55 static void sig_exit(int signo)
56 {
57     exit_sig = signo;
58     return;
59 }
60
61 static void block_sigs_onoff(int block)
62 {
63     sigset_t set;
64
65     sigemptyset(&set);
66     sigaddset(&set, SIGINT);
67     sigaddset(&set, SIGTERM);
68     if (block)
69         sigprocmask(SIG_BLOCK, &set, NULL);
70     else
71         sigprocmask(SIG_UNBLOCK, &set, NULL);
72     return;
73 }
74
75 /*
76   The dbd_XXX and comm_XXX functions all obey the same protocol for return values:
77
78   1: Success, if transactions are used commit.
79   0: Failure, but we continue to serve requests. If transactions are used abort/rollback.
80   -1: Fatal error, either from the database or from the socket. Abort the transaction if applicable
81   (which might fail as well) and then exit.
82
83   We always try to notify the client process about the outcome, the result field
84   of the cnid_dbd_rply structure contains further details.
85
86 */
87 #ifndef min
88 #define min(a,b)        ((a)<(b)?(a):(b))
89 #endif
90
91 static int loop(struct db_param *dbp)
92 {
93     struct cnid_dbd_rqst rqst;
94     struct cnid_dbd_rply rply;
95     time_t timeout;
96     int ret, cret;
97     int count;
98     time_t now, time_next_flush, time_last_rqst;
99     char timebuf[64];
100     static char namebuf[MAXPATHLEN + 1];
101     sigset_t set;
102
103     sigemptyset(&set);
104     sigprocmask(SIG_SETMASK, NULL, &set);
105     sigdelset(&set, SIGINT);
106     sigdelset(&set, SIGTERM);
107
108     count = 0;
109     now = time(NULL);
110     time_next_flush = now + dbp->flush_interval;
111     time_last_rqst = now;
112
113     rqst.name = namebuf;
114
115     strftime(timebuf, 63, "%b %d %H:%M:%S.",localtime(&time_next_flush));
116     LOG(log_debug, logtype_cnid, "Checkpoint interval: %d seconds. Next checkpoint: %s",
117         dbp->flush_interval, timebuf);
118
119     while (1) {
120         timeout = min(time_next_flush, time_last_rqst +dbp->idle_timeout);
121         if (timeout > now)
122             timeout -= now;
123         else
124             timeout = 1;
125
126         if ((cret = comm_rcv(&rqst, timeout, &set)) < 0)
127             return -1;
128
129         now = time(NULL);
130
131         if (cret == 0) {
132             /* comm_rcv returned from select without receiving anything. */
133             if (exit_sig)
134                 /* Received signal (TERM|INT) */
135                 return 0;
136             if (dbp->idle_timeout && comm_nbe() <= 0 && (now - time_last_rqst) > dbp->idle_timeout)
137                 /* Idle timeout */
138                 return 0;
139         } else {
140             /* We got a request */
141             time_last_rqst = now;
142
143             memset(&rply, 0, sizeof(rply));
144             switch(rqst.op) {
145                 /* ret gets set here */
146             case CNID_DBD_OP_OPEN:
147             case CNID_DBD_OP_CLOSE:
148                 /* open/close are noops for now. */
149                 rply.namelen = 0;
150                 ret = 1;
151                 break;
152             case CNID_DBD_OP_ADD:
153                 ret = dbd_add(dbd, &rqst, &rply);
154                 break;
155             case CNID_DBD_OP_GET:
156                 ret = dbd_get(dbd, &rqst, &rply);
157                 break;
158             case CNID_DBD_OP_RESOLVE:
159                 ret = dbd_resolve(dbd, &rqst, &rply);
160                 break;
161             case CNID_DBD_OP_LOOKUP:
162                 ret = dbd_lookup(dbd, &rqst, &rply);
163                 break;
164             case CNID_DBD_OP_UPDATE:
165                 ret = dbd_update(dbd, &rqst, &rply);
166                 break;
167             case CNID_DBD_OP_DELETE:
168                 ret = dbd_delete(dbd, &rqst, &rply, DBIF_CNID);
169                 break;
170             case CNID_DBD_OP_GETSTAMP:
171                 ret = dbd_getstamp(dbd, &rqst, &rply);
172                 break;
173             case CNID_DBD_OP_REBUILD_ADD:
174                 ret = dbd_rebuild_add(dbd, &rqst, &rply);
175                 break;
176             default:
177                 LOG(log_error, logtype_cnid, "loop: unknown op %d", rqst.op);
178                 ret = -1;
179                 break;
180             }
181             
182             if ((cret = comm_snd(&rply)) < 0 || ret < 0) {
183                 dbif_txn_abort(dbd);
184                 return -1;
185             }
186             
187             if (ret == 0 || cret == 0) {
188                 if (dbif_txn_abort(dbd) < 0)
189                     return -1;
190             } else {
191                 ret = dbif_txn_commit(dbd);
192                 if (  ret < 0)
193                     return -1;
194                 else if ( ret > 0 )
195                     /* We had a designated txn because we wrote to the db */
196                     count++;
197             }
198         } /* got a request */
199
200         /*
201           Shall we checkpoint bdb ?
202           "flush_interval" seconds passed ?
203         */
204         if (now > time_next_flush) {
205             LOG(log_info, logtype_cnid, "Checkpointing BerkeleyDB for volume '%s'", dbp->dir);
206             if (dbif_txn_checkpoint(dbd, 0, 0, 0) < 0)
207                 return -1;
208             count = 0;
209             time_next_flush = now + dbp->flush_interval;
210
211             strftime(timebuf, 63, "%b %d %H:%M:%S.",localtime(&time_next_flush));
212             LOG(log_debug, logtype_cnid, "Checkpoint interval: %d seconds. Next checkpoint: %s",
213                 dbp->flush_interval, timebuf);
214         }
215
216         /* 
217            Shall we checkpoint bdb ?
218            Have we commited "count" more changes than "flush_frequency" ?
219         */
220         if (count > dbp->flush_frequency) {
221             LOG(log_info, logtype_cnid, "Checkpointing BerkeleyDB after %d writes for volume '%s'", count, dbp->dir);
222             if (dbif_txn_checkpoint(dbd, 0, 0, 0) < 0)
223                 return -1;
224             count = 0;
225         }
226     } /* while(1) */
227 }
228
229 /* ------------------------ */
230 static void switch_to_user(char *dir)
231 {
232     struct stat st;
233
234     if (chdir(dir) < 0) {
235         LOG(log_error, logtype_cnid, "chdir to %s failed: %s", dir, strerror(errno));
236         exit(1);
237     }
238
239     if (stat(".", &st) < 0) {
240         LOG(log_error, logtype_cnid, "error in stat for %s: %s", dir, strerror(errno));
241         exit(1);
242     }
243     if (!getuid()) {
244         LOG(log_info, logtype_cnid, "Setting uid/gid to %i/%i", st.st_uid, st.st_gid);
245         if (setgid(st.st_gid) < 0 || setuid(st.st_uid) < 0) {
246             LOG(log_error, logtype_cnid, "uid/gid: %s", strerror(errno));
247             exit(1);
248         }
249     }
250 }
251
252 /* ------------------------ */
253 static int get_lock(void)
254 {
255     int lockfd;
256     struct flock lock;
257
258     if ((lockfd = open(LOCKFILENAME, O_RDWR | O_CREAT, 0644)) < 0) {
259         LOG(log_error, logtype_cnid, "main: error opening lockfile: %s", strerror(errno));
260         exit(1);
261     }
262
263     lock.l_start  = 0;
264     lock.l_whence = SEEK_SET;
265     lock.l_len    = 0;
266     lock.l_type   = F_WRLCK;
267
268     if (fcntl(lockfd, F_SETLK, &lock) < 0) {
269         if (errno == EACCES || errno == EAGAIN) {
270             exit(0);
271         } else {
272             LOG(log_error, logtype_cnid, "main: fcntl F_WRLCK lockfile: %s", strerror(errno));
273             exit(1);
274         }
275     }
276
277     return lockfd;
278 }
279
280 /* ----------------------- */
281 static void set_signal(void)
282 {
283     struct sigaction sv;
284
285     sv.sa_handler = sig_exit;
286     sv.sa_flags = 0;
287     sigemptyset(&sv.sa_mask);
288     sigaddset(&sv.sa_mask, SIGINT);
289     sigaddset(&sv.sa_mask, SIGTERM);
290     if (sigaction(SIGINT, &sv, NULL) < 0 || sigaction(SIGTERM, &sv, NULL) < 0) {
291         LOG(log_error, logtype_cnid, "main: sigaction: %s", strerror(errno));
292         exit(1);
293     }
294     sv.sa_handler = SIG_IGN;
295     sigemptyset(&sv.sa_mask);
296     if (sigaction(SIGPIPE, &sv, NULL) < 0) {
297         LOG(log_error, logtype_cnid, "main: sigaction: %s", strerror(errno));
298         exit(1);
299     }
300 }
301
302 /* ----------------------- */
303 static void free_lock(int lockfd)
304 {
305     struct flock lock;
306
307     lock.l_start  = 0;
308     lock.l_whence = SEEK_SET;
309     lock.l_len    = 0;
310     lock.l_type = F_UNLCK;
311     fcntl(lockfd, F_SETLK, &lock);
312     close(lockfd);
313 }
314
315 /* ------------------------ */
316 int main(int argc, char *argv[])
317 {
318     struct db_param *dbp;
319     int err = 0;
320     int lockfd, ctrlfd, clntfd;
321     char *dir, *logconfig;
322
323     set_processname("cnid_dbd");
324
325     /* FIXME: implement -d from cnid_metad */
326     if (argc  != 5) {
327         LOG(log_error, logtype_cnid, "main: not enough arguments");
328         exit(1);
329     }
330
331     dir = argv[1];
332     ctrlfd = atoi(argv[2]);
333     clntfd = atoi(argv[3]);
334     logconfig = strdup(argv[4]);
335     setuplog(logconfig);
336
337     switch_to_user(dir);
338
339     /* Before we do anything else, check if there is an instance of cnid_dbd
340        running already and silently exit if yes. */
341     lockfd = get_lock();
342
343     LOG(log_info, logtype_cnid, "Startup, DB dir %s", dir);
344
345     set_signal();
346
347     /* SIGINT and SIGTERM are always off, unless we are in pselect */
348     block_sigs_onoff(1);
349
350     if ((dbp = db_param_read(dir, CNID_DBD)) == NULL)
351         exit(1);
352     LOG(log_maxdebug, logtype_cnid, "Finished parsing db_param config file");
353
354     if (NULL == (dbd = dbif_init(".", "cnid2.db")))
355         exit(2);
356
357     if (dbif_env_open(dbd, dbp, DBOPTIONS) < 0)
358         exit(2); /* FIXME: same exit code as failure for dbif_open() */
359     LOG(log_debug, logtype_cnid, "Finished initializing BerkeleyDB environment");
360
361     if (dbif_open(dbd, dbp, 0) < 0) {
362         dbif_close(dbd);
363         exit(2);
364     }
365     LOG(log_debug, logtype_cnid, "Finished opening BerkeleyDB databases");
366
367     if (dbd_stamp(dbd) < 0) {
368         dbif_close(dbd);
369         exit(5);
370     }
371     LOG(log_maxdebug, logtype_cnid, "Finished checking database stamp");
372
373     if (comm_init(dbp, ctrlfd, clntfd) < 0) {
374         dbif_close(dbd);
375         exit(3);
376     }
377
378     if (loop(dbp) < 0)
379         err++;
380
381     if (dbif_close(dbd) < 0)
382         err++;
383
384     if (dbif_prep_upgrade(dir) < 0)
385         err++;
386
387     free_lock(lockfd);
388
389     if (err)
390         exit(4);
391     else if (exit_sig)
392         LOG(log_info, logtype_cnid, "main: Exiting on signal %i", exit_sig);
393     else
394         LOG(log_info, logtype_cnid, "main: Idle timeout, exiting");
395
396     return 0;
397 }