]> arthur.barton.de Git - netatalk.git/blob - etc/cnid_dbd/main.c
Mere sf master
[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, "d")) != -1 ) {
292         switch (ret) {
293         case 'd':
294             delete_bdb = 1;
295             break;
296         }
297     }
298
299     if (argc - optind != 4) {
300         LOG(log_error, logtype_cnid, "main: not enough arguments");
301         exit(EXIT_FAILURE);
302     }
303
304     /* Load .volinfo file */
305     if (loadvolinfo(argv[optind], &volinfo) == -1) {
306         LOG(log_error, logtype_cnid, "Cant load volinfo for \"%s\"", argv[1]);
307         exit(EXIT_FAILURE);
308     }
309     /* Put "/.AppleDB" at end of volpath, get path from volinfo file */
310     char dbpath[MAXPATHLEN+1];
311     if ((strlen(volinfo.v_dbpath) + strlen("/.AppleDB")) > MAXPATHLEN ) {
312         LOG(log_error, logtype_cnid, "CNID db pathname too long: \"%s\"", volinfo.v_dbpath);
313         exit(EXIT_FAILURE);
314     }
315     strncpy(dbpath, volinfo.v_dbpath, MAXPATHLEN - strlen("/.AppleDB"));
316     strcat(dbpath, "/.AppleDB");
317
318     ctrlfd = atoi(argv[optind + 1]);
319     clntfd = atoi(argv[optind + 2]);
320     logconfig = strdup(argv[optind + 3]);
321     setuplog(logconfig);
322
323     if (vol_load_charsets(&volinfo) == -1) {
324         LOG(log_error, logtype_cnid, "Error loading charsets!");
325         exit(EXIT_FAILURE);
326     }
327     LOG(log_debug, logtype_cnid, "db dir: \"%s\"", dbpath);
328
329     switch_to_user(dbpath);
330
331     /* Get db lock */
332     if ((db_locked = get_lock(LOCK_EXCL, dbpath)) == -1) {
333         LOG(log_error, logtype_cnid, "main: fatal db lock error");
334         exit(1);
335     }
336     if (db_locked != LOCK_EXCL) {
337         /* Couldn't get exclusive lock, try shared lock  */
338         if ((db_locked = get_lock(LOCK_SHRD, NULL)) != LOCK_SHRD) {
339             LOG(log_error, logtype_cnid, "main: fatal db lock error");
340             exit(1);
341         }
342     }
343
344     if (delete_bdb && (db_locked == LOCK_EXCL)) {
345         LOG(log_warning, logtype_cnid, "main: too many CNID db opening attempts, wiping the slate clean");
346         chdir(dbpath);
347         system("rm -f cnid2.db lock log.* __db.*");
348         if ((db_locked = get_lock(LOCK_EXCL, dbpath)) != LOCK_EXCL) {
349             LOG(log_error, logtype_cnid, "main: fatal db lock error");
350             exit(EXIT_FAILURE);
351         }
352     }
353
354     set_signal();
355
356     /* SIGINT and SIGTERM are always off, unless we are in pselect */
357     block_sigs_onoff(1);
358
359     if ((dbp = db_param_read(dbpath)) == NULL)
360         exit(1);
361     LOG(log_maxdebug, logtype_cnid, "Finished parsing db_param config file");
362
363     if (NULL == (dbd = dbif_init(dbpath, "cnid2.db")))
364         exit(2);
365
366     /* Only recover if we got the lock */
367     if (dbif_env_open(dbd,
368                       dbp,
369                       (db_locked == LOCK_EXCL) ? DBOPTIONS | DB_RECOVER : DBOPTIONS) < 0)
370         exit(2); /* FIXME: same exit code as failure for dbif_open() */
371     LOG(log_debug, logtype_cnid, "Finished initializing BerkeleyDB environment");
372
373     if (dbif_open(dbd, dbp, 0) < 0) {
374         dbif_close(dbd);
375         exit(2);
376     }
377     LOG(log_debug, logtype_cnid, "Finished opening BerkeleyDB databases");
378
379     /* Downgrade db lock  */
380     if (db_locked == LOCK_EXCL) {
381         if (get_lock(LOCK_UNLOCK, NULL) != 0) {
382             dbif_close(dbd);
383             exit(2);
384         }
385         if (get_lock(LOCK_SHRD, NULL) != LOCK_SHRD) {
386             dbif_close(dbd);
387             exit(2);
388         }
389     }
390
391
392     if (comm_init(dbp, ctrlfd, clntfd) < 0) {
393         dbif_close(dbd);
394         exit(3);
395     }
396
397     if (loop(dbp) < 0)
398         err++;
399
400     if (dbif_close(dbd) < 0)
401         err++;
402
403     if (dbif_env_remove(dbpath) < 0)
404         err++;
405
406     if (err)
407         exit(4);
408     else if (exit_sig)
409         LOG(log_info, logtype_cnid, "main: Exiting on signal %i", exit_sig);
410     else
411         LOG(log_info, logtype_cnid, "main: Idle timeout, exiting");
412
413     return 0;
414 }