]> arthur.barton.de Git - netatalk.git/blob - etc/cnid_dbd/main.c
Remove bdb env on exit
[netatalk.git] / etc / cnid_dbd / main.c
1 /*
2  * Copyright (C) Joerg Lenneis 2003
3  * Copyright (c) Frank Lahm 2009
4  * All Rights Reserved.  See COPYING.
5  */
6
7 #ifdef HAVE_CONFIG_H
8 #include "config.h"
9 #endif /* HAVE_CONFIG_H */
10
11 #ifdef HAVE_UNISTD_H
12 #include <unistd.h>
13 #endif /* HAVE_UNISTD_H */
14 #ifdef HAVE_FCNTL_H
15 #include <fcntl.h>
16 #endif /* HAVE_FCNTL_H */
17 #include <stdio.h>
18 #include <stdlib.h>
19 #include <errno.h>
20 #include <signal.h>
21 #include <string.h>
22 #ifdef HAVE_SYS_TYPES_H
23 #include <sys/types.h>
24 #endif /* HAVE_SYS_TYPES_H */
25 #include <sys/param.h>
26 #ifdef HAVE_SYS_STAT_H
27 #include <sys/stat.h>
28 #endif /* HAVE_SYS_STAT_H */
29 #include <time.h>
30 #include <sys/file.h>
31
32 #include <netatalk/endian.h>
33 #include <atalk/cnid_dbd_private.h>
34 #include <atalk/logger.h>
35 #include <atalk/volinfo.h>
36
37 #include "db_param.h"
38 #include "dbif.h"
39 #include "dbd.h"
40 #include "comm.h"
41
42 /* 
43    Note: DB_INIT_LOCK is here so we can run the db_* utilities while netatalk is running.
44    It's a likey performance hit, but it might we worth it.
45  */
46 #define DBOPTIONS (DB_CREATE | DB_INIT_LOG | DB_INIT_MPOOL | DB_INIT_LOCK | DB_INIT_TXN)
47
48 /* Global, needed by pack.c:idxname() */
49 struct volinfo volinfo;
50
51 static DBD *dbd;
52 static int exit_sig = 0;
53 static int db_locked;
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, &now)) < 0)
128             return -1;
129
130         if (cret == 0) {
131             /* comm_rcv returned from select without receiving anything. */
132             if (exit_sig) {
133                 /* Received signal (TERM|INT) */
134                 return 0;
135             }
136             if (now - time_last_rqst >= dbp->idle_timeout && comm_nbe() <= 0) {
137                 /* Idle timeout */
138                 return 0;
139             }
140             /* still active connections, reset time_last_rqst */
141             time_last_rqst = now;
142         } else {
143             /* We got a request */
144             time_last_rqst = now;
145
146             memset(&rply, 0, sizeof(rply));
147             switch(rqst.op) {
148                 /* ret gets set here */
149             case CNID_DBD_OP_OPEN:
150             case CNID_DBD_OP_CLOSE:
151                 /* open/close are noops for now. */
152                 rply.namelen = 0;
153                 ret = 1;
154                 break;
155             case CNID_DBD_OP_ADD:
156                 ret = dbd_add(dbd, &rqst, &rply, 0);
157                 break;
158             case CNID_DBD_OP_GET:
159                 ret = dbd_get(dbd, &rqst, &rply);
160                 break;
161             case CNID_DBD_OP_RESOLVE:
162                 ret = dbd_resolve(dbd, &rqst, &rply);
163                 break;
164             case CNID_DBD_OP_LOOKUP:
165                 ret = dbd_lookup(dbd, &rqst, &rply, 0);
166                 break;
167             case CNID_DBD_OP_UPDATE:
168                 ret = dbd_update(dbd, &rqst, &rply);
169                 break;
170             case CNID_DBD_OP_DELETE:
171                 ret = dbd_delete(dbd, &rqst, &rply, DBIF_CNID);
172                 break;
173             case CNID_DBD_OP_GETSTAMP:
174                 ret = dbd_getstamp(dbd, &rqst, &rply);
175                 break;
176             case CNID_DBD_OP_REBUILD_ADD:
177                 ret = dbd_rebuild_add(dbd, &rqst, &rply);
178                 break;
179             case CNID_DBD_OP_SEARCH:
180                 ret = dbd_search(dbd, &rqst, &rply);
181                 break;
182             default:
183                 LOG(log_error, logtype_cnid, "loop: unknown op %d", rqst.op);
184                 ret = -1;
185                 break;
186             }
187
188             if ((cret = comm_snd(&rply)) < 0 || ret < 0) {
189                 dbif_txn_abort(dbd);
190                 return -1;
191             }
192             
193             if (ret == 0 || cret == 0) {
194                 if (dbif_txn_abort(dbd) < 0)
195                     return -1;
196             } else {
197                 ret = dbif_txn_commit(dbd);
198                 if (  ret < 0)
199                     return -1;
200                 else if ( ret > 0 )
201                     /* We had a designated txn because we wrote to the db */
202                     count++;
203             }
204         } /* got a request */
205
206         /*
207           Shall we checkpoint bdb ?
208           "flush_interval" seconds passed ?
209         */
210         if (now >= time_next_flush) {
211             LOG(log_info, logtype_cnid, "Checkpointing BerkeleyDB for volume '%s'", dbp->dir);
212             if (dbif_txn_checkpoint(dbd, 0, 0, 0) < 0)
213                 return -1;
214             count = 0;
215             time_next_flush = now + dbp->flush_interval;
216
217             strftime(timebuf, 63, "%b %d %H:%M:%S.",localtime(&time_next_flush));
218             LOG(log_debug, logtype_cnid, "Checkpoint interval: %d seconds. Next checkpoint: %s",
219                 dbp->flush_interval, timebuf);
220         }
221
222         /* 
223            Shall we checkpoint bdb ?
224            Have we commited "count" more changes than "flush_frequency" ?
225         */
226         if (count > dbp->flush_frequency) {
227             LOG(log_info, logtype_cnid, "Checkpointing BerkeleyDB after %d writes for volume '%s'", count, dbp->dir);
228             if (dbif_txn_checkpoint(dbd, 0, 0, 0) < 0)
229                 return -1;
230             count = 0;
231         }
232     } /* while(1) */
233 }
234
235 /* ------------------------ */
236 static void switch_to_user(char *dir)
237 {
238     struct stat st;
239
240     if (chdir(dir) < 0) {
241         LOG(log_error, logtype_cnid, "chdir to %s failed: %s", dir, strerror(errno));
242         exit(1);
243     }
244
245     if (stat(".", &st) < 0) {
246         LOG(log_error, logtype_cnid, "error in stat for %s: %s", dir, strerror(errno));
247         exit(1);
248     }
249     if (!getuid()) {
250         LOG(log_info, logtype_cnid, "Setting uid/gid to %i/%i", st.st_uid, st.st_gid);
251         if (setgid(st.st_gid) < 0 || setuid(st.st_uid) < 0) {
252             LOG(log_error, logtype_cnid, "uid/gid: %s", strerror(errno));
253             exit(1);
254         }
255     }
256 }
257
258
259 /* ----------------------- */
260 static void set_signal(void)
261 {
262     struct sigaction sv;
263
264     sv.sa_handler = sig_exit;
265     sv.sa_flags = 0;
266     sigemptyset(&sv.sa_mask);
267     sigaddset(&sv.sa_mask, SIGINT);
268     sigaddset(&sv.sa_mask, SIGTERM);
269     if (sigaction(SIGINT, &sv, NULL) < 0 || sigaction(SIGTERM, &sv, NULL) < 0) {
270         LOG(log_error, logtype_cnid, "main: sigaction: %s", strerror(errno));
271         exit(1);
272     }
273     sv.sa_handler = SIG_IGN;
274     sigemptyset(&sv.sa_mask);
275     if (sigaction(SIGPIPE, &sv, NULL) < 0) {
276         LOG(log_error, logtype_cnid, "main: sigaction: %s", strerror(errno));
277         exit(1);
278     }
279 }
280
281 /* ------------------------ */
282 int main(int argc, char *argv[])
283 {
284     struct db_param *dbp;
285     int err = 0, ret, delete_bdb = 0;
286     int ctrlfd, clntfd;
287     char *logconfig;
288
289     set_processname("cnid_dbd");
290
291     while (( ret = getopt( argc, argv, "vVd")) != -1 ) {
292         switch (ret) {
293         case 'v':
294         case 'V':
295             printf("cnid_dbd (Netatalk %s)\n", VERSION);
296             return -1;
297         case 'd':
298             delete_bdb = 1;
299             break;
300         }
301     }
302
303     if (argc - optind != 4) {
304         LOG(log_error, logtype_cnid, "main: not enough arguments");
305         exit(EXIT_FAILURE);
306     }
307
308     /* Load .volinfo file */
309     if (loadvolinfo(argv[optind], &volinfo) == -1) {
310         LOG(log_error, logtype_cnid, "Cant load volinfo for \"%s\"", argv[1]);
311         exit(EXIT_FAILURE);
312     }
313     /* Put "/.AppleDB" at end of volpath, get path from volinfo file */
314     char dbpath[MAXPATHLEN+1];
315     if ((strlen(volinfo.v_dbpath) + strlen("/.AppleDB")) > MAXPATHLEN ) {
316         LOG(log_error, logtype_cnid, "CNID db pathname too long: \"%s\"", volinfo.v_dbpath);
317         exit(EXIT_FAILURE);
318     }
319     strncpy(dbpath, volinfo.v_dbpath, MAXPATHLEN - strlen("/.AppleDB"));
320     strcat(dbpath, "/.AppleDB");
321
322     ctrlfd = atoi(argv[optind + 1]);
323     clntfd = atoi(argv[optind + 2]);
324     logconfig = strdup(argv[optind + 3]);
325     setuplog(logconfig);
326
327     if (vol_load_charsets(&volinfo) == -1) {
328         LOG(log_error, logtype_cnid, "Error loading charsets!");
329         exit(EXIT_FAILURE);
330     }
331     LOG(log_debug, logtype_cnid, "db dir: \"%s\"", dbpath);
332
333     switch_to_user(dbpath);
334
335     /* Get db lock */
336     if ((db_locked = get_lock(LOCK_EXCL, dbpath)) == -1) {
337         LOG(log_error, logtype_cnid, "main: fatal db lock error");
338         exit(1);
339     }
340     if (db_locked != LOCK_EXCL) {
341         /* Couldn't get exclusive lock, try shared lock  */
342         if ((db_locked = get_lock(LOCK_SHRD, NULL)) != LOCK_SHRD) {
343             LOG(log_error, logtype_cnid, "main: fatal db lock error");
344             exit(1);
345         }
346     }
347
348     if (delete_bdb && (db_locked == LOCK_EXCL)) {
349         LOG(log_warning, logtype_cnid, "main: too many CNID db opening attempts, wiping the slate clean");
350         chdir(dbpath);
351         system("rm -f cnid2.db lock log.* __db.*");
352         if ((db_locked = get_lock(LOCK_EXCL, dbpath)) != LOCK_EXCL) {
353             LOG(log_error, logtype_cnid, "main: fatal db lock error");
354             exit(EXIT_FAILURE);
355         }
356     }
357
358     set_signal();
359
360     /* SIGINT and SIGTERM are always off, unless we are in pselect */
361     block_sigs_onoff(1);
362
363     if ((dbp = db_param_read(dbpath)) == NULL)
364         exit(1);
365     LOG(log_maxdebug, logtype_cnid, "Finished parsing db_param config file");
366
367     if (NULL == (dbd = dbif_init(dbpath, "cnid2.db")))
368         exit(2);
369
370     /* Only recover if we got the lock */
371     if (dbif_env_open(dbd,
372                       dbp,
373                       (db_locked == LOCK_EXCL) ? DBOPTIONS | DB_RECOVER : DBOPTIONS) < 0)
374         exit(2); /* FIXME: same exit code as failure for dbif_open() */
375     LOG(log_debug, logtype_cnid, "Finished initializing BerkeleyDB environment");
376
377     if (dbif_open(dbd, dbp, 0) < 0) {
378         dbif_close(dbd);
379         exit(2);
380     }
381     LOG(log_debug, logtype_cnid, "Finished opening BerkeleyDB databases");
382
383     /* Downgrade db lock  */
384     if (db_locked == LOCK_EXCL) {
385         if (get_lock(LOCK_UNLOCK, NULL) != 0) {
386             dbif_close(dbd);
387             exit(2);
388         }
389         if (get_lock(LOCK_SHRD, NULL) != LOCK_SHRD) {
390             dbif_close(dbd);
391             exit(2);
392         }
393     }
394
395
396     if (comm_init(dbp, ctrlfd, clntfd) < 0) {
397         dbif_close(dbd);
398         exit(3);
399     }
400
401     if (loop(dbp) < 0)
402         err++;
403
404     if (dbif_close(dbd) < 0)
405         err++;
406
407     if (dbif_env_remove(dbpath) < 0)
408         err++;
409
410     if (err)
411         exit(4);
412     else if (exit_sig)
413         LOG(log_info, logtype_cnid, "main: Exiting on signal %i", exit_sig);
414     else
415         LOG(log_info, logtype_cnid, "main: Idle timeout, exiting");
416
417     return 0;
418 }