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