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/bstradd.h>
30#include <atalk/netatalk_conf.h>
31#include <atalk/util.h>
32
33#include "db_param.h"
34#include "dbif.h"
35#include "dbd.h"
36#include "comm.h"
37#include "pack.h"
38
39/*
40   Note: DB_INIT_LOCK is here so we can run the db_* utilities while netatalk is running.
41   It's a likey performance hit, but it might we worth it.
42 */
43#define DBOPTIONS (DB_CREATE | DB_INIT_LOG | DB_INIT_MPOOL | DB_INIT_LOCK | DB_INIT_TXN)
44
45static DBD *dbd;
46static int exit_sig = 0;
47static int db_locked;
48static bstring dbpath;
49static struct db_param *dbp;
50static struct vol *vol;
51
52static void sig_exit(int signo)
53{
54    exit_sig = signo;
55    return;
56}
57
58static void block_sigs_onoff(int block)
59{
60    sigset_t set;
61
62    sigemptyset(&set);
63    sigaddset(&set, SIGINT);
64    sigaddset(&set, SIGTERM);
65    if (block)
66        sigprocmask(SIG_BLOCK, &set, NULL);
67    else
68        sigprocmask(SIG_UNBLOCK, &set, NULL);
69    return;
70}
71
72/*
73  The dbd_XXX and comm_XXX functions all obey the same protocol for return values:
74
75  1: Success, if transactions are used commit.
76  0: Failure, but we continue to serve requests. If transactions are used abort/rollback.
77  -1: Fatal error, either from t
78  he database or from the socket. Abort the transaction if applicable
79  (which might fail as well) and then exit.
80
81  We always try to notify the client process about the outcome, the result field
82  of the cnid_dbd_rply structure contains further details.
83
84*/
85
86/*!
87 * Get lock on db lock file
88 *
89 * @args cmd       (r) lock command:
90 *                     LOCK_FREE:   close lockfd
91 *                     LOCK_UNLOCK: unlock lockm keep lockfd open
92 *                     LOCK_EXCL:   F_WRLCK on lockfd
93 *                     LOCK_SHRD:   F_RDLCK on lockfd
94 * @args dbpath    (r) path to lockfile, only used on first call,
95 *                     later the stored fd is used
96 * @returns            LOCK_FREE/LOCK_UNLOCK return 0 on success, -1 on error
97 *                     LOCK_EXCL/LOCK_SHRD return LOCK_EXCL or LOCK_SHRD respectively on
98 *                     success, 0 if the lock couldn't be acquired, -1 on other errors
99 */
100static int get_lock(int cmd, const char *dbpath)
101{
102    static int lockfd = -1;
103    int ret;
104    char lockpath[PATH_MAX];
105    struct stat st;
106
107    LOG(log_debug, logtype_cnid, "get_lock(%s, \"%s\")",
108        cmd == LOCK_EXCL ? "LOCK_EXCL" :
109        cmd == LOCK_SHRD ? "LOCK_SHRD" :
110        cmd == LOCK_FREE ? "LOCK_FREE" :
111        cmd == LOCK_UNLOCK ? "LOCK_UNLOCK" : "UNKNOWN",
112        dbpath ? dbpath : "");
113
114    switch (cmd) {
115    case LOCK_FREE:
116        if (lockfd == -1)
117            return -1;
118        close(lockfd);
119        lockfd = -1;
120        return 0;
121
122    case LOCK_UNLOCK:
123        if (lockfd == -1)
124            return -1;
125        return unlock(lockfd, 0, SEEK_SET, 0);
126
127    case LOCK_EXCL:
128    case LOCK_SHRD:
129        if (lockfd == -1) {
130            if ( (strlen(dbpath) + strlen(LOCKFILENAME+1)) > (PATH_MAX - 1) ) {
131                LOG(log_error, logtype_cnid, ".AppleDB pathname too long");
132                return -1;
133            }
134            strncpy(lockpath, dbpath, PATH_MAX - 1);
135            strcat(lockpath, "/");
136            strcat(lockpath, LOCKFILENAME);
137
138            if ((lockfd = open(lockpath, O_RDWR | O_CREAT, 0644)) < 0) {
139                LOG(log_error, logtype_cnid, "Error opening lockfile: %s", strerror(errno));
140                return -1;
141            }
142
143            if ((stat(dbpath, &st)) != 0) {
144                LOG(log_error, logtype_cnid, "Error statting lockfile: %s", strerror(errno));
145                return -1;
146            }
147
148            if ((chown(lockpath, st.st_uid, st.st_gid)) != 0) {
149                LOG(log_error, logtype_cnid, "Error inheriting lockfile permissions: %s",
150                         strerror(errno));
151                return -1;
152            }
153        }
154
155        if (cmd == LOCK_EXCL)
156            ret = write_lock(lockfd, 0, SEEK_SET, 0);
157        else
158            ret = read_lock(lockfd, 0, SEEK_SET, 0);
159
160        if (ret != 0) {
161            if (cmd == LOCK_SHRD)
162                LOG(log_error, logtype_cnid, "Volume CNID db is locked, try again...");
163            return 0;
164        }
165
166        LOG(log_debug, logtype_cnid, "get_lock: got %s lock",
167            cmd == LOCK_EXCL ? "LOCK_EXCL" : "LOCK_SHRD");
168        return cmd;
169
170    default:
171        return -1;
172    } /* switch(cmd) */
173
174    /* deadc0de, never get here */
175    return -1;
176}
177
178static int open_db(void)
179{
180    EC_INIT;
181
182    /* Get db lock */
183    if ((db_locked = get_lock(LOCK_EXCL, bdata(dbpath))) != LOCK_EXCL) {
184        LOG(log_error, logtype_cnid, "main: fatal db lock error");
185        EC_FAIL;
186    }
187
188    if (NULL == (dbd = dbif_init(bdata(dbpath), "cnid2.db")))
189        EC_FAIL;
190
191    /* Only recover if we got the lock */
192    if (dbif_env_open(dbd, dbp, DBOPTIONS | DB_RECOVER) < 0)
193        EC_FAIL;
194
195    LOG(log_debug, logtype_cnid, "Finished initializing BerkeleyDB environment");
196
197    if (dbif_open(dbd, dbp, 0) < 0)
198        EC_FAIL;
199
200    LOG(log_debug, logtype_cnid, "Finished opening BerkeleyDB databases");
201
202EC_CLEANUP:
203    if (ret != 0) {
204        if (dbd) {
205            (void)dbif_close(dbd);
206            dbd = NULL;
207        }
208    }
209
210    EC_EXIT;
211}
212
213static int delete_db(void)
214{
215    EC_INIT;
216    int cwd = -1;
217
218    EC_ZERO( get_lock(LOCK_FREE, bdata(dbpath)) );
219    EC_NEG1( cwd = open(".", O_RDONLY) );
220    chdir(cfrombstr(dbpath));
221    system("rm -f cnid2.db lock log.* __db.*");
222
223    if ((db_locked = get_lock(LOCK_EXCL, bdata(dbpath))) != LOCK_EXCL) {
224        LOG(log_error, logtype_cnid, "main: fatal db lock error");
225        EC_FAIL;
226    }
227
228    LOG(log_warning, logtype_cnid, "Recreated CNID BerkeleyDB databases of volume \"%s\"", vol->v_localname);
229
230EC_CLEANUP:
231    if (cwd != -1) {
232        fchdir(cwd);
233        close(cwd);
234    }
235    EC_EXIT;
236}
237
238
239/**
240 * Close dbd if open, delete it, reopen
241 *
242 * Also tries to copy the rootinfo key, that would allow for keeping the db stamp
243 * and last used CNID
244 **/
245static int reinit_db(void)
246{
247    EC_INIT;
248    DBT key, data;
249    bool copyRootInfo = false;
250
251    if (dbd) {
252        memset(&key, 0, sizeof(key));
253        memset(&data, 0, sizeof(data));
254
255        key.data = ROOTINFO_KEY;
256        key.size = ROOTINFO_KEYLEN;
257
258        if (dbif_get(dbd, DBIF_CNID, &key, &data, 0) <= 0) {
259            LOG(log_error, logtype_cnid, "dbif_copy_rootinfokey: Error getting rootinfo record");
260            copyRootInfo = false;
261        } else {
262            copyRootInfo = true;
263        }
264        (void)dbif_close(dbd);
265    }
266
267    EC_ZERO_LOG( delete_db() );
268    EC_ZERO_LOG( open_db() );
269
270    if (copyRootInfo == true) {
271        memset(&key, 0, sizeof(key));
272        key.data = ROOTINFO_KEY;
273        key.size = ROOTINFO_KEYLEN;
274
275        if (dbif_put(dbd, DBIF_CNID, &key, &data, 0) != 0) {
276            LOG(log_error, logtype_cnid, "dbif_copy_rootinfokey: Error writing rootinfo key");
277            EC_FAIL;
278        }
279    }
280
281EC_CLEANUP:
282    EC_EXIT;
283}
284
285static int loop(struct db_param *dbp)
286{
287    struct cnid_dbd_rqst rqst;
288    struct cnid_dbd_rply rply;
289    time_t timeout;
290    int ret, cret;
291    int count;
292    time_t now, time_next_flush, time_last_rqst;
293    char timebuf[64];
294    static char namebuf[MAXPATHLEN + 1];
295    sigset_t set;
296
297    sigemptyset(&set);
298    sigprocmask(SIG_SETMASK, NULL, &set);
299    sigdelset(&set, SIGINT);
300    sigdelset(&set, SIGTERM);
301
302    count = 0;
303    now = time(NULL);
304    time_next_flush = now + dbp->flush_interval;
305    time_last_rqst = now;
306
307    rqst.name = namebuf;
308
309    strftime(timebuf, 63, "%b %d %H:%M:%S.",localtime(&time_next_flush));
310    LOG(log_debug, logtype_cnid, "Checkpoint interval: %d seconds. Next checkpoint: %s",
311        dbp->flush_interval, timebuf);
312
313    while (1) {
314        timeout = MIN(time_next_flush, time_last_rqst + dbp->idle_timeout);
315        if (timeout > now)
316            timeout -= now;
317        else
318            timeout = 1;
319
320        if ((cret = comm_rcv(&rqst, timeout, &set, &now)) < 0)
321            return -1;
322
323        if (cret == 0) {
324            /* comm_rcv returned from select without receiving anything. */
325            if (exit_sig) {
326                /* Received signal (TERM|INT) */
327                return 0;
328            }
329            if (now - time_last_rqst >= dbp->idle_timeout && comm_nbe() <= 0) {
330                /* Idle timeout */
331                return 0;
332            }
333            /* still active connections, reset time_last_rqst */
334            time_last_rqst = now;
335        } else {
336            /* We got a request */
337            time_last_rqst = now;
338
339            memset(&rply, 0, sizeof(rply));
340            switch(rqst.op) {
341                /* ret gets set here */
342            case CNID_DBD_OP_OPEN:
343            case CNID_DBD_OP_CLOSE:
344                /* open/close are noops for now. */
345                rply.namelen = 0;
346                ret = 1;
347                break;
348            case CNID_DBD_OP_ADD:
349                ret = dbd_add(dbd, &rqst, &rply);
350                break;
351            case CNID_DBD_OP_GET:
352                ret = dbd_get(dbd, &rqst, &rply);
353                break;
354            case CNID_DBD_OP_RESOLVE:
355                ret = dbd_resolve(dbd, &rqst, &rply);
356                break;
357            case CNID_DBD_OP_LOOKUP:
358                ret = dbd_lookup(dbd, &rqst, &rply);
359                break;
360            case CNID_DBD_OP_UPDATE:
361                ret = dbd_update(dbd, &rqst, &rply);
362                break;
363            case CNID_DBD_OP_DELETE:
364                ret = dbd_delete(dbd, &rqst, &rply, DBIF_CNID);
365                break;
366            case CNID_DBD_OP_GETSTAMP:
367                ret = dbd_getstamp(dbd, &rqst, &rply);
368                break;
369            case CNID_DBD_OP_REBUILD_ADD:
370                ret = dbd_rebuild_add(dbd, &rqst, &rply);
371                break;
372            case CNID_DBD_OP_SEARCH:
373                ret = dbd_search(dbd, &rqst, &rply);
374                break;
375            case CNID_DBD_OP_WIPE:
376                ret = reinit_db();
377                break;
378            default:
379                LOG(log_error, logtype_cnid, "loop: unknown op %d", rqst.op);
380                ret = -1;
381                break;
382            }
383
384            if ((cret = comm_snd(&rply)) < 0 || ret < 0) {
385                dbif_txn_abort(dbd);
386                return -1;
387            }
388
389            if (ret == 0 || cret == 0) {
390                if (dbif_txn_abort(dbd) < 0)
391                    return -1;
392            } else {
393                ret = dbif_txn_commit(dbd);
394                if (  ret < 0)
395                    return -1;
396                else if ( ret > 0 )
397                    /* We had a designated txn because we wrote to the db */
398                    count++;
399            }
400        } /* got a request */
401
402        /*
403          Shall we checkpoint bdb ?
404          "flush_interval" seconds passed ?
405        */
406        if (now >= time_next_flush) {
407            LOG(log_info, logtype_cnid, "Checkpointing BerkeleyDB for volume '%s'", dbp->dir);
408            if (dbif_txn_checkpoint(dbd, 0, 0, 0) < 0)
409                return -1;
410            count = 0;
411            time_next_flush = now + dbp->flush_interval;
412
413            strftime(timebuf, 63, "%b %d %H:%M:%S.",localtime(&time_next_flush));
414            LOG(log_debug, logtype_cnid, "Checkpoint interval: %d seconds. Next checkpoint: %s",
415                dbp->flush_interval, timebuf);
416        }
417
418        /*
419           Shall we checkpoint bdb ?
420           Have we commited "count" more changes than "flush_frequency" ?
421        */
422        if (count > dbp->flush_frequency) {
423            LOG(log_info, logtype_cnid, "Checkpointing BerkeleyDB after %d writes for volume '%s'", count, dbp->dir);
424            if (dbif_txn_checkpoint(dbd, 0, 0, 0) < 0)
425                return -1;
426            count = 0;
427        }
428    } /* while(1) */
429}
430
431/* ------------------------ */
432static void switch_to_user(char *dir)
433{
434    struct stat st;
435
436    if (chdir(dir) < 0) {
437        LOG(log_error, logtype_cnid, "chdir to %s failed: %s", dir, strerror(errno));
438        exit(1);
439    }
440
441    if (stat(".", &st) < 0) {
442        LOG(log_error, logtype_cnid, "error in stat for %s: %s", dir, strerror(errno));
443        exit(1);
444    }
445    if (!getuid()) {
446        LOG(log_debug, logtype_cnid, "Setting uid/gid to %i/%i", st.st_uid, st.st_gid);
447        if (setgid(st.st_gid) < 0 || setuid(st.st_uid) < 0) {
448            LOG(log_error, logtype_cnid, "uid/gid: %s", strerror(errno));
449            exit(1);
450        }
451    }
452}
453
454
455/* ----------------------- */
456static void set_signal(void)
457{
458    struct sigaction sv;
459
460    sv.sa_handler = sig_exit;
461    sv.sa_flags = 0;
462    sigemptyset(&sv.sa_mask);
463    sigaddset(&sv.sa_mask, SIGINT);
464    sigaddset(&sv.sa_mask, SIGTERM);
465    if (sigaction(SIGINT, &sv, NULL) < 0 || sigaction(SIGTERM, &sv, NULL) < 0) {
466        LOG(log_error, logtype_cnid, "main: sigaction: %s", strerror(errno));
467        exit(1);
468    }
469    sv.sa_handler = SIG_IGN;
470    sigemptyset(&sv.sa_mask);
471    if (sigaction(SIGPIPE, &sv, NULL) < 0) {
472        LOG(log_error, logtype_cnid, "main: sigaction: %s", strerror(errno));
473        exit(1);
474    }
475}
476
477/* ------------------------ */
478int main(int argc, char *argv[])
479{
480    EC_INIT;
481    int delete_bdb = 0;
482    int ctrlfd = -1, clntfd = -1;
483    AFPObj obj = { 0 };
484    char *volpath = NULL;
485
486    while (( ret = getopt( argc, argv, "dF:l:p:t:vV")) != -1 ) {
487        switch (ret) {
488        case 'd':
489            /* this is now just ignored, as we do it automatically anyway */
490            delete_bdb = 1;
491            break;
492        case 'F':
493            obj.cmdlineconfigfile = strdup(optarg);
494            break;
495        case 'p':
496            volpath = strdup(optarg);
497            break;
498        case 'l':
499            clntfd = atoi(optarg);
500            break;
501        case 't':
502            ctrlfd = atoi(optarg);
503            break;
504        case 'v':
505        case 'V':
506            printf("cnid_dbd (Netatalk %s)\n", VERSION);
507            return -1;
508        }
509    }
510
511    if (ctrlfd == -1 || clntfd == -1 || !volpath) {
512        LOG(log_error, logtype_cnid, "main: bad IPC fds");
513        exit(EXIT_FAILURE);
514    }
515
516    EC_ZERO( afp_config_parse(&obj, "cnid_dbd") );
517
518    EC_ZERO( load_volumes(&obj) );
519    EC_NULL( vol = getvolbypath(&obj, volpath) );
520    EC_ZERO( load_charset(vol) );
521    pack_setvol(vol);
522
523    EC_NULL( dbpath = bfromcstr(vol->v_dbpath) );
524    EC_ZERO( bcatcstr(dbpath, "/.AppleDB") );
525
526    LOG(log_debug, logtype_cnid, "db dir: \"%s\"", bdata(dbpath));
527
528    switch_to_user(bdata(dbpath));
529
530    set_signal();
531
532    /* SIGINT and SIGTERM are always off, unless we are in pselect */
533    block_sigs_onoff(1);
534
535    if ((dbp = db_param_read(bdata(dbpath))) == NULL)
536        EC_FAIL;
537    LOG(log_maxdebug, logtype_cnid, "Finished parsing db_param config file");
538
539    if (open_db() != 0) {
540        LOG(log_error, logtype_cnid, "Failed to open CNID database for volume \"%s\"", vol->v_localname);
541        EC_ZERO_LOG( reinit_db() );
542    }
543
544    if (comm_init(dbp, ctrlfd, clntfd) < 0) {
545        ret = -1;
546        goto close_db;
547    }
548
549    if (loop(dbp) < 0) {
550        ret = -1;
551        goto close_db;
552    }
553
554close_db:
555    if (dbif_close(dbd) < 0)
556        ret = -1;
557
558    if (dbif_env_remove(bdata(dbpath)) < 0)
559        ret = -1;
560
561EC_CLEANUP:
562    if (ret != 0)
563        exit(1);
564
565    if (exit_sig)
566        LOG(log_info, logtype_cnid, "main: Exiting on signal %i", exit_sig);
567    else
568        LOG(log_info, logtype_cnid, "main: Idle timeout, exiting");
569
570    EC_EXIT;
571}
572