]> arthur.barton.de Git - netatalk.git/blob - etc/cnid_dbd/main.c
Merge sf/master
[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 #include <atalk/volinfo.h>
36
37 #include "db_param.h"
38 #include "dbif.h"
39 #include "dbd.h"
40 #include "comm.h"
41
42 /* 
43    Note: DB_INIT_LOCK is here so we can run the db_* utilities while netatalk is running.
44    It's a likey performance hit, but it might we worth it.
45  */
46 #define DBOPTIONS (DB_CREATE | DB_INIT_LOG | DB_INIT_MPOOL | DB_INIT_LOCK | DB_INIT_TXN)
47
48 /* Global, needed by pack.c:idxname() */
49 struct volinfo volinfo;
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   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             case CNID_DBD_OP_SEARCH:
180                 ret = dbd_search(dbd, &rqst, &rply);
181                 break;
182             default:
183                 LOG(log_error, logtype_cnid, "loop: unknown op %d", rqst.op);
184                 ret = -1;
185                 break;
186             }
187
188             if ((cret = comm_snd(&rply)) < 0 || ret < 0) {
189                 dbif_txn_abort(dbd);
190                 return -1;
191             }
192             
193             if (ret == 0 || cret == 0) {
194                 if (dbif_txn_abort(dbd) < 0)
195                     return -1;
196             } else {
197                 ret = dbif_txn_commit(dbd);
198                 if (  ret < 0)
199                     return -1;
200                 else if ( ret > 0 )
201                     /* We had a designated txn because we wrote to the db */
202                     count++;
203             }
204         } /* got a request */
205
206         /*
207           Shall we checkpoint bdb ?
208           "flush_interval" seconds passed ?
209         */
210         if (now >= time_next_flush) {
211             LOG(log_info, logtype_cnid, "Checkpointing BerkeleyDB for volume '%s'", dbp->dir);
212             if (dbif_txn_checkpoint(dbd, 0, 0, 0) < 0)
213                 return -1;
214             count = 0;
215             time_next_flush = now + dbp->flush_interval;
216
217             strftime(timebuf, 63, "%b %d %H:%M:%S.",localtime(&time_next_flush));
218             LOG(log_debug, logtype_cnid, "Checkpoint interval: %d seconds. Next checkpoint: %s",
219                 dbp->flush_interval, timebuf);
220         }
221
222         /* 
223            Shall we checkpoint bdb ?
224            Have we commited "count" more changes than "flush_frequency" ?
225         */
226         if (count > dbp->flush_frequency) {
227             LOG(log_info, logtype_cnid, "Checkpointing BerkeleyDB after %d writes for volume '%s'", count, dbp->dir);
228             if (dbif_txn_checkpoint(dbd, 0, 0, 0) < 0)
229                 return -1;
230             count = 0;
231         }
232     } /* while(1) */
233 }
234
235 /* ------------------------ */
236 static void switch_to_user(char *dir)
237 {
238     struct stat st;
239
240     if (chdir(dir) < 0) {
241         LOG(log_error, logtype_cnid, "chdir to %s failed: %s", dir, strerror(errno));
242         exit(1);
243     }
244
245     if (stat(".", &st) < 0) {
246         LOG(log_error, logtype_cnid, "error in stat for %s: %s", dir, strerror(errno));
247         exit(1);
248     }
249     if (!getuid()) {
250         LOG(log_info, logtype_cnid, "Setting uid/gid to %i/%i", st.st_uid, st.st_gid);
251         if (setgid(st.st_gid) < 0 || setuid(st.st_uid) < 0) {
252             LOG(log_error, logtype_cnid, "uid/gid: %s", strerror(errno));
253             exit(1);
254         }
255     }
256 }
257
258
259 /* ----------------------- */
260 static void set_signal(void)
261 {
262     struct sigaction sv;
263
264     sv.sa_handler = sig_exit;
265     sv.sa_flags = 0;
266     sigemptyset(&sv.sa_mask);
267     sigaddset(&sv.sa_mask, SIGINT);
268     sigaddset(&sv.sa_mask, SIGTERM);
269     if (sigaction(SIGINT, &sv, NULL) < 0 || sigaction(SIGTERM, &sv, NULL) < 0) {
270         LOG(log_error, logtype_cnid, "main: sigaction: %s", strerror(errno));
271         exit(1);
272     }
273     sv.sa_handler = SIG_IGN;
274     sigemptyset(&sv.sa_mask);
275     if (sigaction(SIGPIPE, &sv, NULL) < 0) {
276         LOG(log_error, logtype_cnid, "main: sigaction: %s", strerror(errno));
277         exit(1);
278     }
279 }
280
281 /* ------------------------ */
282 int main(int argc, char *argv[])
283 {
284     struct db_param *dbp;
285     int err = 0;
286     int ctrlfd, clntfd;
287     char *logconfig;
288
289     set_processname("cnid_dbd");
290
291     /* FIXME: implement -d from cnid_metad */
292     if (argc  != 5) {
293         LOG(log_error, logtype_cnid, "main: not enough arguments");
294         exit(1);
295     }
296
297     ctrlfd = atoi(argv[2]);
298     clntfd = atoi(argv[3]);
299     logconfig = strdup(argv[4]);
300     setuplog(logconfig);
301
302     /* Load .volinfo file */
303     if (loadvolinfo(argv[1], &volinfo) == -1) {
304         LOG(log_error, logtype_cnid, "Cant load volinfo for \"%s\"", argv[1]);
305         exit(EXIT_FAILURE);
306     }
307     /* Put "/.AppleDB" at end of volpath, get path from volinfo file */
308     char dbpath[MAXPATHLEN+1];
309     if ((strlen(volinfo.v_dbpath) + strlen("/.AppleDB")) > MAXPATHLEN ) {
310         LOG(log_error, logtype_cnid, "CNID db pathname too long: \"%s\"", volinfo.v_dbpath);
311         exit(EXIT_FAILURE);
312     }
313     strncpy(dbpath, volinfo.v_dbpath, MAXPATHLEN - strlen("/.AppleDB"));
314     strcat(dbpath, "/.AppleDB");
315
316     if (vol_load_charsets(&volinfo) == -1) {
317         LOG(log_error, logtype_cnid, "Error loading charsets!");
318         exit(EXIT_FAILURE);
319     }
320     LOG(log_debug, logtype_cnid, "db dir: \"%s\"", dbpath);
321
322     switch_to_user(dbpath);
323
324     /* Get db lock */
325     if ((db_locked = get_lock(LOCK_EXCL, dbpath)) == -1) {
326         LOG(log_error, logtype_cnid, "main: fatal db lock error");
327         exit(1);
328     }
329     if (db_locked != LOCK_EXCL) {
330         /* Couldn't get exclusive lock, try shared lock  */
331         if ((db_locked = get_lock(LOCK_SHRD, NULL)) != LOCK_SHRD) {
332             LOG(log_error, logtype_cnid, "main: fatal db lock error");
333             exit(1);
334         }
335     }
336
337     set_signal();
338
339     /* SIGINT and SIGTERM are always off, unless we are in pselect */
340     block_sigs_onoff(1);
341
342     if ((dbp = db_param_read(dbpath)) == NULL)
343         exit(1);
344     LOG(log_maxdebug, logtype_cnid, "Finished parsing db_param config file");
345
346     if (NULL == (dbd = dbif_init(dbpath, "cnid2.db")))
347         exit(2);
348
349     /* Only recover if we got the lock */
350     if (dbif_env_open(dbd,
351                       dbp,
352                       (db_locked == LOCK_EXCL) ? DBOPTIONS | DB_RECOVER : 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(dbd, dbp, 0) < 0) {
357         dbif_close(dbd);
358         exit(2);
359     }
360     LOG(log_debug, logtype_cnid, "Finished opening BerkeleyDB databases");
361
362     /* Downgrade db lock  */
363     if (db_locked == LOCK_EXCL) {
364         if (get_lock(LOCK_UNLOCK, NULL) != 0) {
365             dbif_close(dbd);
366             exit(2);
367         }
368         if (get_lock(LOCK_SHRD, NULL) != LOCK_SHRD) {
369             dbif_close(dbd);
370             exit(2);
371         }
372     }
373
374
375     if (comm_init(dbp, ctrlfd, clntfd) < 0) {
376         dbif_close(dbd);
377         exit(3);
378     }
379
380     if (loop(dbp) < 0)
381         err++;
382
383     if (dbif_close(dbd) < 0)
384         err++;
385
386     if (dbif_env_remove(dbpath) < 0)
387         err++;
388
389     if (err)
390         exit(4);
391     else if (exit_sig)
392         LOG(log_info, logtype_cnid, "main: Exiting on signal %i", exit_sig);
393     else
394         LOG(log_info, logtype_cnid, "main: Idle timeout, exiting");
395
396     return 0;
397 }