]> arthur.barton.de Git - netatalk.git/blob - etc/cnid_dbd/main.c
Add a flag to load_volumes() that controls load behaviour
[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_bdb_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
45 static DBD *dbd;
46 static int exit_sig = 0;
47 static int db_locked;
48 static bstring dbpath;
49 static struct db_param *dbp;
50 static struct vol *vol;
51
52 static void sig_exit(int signo)
53 {
54     exit_sig = signo;
55     return;
56 }
57
58 static 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  */
100 static 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
178 static 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
202 EC_CLEANUP:
203     if (ret != 0) {
204         if (dbd) {
205             (void)dbif_close(dbd);
206             dbd = NULL;
207         }
208     }
209
210     EC_EXIT;
211 }
212
213 static 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
230 EC_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  **/
245 static 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
281 EC_CLEANUP:
282     EC_EXIT;
283 }
284
285 static 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 /* ------------------------ */
432 static 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 /* ----------------------- */
456 static 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 /* ------------------------ */
478 int 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, lv_all) );
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
554 close_db:
555     if (dbif_close(dbd) < 0)
556         ret = -1;
557
558     if (dbif_env_remove(bdata(dbpath)) < 0)
559         ret = -1;
560
561 EC_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 }