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