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