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