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