]> arthur.barton.de Git - netatalk.git/blob - etc/cnid_dbd/main.c
cnid dbd don't fall back to 1 sec timeout after an idle timer if active connections
[netatalk.git] / etc / cnid_dbd / main.c
1 /*
2  * $Id: main.c,v 1.14 2009-10-19 07:46:35 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 t
81   he database or from the socket. Abort the transaction if applicable
82   (which might fail as well) and then exit.
83
84   We always try to notify the client process about the outcome, the result field
85   of the cnid_dbd_rply structure contains further details.
86
87 */
88 #ifndef min
89 #define min(a,b)        ((a)<(b)?(a):(b))
90 #endif
91
92 static int loop(struct db_param *dbp)
93 {
94     struct cnid_dbd_rqst rqst;
95     struct cnid_dbd_rply rply;
96     time_t timeout;
97     int ret, cret;
98     int count;
99     time_t now, time_next_flush, time_last_rqst;
100     char timebuf[64];
101     static char namebuf[MAXPATHLEN + 1];
102     sigset_t set;
103
104     sigemptyset(&set);
105     sigprocmask(SIG_SETMASK, NULL, &set);
106     sigdelset(&set, SIGINT);
107     sigdelset(&set, SIGTERM);
108
109     count = 0;
110     now = time(NULL);
111     time_next_flush = now + dbp->flush_interval;
112     time_last_rqst = now;
113
114     rqst.name = namebuf;
115
116     strftime(timebuf, 63, "%b %d %H:%M:%S.",localtime(&time_next_flush));
117     LOG(log_debug, logtype_cnid, "Checkpoint interval: %d seconds. Next checkpoint: %s",
118         dbp->flush_interval, timebuf);
119
120     while (1) {
121         timeout = min(time_next_flush, time_last_rqst +dbp->idle_timeout);
122         if (timeout > now)
123             timeout -= now;
124         else
125             timeout = 1;
126
127         if ((cret = comm_rcv(&rqst, timeout, &set)) < 0)
128             return -1;
129
130         now = time(NULL);
131
132         if (cret == 0) {
133             /* comm_rcv returned from select without receiving anything. */
134             if (exit_sig) {
135                 /* Received signal (TERM|INT) */
136                 return 0;
137             }
138             if (now - time_last_rqst >= dbp->idle_timeout && comm_nbe() <= 0) {
139                 /* Idle timeout */
140                 return 0;
141             }
142             /* still active connections, reset time_last_rqst */
143             time_last_rqst = now;
144         } else {
145             /* We got a request */
146             time_last_rqst = now;
147
148             memset(&rply, 0, sizeof(rply));
149             switch(rqst.op) {
150                 /* ret gets set here */
151             case CNID_DBD_OP_OPEN:
152             case CNID_DBD_OP_CLOSE:
153                 /* open/close are noops for now. */
154                 rply.namelen = 0;
155                 ret = 1;
156                 break;
157             case CNID_DBD_OP_ADD:
158                 ret = dbd_add(dbd, &rqst, &rply);
159                 break;
160             case CNID_DBD_OP_GET:
161                 ret = dbd_get(dbd, &rqst, &rply);
162                 break;
163             case CNID_DBD_OP_RESOLVE:
164                 ret = dbd_resolve(dbd, &rqst, &rply);
165                 break;
166             case CNID_DBD_OP_LOOKUP:
167                 ret = dbd_lookup(dbd, &rqst, &rply);
168                 break;
169             case CNID_DBD_OP_UPDATE:
170                 ret = dbd_update(dbd, &rqst, &rply);
171                 break;
172             case CNID_DBD_OP_DELETE:
173                 ret = dbd_delete(dbd, &rqst, &rply, DBIF_CNID);
174                 break;
175             case CNID_DBD_OP_GETSTAMP:
176                 ret = dbd_getstamp(dbd, &rqst, &rply);
177                 break;
178             case CNID_DBD_OP_REBUILD_ADD:
179                 ret = dbd_rebuild_add(dbd, &rqst, &rply);
180                 break;
181             default:
182                 LOG(log_error, logtype_cnid, "loop: unknown op %d", rqst.op);
183                 ret = -1;
184                 break;
185             }
186             
187             if ((cret = comm_snd(&rply)) < 0 || ret < 0) {
188                 dbif_txn_abort(dbd);
189                 return -1;
190             }
191             
192             if (ret == 0 || cret == 0) {
193                 if (dbif_txn_abort(dbd) < 0)
194                     return -1;
195             } else {
196                 ret = dbif_txn_commit(dbd);
197                 if (  ret < 0)
198                     return -1;
199                 else if ( ret > 0 )
200                     /* We had a designated txn because we wrote to the db */
201                     count++;
202             }
203         } /* got a request */
204
205         /*
206           Shall we checkpoint bdb ?
207           "flush_interval" seconds passed ?
208         */
209         if (now >= time_next_flush) {
210             LOG(log_info, logtype_cnid, "Checkpointing BerkeleyDB for volume '%s'", dbp->dir);
211             if (dbif_txn_checkpoint(dbd, 0, 0, 0) < 0)
212                 return -1;
213             count = 0;
214             time_next_flush = now + dbp->flush_interval;
215
216             strftime(timebuf, 63, "%b %d %H:%M:%S.",localtime(&time_next_flush));
217             LOG(log_debug, logtype_cnid, "Checkpoint interval: %d seconds. Next checkpoint: %s",
218                 dbp->flush_interval, timebuf);
219         }
220
221         /* 
222            Shall we checkpoint bdb ?
223            Have we commited "count" more changes than "flush_frequency" ?
224         */
225         if (count > dbp->flush_frequency) {
226             LOG(log_info, logtype_cnid, "Checkpointing BerkeleyDB after %d writes for volume '%s'", count, dbp->dir);
227             if (dbif_txn_checkpoint(dbd, 0, 0, 0) < 0)
228                 return -1;
229             count = 0;
230         }
231     } /* while(1) */
232 }
233
234 /* ------------------------ */
235 static void switch_to_user(char *dir)
236 {
237     struct stat st;
238
239     if (chdir(dir) < 0) {
240         LOG(log_error, logtype_cnid, "chdir to %s failed: %s", dir, strerror(errno));
241         exit(1);
242     }
243
244     if (stat(".", &st) < 0) {
245         LOG(log_error, logtype_cnid, "error in stat for %s: %s", dir, strerror(errno));
246         exit(1);
247     }
248     if (!getuid()) {
249         LOG(log_info, logtype_cnid, "Setting uid/gid to %i/%i", st.st_uid, st.st_gid);
250         if (setgid(st.st_gid) < 0 || setuid(st.st_uid) < 0) {
251             LOG(log_error, logtype_cnid, "uid/gid: %s", strerror(errno));
252             exit(1);
253         }
254     }
255 }
256
257 /* ------------------------ */
258 static int get_lock(void)
259 {
260     int lockfd;
261     struct flock lock;
262
263     if ((lockfd = open(LOCKFILENAME, O_RDWR | O_CREAT, 0644)) < 0) {
264         LOG(log_error, logtype_cnid, "main: error opening lockfile: %s", strerror(errno));
265         exit(1);
266     }
267
268     lock.l_start  = 0;
269     lock.l_whence = SEEK_SET;
270     lock.l_len    = 0;
271     lock.l_type   = F_WRLCK;
272
273     if (fcntl(lockfd, F_SETLK, &lock) < 0) {
274         if (errno == EACCES || errno == EAGAIN) {
275             exit(0);
276         } else {
277             LOG(log_error, logtype_cnid, "main: fcntl F_WRLCK lockfile: %s", strerror(errno));
278             exit(1);
279         }
280     }
281
282     return lockfd;
283 }
284
285 /* ----------------------- */
286 static void set_signal(void)
287 {
288     struct sigaction sv;
289
290     sv.sa_handler = sig_exit;
291     sv.sa_flags = 0;
292     sigemptyset(&sv.sa_mask);
293     sigaddset(&sv.sa_mask, SIGINT);
294     sigaddset(&sv.sa_mask, SIGTERM);
295     if (sigaction(SIGINT, &sv, NULL) < 0 || sigaction(SIGTERM, &sv, NULL) < 0) {
296         LOG(log_error, logtype_cnid, "main: sigaction: %s", strerror(errno));
297         exit(1);
298     }
299     sv.sa_handler = SIG_IGN;
300     sigemptyset(&sv.sa_mask);
301     if (sigaction(SIGPIPE, &sv, NULL) < 0) {
302         LOG(log_error, logtype_cnid, "main: sigaction: %s", strerror(errno));
303         exit(1);
304     }
305 }
306
307 /* ----------------------- */
308 static void free_lock(int lockfd)
309 {
310     struct flock lock;
311
312     lock.l_start  = 0;
313     lock.l_whence = SEEK_SET;
314     lock.l_len    = 0;
315     lock.l_type = F_UNLCK;
316     fcntl(lockfd, F_SETLK, &lock);
317     close(lockfd);
318 }
319
320 /* ------------------------ */
321 int main(int argc, char *argv[])
322 {
323     struct db_param *dbp;
324     int err = 0;
325     int lockfd, ctrlfd, clntfd;
326     char *dir, *logconfig;
327
328     set_processname("cnid_dbd");
329
330     /* FIXME: implement -d from cnid_metad */
331     if (argc  != 5) {
332         LOG(log_error, logtype_cnid, "main: not enough arguments");
333         exit(1);
334     }
335
336     dir = argv[1];
337     ctrlfd = atoi(argv[2]);
338     clntfd = atoi(argv[3]);
339     logconfig = strdup(argv[4]);
340     setuplog(logconfig);
341
342     switch_to_user(dir);
343
344     /* Before we do anything else, check if there is an instance of cnid_dbd
345        running already and silently exit if yes. */
346     lockfd = get_lock();
347
348     LOG(log_info, logtype_cnid, "Startup, DB dir %s", dir);
349
350     set_signal();
351
352     /* SIGINT and SIGTERM are always off, unless we are in pselect */
353     block_sigs_onoff(1);
354
355     if ((dbp = db_param_read(dir, CNID_DBD)) == NULL)
356         exit(1);
357     LOG(log_maxdebug, logtype_cnid, "Finished parsing db_param config file");
358
359     if (NULL == (dbd = dbif_init(".", "cnid2.db")))
360         exit(2);
361
362     if (dbif_env_open(dbd, dbp, DBOPTIONS) < 0)
363         exit(2); /* FIXME: same exit code as failure for dbif_open() */
364     LOG(log_debug, logtype_cnid, "Finished initializing BerkeleyDB environment");
365
366     if (dbif_open(dbd, dbp, 0) < 0) {
367         dbif_close(dbd);
368         exit(2);
369     }
370     LOG(log_debug, logtype_cnid, "Finished opening BerkeleyDB databases");
371
372     if (dbd_stamp(dbd) < 0) {
373         dbif_close(dbd);
374         exit(5);
375     }
376     LOG(log_maxdebug, logtype_cnid, "Finished checking database stamp");
377
378     if (comm_init(dbp, ctrlfd, clntfd) < 0) {
379         dbif_close(dbd);
380         exit(3);
381     }
382
383     if (loop(dbp) < 0)
384         err++;
385
386     if (dbif_close(dbd) < 0)
387         err++;
388
389     if (dbif_prep_upgrade(dir) < 0)
390         err++;
391
392     free_lock(lockfd);
393
394     if (err)
395         exit(4);
396     else if (exit_sig)
397         LOG(log_info, logtype_cnid, "main: Exiting on signal %i", exit_sig);
398     else
399         LOG(log_info, logtype_cnid, "main: Idle timeout, exiting");
400
401     return 0;
402 }