]> arthur.barton.de Git - netatalk.git/blob - etc/cnid_dbd/main.c
Fix dbd vs cnid_dbd locking
[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)
50
51 static DBD *dbd;
52 static int exit_sig = 0;
53 static int db_locked;
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  * Get lock on db lock file
77  *
78  * @args cmd       (r) !=0: lock, 0: unlock
79  * @returns            1 if lock was acquired, 0 if file is already locked, -1 on error
80  */
81 static int get_lock(int cmd)
82 {
83     static int lockfd = -1;
84     struct flock lock;
85
86     if (cmd == 0) {
87         if (lockfd == -1)
88             return -1;
89
90         lock.l_start  = 0;
91         lock.l_whence = SEEK_SET;
92         lock.l_len    = 0;
93         lock.l_type = F_UNLCK;
94         fcntl(lockfd, F_SETLK, &lock);
95         close(lockfd);
96         lockfd = -1;
97         return 0;
98     }
99
100     if (lockfd == -1) {
101         if ((lockfd = open(LOCKFILENAME, O_RDWR | O_CREAT, 0644)) < 0) {
102             LOG(log_error, logtype_cnid, "get_lock: error opening lockfile: %s", strerror(errno));
103             return -1;
104         }
105     }
106
107     lock.l_start  = 0;
108     lock.l_whence = SEEK_SET;
109     lock.l_len    = 0;
110     lock.l_type   = F_WRLCK;
111
112     if (fcntl(lockfd, F_SETLK, &lock) < 0) {
113         if (errno == EACCES || errno == EAGAIN) {
114             LOG(log_debug, logtype_cnid, "get_lock: couldn't lock");
115             return 0;
116         } else {
117             LOG(log_error, logtype_cnid, "get_lock: fcntl F_WRLCK lockfile: %s", strerror(errno));
118             return -1;
119         }
120     }
121
122     LOG(log_debug, logtype_cnid, "get_lock: got lock");
123     return 1;
124 }
125
126 /*
127   The dbd_XXX and comm_XXX functions all obey the same protocol for return values:
128
129   1: Success, if transactions are used commit.
130   0: Failure, but we continue to serve requests. If transactions are used abort/rollback.
131   -1: Fatal error, either from t
132   he database or from the socket. Abort the transaction if applicable
133   (which might fail as well) and then exit.
134
135   We always try to notify the client process about the outcome, the result field
136   of the cnid_dbd_rply structure contains further details.
137
138 */
139 #ifndef min
140 #define min(a,b)        ((a)<(b)?(a):(b))
141 #endif
142
143 static int loop(struct db_param *dbp)
144 {
145     struct cnid_dbd_rqst rqst;
146     struct cnid_dbd_rply rply;
147     time_t timeout;
148     int ret, cret;
149     int count;
150     time_t now, time_next_flush, time_last_rqst;
151     char timebuf[64];
152     static char namebuf[MAXPATHLEN + 1];
153     sigset_t set;
154
155     sigemptyset(&set);
156     sigprocmask(SIG_SETMASK, NULL, &set);
157     sigdelset(&set, SIGINT);
158     sigdelset(&set, SIGTERM);
159
160     count = 0;
161     now = time(NULL);
162     time_next_flush = now + dbp->flush_interval;
163     time_last_rqst = now;
164
165     rqst.name = namebuf;
166
167     strftime(timebuf, 63, "%b %d %H:%M:%S.",localtime(&time_next_flush));
168     LOG(log_debug, logtype_cnid, "Checkpoint interval: %d seconds. Next checkpoint: %s",
169         dbp->flush_interval, timebuf);
170
171     while (1) {
172         /*
173          * If we haven't got the lock yet, get it now.
174          * Prevents a race with dbd:
175          *   1. no cnid_dbd running
176          *   2. dbd -r starts, gets the lock
177          *   3. cnid_dbd starts, doesn't get lock, doesn't run recovery, all is fine
178          *   4. dbd from (2) finishes, drops lock
179          *   5. anothet dbd but this time with -re is started which
180          *      - succeeds getting the lock
181          *      - runs recovery => this kills (3)
182          */
183         if (!db_locked)
184             if ((db_locked = get_lock(1)) == -1)
185                 return -1;
186         timeout = min(time_next_flush, time_last_rqst +dbp->idle_timeout);
187         if (timeout > now)
188             timeout -= now;
189         else
190             timeout = 1;
191
192         if ((cret = comm_rcv(&rqst, timeout, &set, &now)) < 0)
193             return -1;
194
195         if (cret == 0) {
196             /* comm_rcv returned from select without receiving anything. */
197             if (exit_sig) {
198                 /* Received signal (TERM|INT) */
199                 return 0;
200             }
201             if (now - time_last_rqst >= dbp->idle_timeout && comm_nbe() <= 0) {
202                 /* Idle timeout */
203                 return 0;
204             }
205             /* still active connections, reset time_last_rqst */
206             time_last_rqst = now;
207         } else {
208             /* We got a request */
209             time_last_rqst = now;
210
211             memset(&rply, 0, sizeof(rply));
212             switch(rqst.op) {
213                 /* ret gets set here */
214             case CNID_DBD_OP_OPEN:
215             case CNID_DBD_OP_CLOSE:
216                 /* open/close are noops for now. */
217                 rply.namelen = 0;
218                 ret = 1;
219                 break;
220             case CNID_DBD_OP_ADD:
221                 ret = dbd_add(dbd, &rqst, &rply, 0);
222                 break;
223             case CNID_DBD_OP_GET:
224                 ret = dbd_get(dbd, &rqst, &rply);
225                 break;
226             case CNID_DBD_OP_RESOLVE:
227                 ret = dbd_resolve(dbd, &rqst, &rply);
228                 break;
229             case CNID_DBD_OP_LOOKUP:
230                 ret = dbd_lookup(dbd, &rqst, &rply, 0);
231                 break;
232             case CNID_DBD_OP_UPDATE:
233                 ret = dbd_update(dbd, &rqst, &rply);
234                 break;
235             case CNID_DBD_OP_DELETE:
236                 ret = dbd_delete(dbd, &rqst, &rply, DBIF_CNID);
237                 break;
238             case CNID_DBD_OP_GETSTAMP:
239                 ret = dbd_getstamp(dbd, &rqst, &rply);
240                 break;
241             case CNID_DBD_OP_REBUILD_ADD:
242                 ret = dbd_rebuild_add(dbd, &rqst, &rply);
243                 break;
244             default:
245                 LOG(log_error, logtype_cnid, "loop: unknown op %d", rqst.op);
246                 ret = -1;
247                 break;
248             }
249             
250             if ((cret = comm_snd(&rply)) < 0 || ret < 0) {
251                 dbif_txn_abort(dbd);
252                 return -1;
253             }
254             
255             if (ret == 0 || cret == 0) {
256                 if (dbif_txn_abort(dbd) < 0)
257                     return -1;
258             } else {
259                 ret = dbif_txn_commit(dbd);
260                 if (  ret < 0)
261                     return -1;
262                 else if ( ret > 0 )
263                     /* We had a designated txn because we wrote to the db */
264                     count++;
265             }
266         } /* got a request */
267
268         /*
269           Shall we checkpoint bdb ?
270           "flush_interval" seconds passed ?
271         */
272         if (now >= time_next_flush) {
273             LOG(log_info, logtype_cnid, "Checkpointing BerkeleyDB for volume '%s'", dbp->dir);
274             if (dbif_txn_checkpoint(dbd, 0, 0, 0) < 0)
275                 return -1;
276             count = 0;
277             time_next_flush = now + dbp->flush_interval;
278
279             strftime(timebuf, 63, "%b %d %H:%M:%S.",localtime(&time_next_flush));
280             LOG(log_debug, logtype_cnid, "Checkpoint interval: %d seconds. Next checkpoint: %s",
281                 dbp->flush_interval, timebuf);
282         }
283
284         /* 
285            Shall we checkpoint bdb ?
286            Have we commited "count" more changes than "flush_frequency" ?
287         */
288         if (count > dbp->flush_frequency) {
289             LOG(log_info, logtype_cnid, "Checkpointing BerkeleyDB after %d writes for volume '%s'", count, dbp->dir);
290             if (dbif_txn_checkpoint(dbd, 0, 0, 0) < 0)
291                 return -1;
292             count = 0;
293         }
294     } /* while(1) */
295 }
296
297 /* ------------------------ */
298 static void switch_to_user(char *dir)
299 {
300     struct stat st;
301
302     if (chdir(dir) < 0) {
303         LOG(log_error, logtype_cnid, "chdir to %s failed: %s", dir, strerror(errno));
304         exit(1);
305     }
306
307     if (stat(".", &st) < 0) {
308         LOG(log_error, logtype_cnid, "error in stat for %s: %s", dir, strerror(errno));
309         exit(1);
310     }
311     if (!getuid()) {
312         LOG(log_info, logtype_cnid, "Setting uid/gid to %i/%i", st.st_uid, st.st_gid);
313         if (setgid(st.st_gid) < 0 || setuid(st.st_uid) < 0) {
314             LOG(log_error, logtype_cnid, "uid/gid: %s", strerror(errno));
315             exit(1);
316         }
317     }
318 }
319
320
321 /* ----------------------- */
322 static void set_signal(void)
323 {
324     struct sigaction sv;
325
326     sv.sa_handler = sig_exit;
327     sv.sa_flags = 0;
328     sigemptyset(&sv.sa_mask);
329     sigaddset(&sv.sa_mask, SIGINT);
330     sigaddset(&sv.sa_mask, SIGTERM);
331     if (sigaction(SIGINT, &sv, NULL) < 0 || sigaction(SIGTERM, &sv, NULL) < 0) {
332         LOG(log_error, logtype_cnid, "main: sigaction: %s", strerror(errno));
333         exit(1);
334     }
335     sv.sa_handler = SIG_IGN;
336     sigemptyset(&sv.sa_mask);
337     if (sigaction(SIGPIPE, &sv, NULL) < 0) {
338         LOG(log_error, logtype_cnid, "main: sigaction: %s", strerror(errno));
339         exit(1);
340     }
341 }
342
343 /* ------------------------ */
344 int main(int argc, char *argv[])
345 {
346     struct db_param *dbp;
347     int err = 0;
348     int ctrlfd, clntfd;
349     char *dir, *logconfig;
350
351     set_processname("cnid_dbd");
352
353     /* FIXME: implement -d from cnid_metad */
354     if (argc  != 5) {
355         LOG(log_error, logtype_cnid, "main: not enough arguments");
356         exit(1);
357     }
358
359     dir = argv[1];
360     ctrlfd = atoi(argv[2]);
361     clntfd = atoi(argv[3]);
362     logconfig = strdup(argv[4]);
363     setuplog(logconfig);
364
365     switch_to_user(dir);
366
367     /* Before we do anything else, check if there is an instance of cnid_dbd
368        running already and silently exit if yes. */
369     if ((db_locked = get_lock(1)) == -1) {
370         exit(1);
371     }
372
373     LOG(log_info, logtype_cnid, "Startup, DB dir %s", dir);
374
375     set_signal();
376
377     /* SIGINT and SIGTERM are always off, unless we are in pselect */
378     block_sigs_onoff(1);
379
380     if ((dbp = db_param_read(dir, CNID_DBD)) == NULL)
381         exit(1);
382     LOG(log_maxdebug, logtype_cnid, "Finished parsing db_param config file");
383
384     if (NULL == (dbd = dbif_init(".", "cnid2.db")))
385         exit(2);
386
387     /* Only recover if we got the lock */
388     if (dbif_env_open(dbd,
389                       dbp,
390                       db_locked ? DBOPTIONS | DB_RECOVER : DBOPTIONS) < 0)
391         exit(2); /* FIXME: same exit code as failure for dbif_open() */
392     LOG(log_debug, logtype_cnid, "Finished initializing BerkeleyDB environment");
393
394     if (dbif_open(dbd, dbp, 0) < 0) {
395         dbif_close(dbd);
396         exit(2);
397     }
398     LOG(log_debug, logtype_cnid, "Finished opening BerkeleyDB databases");
399
400     if (dbd_stamp(dbd) < 0) {
401         dbif_close(dbd);
402         exit(5);
403     }
404     LOG(log_maxdebug, logtype_cnid, "Finished checking database stamp");
405
406     if (comm_init(dbp, ctrlfd, clntfd) < 0) {
407         dbif_close(dbd);
408         exit(3);
409     }
410
411     if (loop(dbp) < 0)
412         err++;
413
414     if (dbif_close(dbd) < 0)
415         err++;
416
417     if (dbif_prep_upgrade(dir) < 0)
418         err++;
419
420     (void)get_lock(0);
421
422     if (err)
423         exit(4);
424     else if (exit_sig)
425         LOG(log_info, logtype_cnid, "main: Exiting on signal %i", exit_sig);
426     else
427         LOG(log_info, logtype_cnid, "main: Idle timeout, exiting");
428
429     return 0;
430 }