]> arthur.barton.de Git - netatalk.git/blob - etc/cnid_dbd/main.c
df29d15198d470e73cab6a200121aedfb5236769
[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 #include <unistd.h>
12 #include <fcntl.h>
13 #include <stdio.h>
14 #include <stdlib.h>
15 #include <errno.h>
16 #include <signal.h>
17 #include <string.h>
18 #include <sys/types.h>
19 #include <sys/param.h>
20 #include <sys/stat.h>
21 #include <time.h>
22 #include <sys/file.h>
23 #include <arpa/inet.h>
24
25 #include <atalk/cnid_dbd_private.h>
26 #include <atalk/logger.h>
27 #include <atalk/errchk.h>
28 #include <atalk/bstrlib.h>
29 #include <atalk/netatalk_conf.h>
30
31 #include "db_param.h"
32 #include "dbif.h"
33 #include "dbd.h"
34 #include "comm.h"
35 #include "pack.h"
36
37 /* 
38    Note: DB_INIT_LOCK is here so we can run the db_* utilities while netatalk is running.
39    It's a likey performance hit, but it might we worth it.
40  */
41 #define DBOPTIONS (DB_CREATE | DB_INIT_LOG | DB_INIT_MPOOL | DB_INIT_LOCK | DB_INIT_TXN)
42
43 static DBD *dbd;
44 static int exit_sig = 0;
45 static int db_locked;
46
47 static void sig_exit(int signo)
48 {
49     exit_sig = signo;
50     return;
51 }
52
53 static void block_sigs_onoff(int block)
54 {
55     sigset_t set;
56
57     sigemptyset(&set);
58     sigaddset(&set, SIGINT);
59     sigaddset(&set, SIGTERM);
60     if (block)
61         sigprocmask(SIG_BLOCK, &set, NULL);
62     else
63         sigprocmask(SIG_UNBLOCK, &set, NULL);
64     return;
65 }
66
67 /*
68   The dbd_XXX and comm_XXX functions all obey the same protocol for return values:
69
70   1: Success, if transactions are used commit.
71   0: Failure, but we continue to serve requests. If transactions are used abort/rollback.
72   -1: Fatal error, either from t
73   he database or from the socket. Abort the transaction if applicable
74   (which might fail as well) and then exit.
75
76   We always try to notify the client process about the outcome, the result field
77   of the cnid_dbd_rply structure contains further details.
78
79 */
80 #ifndef min
81 #define min(a,b)        ((a)<(b)?(a):(b))
82 #endif
83
84 static int loop(struct db_param *dbp)
85 {
86     struct cnid_dbd_rqst rqst;
87     struct cnid_dbd_rply rply;
88     time_t timeout;
89     int ret, cret;
90     int count;
91     time_t now, time_next_flush, time_last_rqst;
92     char timebuf[64];
93     static char namebuf[MAXPATHLEN + 1];
94     sigset_t set;
95
96     sigemptyset(&set);
97     sigprocmask(SIG_SETMASK, NULL, &set);
98     sigdelset(&set, SIGINT);
99     sigdelset(&set, SIGTERM);
100
101     count = 0;
102     now = time(NULL);
103     time_next_flush = now + dbp->flush_interval;
104     time_last_rqst = now;
105
106     rqst.name = namebuf;
107
108     strftime(timebuf, 63, "%b %d %H:%M:%S.",localtime(&time_next_flush));
109     LOG(log_debug, logtype_cnid, "Checkpoint interval: %d seconds. Next checkpoint: %s",
110         dbp->flush_interval, timebuf);
111
112     while (1) {
113         timeout = min(time_next_flush, time_last_rqst +dbp->idle_timeout);
114         if (timeout > now)
115             timeout -= now;
116         else
117             timeout = 1;
118
119         if ((cret = comm_rcv(&rqst, timeout, &set, &now)) < 0)
120             return -1;
121
122         if (cret == 0) {
123             /* comm_rcv returned from select without receiving anything. */
124             if (exit_sig) {
125                 /* Received signal (TERM|INT) */
126                 return 0;
127             }
128             if (now - time_last_rqst >= dbp->idle_timeout && comm_nbe() <= 0) {
129                 /* Idle timeout */
130                 return 0;
131             }
132             /* still active connections, reset time_last_rqst */
133             time_last_rqst = now;
134         } else {
135             /* We got a request */
136             time_last_rqst = now;
137
138             memset(&rply, 0, sizeof(rply));
139             switch(rqst.op) {
140                 /* ret gets set here */
141             case CNID_DBD_OP_OPEN:
142             case CNID_DBD_OP_CLOSE:
143                 /* open/close are noops for now. */
144                 rply.namelen = 0;
145                 ret = 1;
146                 break;
147             case CNID_DBD_OP_ADD:
148                 ret = dbd_add(dbd, &rqst, &rply, 0);
149                 break;
150             case CNID_DBD_OP_GET:
151                 ret = dbd_get(dbd, &rqst, &rply);
152                 break;
153             case CNID_DBD_OP_RESOLVE:
154                 ret = dbd_resolve(dbd, &rqst, &rply);
155                 break;
156             case CNID_DBD_OP_LOOKUP:
157                 ret = dbd_lookup(dbd, &rqst, &rply, 0);
158                 break;
159             case CNID_DBD_OP_UPDATE:
160                 ret = dbd_update(dbd, &rqst, &rply);
161                 break;
162             case CNID_DBD_OP_DELETE:
163                 ret = dbd_delete(dbd, &rqst, &rply, DBIF_CNID);
164                 break;
165             case CNID_DBD_OP_GETSTAMP:
166                 ret = dbd_getstamp(dbd, &rqst, &rply);
167                 break;
168             case CNID_DBD_OP_REBUILD_ADD:
169                 ret = dbd_rebuild_add(dbd, &rqst, &rply);
170                 break;
171             case CNID_DBD_OP_SEARCH:
172                 ret = dbd_search(dbd, &rqst, &rply);
173                 break;
174             default:
175                 LOG(log_error, logtype_cnid, "loop: unknown op %d", rqst.op);
176                 ret = -1;
177                 break;
178             }
179
180             if ((cret = comm_snd(&rply)) < 0 || ret < 0) {
181                 dbif_txn_abort(dbd);
182                 return -1;
183             }
184             
185             if (ret == 0 || cret == 0) {
186                 if (dbif_txn_abort(dbd) < 0)
187                     return -1;
188             } else {
189                 ret = dbif_txn_commit(dbd);
190                 if (  ret < 0)
191                     return -1;
192                 else if ( ret > 0 )
193                     /* We had a designated txn because we wrote to the db */
194                     count++;
195             }
196         } /* got a request */
197
198         /*
199           Shall we checkpoint bdb ?
200           "flush_interval" seconds passed ?
201         */
202         if (now >= time_next_flush) {
203             LOG(log_info, logtype_cnid, "Checkpointing BerkeleyDB for volume '%s'", dbp->dir);
204             if (dbif_txn_checkpoint(dbd, 0, 0, 0) < 0)
205                 return -1;
206             count = 0;
207             time_next_flush = now + dbp->flush_interval;
208
209             strftime(timebuf, 63, "%b %d %H:%M:%S.",localtime(&time_next_flush));
210             LOG(log_debug, logtype_cnid, "Checkpoint interval: %d seconds. Next checkpoint: %s",
211                 dbp->flush_interval, timebuf);
212         }
213
214         /* 
215            Shall we checkpoint bdb ?
216            Have we commited "count" more changes than "flush_frequency" ?
217         */
218         if (count > dbp->flush_frequency) {
219             LOG(log_info, logtype_cnid, "Checkpointing BerkeleyDB after %d writes for volume '%s'", count, dbp->dir);
220             if (dbif_txn_checkpoint(dbd, 0, 0, 0) < 0)
221                 return -1;
222             count = 0;
223         }
224     } /* while(1) */
225 }
226
227 /* ------------------------ */
228 static void switch_to_user(char *dir)
229 {
230     struct stat st;
231
232     if (chdir(dir) < 0) {
233         LOG(log_error, logtype_cnid, "chdir to %s failed: %s", dir, strerror(errno));
234         exit(1);
235     }
236
237     if (stat(".", &st) < 0) {
238         LOG(log_error, logtype_cnid, "error in stat for %s: %s", dir, strerror(errno));
239         exit(1);
240     }
241     if (!getuid()) {
242         LOG(log_debug, logtype_cnid, "Setting uid/gid to %i/%i", st.st_uid, st.st_gid);
243         if (setgid(st.st_gid) < 0 || setuid(st.st_uid) < 0) {
244             LOG(log_error, logtype_cnid, "uid/gid: %s", strerror(errno));
245             exit(1);
246         }
247     }
248 }
249
250
251 /* ----------------------- */
252 static void set_signal(void)
253 {
254     struct sigaction sv;
255
256     sv.sa_handler = sig_exit;
257     sv.sa_flags = 0;
258     sigemptyset(&sv.sa_mask);
259     sigaddset(&sv.sa_mask, SIGINT);
260     sigaddset(&sv.sa_mask, SIGTERM);
261     if (sigaction(SIGINT, &sv, NULL) < 0 || sigaction(SIGTERM, &sv, NULL) < 0) {
262         LOG(log_error, logtype_cnid, "main: sigaction: %s", strerror(errno));
263         exit(1);
264     }
265     sv.sa_handler = SIG_IGN;
266     sigemptyset(&sv.sa_mask);
267     if (sigaction(SIGPIPE, &sv, NULL) < 0) {
268         LOG(log_error, logtype_cnid, "main: sigaction: %s", strerror(errno));
269         exit(1);
270     }
271 }
272
273 /* ------------------------ */
274 int main(int argc, char *argv[])
275 {
276     EC_INIT;
277     struct db_param *dbp;
278     int delete_bdb = 0;
279     int ctrlfd = -1, clntfd = -1;
280     char *logconfig;
281     AFPObj obj = { 0 };
282     struct vol *vol;
283     char *volpath = NULL;
284     bstring dbpath;
285
286     while (( ret = getopt( argc, argv, "dF:l:p:t:vV")) != -1 ) {
287         switch (ret) {
288         case 'd':
289             delete_bdb = 1;
290             break;
291         case 'F':
292             obj.cmdlineconfigfile = strdup(optarg);
293             break;
294         case 'p':
295             volpath = strdup(optarg);
296             break;
297         case 'l':
298             clntfd = atoi(optarg);
299             break;
300         case 't':
301             ctrlfd = atoi(optarg);
302             break;
303         case 'v':
304         case 'V':
305             printf("cnid_dbd (Netatalk %s)\n", VERSION);
306             return -1;
307         }
308     }
309
310     if (ctrlfd == -1 || clntfd == -1 || !volpath) {
311         LOG(log_error, logtype_cnid, "main: bad IPC fds");
312         exit(EXIT_FAILURE);
313     }
314
315     EC_ZERO( afp_config_parse(&obj, "cnid_dbd") );
316
317     EC_ZERO( load_volumes(&obj) );
318     EC_NULL( vol = getvolbypath(&obj, volpath) );
319     EC_ZERO( load_charset(vol) );
320     pack_setvol(vol);
321
322     EC_NULL( dbpath = bfromcstr(vol->v_dbpath) );
323     EC_ZERO( bcatcstr(dbpath, "/.AppleDB") );
324
325     LOG(log_debug, logtype_cnid, "db dir: \"%s\"", bdata(dbpath));
326
327     switch_to_user(bdata(dbpath));
328
329     /* Get db lock */
330     if ((db_locked = get_lock(LOCK_EXCL, bdata(dbpath))) == -1) {
331         LOG(log_error, logtype_cnid, "main: fatal db lock error");
332         EC_FAIL;
333     }
334     if (db_locked != LOCK_EXCL) {
335         /* Couldn't get exclusive lock, try shared lock  */
336         if ((db_locked = get_lock(LOCK_SHRD, NULL)) != LOCK_SHRD) {
337             LOG(log_error, logtype_cnid, "main: fatal db lock error");
338             EC_FAIL;
339         }
340     }
341
342     if (delete_bdb && (db_locked == LOCK_EXCL)) {
343         LOG(log_warning, logtype_cnid, "main: too many CNID db opening attempts, wiping the slate clean");
344         chdir(bdata(dbpath));
345         system("rm -f cnid2.db lock log.* __db.*");
346         if ((db_locked = get_lock(LOCK_EXCL, bdata(dbpath))) != LOCK_EXCL) {
347             LOG(log_error, logtype_cnid, "main: fatal db lock error");
348             EC_FAIL;
349         }
350     }
351
352     set_signal();
353
354     /* SIGINT and SIGTERM are always off, unless we are in pselect */
355     block_sigs_onoff(1);
356
357     if ((dbp = db_param_read(bdata(dbpath))) == NULL)
358         EC_FAIL;
359     LOG(log_maxdebug, logtype_cnid, "Finished parsing db_param config file");
360
361     if (NULL == (dbd = dbif_init(bdata(dbpath), "cnid2.db")))
362         EC_FAIL;
363
364     /* Only recover if we got the lock */
365     if (dbif_env_open(dbd,
366                       dbp,
367                       (db_locked == LOCK_EXCL) ? DBOPTIONS | DB_RECOVER : DBOPTIONS) < 0)
368         EC_FAIL;
369     LOG(log_debug, logtype_cnid, "Finished initializing BerkeleyDB environment");
370
371     if (dbif_open(dbd, dbp, 0) < 0) {
372         ret = -1;
373         goto close_db;
374     }
375
376     LOG(log_debug, logtype_cnid, "Finished opening BerkeleyDB databases");
377
378     /* Downgrade db lock  */
379     if (db_locked == LOCK_EXCL) {
380         if (get_lock(LOCK_UNLOCK, NULL) != 0) {
381             ret = -1;
382             goto close_db;
383         }
384
385         if (get_lock(LOCK_SHRD, NULL) != LOCK_SHRD) {
386             ret = -1;
387             goto close_db;
388         }
389     }
390
391
392     if (comm_init(dbp, ctrlfd, clntfd) < 0) {
393         ret = -1;
394         goto close_db;
395     }
396
397     if (loop(dbp) < 0) {
398         ret = -1;
399         goto close_db;
400     }
401
402 close_db:
403     if (dbif_close(dbd) < 0)
404         ret = -1;
405
406     if (dbif_env_remove(bdata(dbpath)) < 0)
407         ret = -1;
408
409 EC_CLEANUP:
410     if (ret != 0)
411         exit(1);
412
413     if (exit_sig)
414         LOG(log_info, logtype_cnid, "main: Exiting on signal %i", exit_sig);
415     else
416         LOG(log_info, logtype_cnid, "main: Idle timeout, exiting");
417
418     EC_EXIT;
419 }