]> arthur.barton.de Git - netatalk.git/blob - etc/cnid_dbd/main.c
d1dde1bbf640bcd0ebd3c1f647c8df71dddb7c18
[netatalk.git] / etc / cnid_dbd / main.c
1 /*
2  * $Id: main.c,v 1.4 2009-04-21 08:55:44 franklahm Exp $
3  *
4  * Copyright (C) Joerg Lenneis 2003
5  * Copyright (c) Frank Lahm 2009
6  * All Rights Reserved.  See COPYING.
7  */
8
9 /* 
10    dbd and transactions
11    ====================
12
13    We use AUTO_COMMIT for our BerkeleyDB environment. This avoids explicit transactions
14    for every bdb access which speeds up reads. But in order to be able to rollback
15    in case of errors we start a transaction once we encounter the first write.
16    The logic to do this is stuffed two levels lower into the dbif.c file and functions
17    dbif_put and dbif_del.
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #include "config.h"
22 #endif /* HAVE_CONFIG_H */
23
24 #ifdef HAVE_UNISTD_H
25 #include <unistd.h>
26 #endif /* HAVE_UNISTD_H */
27 #ifdef HAVE_FCNTL_H
28 #include <fcntl.h>
29 #endif /* HAVE_FCNTL_H */
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <errno.h>
33 #include <signal.h>
34 #include <string.h>
35 #ifdef HAVE_SYS_TYPES_H
36 #include <sys/types.h>
37 #endif /* HAVE_SYS_TYPES_H */
38 #include <sys/param.h>
39 #ifdef HAVE_SYS_STAT_H
40 #include <sys/stat.h>
41 #endif /* HAVE_SYS_STAT_H */
42 #include <time.h>
43 #include <sys/file.h>
44
45 #include <netatalk/endian.h>
46 #include <atalk/cnid_dbd_private.h>
47 #include <atalk/logger.h>
48
49 #include "db_param.h"
50 #include "dbif.h"
51 #include "dbd.h"
52 #include "comm.h"
53
54
55 #define LOCKFILENAME  "lock"
56
57 static int exit_sig = 0;
58
59
60 static void sig_exit(int signo)
61 {
62     exit_sig = signo;
63     return;
64 }
65
66 static void block_sigs_onoff(int block)
67 {
68     sigset_t set;
69
70     sigemptyset(&set);
71     sigaddset(&set, SIGINT);
72     sigaddset(&set, SIGTERM);
73     if (block)
74         sigprocmask(SIG_BLOCK, &set, NULL);
75     else
76         sigprocmask(SIG_UNBLOCK, &set, NULL);
77     return;
78 }
79
80 /*
81   The dbd_XXX and comm_XXX functions all obey the same protocol for return values:
82
83   1: Success, if transactions are used commit.
84   0: Failure, but we continue to serve requests. If transactions are used abort/rollback.
85   -1: Fatal error, either from the database or from the socket. Abort the transaction if applicable
86   (which might fail as well) and then exit.
87
88   We always try to notify the client process about the outcome, the result field
89   of the cnid_dbd_rply structure contains further details.
90
91 */
92
93 static int loop(struct db_param *dbp)
94 {
95     struct cnid_dbd_rqst rqst;
96     struct cnid_dbd_rply rply;
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
103     count = 0;
104     now = time(NULL);
105     time_next_flush = now + dbp->flush_interval;
106     time_last_rqst = now;
107
108     rqst.name = namebuf;
109
110     strftime(timebuf, 63, "%b %d %H:%M:%S.",localtime(&time_next_flush));
111     LOG(log_debug, logtype_cnid, "Checkpoint interval: %d seconds. Next checkpoint: %s",
112         dbp->flush_interval, timebuf);
113
114     while (1) {
115         if ((cret = comm_rcv(&rqst)) < 0)
116             return -1;
117
118         now = time(NULL);
119
120         if (cret == 0) {
121             /* comm_rcv returned from select without receiving anything. */
122
123             /* Give signals a chance... */
124             block_sigs_onoff(0);
125             block_sigs_onoff(1);
126             if (exit_sig)
127                 /* Received signal (TERM|INT) */
128                 return 0;
129             if (dbp->idle_timeout && comm_nbe() <= 0 && (now - time_last_rqst) > dbp->idle_timeout)
130                 /* Idle timeout */
131                 return 0;
132         } else {
133             /* We got a request */
134             time_last_rqst = now;
135
136             memset(&rply, 0, sizeof(rply));
137             switch(rqst.op) {
138                 /* ret gets set here */
139             case CNID_DBD_OP_OPEN:
140             case CNID_DBD_OP_CLOSE:
141                 /* open/close are noops for now. */
142                 rply.namelen = 0;
143                 ret = 1;
144                 break;
145             case CNID_DBD_OP_ADD:
146                 ret = dbd_add(&rqst, &rply);
147                 break;
148             case CNID_DBD_OP_GET:
149                 ret = dbd_get(&rqst, &rply);
150                 break;
151             case CNID_DBD_OP_RESOLVE:
152                 ret = dbd_resolve(&rqst, &rply);
153                 break;
154             case CNID_DBD_OP_LOOKUP:
155                 ret = dbd_lookup(&rqst, &rply);
156                 break;
157             case CNID_DBD_OP_UPDATE:
158                 ret = dbd_update(&rqst, &rply);
159                 break;
160             case CNID_DBD_OP_DELETE:
161                 ret = dbd_delete(&rqst, &rply);
162                 break;
163             case CNID_DBD_OP_GETSTAMP:
164                 ret = dbd_getstamp(&rqst, &rply);
165                 break;
166             case CNID_DBD_OP_REBUILD_ADD:
167                 ret = dbd_rebuild_add(&rqst, &rply);
168                 break;
169             default:
170                 LOG(log_error, logtype_cnid, "loop: unknown op %d", rqst.op);
171                 ret = -1;
172                 break;
173             }
174             
175             if ((cret = comm_snd(&rply)) < 0 || ret < 0) {
176                 dbif_txn_abort();
177                 return -1;
178             }
179             
180             if (ret == 0 || cret == 0) {
181                 if (dbif_txn_abort() < 0)
182                     return -1;
183             } else {
184                 ret = dbif_txn_commit();
185                 if (  ret < 0)
186                     return -1;
187                 else if ( ret > 0 )
188                     /* We had a designated txn because we wrote to the db */
189                     count++;
190             }
191         } /* got a request */
192
193         /*
194           Shall we checkpoint bdb ?
195           "flush_interval" seconds passed ?
196         */
197         if (now > time_next_flush) {
198             LOG(log_info, logtype_cnid, "Checkpointing BerkeleyDB for volume '%s'", dbp->dir);
199             if (dbif_txn_checkpoint(0, 0, 0) < 0)
200                 return -1;
201             count = 0;
202             time_next_flush = now + dbp->flush_interval;
203
204             strftime(timebuf, 63, "%b %d %H:%M:%S.",localtime(&time_next_flush));
205             LOG(log_debug, logtype_cnid, "Checkpoint interval: %d seconds. Next checkpoint: %s",
206                 dbp->flush_interval, timebuf);
207         }
208
209         /* 
210            Shall we checkpoint bdb ?
211            Have we commited "count" more changes than "flush_frequency" ?
212         */
213         if (count > dbp->flush_frequency) {
214             LOG(log_info, logtype_cnid, "Checkpointing BerkeleyDB after %d writes for volume '%s'", count, dbp->dir);
215             if (dbif_txn_checkpoint(0, 0, 0) < 0)
216                 return -1;
217             count = 0;
218         }
219     } /* while(1) */
220 }
221
222 /* ------------------------ */
223 static void switch_to_user(char *dir)
224 {
225     struct stat st;
226
227     if (chdir(dir) < 0) {
228         LOG(log_error, logtype_cnid, "chdir to %s failed: %s", dir, strerror(errno));
229         exit(1);
230     }
231
232     if (stat(".", &st) < 0) {
233         LOG(log_error, logtype_cnid, "error in stat for %s: %s", dir, strerror(errno));
234         exit(1);
235     }
236     if (!getuid()) {
237         LOG(log_info, logtype_cnid, "Setting uid/gid to %i/%i", st.st_uid, st.st_gid);
238         if (setgid(st.st_gid) < 0 || setuid(st.st_uid) < 0) {
239             LOG(log_error, logtype_cnid, "uid/gid: %s", strerror(errno));
240             exit(1);
241         }
242     }
243 }
244
245 /* ------------------------ */
246 int get_lock(void)
247 {
248     int lockfd;
249     struct flock lock;
250
251     if ((lockfd = open(LOCKFILENAME, O_RDWR | O_CREAT, 0644)) < 0) {
252         LOG(log_error, logtype_cnid, "main: error opening lockfile: %s", strerror(errno));
253         exit(1);
254     }
255
256     lock.l_start  = 0;
257     lock.l_whence = SEEK_SET;
258     lock.l_len    = 0;
259     lock.l_type   = F_WRLCK;
260
261     if (fcntl(lockfd, F_SETLK, &lock) < 0) {
262         if (errno == EACCES || errno == EAGAIN) {
263             exit(0);
264         } else {
265             LOG(log_error, logtype_cnid, "main: fcntl F_WRLCK lockfile: %s", strerror(errno));
266             exit(1);
267         }
268     }
269
270     return lockfd;
271 }
272
273 /* ----------------------- */
274 void set_signal(void)
275 {
276     struct sigaction sv;
277
278     sv.sa_handler = sig_exit;
279     sv.sa_flags = 0;
280     sigemptyset(&sv.sa_mask);
281     sigaddset(&sv.sa_mask, SIGINT);
282     sigaddset(&sv.sa_mask, SIGTERM);
283     if (sigaction(SIGINT, &sv, NULL) < 0 || sigaction(SIGTERM, &sv, NULL) < 0) {
284         LOG(log_error, logtype_cnid, "main: sigaction: %s", strerror(errno));
285         exit(1);
286     }
287     sv.sa_handler = SIG_IGN;
288     sigemptyset(&sv.sa_mask);
289     if (sigaction(SIGPIPE, &sv, NULL) < 0) {
290         LOG(log_error, logtype_cnid, "main: sigaction: %s", strerror(errno));
291         exit(1);
292     }
293 }
294
295 /* ----------------------- */
296 void free_lock(int lockfd)
297 {
298     struct flock lock;
299
300     lock.l_start  = 0;
301     lock.l_whence = SEEK_SET;
302     lock.l_len    = 0;
303     lock.l_type = F_UNLCK;
304     fcntl(lockfd, F_SETLK, &lock);
305     close(lockfd);
306 }
307
308 /* ------------------------ */
309 int main(int argc, char *argv[])
310 {
311     struct db_param *dbp;
312     int err = 0;
313     int lockfd, ctrlfd, clntfd;
314     char *dir, *logconfig;
315
316     set_processname("cnid_dbd");
317
318     if (argc  != 5) {
319         LOG(log_error, logtype_cnid, "main: not enough arguments");
320         exit(1);
321     }
322
323     dir = argv[1];
324     ctrlfd = atoi(argv[2]);
325     clntfd = atoi(argv[3]);
326     logconfig = strdup(argv[4]);
327     setuplog(logconfig);
328
329     switch_to_user(dir);
330
331     /* Before we do anything else, check if there is an instance of cnid_dbd
332        running already and silently exit if yes. */
333     lockfd = get_lock();
334
335     LOG(log_info, logtype_cnid, "Startup, DB dir %s", dir);
336
337     set_signal();
338
339     /* SIGINT and SIGTERM are always off, unless we get a return code of 0 from
340        comm_rcv (no requests for one second, see above in loop()). That means we
341        only shut down after one second of inactivity. */
342     block_sigs_onoff(1);
343
344     if ((dbp = db_param_read(dir, DBD)) == NULL)
345         exit(1);
346     LOG(log_maxdebug, logtype_cnid, "Finished parsing db_param config file");
347
348     if (dbif_env_init(dbp) < 0)
349         exit(2); /* FIXME: same exit code as failure for dbif_open() */
350     LOG(log_debug, logtype_cnid, "Finished initializing BerkeleyDB environment");
351
352     if (dbif_open(dbp, 0) < 0) {
353         dbif_close();
354         exit(2);
355     }
356     LOG(log_debug, logtype_cnid, "Finished opening BerkeleyDB databases");
357
358     if (dbd_stamp() < 0) {
359         dbif_close();
360         exit(5);
361     }
362     LOG(log_maxdebug, logtype_cnid, "Finished checking database stamp");
363
364     if (comm_init(dbp, ctrlfd, clntfd) < 0) {
365         dbif_close();
366         exit(3);
367     }
368
369     if (loop(dbp) < 0)
370         err++;
371
372     if (dbif_close() < 0)
373         err++;
374
375     free_lock(lockfd);
376
377     if (err)
378         exit(4);
379     else if (exit_sig)
380         LOG(log_info, logtype_cnid, "main: Exiting on signal %i", exit_sig);
381     else
382         LOG(log_info, logtype_cnid, "main: Idle timeout, exiting");
383
384     return 0;
385 }