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