]> arthur.barton.de Git - netatalk.git/blob - etc/cnid_dbd/main.c
Fix build
[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/volinfo.h>
28
29 #include "db_param.h"
30 #include "dbif.h"
31 #include "dbd.h"
32 #include "comm.h"
33
34 /* 
35    Note: DB_INIT_LOCK is here so we can run the db_* utilities while netatalk is running.
36    It's a likey performance hit, but it might we worth it.
37  */
38 #define DBOPTIONS (DB_CREATE | DB_INIT_LOG | DB_INIT_MPOOL | DB_INIT_LOCK | DB_INIT_TXN)
39
40 /* Global, needed by pack.c:idxname() */
41 struct volinfo volinfo;
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     struct db_param *dbp;
277     int err = 0, ret, delete_bdb = 0;
278     int ctrlfd, clntfd;
279     char *logconfig;
280
281     set_processname("cnid_dbd");
282
283     while (( ret = getopt( argc, argv, "vVd")) != -1 ) {
284         switch (ret) {
285         case 'v':
286         case 'V':
287             printf("cnid_dbd (Netatalk %s)\n", VERSION);
288             return -1;
289         case 'd':
290             delete_bdb = 1;
291             break;
292         }
293     }
294
295     if (argc - optind != 4) {
296         LOG(log_error, logtype_cnid, "main: not enough arguments");
297         exit(EXIT_FAILURE);
298     }
299
300     /* Load .volinfo file */
301     if (loadvolinfo(argv[optind], &volinfo) == -1) {
302         LOG(log_error, logtype_cnid, "Cant load volinfo for \"%s\"", argv[1]);
303         exit(EXIT_FAILURE);
304     }
305     /* Put "/.AppleDB" at end of volpath, get path from volinfo file */
306     char dbpath[MAXPATHLEN+1];
307     if ((strlen(volinfo.v_dbpath) + strlen("/.AppleDB")) > MAXPATHLEN ) {
308         LOG(log_error, logtype_cnid, "CNID db pathname too long: \"%s\"", volinfo.v_dbpath);
309         exit(EXIT_FAILURE);
310     }
311     strncpy(dbpath, volinfo.v_dbpath, MAXPATHLEN - strlen("/.AppleDB"));
312     strcat(dbpath, "/.AppleDB");
313
314     ctrlfd = atoi(argv[optind + 1]);
315     clntfd = atoi(argv[optind + 2]);
316
317     setuplog("default:note", NULL);
318
319     if (vol_load_charsets(&volinfo) == -1) {
320         LOG(log_error, logtype_cnid, "Error loading charsets!");
321         exit(EXIT_FAILURE);
322     }
323     LOG(log_debug, logtype_cnid, "db dir: \"%s\"", dbpath);
324
325     switch_to_user(dbpath);
326
327     /* Get db lock */
328     if ((db_locked = get_lock(LOCK_EXCL, dbpath)) == -1) {
329         LOG(log_error, logtype_cnid, "main: fatal db lock error");
330         exit(1);
331     }
332     if (db_locked != LOCK_EXCL) {
333         /* Couldn't get exclusive lock, try shared lock  */
334         if ((db_locked = get_lock(LOCK_SHRD, NULL)) != LOCK_SHRD) {
335             LOG(log_error, logtype_cnid, "main: fatal db lock error");
336             exit(1);
337         }
338     }
339
340     if (delete_bdb && (db_locked == LOCK_EXCL)) {
341         LOG(log_warning, logtype_cnid, "main: too many CNID db opening attempts, wiping the slate clean");
342         chdir(dbpath);
343         system("rm -f cnid2.db lock log.* __db.*");
344         if ((db_locked = get_lock(LOCK_EXCL, dbpath)) != LOCK_EXCL) {
345             LOG(log_error, logtype_cnid, "main: fatal db lock error");
346             exit(EXIT_FAILURE);
347         }
348     }
349
350     set_signal();
351
352     /* SIGINT and SIGTERM are always off, unless we are in pselect */
353     block_sigs_onoff(1);
354
355     if ((dbp = db_param_read(dbpath)) == NULL)
356         exit(1);
357     LOG(log_maxdebug, logtype_cnid, "Finished parsing db_param config file");
358
359     if (NULL == (dbd = dbif_init(dbpath, "cnid2.db")))
360         exit(2);
361
362     /* Only recover if we got the lock */
363     if (dbif_env_open(dbd,
364                       dbp,
365                       (db_locked == LOCK_EXCL) ? DBOPTIONS | DB_RECOVER : DBOPTIONS) < 0)
366         exit(2); /* FIXME: same exit code as failure for dbif_open() */
367     LOG(log_debug, logtype_cnid, "Finished initializing BerkeleyDB environment");
368
369     if (dbif_open(dbd, dbp, 0) < 0) {
370         dbif_close(dbd);
371         exit(2);
372     }
373     LOG(log_debug, logtype_cnid, "Finished opening BerkeleyDB databases");
374
375     /* Downgrade db lock  */
376     if (db_locked == LOCK_EXCL) {
377         if (get_lock(LOCK_UNLOCK, NULL) != 0) {
378             dbif_close(dbd);
379             exit(2);
380         }
381         if (get_lock(LOCK_SHRD, NULL) != LOCK_SHRD) {
382             dbif_close(dbd);
383             exit(2);
384         }
385     }
386
387
388     if (comm_init(dbp, ctrlfd, clntfd) < 0) {
389         dbif_close(dbd);
390         exit(3);
391     }
392
393     if (loop(dbp) < 0)
394         err++;
395
396     if (dbif_close(dbd) < 0)
397         err++;
398
399     if (dbif_env_remove(dbpath) < 0)
400         err++;
401
402     if (err)
403         exit(4);
404     else if (exit_sig)
405         LOG(log_info, logtype_cnid, "main: Exiting on signal %i", exit_sig);
406     else
407         LOG(log_info, logtype_cnid, "main: Idle timeout, exiting");
408
409     return 0;
410 }