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