]> arthur.barton.de Git - netatalk.git/blob - etc/cnid_dbd/main.c
Case-insensitive name search with the CNID db
[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 */
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             exit(0);
278         } else {
279             LOG(log_error, logtype_cnid, "main: fcntl F_WRLCK lockfile: %s", strerror(errno));
280             exit(1);
281         }
282     }
283
284     return lockfd;
285 }
286
287 /* ----------------------- */
288 static void set_signal(void)
289 {
290     struct sigaction sv;
291
292     sv.sa_handler = sig_exit;
293     sv.sa_flags = 0;
294     sigemptyset(&sv.sa_mask);
295     sigaddset(&sv.sa_mask, SIGINT);
296     sigaddset(&sv.sa_mask, SIGTERM);
297     if (sigaction(SIGINT, &sv, NULL) < 0 || sigaction(SIGTERM, &sv, NULL) < 0) {
298         LOG(log_error, logtype_cnid, "main: sigaction: %s", strerror(errno));
299         exit(1);
300     }
301     sv.sa_handler = SIG_IGN;
302     sigemptyset(&sv.sa_mask);
303     if (sigaction(SIGPIPE, &sv, NULL) < 0) {
304         LOG(log_error, logtype_cnid, "main: sigaction: %s", strerror(errno));
305         exit(1);
306     }
307 }
308
309 /* ----------------------- */
310 static void free_lock(int lockfd)
311 {
312     struct flock lock;
313
314     lock.l_start  = 0;
315     lock.l_whence = SEEK_SET;
316     lock.l_len    = 0;
317     lock.l_type = F_UNLCK;
318     fcntl(lockfd, F_SETLK, &lock);
319     close(lockfd);
320 }
321
322 /* ------------------------ */
323 int main(int argc, char *argv[])
324 {
325     struct db_param *dbp;
326     int err = 0;
327     int lockfd, ctrlfd, clntfd;
328     char *dir, *logconfig;
329
330     set_processname("cnid_dbd");
331
332     /* FIXME: implement -d from cnid_metad */
333     if (argc  != 5) {
334         LOG(log_error, logtype_cnid, "main: not enough arguments");
335         exit(1);
336     }
337
338     dir = argv[1];
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(dir, &volinfo) == -1) {
346         LOG(log_error, logtype_cnid, "Cant load volinfo for \"%s\"", dir);
347         exit(EXIT_FAILURE);
348     }
349     dir = volinfo.v_dbpath;
350     if (vol_load_charsets(&volinfo) == -1) {
351         LOG(log_error, logtype_cnid, "Error loading charsets!");
352         exit(EXIT_FAILURE);
353     }
354     LOG(log_note, logtype_cnid, "db dir: \"%s\"", dir);
355
356     switch_to_user(dir);
357
358     /* Before we do anything else, check if there is an instance of cnid_dbd
359        running already and silently exit if yes. */
360     lockfd = get_lock();
361
362     set_signal();
363
364     /* SIGINT and SIGTERM are always off, unless we are in pselect */
365     block_sigs_onoff(1);
366
367     if ((dbp = db_param_read(dir, CNID_DBD)) == NULL)
368         exit(1);
369     LOG(log_maxdebug, logtype_cnid, "Finished parsing db_param config file");
370
371     if (NULL == (dbd = dbif_init(".", "cnid2.db")))
372         exit(2);
373
374     if (dbif_env_open(dbd, dbp, DBOPTIONS) < 0)
375         exit(2); /* FIXME: same exit code as failure for dbif_open() */
376     LOG(log_debug, logtype_cnid, "Finished initializing BerkeleyDB environment");
377
378     if (dbif_open(dbd, dbp, 0) < 0) {
379         dbif_close(dbd);
380         exit(2);
381     }
382     LOG(log_debug, logtype_cnid, "Finished opening BerkeleyDB databases");
383
384     if (dbd_stamp(dbd) < 0) {
385         dbif_close(dbd);
386         exit(5);
387     }
388     LOG(log_maxdebug, logtype_cnid, "Finished checking database stamp");
389
390     if (comm_init(dbp, ctrlfd, clntfd) < 0) {
391         dbif_close(dbd);
392         exit(3);
393     }
394
395     if (loop(dbp) < 0)
396         err++;
397
398     if (dbif_close(dbd) < 0)
399         err++;
400
401     if (dbif_env_remove(dir) < 0)
402         err++;
403
404     free_lock(lockfd);
405
406     if (err)
407         exit(4);
408     else if (exit_sig)
409         LOG(log_info, logtype_cnid, "main: Exiting on signal %i", exit_sig);
410     else
411         LOG(log_info, logtype_cnid, "main: Idle timeout, exiting");
412
413     return 0;
414 }