]> arthur.barton.de Git - netatalk.git/blob - etc/cnid_dbd/main.c
Support for using $u username variable in AFP volume definitions
[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_bdb_private.h>
26 #include <atalk/logger.h>
27 #include <atalk/errchk.h>
28 #include <atalk/bstrlib.h>
29 #include <atalk/bstradd.h>
30 #include <atalk/netatalk_conf.h>
31 #include <atalk/util.h>
32
33 #include "db_param.h"
34 #include "dbif.h"
35 #include "dbd.h"
36 #include "comm.h"
37 #include "pack.h"
38
39 /* 
40    Note: DB_INIT_LOCK is here so we can run the db_* utilities while netatalk is running.
41    It's a likey performance hit, but it might we worth it.
42  */
43 #define DBOPTIONS (DB_CREATE | DB_INIT_LOG | DB_INIT_MPOOL | DB_INIT_LOCK | DB_INIT_TXN)
44
45 static DBD *dbd;
46 static int exit_sig = 0;
47 static int db_locked;
48 static bstring dbpath;
49 static struct db_param *dbp;
50 static struct vol *vol;
51
52 static void sig_exit(int signo)
53 {
54     exit_sig = signo;
55     return;
56 }
57
58 static void block_sigs_onoff(int block)
59 {
60     sigset_t set;
61
62     sigemptyset(&set);
63     sigaddset(&set, SIGINT);
64     sigaddset(&set, SIGTERM);
65     if (block)
66         sigprocmask(SIG_BLOCK, &set, NULL);
67     else
68         sigprocmask(SIG_UNBLOCK, &set, NULL);
69     return;
70 }
71
72 /*
73   The dbd_XXX and comm_XXX functions all obey the same protocol for return values:
74
75   1: Success, if transactions are used commit.
76   0: Failure, but we continue to serve requests. If transactions are used abort/rollback.
77   -1: Fatal error, either from t
78   he database or from the socket. Abort the transaction if applicable
79   (which might fail as well) and then exit.
80
81   We always try to notify the client process about the outcome, the result field
82   of the cnid_dbd_rply structure contains further details.
83
84 */
85
86 /*!
87  * Get lock on db lock file
88  *
89  * @args cmd       (r) lock command:
90  *                     LOCK_FREE:   close lockfd
91  *                     LOCK_UNLOCK: unlock lockm keep lockfd open
92  *                     LOCK_EXCL:   F_WRLCK on lockfd
93  *                     LOCK_SHRD:   F_RDLCK on lockfd
94  * @args dbpath    (r) path to lockfile, only used on first call,
95  *                     later the stored fd is used
96  * @returns            LOCK_FREE/LOCK_UNLOCK return 0 on success, -1 on error
97  *                     LOCK_EXCL/LOCK_SHRD return LOCK_EXCL or LOCK_SHRD respectively on
98  *                     success, 0 if the lock couldn't be acquired, -1 on other errors
99  */
100 static int get_lock(int cmd, const char *dbpath)
101 {
102     static int lockfd = -1;
103     int ret;
104     char lockpath[PATH_MAX];
105     struct stat st;
106
107     LOG(log_debug, logtype_cnid, "get_lock(%s, \"%s\")",
108         cmd == LOCK_EXCL ? "LOCK_EXCL" :
109         cmd == LOCK_SHRD ? "LOCK_SHRD" :
110         cmd == LOCK_FREE ? "LOCK_FREE" :
111         cmd == LOCK_UNLOCK ? "LOCK_UNLOCK" : "UNKNOWN",
112         dbpath ? dbpath : "");
113
114     switch (cmd) {
115     case LOCK_FREE:
116         if (lockfd == -1)
117             return -1;
118         close(lockfd);
119         lockfd = -1;
120         return 0;
121
122     case LOCK_UNLOCK:
123         if (lockfd == -1)
124             return -1;
125         return unlock(lockfd, 0, SEEK_SET, 0);
126
127     case LOCK_EXCL:
128     case LOCK_SHRD:
129         if (lockfd == -1) {
130             if ( (strlen(dbpath) + strlen(LOCKFILENAME+1)) > (PATH_MAX - 1) ) {
131                 LOG(log_error, logtype_cnid, ".AppleDB pathname too long");
132                 return -1;
133             }
134             strncpy(lockpath, dbpath, PATH_MAX - 1);
135             strcat(lockpath, "/");
136             strcat(lockpath, LOCKFILENAME);
137
138             if ((lockfd = open(lockpath, O_RDWR | O_CREAT, 0644)) < 0) {
139                 LOG(log_error, logtype_cnid, "Error opening lockfile: %s", strerror(errno));
140                 return -1;
141             }
142
143             if ((stat(dbpath, &st)) != 0) {
144                 LOG(log_error, logtype_cnid, "Error statting lockfile: %s", strerror(errno));
145                 return -1;
146             }
147
148             if ((chown(lockpath, st.st_uid, st.st_gid)) != 0) {
149                 LOG(log_error, logtype_cnid, "Error inheriting lockfile permissions: %s",
150                          strerror(errno));
151                 return -1;
152             }
153         }
154     
155         if (cmd == LOCK_EXCL)
156             ret = write_lock(lockfd, 0, SEEK_SET, 0);
157         else
158             ret = read_lock(lockfd, 0, SEEK_SET, 0);
159
160         if (ret != 0) {
161             if (cmd == LOCK_SHRD)
162                 LOG(log_error, logtype_cnid, "Volume CNID db is locked, try again...");
163             return 0; 
164         }
165
166         LOG(log_debug, logtype_cnid, "get_lock: got %s lock",
167             cmd == LOCK_EXCL ? "LOCK_EXCL" : "LOCK_SHRD");    
168         return cmd;
169
170     default:
171         return -1;
172     } /* switch(cmd) */
173
174     /* deadc0de, never get here */
175     return -1;
176 }
177
178 static int open_db(void)
179 {
180     EC_INIT;
181
182     /* Get db lock */
183     if ((db_locked = get_lock(LOCK_EXCL, bdata(dbpath))) != LOCK_EXCL) {
184         LOG(log_error, logtype_cnid, "main: fatal db lock error");
185         EC_FAIL;
186     }
187
188     if (NULL == (dbd = dbif_init(bdata(dbpath), "cnid2.db")))
189         EC_FAIL;
190
191     /* Only recover if we got the lock */
192     if (dbif_env_open(dbd, dbp, DBOPTIONS | DB_RECOVER) < 0)
193         EC_FAIL;
194
195     LOG(log_debug, logtype_cnid, "Finished initializing BerkeleyDB environment");
196
197     if (dbif_open(dbd, dbp, 0) < 0)
198         EC_FAIL;
199
200     LOG(log_debug, logtype_cnid, "Finished opening BerkeleyDB databases");
201
202 EC_CLEANUP:
203     if (ret != 0) {
204         if (dbd) {
205             (void)dbif_close(dbd);
206             dbd = NULL;
207         }
208     }
209
210     EC_EXIT;
211 }
212
213 static int delete_db(void)
214 {
215     EC_INIT;
216     int cwd = -1;
217
218     EC_ZERO( get_lock(LOCK_FREE, bdata(dbpath)) );
219     EC_NEG1( cwd = open(".", O_RDONLY) );
220     chdir(cfrombstr(dbpath));
221     system("rm -f cnid2.db lock log.* __db.*");
222
223     if ((db_locked = get_lock(LOCK_EXCL, bdata(dbpath))) != LOCK_EXCL) {
224         LOG(log_error, logtype_cnid, "main: fatal db lock error");
225         EC_FAIL;
226     }
227
228     LOG(log_warning, logtype_cnid, "Recreated CNID BerkeleyDB databases of volume \"%s\"", vol->v_localname);
229
230 EC_CLEANUP:
231     if (cwd != -1) {
232         fchdir(cwd);
233         close(cwd);
234     }
235     EC_EXIT;
236 }
237
238
239 /**
240  * Close dbd if open, delete it, reopen
241  *
242  * Also tries to copy the rootinfo key, that would allow for keeping the db stamp
243  * and last used CNID
244  **/
245 static int reinit_db(void)
246 {
247     EC_INIT;
248     DBT key, data;
249     bool copyRootInfo = false;
250
251     if (dbd) {
252         memset(&key, 0, sizeof(key));
253         memset(&data, 0, sizeof(data));
254
255         key.data = ROOTINFO_KEY;
256         key.size = ROOTINFO_KEYLEN;
257
258         if (dbif_get(dbd, DBIF_CNID, &key, &data, 0) <= 0) {
259             LOG(log_error, logtype_cnid, "dbif_copy_rootinfokey: Error getting rootinfo record");
260             copyRootInfo = false;
261         } else {
262             copyRootInfo = true;
263         }
264         (void)dbif_close(dbd);
265     }
266
267     EC_ZERO_LOG( delete_db() );
268     EC_ZERO_LOG( open_db() );
269
270     if (copyRootInfo == true) {
271         memset(&key, 0, sizeof(key));
272         key.data = ROOTINFO_KEY;
273         key.size = ROOTINFO_KEYLEN;
274
275         if (dbif_put(dbd, DBIF_CNID, &key, &data, 0) != 0) {
276             LOG(log_error, logtype_cnid, "dbif_copy_rootinfokey: Error writing rootinfo key");
277             EC_FAIL;
278         }
279     }
280
281 EC_CLEANUP:
282     EC_EXIT;
283 }
284
285 static int loop(struct db_param *dbp)
286 {
287     struct cnid_dbd_rqst rqst;
288     struct cnid_dbd_rply rply;
289     time_t timeout;
290     int ret, cret;
291     int count;
292     time_t now, time_next_flush, time_last_rqst;
293     char timebuf[64];
294     static char namebuf[MAXPATHLEN + 1];
295     sigset_t set;
296
297     sigemptyset(&set);
298     sigprocmask(SIG_SETMASK, NULL, &set);
299     sigdelset(&set, SIGINT);
300     sigdelset(&set, SIGTERM);
301
302     count = 0;
303     now = time(NULL);
304     time_next_flush = now + dbp->flush_interval;
305     time_last_rqst = now;
306
307     rqst.name = namebuf;
308
309     strftime(timebuf, 63, "%b %d %H:%M:%S.",localtime(&time_next_flush));
310     LOG(log_debug, logtype_cnid, "Checkpoint interval: %d seconds. Next checkpoint: %s",
311         dbp->flush_interval, timebuf);
312
313     while (1) {
314         timeout = MIN(time_next_flush, time_last_rqst + dbp->idle_timeout);
315         if (timeout > now)
316             timeout -= now;
317         else
318             timeout = 1;
319
320         if ((cret = comm_rcv(&rqst, timeout, &set, &now)) < 0)
321             return -1;
322
323         if (cret == 0) {
324             /* comm_rcv returned from select without receiving anything. */
325             if (exit_sig) {
326                 /* Received signal (TERM|INT) */
327                 return 0;
328             }
329             if (now - time_last_rqst >= dbp->idle_timeout && comm_nbe() <= 0) {
330                 /* Idle timeout */
331                 return 0;
332             }
333             /* still active connections, reset time_last_rqst */
334             time_last_rqst = now;
335         } else {
336             /* We got a request */
337             time_last_rqst = now;
338
339             memset(&rply, 0, sizeof(rply));
340             switch(rqst.op) {
341                 /* ret gets set here */
342             case CNID_DBD_OP_OPEN:
343             case CNID_DBD_OP_CLOSE:
344                 /* open/close are noops for now. */
345                 rply.namelen = 0;
346                 ret = 1;
347                 break;
348             case CNID_DBD_OP_ADD:
349                 ret = dbd_add(dbd, &rqst, &rply);
350                 break;
351             case CNID_DBD_OP_GET:
352                 ret = dbd_get(dbd, &rqst, &rply);
353                 break;
354             case CNID_DBD_OP_RESOLVE:
355                 ret = dbd_resolve(dbd, &rqst, &rply);
356                 break;
357             case CNID_DBD_OP_LOOKUP:
358                 ret = dbd_lookup(dbd, &rqst, &rply);
359                 break;
360             case CNID_DBD_OP_UPDATE:
361                 ret = dbd_update(dbd, &rqst, &rply);
362                 break;
363             case CNID_DBD_OP_DELETE:
364                 ret = dbd_delete(dbd, &rqst, &rply, DBIF_CNID);
365                 break;
366             case CNID_DBD_OP_GETSTAMP:
367                 ret = dbd_getstamp(dbd, &rqst, &rply);
368                 break;
369             case CNID_DBD_OP_REBUILD_ADD:
370                 ret = dbd_rebuild_add(dbd, &rqst, &rply);
371                 break;
372             case CNID_DBD_OP_SEARCH:
373                 ret = dbd_search(dbd, &rqst, &rply);
374                 break;
375             case CNID_DBD_OP_WIPE:
376                 ret = reinit_db();
377                 break;
378             default:
379                 LOG(log_error, logtype_cnid, "loop: unknown op %d", rqst.op);
380                 ret = -1;
381                 break;
382             }
383
384             if ((cret = comm_snd(&rply)) < 0 || ret < 0) {
385                 dbif_txn_abort(dbd);
386                 return -1;
387             }
388             
389             if (ret == 0 || cret == 0) {
390                 if (dbif_txn_abort(dbd) < 0)
391                     return -1;
392             } else {
393                 ret = dbif_txn_commit(dbd);
394                 if (  ret < 0)
395                     return -1;
396                 else if ( ret > 0 )
397                     /* We had a designated txn because we wrote to the db */
398                     count++;
399             }
400         } /* got a request */
401
402         /*
403           Shall we checkpoint bdb ?
404           "flush_interval" seconds passed ?
405         */
406         if (now >= time_next_flush) {
407             LOG(log_info, logtype_cnid, "Checkpointing BerkeleyDB for volume '%s'", dbp->dir);
408             if (dbif_txn_checkpoint(dbd, 0, 0, 0) < 0)
409                 return -1;
410             count = 0;
411             time_next_flush = now + dbp->flush_interval;
412
413             strftime(timebuf, 63, "%b %d %H:%M:%S.",localtime(&time_next_flush));
414             LOG(log_debug, logtype_cnid, "Checkpoint interval: %d seconds. Next checkpoint: %s",
415                 dbp->flush_interval, timebuf);
416         }
417
418         /* 
419            Shall we checkpoint bdb ?
420            Have we commited "count" more changes than "flush_frequency" ?
421         */
422         if (count > dbp->flush_frequency) {
423             LOG(log_info, logtype_cnid, "Checkpointing BerkeleyDB after %d writes for volume '%s'", count, dbp->dir);
424             if (dbif_txn_checkpoint(dbd, 0, 0, 0) < 0)
425                 return -1;
426             count = 0;
427         }
428     } /* while(1) */
429 }
430
431 /* ------------------------ */
432 static void switch_to_user(char *dir)
433 {
434     struct stat st;
435
436     if (chdir(dir) < 0) {
437         LOG(log_error, logtype_cnid, "chdir to %s failed: %s", dir, strerror(errno));
438         exit(1);
439     }
440
441     if (stat(".", &st) < 0) {
442         LOG(log_error, logtype_cnid, "error in stat for %s: %s", dir, strerror(errno));
443         exit(1);
444     }
445     if (!getuid()) {
446         LOG(log_debug, logtype_cnid, "Setting uid/gid to %i/%i", st.st_uid, st.st_gid);
447         if (setgid(st.st_gid) < 0 || setuid(st.st_uid) < 0) {
448             LOG(log_error, logtype_cnid, "uid/gid: %s", strerror(errno));
449             exit(1);
450         }
451     }
452 }
453
454
455 /* ----------------------- */
456 static void set_signal(void)
457 {
458     struct sigaction sv;
459
460     sv.sa_handler = sig_exit;
461     sv.sa_flags = 0;
462     sigemptyset(&sv.sa_mask);
463     sigaddset(&sv.sa_mask, SIGINT);
464     sigaddset(&sv.sa_mask, SIGTERM);
465     if (sigaction(SIGINT, &sv, NULL) < 0 || sigaction(SIGTERM, &sv, NULL) < 0) {
466         LOG(log_error, logtype_cnid, "main: sigaction: %s", strerror(errno));
467         exit(1);
468     }
469     sv.sa_handler = SIG_IGN;
470     sigemptyset(&sv.sa_mask);
471     if (sigaction(SIGPIPE, &sv, NULL) < 0) {
472         LOG(log_error, logtype_cnid, "main: sigaction: %s", strerror(errno));
473         exit(1);
474     }
475 }
476
477 static uid_t uid_from_name(const char *name)
478 {
479     struct passwd *pwd;
480
481     pwd = getpwnam(name);
482     if (pwd == NULL)
483         return 0;
484     return pwd->pw_uid;
485 }
486
487 /* ------------------------ */
488 int main(int argc, char *argv[])
489 {
490     EC_INIT;
491     int delete_bdb = 0;
492     int ctrlfd = -1, clntfd = -1;
493     AFPObj obj = { 0 };
494     char *volpath = NULL;
495     char *username = NULL;
496
497     while (( ret = getopt( argc, argv, ":dF:l:p:t:u:vV")) != -1 ) {
498         switch (ret) {
499         case 'd':
500             /* this is now just ignored, as we do it automatically anyway */
501             delete_bdb = 1;
502             break;
503         case 'F':
504             obj.cmdlineconfigfile = strdup(optarg);
505             break;
506         case 'p':
507             volpath = strdup(optarg);
508             break;
509         case 'l':
510             clntfd = atoi(optarg);
511             break;
512         case 't':
513             ctrlfd = atoi(optarg);
514             break;
515         case 'u':
516             username = strdup(optarg);
517             break;
518         case 'v':
519         case 'V':
520             printf("cnid_dbd (Netatalk %s)\n", VERSION);
521             return -1;
522         case ':':
523             break;
524         }
525     }
526
527     if (ctrlfd == -1 || clntfd == -1 || !volpath) {
528         LOG(log_error, logtype_cnid, "main: bad IPC fds");
529         exit(EXIT_FAILURE);
530     }
531
532     EC_ZERO( afp_config_parse(&obj, "cnid_dbd") );
533
534     if (username) {
535         strlcpy(obj.username, username, MAXUSERLEN);
536         obj.uid = uid_from_name(username);
537         if (!obj.uid) {
538             EC_FAIL_LOG("unknown user: '%s'", username);
539         }
540     }
541
542     LOG(log_debug, logtype_cnid, "user: %s, path %s",
543         username ? username : "-", volpath);
544
545     EC_ZERO( load_volumes(&obj, lv_all) );
546     EC_NULL( vol = getvolbypath(&obj, volpath) );
547     EC_ZERO( load_charset(vol) );
548     pack_setvol(vol);
549
550     EC_NULL( dbpath = bfromcstr(vol->v_dbpath) );
551     EC_ZERO( bcatcstr(dbpath, "/.AppleDB") );
552
553     LOG(log_debug, logtype_cnid, "db dir: \"%s\"", bdata(dbpath));
554
555     switch_to_user(bdata(dbpath));
556
557     set_signal();
558
559     /* SIGINT and SIGTERM are always off, unless we are in pselect */
560     block_sigs_onoff(1);
561
562     if ((dbp = db_param_read(bdata(dbpath))) == NULL)
563         EC_FAIL;
564     LOG(log_maxdebug, logtype_cnid, "Finished parsing db_param config file");
565
566     if (open_db() != 0) {
567         LOG(log_error, logtype_cnid, "Failed to open CNID database for volume \"%s\"", vol->v_localname);
568         EC_ZERO_LOG( reinit_db() );
569     }
570
571     if (comm_init(dbp, ctrlfd, clntfd) < 0) {
572         ret = -1;
573         goto close_db;
574     }
575
576     if (loop(dbp) < 0) {
577         ret = -1;
578         goto close_db;
579     }
580
581 close_db:
582     if (dbif_close(dbd) < 0)
583         ret = -1;
584
585     if (dbif_env_remove(bdata(dbpath)) < 0)
586         ret = -1;
587
588 EC_CLEANUP:
589     if (ret != 0)
590         exit(1);
591
592     if (exit_sig)
593         LOG(log_info, logtype_cnid, "main: Exiting on signal %i", exit_sig);
594     else
595         LOG(log_info, logtype_cnid, "main: Idle timeout, exiting");
596
597     EC_EXIT;
598 }