]> arthur.barton.de Git - netatalk.git/blob - etc/cnid_dbd/main.c
Fix compiler diagnostics
[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/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     EC_EXIT;
234 }
235
236
237 /**
238  * Close dbd if open, delete it, reopen
239  *
240  * Also tries to copy the rootinfo key, that would allow for keeping the db stamp
241  * and last used CNID
242  **/
243 static int reinit_db(void)
244 {
245     EC_INIT;
246     DBT key, data;
247     bool copyRootInfo = false;
248
249     if (dbd) {
250         memset(&key, 0, sizeof(key));
251         memset(&data, 0, sizeof(data));
252
253         key.data = ROOTINFO_KEY;
254         key.size = ROOTINFO_KEYLEN;
255
256         if (dbif_get(dbd, DBIF_CNID, &key, &data, 0) <= 0) {
257             LOG(log_error, logtype_cnid, "dbif_copy_rootinfokey: Error getting rootinfo record");
258             copyRootInfo = false;
259         } else {
260             copyRootInfo = true;
261         }
262         (void)dbif_close(dbd);
263     }
264
265     EC_ZERO_LOG( delete_db() );
266     EC_ZERO_LOG( open_db() );
267
268     if (copyRootInfo == true) {
269         memset(&key, 0, sizeof(key));
270         key.data = ROOTINFO_KEY;
271         key.size = ROOTINFO_KEYLEN;
272
273         if (dbif_put(dbd, DBIF_CNID, &key, &data, 0) != 0) {
274             LOG(log_error, logtype_cnid, "dbif_copy_rootinfokey: Error writing rootinfo key");
275             EC_FAIL;
276         }
277     }
278
279 EC_CLEANUP:
280     EC_EXIT;
281 }
282
283 static int loop(struct db_param *dbp)
284 {
285     struct cnid_dbd_rqst rqst;
286     struct cnid_dbd_rply rply;
287     time_t timeout;
288     int ret, cret;
289     int count;
290     time_t now, time_next_flush, time_last_rqst;
291     char timebuf[64];
292     static char namebuf[MAXPATHLEN + 1];
293     sigset_t set;
294
295     sigemptyset(&set);
296     sigprocmask(SIG_SETMASK, NULL, &set);
297     sigdelset(&set, SIGINT);
298     sigdelset(&set, SIGTERM);
299
300     count = 0;
301     now = time(NULL);
302     time_next_flush = now + dbp->flush_interval;
303     time_last_rqst = now;
304
305     rqst.name = namebuf;
306
307     strftime(timebuf, 63, "%b %d %H:%M:%S.",localtime(&time_next_flush));
308     LOG(log_debug, logtype_cnid, "Checkpoint interval: %d seconds. Next checkpoint: %s",
309         dbp->flush_interval, timebuf);
310
311     while (1) {
312         timeout = MIN(time_next_flush, time_last_rqst + dbp->idle_timeout);
313         if (timeout > now)
314             timeout -= now;
315         else
316             timeout = 1;
317
318         if ((cret = comm_rcv(&rqst, timeout, &set, &now)) < 0)
319             return -1;
320
321         if (cret == 0) {
322             /* comm_rcv returned from select without receiving anything. */
323             if (exit_sig) {
324                 /* Received signal (TERM|INT) */
325                 return 0;
326             }
327             if (now - time_last_rqst >= dbp->idle_timeout && comm_nbe() <= 0) {
328                 /* Idle timeout */
329                 return 0;
330             }
331             /* still active connections, reset time_last_rqst */
332             time_last_rqst = now;
333         } else {
334             /* We got a request */
335             time_last_rqst = now;
336
337             memset(&rply, 0, sizeof(rply));
338             switch(rqst.op) {
339                 /* ret gets set here */
340             case CNID_DBD_OP_OPEN:
341             case CNID_DBD_OP_CLOSE:
342                 /* open/close are noops for now. */
343                 rply.namelen = 0;
344                 ret = 1;
345                 break;
346             case CNID_DBD_OP_ADD:
347                 ret = dbd_add(dbd, &rqst, &rply);
348                 break;
349             case CNID_DBD_OP_GET:
350                 ret = dbd_get(dbd, &rqst, &rply);
351                 break;
352             case CNID_DBD_OP_RESOLVE:
353                 ret = dbd_resolve(dbd, &rqst, &rply);
354                 break;
355             case CNID_DBD_OP_LOOKUP:
356                 ret = dbd_lookup(dbd, &rqst, &rply);
357                 break;
358             case CNID_DBD_OP_UPDATE:
359                 ret = dbd_update(dbd, &rqst, &rply);
360                 break;
361             case CNID_DBD_OP_DELETE:
362                 ret = dbd_delete(dbd, &rqst, &rply, DBIF_CNID);
363                 break;
364             case CNID_DBD_OP_GETSTAMP:
365                 ret = dbd_getstamp(dbd, &rqst, &rply);
366                 break;
367             case CNID_DBD_OP_REBUILD_ADD:
368                 ret = dbd_rebuild_add(dbd, &rqst, &rply);
369                 break;
370             case CNID_DBD_OP_SEARCH:
371                 ret = dbd_search(dbd, &rqst, &rply);
372                 break;
373             case CNID_DBD_OP_WIPE:
374                 ret = reinit_db();
375                 break;
376             default:
377                 LOG(log_error, logtype_cnid, "loop: unknown op %d", rqst.op);
378                 ret = -1;
379                 break;
380             }
381
382             if ((cret = comm_snd(&rply)) < 0 || ret < 0) {
383                 dbif_txn_abort(dbd);
384                 return -1;
385             }
386             
387             if (ret == 0 || cret == 0) {
388                 if (dbif_txn_abort(dbd) < 0)
389                     return -1;
390             } else {
391                 ret = dbif_txn_commit(dbd);
392                 if (  ret < 0)
393                     return -1;
394                 else if ( ret > 0 )
395                     /* We had a designated txn because we wrote to the db */
396                     count++;
397             }
398         } /* got a request */
399
400         /*
401           Shall we checkpoint bdb ?
402           "flush_interval" seconds passed ?
403         */
404         if (now >= time_next_flush) {
405             LOG(log_info, logtype_cnid, "Checkpointing BerkeleyDB for volume '%s'", dbp->dir);
406             if (dbif_txn_checkpoint(dbd, 0, 0, 0) < 0)
407                 return -1;
408             count = 0;
409             time_next_flush = now + dbp->flush_interval;
410
411             strftime(timebuf, 63, "%b %d %H:%M:%S.",localtime(&time_next_flush));
412             LOG(log_debug, logtype_cnid, "Checkpoint interval: %d seconds. Next checkpoint: %s",
413                 dbp->flush_interval, timebuf);
414         }
415
416         /* 
417            Shall we checkpoint bdb ?
418            Have we commited "count" more changes than "flush_frequency" ?
419         */
420         if (count > dbp->flush_frequency) {
421             LOG(log_info, logtype_cnid, "Checkpointing BerkeleyDB after %d writes for volume '%s'", count, dbp->dir);
422             if (dbif_txn_checkpoint(dbd, 0, 0, 0) < 0)
423                 return -1;
424             count = 0;
425         }
426     } /* while(1) */
427 }
428
429 /* ------------------------ */
430 static void switch_to_user(char *dir)
431 {
432     struct stat st;
433
434     if (chdir(dir) < 0) {
435         LOG(log_error, logtype_cnid, "chdir to %s failed: %s", dir, strerror(errno));
436         exit(1);
437     }
438
439     if (stat(".", &st) < 0) {
440         LOG(log_error, logtype_cnid, "error in stat for %s: %s", dir, strerror(errno));
441         exit(1);
442     }
443     if (!getuid()) {
444         LOG(log_debug, logtype_cnid, "Setting uid/gid to %i/%i", st.st_uid, st.st_gid);
445         if (setgid(st.st_gid) < 0 || setuid(st.st_uid) < 0) {
446             LOG(log_error, logtype_cnid, "uid/gid: %s", strerror(errno));
447             exit(1);
448         }
449     }
450 }
451
452
453 /* ----------------------- */
454 static void set_signal(void)
455 {
456     struct sigaction sv;
457
458     sv.sa_handler = sig_exit;
459     sv.sa_flags = 0;
460     sigemptyset(&sv.sa_mask);
461     sigaddset(&sv.sa_mask, SIGINT);
462     sigaddset(&sv.sa_mask, SIGTERM);
463     if (sigaction(SIGINT, &sv, NULL) < 0 || sigaction(SIGTERM, &sv, NULL) < 0) {
464         LOG(log_error, logtype_cnid, "main: sigaction: %s", strerror(errno));
465         exit(1);
466     }
467     sv.sa_handler = SIG_IGN;
468     sigemptyset(&sv.sa_mask);
469     if (sigaction(SIGPIPE, &sv, NULL) < 0) {
470         LOG(log_error, logtype_cnid, "main: sigaction: %s", strerror(errno));
471         exit(1);
472     }
473 }
474
475 /* ------------------------ */
476 int main(int argc, char *argv[])
477 {
478     EC_INIT;
479     int delete_bdb = 0;
480     int ctrlfd = -1, clntfd = -1;
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 }