]> arthur.barton.de Git - netatalk.git/blob - etc/cnid_dbd/main.c
eb99102668dc0d1a1f34ea54a7145faa0de12695
[netatalk.git] / etc / cnid_dbd / main.c
1 /*
2  * $Id: main.c,v 1.13 2009-10-19 05:38:22 didg Exp $
3  *
4  * Copyright (C) Joerg Lenneis 2003
5  * Copyright (c) Frank Lahm 2009
6  * All Rights Reserved.  See COPYING.
7  */
8
9 #ifdef HAVE_CONFIG_H
10 #include "config.h"
11 #endif /* HAVE_CONFIG_H */
12
13 #ifdef HAVE_UNISTD_H
14 #include <unistd.h>
15 #endif /* HAVE_UNISTD_H */
16 #ifdef HAVE_FCNTL_H
17 #include <fcntl.h>
18 #endif /* HAVE_FCNTL_H */
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <errno.h>
22 #include <signal.h>
23 #include <string.h>
24 #ifdef HAVE_SYS_TYPES_H
25 #include <sys/types.h>
26 #endif /* HAVE_SYS_TYPES_H */
27 #include <sys/param.h>
28 #ifdef HAVE_SYS_STAT_H
29 #include <sys/stat.h>
30 #endif /* HAVE_SYS_STAT_H */
31 #include <time.h>
32 #include <sys/file.h>
33
34 #include <netatalk/endian.h>
35 #include <atalk/cnid_dbd_private.h>
36 #include <atalk/logger.h>
37
38 #include "db_param.h"
39 #include "dbif.h"
40 #include "dbd.h"
41 #include "comm.h"
42
43 #define LOCKFILENAME  "lock"
44
45 /* 
46    Note: DB_INIT_LOCK is here so we can run the db_* utilities while netatalk is running.
47    It's a likey performance hit, but it might we worth it.
48  */
49 #define DBOPTIONS (DB_CREATE | DB_INIT_LOG | DB_INIT_MPOOL | DB_INIT_LOCK | DB_INIT_TXN | DB_RECOVER)
50
51 static DBD *dbd;
52
53 static int exit_sig = 0;
54
55 static void sig_exit(int signo)
56 {
57     exit_sig = signo;
58     return;
59 }
60
61 static 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
92 static 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)) < 0)
128             return -1;
129
130         now = time(NULL);
131
132         if (cret == 0) {
133             /* comm_rcv returned from select without receiving anything. */
134             if (exit_sig)
135                 /* Received signal (TERM|INT) */
136                 return 0;
137             if (now - time_last_rqst > dbp->idle_timeout) {
138                 if (comm_nbe() <= 0) {
139                     /* Idle timeout */
140                     return 0;
141                 }
142                 else {
143                     /* still active connections, reset time_last_rqst */
144                     time_last_rqst = now;
145                 }
146             }
147         } else {
148             /* We got a request */
149             time_last_rqst = now;
150
151             memset(&rply, 0, sizeof(rply));
152             switch(rqst.op) {
153                 /* ret gets set here */
154             case CNID_DBD_OP_OPEN:
155             case CNID_DBD_OP_CLOSE:
156                 /* open/close are noops for now. */
157                 rply.namelen = 0;
158                 ret = 1;
159                 break;
160             case CNID_DBD_OP_ADD:
161                 ret = dbd_add(dbd, &rqst, &rply);
162                 break;
163             case CNID_DBD_OP_GET:
164                 ret = dbd_get(dbd, &rqst, &rply);
165                 break;
166             case CNID_DBD_OP_RESOLVE:
167                 ret = dbd_resolve(dbd, &rqst, &rply);
168                 break;
169             case CNID_DBD_OP_LOOKUP:
170                 ret = dbd_lookup(dbd, &rqst, &rply);
171                 break;
172             case CNID_DBD_OP_UPDATE:
173                 ret = dbd_update(dbd, &rqst, &rply);
174                 break;
175             case CNID_DBD_OP_DELETE:
176                 ret = dbd_delete(dbd, &rqst, &rply, DBIF_CNID);
177                 break;
178             case CNID_DBD_OP_GETSTAMP:
179                 ret = dbd_getstamp(dbd, &rqst, &rply);
180                 break;
181             case CNID_DBD_OP_REBUILD_ADD:
182                 ret = dbd_rebuild_add(dbd, &rqst, &rply);
183                 break;
184             default:
185                 LOG(log_error, logtype_cnid, "loop: unknown op %d", rqst.op);
186                 ret = -1;
187                 break;
188             }
189             
190             if ((cret = comm_snd(&rply)) < 0 || ret < 0) {
191                 dbif_txn_abort(dbd);
192                 return -1;
193             }
194             
195             if (ret == 0 || cret == 0) {
196                 if (dbif_txn_abort(dbd) < 0)
197                     return -1;
198             } else {
199                 ret = dbif_txn_commit(dbd);
200                 if (  ret < 0)
201                     return -1;
202                 else if ( ret > 0 )
203                     /* We had a designated txn because we wrote to the db */
204                     count++;
205             }
206         } /* got a request */
207
208         /*
209           Shall we checkpoint bdb ?
210           "flush_interval" seconds passed ?
211         */
212         if (now > time_next_flush) {
213             LOG(log_info, logtype_cnid, "Checkpointing BerkeleyDB for volume '%s'", dbp->dir);
214             if (dbif_txn_checkpoint(dbd, 0, 0, 0) < 0)
215                 return -1;
216             count = 0;
217             time_next_flush = now + dbp->flush_interval;
218
219             strftime(timebuf, 63, "%b %d %H:%M:%S.",localtime(&time_next_flush));
220             LOG(log_debug, logtype_cnid, "Checkpoint interval: %d seconds. Next checkpoint: %s",
221                 dbp->flush_interval, timebuf);
222         }
223
224         /* 
225            Shall we checkpoint bdb ?
226            Have we commited "count" more changes than "flush_frequency" ?
227         */
228         if (count > dbp->flush_frequency) {
229             LOG(log_info, logtype_cnid, "Checkpointing BerkeleyDB after %d writes for volume '%s'", count, dbp->dir);
230             if (dbif_txn_checkpoint(dbd, 0, 0, 0) < 0)
231                 return -1;
232             count = 0;
233         }
234     } /* while(1) */
235 }
236
237 /* ------------------------ */
238 static void switch_to_user(char *dir)
239 {
240     struct stat st;
241
242     if (chdir(dir) < 0) {
243         LOG(log_error, logtype_cnid, "chdir to %s failed: %s", dir, strerror(errno));
244         exit(1);
245     }
246
247     if (stat(".", &st) < 0) {
248         LOG(log_error, logtype_cnid, "error in stat for %s: %s", dir, strerror(errno));
249         exit(1);
250     }
251     if (!getuid()) {
252         LOG(log_info, logtype_cnid, "Setting uid/gid to %i/%i", st.st_uid, st.st_gid);
253         if (setgid(st.st_gid) < 0 || setuid(st.st_uid) < 0) {
254             LOG(log_error, logtype_cnid, "uid/gid: %s", strerror(errno));
255             exit(1);
256         }
257     }
258 }
259
260 /* ------------------------ */
261 static int get_lock(void)
262 {
263     int lockfd;
264     struct flock lock;
265
266     if ((lockfd = open(LOCKFILENAME, O_RDWR | O_CREAT, 0644)) < 0) {
267         LOG(log_error, logtype_cnid, "main: error opening lockfile: %s", strerror(errno));
268         exit(1);
269     }
270
271     lock.l_start  = 0;
272     lock.l_whence = SEEK_SET;
273     lock.l_len    = 0;
274     lock.l_type   = F_WRLCK;
275
276     if (fcntl(lockfd, F_SETLK, &lock) < 0) {
277         if (errno == EACCES || errno == EAGAIN) {
278             exit(0);
279         } else {
280             LOG(log_error, logtype_cnid, "main: fcntl F_WRLCK lockfile: %s", strerror(errno));
281             exit(1);
282         }
283     }
284
285     return lockfd;
286 }
287
288 /* ----------------------- */
289 static void set_signal(void)
290 {
291     struct sigaction sv;
292
293     sv.sa_handler = sig_exit;
294     sv.sa_flags = 0;
295     sigemptyset(&sv.sa_mask);
296     sigaddset(&sv.sa_mask, SIGINT);
297     sigaddset(&sv.sa_mask, SIGTERM);
298     if (sigaction(SIGINT, &sv, NULL) < 0 || sigaction(SIGTERM, &sv, NULL) < 0) {
299         LOG(log_error, logtype_cnid, "main: sigaction: %s", strerror(errno));
300         exit(1);
301     }
302     sv.sa_handler = SIG_IGN;
303     sigemptyset(&sv.sa_mask);
304     if (sigaction(SIGPIPE, &sv, NULL) < 0) {
305         LOG(log_error, logtype_cnid, "main: sigaction: %s", strerror(errno));
306         exit(1);
307     }
308 }
309
310 /* ----------------------- */
311 static void free_lock(int lockfd)
312 {
313     struct flock lock;
314
315     lock.l_start  = 0;
316     lock.l_whence = SEEK_SET;
317     lock.l_len    = 0;
318     lock.l_type = F_UNLCK;
319     fcntl(lockfd, F_SETLK, &lock);
320     close(lockfd);
321 }
322
323 /* ------------------------ */
324 int main(int argc, char *argv[])
325 {
326     struct db_param *dbp;
327     int err = 0;
328     int lockfd, ctrlfd, clntfd;
329     char *dir, *logconfig;
330
331     set_processname("cnid_dbd");
332
333     /* FIXME: implement -d from cnid_metad */
334     if (argc  != 5) {
335         LOG(log_error, logtype_cnid, "main: not enough arguments");
336         exit(1);
337     }
338
339     dir = argv[1];
340     ctrlfd = atoi(argv[2]);
341     clntfd = atoi(argv[3]);
342     logconfig = strdup(argv[4]);
343     setuplog(logconfig);
344
345     switch_to_user(dir);
346
347     /* Before we do anything else, check if there is an instance of cnid_dbd
348        running already and silently exit if yes. */
349     lockfd = get_lock();
350
351     LOG(log_info, logtype_cnid, "Startup, DB dir %s", dir);
352
353     set_signal();
354
355     /* SIGINT and SIGTERM are always off, unless we are in pselect */
356     block_sigs_onoff(1);
357
358     if ((dbp = db_param_read(dir, CNID_DBD)) == NULL)
359         exit(1);
360     LOG(log_maxdebug, logtype_cnid, "Finished parsing db_param config file");
361
362     if (NULL == (dbd = dbif_init(".", "cnid2.db")))
363         exit(2);
364
365     if (dbif_env_open(dbd, dbp, DBOPTIONS) < 0)
366         exit(2); /* FIXME: same exit code as failure for dbif_open() */
367     LOG(log_debug, logtype_cnid, "Finished initializing BerkeleyDB environment");
368
369     if (dbif_open(dbd, dbp, 0) < 0) {
370         dbif_close(dbd);
371         exit(2);
372     }
373     LOG(log_debug, logtype_cnid, "Finished opening BerkeleyDB databases");
374
375     if (dbd_stamp(dbd) < 0) {
376         dbif_close(dbd);
377         exit(5);
378     }
379     LOG(log_maxdebug, logtype_cnid, "Finished checking database stamp");
380
381     if (comm_init(dbp, ctrlfd, clntfd) < 0) {
382         dbif_close(dbd);
383         exit(3);
384     }
385
386     if (loop(dbp) < 0)
387         err++;
388
389     if (dbif_close(dbd) < 0)
390         err++;
391
392     if (dbif_prep_upgrade(dir) < 0)
393         err++;
394
395     free_lock(lockfd);
396
397     if (err)
398         exit(4);
399     else if (exit_sig)
400         LOG(log_info, logtype_cnid, "main: Exiting on signal %i", exit_sig);
401     else
402         LOG(log_info, logtype_cnid, "main: Idle timeout, exiting");
403
404     return 0;
405 }