]> arthur.barton.de Git - netatalk.git/blob - etc/cnid_dbd/main.c
9b5fd7856a0e79565842c27fb83e08e03f046564
[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_info, 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) );
316
317     set_processname("cnid_dbd");
318     setuplog(obj.options.logconfig, obj.options.logfile);
319
320     EC_ZERO( load_volumes(&obj, NULL) );
321     EC_NULL( vol = getvolbypath(volpath) );
322
323     pack_setvol(vol);
324
325     EC_NULL( dbpath = bfromcstr(vol->v_dbpath) );
326     EC_ZERO( bcatcstr(dbpath, "/.AppleDB") );
327
328     LOG(log_debug, logtype_cnid, "db dir: \"%s\"", bdata(dbpath));
329
330     switch_to_user(bdata(dbpath));
331
332     /* Get db lock */
333     if ((db_locked = get_lock(LOCK_EXCL, bdata(dbpath))) == -1) {
334         LOG(log_error, logtype_cnid, "main: fatal db lock error");
335         EC_FAIL;
336     }
337     if (db_locked != LOCK_EXCL) {
338         /* Couldn't get exclusive lock, try shared lock  */
339         if ((db_locked = get_lock(LOCK_SHRD, NULL)) != LOCK_SHRD) {
340             LOG(log_error, logtype_cnid, "main: fatal db lock error");
341             EC_FAIL;
342         }
343     }
344
345     if (delete_bdb && (db_locked == LOCK_EXCL)) {
346         LOG(log_warning, logtype_cnid, "main: too many CNID db opening attempts, wiping the slate clean");
347         chdir(bdata(dbpath));
348         system("rm -f cnid2.db lock log.* __db.*");
349         if ((db_locked = get_lock(LOCK_EXCL, bdata(dbpath))) != LOCK_EXCL) {
350             LOG(log_error, logtype_cnid, "main: fatal db lock error");
351             EC_FAIL;
352         }
353     }
354
355     set_signal();
356
357     /* SIGINT and SIGTERM are always off, unless we are in pselect */
358     block_sigs_onoff(1);
359
360     if ((dbp = db_param_read(bdata(dbpath))) == NULL)
361         EC_FAIL;
362     LOG(log_maxdebug, logtype_cnid, "Finished parsing db_param config file");
363
364     if (NULL == (dbd = dbif_init(bdata(dbpath), "cnid2.db")))
365         EC_FAIL;
366
367     /* Only recover if we got the lock */
368     if (dbif_env_open(dbd,
369                       dbp,
370                       (db_locked == LOCK_EXCL) ? DBOPTIONS | DB_RECOVER : DBOPTIONS) < 0)
371         EC_FAIL;
372     LOG(log_debug, logtype_cnid, "Finished initializing BerkeleyDB environment");
373
374     if (dbif_open(dbd, dbp, 0) < 0) {
375         ret = -1;
376         goto close_db;
377     }
378
379     LOG(log_debug, logtype_cnid, "Finished opening BerkeleyDB databases");
380
381     /* Downgrade db lock  */
382     if (db_locked == LOCK_EXCL) {
383         if (get_lock(LOCK_UNLOCK, NULL) != 0) {
384             ret = -1;
385             goto close_db;
386         }
387
388         if (get_lock(LOCK_SHRD, NULL) != LOCK_SHRD) {
389             ret = -1;
390             goto close_db;
391         }
392     }
393
394
395     if (comm_init(dbp, ctrlfd, clntfd) < 0) {
396         ret = -1;
397         goto close_db;
398     }
399
400     if (loop(dbp) < 0) {
401         ret = -1;
402         goto close_db;
403     }
404
405 close_db:
406     if (dbif_close(dbd) < 0)
407         ret = -1;
408
409     if (dbif_env_remove(bdata(dbpath)) < 0)
410         ret = -1;
411
412 EC_CLEANUP:
413     if (ret != 0)
414         exit(1);
415
416     if (exit_sig)
417         LOG(log_info, logtype_cnid, "main: Exiting on signal %i", exit_sig);
418     else
419         LOG(log_info, logtype_cnid, "main: Idle timeout, exiting");
420
421     EC_EXIT;
422 }