]> arthur.barton.de Git - netatalk.git/blob - etc/cnid_dbd/main.c
Reliable db locking
[netatalk.git] / etc / cnid_dbd / main.c
1 /*
2  * $Id: main.c,v 1.16 2009-11-25 14:59:15 franklahm 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 #include <atalk/util.h>
38
39 #include "db_param.h"
40 #include "dbif.h"
41 #include "dbd.h"
42 #include "comm.h"
43
44 /* 
45    Note: DB_INIT_LOCK is here so we can run the db_* utilities while netatalk is running.
46    It's a likey performance hit, but it might we worth it.
47  */
48 #define DBOPTIONS (DB_CREATE | DB_INIT_LOG | DB_INIT_MPOOL | DB_INIT_LOCK | DB_INIT_TXN)
49
50 static DBD *dbd;
51 static int exit_sig = 0;
52 static int db_locked;
53
54 static void sig_exit(int signo)
55 {
56     exit_sig = signo;
57     return;
58 }
59
60 static void block_sigs_onoff(int block)
61 {
62     sigset_t set;
63
64     sigemptyset(&set);
65     sigaddset(&set, SIGINT);
66     sigaddset(&set, SIGTERM);
67     if (block)
68         sigprocmask(SIG_BLOCK, &set, NULL);
69     else
70         sigprocmask(SIG_UNBLOCK, &set, NULL);
71     return;
72 }
73
74 /*
75   The dbd_XXX and comm_XXX functions all obey the same protocol for return values:
76
77   1: Success, if transactions are used commit.
78   0: Failure, but we continue to serve requests. If transactions are used abort/rollback.
79   -1: Fatal error, either from t
80   he 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, &now)) < 0)
127             return -1;
128
129         if (cret == 0) {
130             /* comm_rcv returned from select without receiving anything. */
131             if (exit_sig) {
132                 /* Received signal (TERM|INT) */
133                 return 0;
134             }
135             if (now - time_last_rqst >= dbp->idle_timeout && comm_nbe() <= 0) {
136                 /* Idle timeout */
137                 return 0;
138             }
139             /* still active connections, reset time_last_rqst */
140             time_last_rqst = now;
141         } else {
142             /* We got a request */
143             time_last_rqst = now;
144
145             memset(&rply, 0, sizeof(rply));
146             switch(rqst.op) {
147                 /* ret gets set here */
148             case CNID_DBD_OP_OPEN:
149             case CNID_DBD_OP_CLOSE:
150                 /* open/close are noops for now. */
151                 rply.namelen = 0;
152                 ret = 1;
153                 break;
154             case CNID_DBD_OP_ADD:
155                 ret = dbd_add(dbd, &rqst, &rply, 0);
156                 break;
157             case CNID_DBD_OP_GET:
158                 ret = dbd_get(dbd, &rqst, &rply);
159                 break;
160             case CNID_DBD_OP_RESOLVE:
161                 ret = dbd_resolve(dbd, &rqst, &rply);
162                 break;
163             case CNID_DBD_OP_LOOKUP:
164                 ret = dbd_lookup(dbd, &rqst, &rply, 0);
165                 break;
166             case CNID_DBD_OP_UPDATE:
167                 ret = dbd_update(dbd, &rqst, &rply);
168                 break;
169             case CNID_DBD_OP_DELETE:
170                 ret = dbd_delete(dbd, &rqst, &rply, DBIF_CNID);
171                 break;
172             case CNID_DBD_OP_GETSTAMP:
173                 ret = dbd_getstamp(dbd, &rqst, &rply);
174                 break;
175             case CNID_DBD_OP_REBUILD_ADD:
176                 ret = dbd_rebuild_add(dbd, &rqst, &rply);
177                 break;
178             default:
179                 LOG(log_error, logtype_cnid, "loop: unknown op %d", rqst.op);
180                 ret = -1;
181                 break;
182             }
183             
184             if ((cret = comm_snd(&rply)) < 0 || ret < 0) {
185                 dbif_txn_abort(dbd);
186                 return -1;
187             }
188             
189             if (ret == 0 || cret == 0) {
190                 if (dbif_txn_abort(dbd) < 0)
191                     return -1;
192             } else {
193                 ret = dbif_txn_commit(dbd);
194                 if (  ret < 0)
195                     return -1;
196                 else if ( ret > 0 )
197                     /* We had a designated txn because we wrote to the db */
198                     count++;
199             }
200         } /* got a request */
201
202         /*
203           Shall we checkpoint bdb ?
204           "flush_interval" seconds passed ?
205         */
206         if (now >= time_next_flush) {
207             LOG(log_info, logtype_cnid, "Checkpointing BerkeleyDB for volume '%s'", dbp->dir);
208             if (dbif_txn_checkpoint(dbd, 0, 0, 0) < 0)
209                 return -1;
210             count = 0;
211             time_next_flush = now + dbp->flush_interval;
212
213             strftime(timebuf, 63, "%b %d %H:%M:%S.",localtime(&time_next_flush));
214             LOG(log_debug, logtype_cnid, "Checkpoint interval: %d seconds. Next checkpoint: %s",
215                 dbp->flush_interval, timebuf);
216         }
217
218         /* 
219            Shall we checkpoint bdb ?
220            Have we commited "count" more changes than "flush_frequency" ?
221         */
222         if (count > dbp->flush_frequency) {
223             LOG(log_info, logtype_cnid, "Checkpointing BerkeleyDB after %d writes for volume '%s'", count, dbp->dir);
224             if (dbif_txn_checkpoint(dbd, 0, 0, 0) < 0)
225                 return -1;
226             count = 0;
227         }
228     } /* while(1) */
229 }
230
231 /* ------------------------ */
232 static void switch_to_user(char *dir)
233 {
234     struct stat st;
235
236     if (chdir(dir) < 0) {
237         LOG(log_error, logtype_cnid, "chdir to %s failed: %s", dir, strerror(errno));
238         exit(1);
239     }
240
241     if (stat(".", &st) < 0) {
242         LOG(log_error, logtype_cnid, "error in stat for %s: %s", dir, strerror(errno));
243         exit(1);
244     }
245     if (!getuid()) {
246         LOG(log_info, logtype_cnid, "Setting uid/gid to %i/%i", st.st_uid, st.st_gid);
247         if (setgid(st.st_gid) < 0 || setuid(st.st_uid) < 0) {
248             LOG(log_error, logtype_cnid, "uid/gid: %s", strerror(errno));
249             exit(1);
250         }
251     }
252 }
253
254
255 /* ----------------------- */
256 static void set_signal(void)
257 {
258     struct sigaction sv;
259
260     sv.sa_handler = sig_exit;
261     sv.sa_flags = 0;
262     sigemptyset(&sv.sa_mask);
263     sigaddset(&sv.sa_mask, SIGINT);
264     sigaddset(&sv.sa_mask, SIGTERM);
265     if (sigaction(SIGINT, &sv, NULL) < 0 || sigaction(SIGTERM, &sv, NULL) < 0) {
266         LOG(log_error, logtype_cnid, "main: sigaction: %s", strerror(errno));
267         exit(1);
268     }
269     sv.sa_handler = SIG_IGN;
270     sigemptyset(&sv.sa_mask);
271     if (sigaction(SIGPIPE, &sv, NULL) < 0) {
272         LOG(log_error, logtype_cnid, "main: sigaction: %s", strerror(errno));
273         exit(1);
274     }
275 }
276
277 /* ------------------------ */
278 int main(int argc, char *argv[])
279 {
280     struct db_param *dbp;
281     int err = 0;
282     int ctrlfd, clntfd;
283     char *dir, *logconfig;
284
285     set_processname("cnid_dbd");
286
287     /* FIXME: implement -d from cnid_metad */
288     if (argc  != 5) {
289         LOG(log_error, logtype_cnid, "main: not enough arguments");
290         exit(1);
291     }
292
293     dir = argv[1];
294     ctrlfd = atoi(argv[2]);
295     clntfd = atoi(argv[3]);
296     logconfig = strdup(argv[4]);
297     setuplog(logconfig);
298
299     switch_to_user(dir);
300
301     /* Get db lock */
302     if ((db_locked = get_lock(LOCK_EXCL, dir)) == -1) {
303         LOG(log_error, logtype_cnid, "main: fatal db lock error");
304         exit(1);
305     }
306     if (db_locked != LOCK_EXCL) {
307         /* Couldn't get exclusive lock, try shared lock  */
308         if ((db_locked = get_lock(LOCK_SHRD, NULL)) != LOCK_SHRD) {
309             LOG(log_error, logtype_cnid, "main: fatal db lock error");
310             exit(1);
311         }
312     }
313
314     LOG(log_info, logtype_cnid, "Startup, DB dir %s", dir);
315
316     set_signal();
317
318     /* SIGINT and SIGTERM are always off, unless we are in pselect */
319     block_sigs_onoff(1);
320
321     if ((dbp = db_param_read(dir, CNID_DBD)) == NULL)
322         exit(1);
323     LOG(log_maxdebug, logtype_cnid, "Finished parsing db_param config file");
324
325     if (NULL == (dbd = dbif_init(".", "cnid2.db")))
326         exit(2);
327
328     /* Only recover if we got the lock */
329     if (dbif_env_open(dbd,
330                       dbp,
331                       (db_locked == LOCK_EXCL) ? DBOPTIONS | DB_RECOVER : DBOPTIONS) < 0)
332         exit(2); /* FIXME: same exit code as failure for dbif_open() */
333     LOG(log_debug, logtype_cnid, "Finished initializing BerkeleyDB environment");
334
335     if (dbif_open(dbd, dbp, 0) < 0) {
336         dbif_close(dbd);
337         exit(2);
338     }
339     LOG(log_debug, logtype_cnid, "Finished opening BerkeleyDB databases");
340
341     /* Downgrade db lock  */
342     if (db_locked == LOCK_EXCL) {
343         if (get_lock(LOCK_UNLOCK, NULL) != 0) {
344             dbif_close(dbd);
345             exit(2);
346         }
347         if (get_lock(LOCK_SHRD, NULL) != LOCK_SHRD) {
348             dbif_close(dbd);
349             exit(2);
350         }
351     }
352
353
354     if (dbd_stamp(dbd) < 0) {
355         dbif_close(dbd);
356         exit(5);
357     }
358     LOG(log_maxdebug, logtype_cnid, "Finished checking database stamp");
359
360     if (comm_init(dbp, ctrlfd, clntfd) < 0) {
361         dbif_close(dbd);
362         exit(3);
363     }
364
365     if (loop(dbp) < 0)
366         err++;
367
368     if (dbif_close(dbd) < 0)
369         err++;
370
371     if (dbif_prep_upgrade(dir) < 0)
372         err++;
373
374     if (err)
375         exit(4);
376     else if (exit_sig)
377         LOG(log_info, logtype_cnid, "main: Exiting on signal %i", exit_sig);
378     else
379         LOG(log_info, logtype_cnid, "main: Idle timeout, exiting");
380
381     return 0;
382 }