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