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