#include <atalk/util.h>
#include <atalk/logger.h>
-#include <atalk/cnid_dbd_private.h>
+#include <atalk/cnid_bdb_private.h>
#include <atalk/paths.h>
#include <atalk/compat.h>
#include <atalk/errchk.h>
#include <atalk/bstrlib.h>
+#include <atalk/bstradd.h>
#include <atalk/netatalk_conf.h>
#include <atalk/volume.h>
static uint maxvol;
#define MAXSPAWN 3 /* Max times respawned in.. */
-#define TESTTIME 42 /* this much seconds apfd client tries to *
+#define TESTTIME 10 /* this much seconds apfd client tries to *
* to reconnect every 5 secondes, catch it */
#define MAXVOLS 4096
#define DEFAULTHOST "localhost"
#define DEFAULTPORT "4700"
struct server {
- struct vol *vol;
+ char *v_path;
pid_t pid;
time_t tm; /* When respawned last */
- int count; /* Times respawned in the last TESTTIME secondes */
+ unsigned int count; /* Times respawned in the last TESTTIME secondes */
int control_fd; /* file descriptor to child cnid_dbd process */
};
static void daemon_exit(int i)
{
- server_unlock(_PATH_CNID_METAD_LOCK);
exit(i);
}
/* ------------------ */
-static void sigterm_handler(int sig)
+static void sig_handler(int sig)
{
switch( sig ) {
- case SIGTERM :
- LOG(log_info, logtype_afpd, "shutting down on signal %d", sig );
+ case SIGTERM:
+ case SIGQUIT:
+ LOG(log_note, logtype_afpd, "shutting down on %s",
+ sig == SIGTERM ? "SIGTERM" : "SIGQUIT");
break;
default :
LOG(log_error, logtype_afpd, "unexpected signal: %d", sig);
daemon_exit(0);
}
-static struct server *test_usockfn(const struct vol *vol)
+static struct server *test_usockfn(const char *path)
{
int i;
- if (!(vol->v_flags & AFPVOL_OPEN))
- return NULL;
-
for (i = 0; i < maxvol; i++) {
- if (vol->v_vid == srv[i].vol->v_vid)
+ if (srv[i].v_path && STRCMP(path, ==, srv[i].v_path))
return &srv[i];
}
return NULL;
}
-/* -------------------- */
-static int maybe_start_dbd(const AFPObj *obj, char *dbdpn, struct vol *vol)
+/**
+ * Pass connection request to existing cnid_dbd process or start a new one
+ *
+ * @param[in] obj handle
+ * @param[in] dbdpn Path to cnid_dbd binary
+ * @param[in] volpath Path of AFP volume
+ * @param[in] username Optional username, may be NULL
+ *
+ * @return 0 on success, -1 on error
+ **/
+ int maybe_start_dbd(const AFPObj *obj, char *dbdpn, const char *volpath, const char *username)
{
pid_t pid;
struct server *up;
time_t t;
char buf1[8];
char buf2[8];
- char *volpath = vol->v_path;
- LOG(log_debug, logtype_cnid, "maybe_start_dbd: Volume: \"%s\"", volpath);
+ LOG(log_debug, logtype_cnid, "maybe_start_dbd(\"%s\"): BEGIN", volpath);
- up = test_usockfn(vol);
+ up = test_usockfn(volpath);
if (up && up->pid) {
/* we already have a process, send our fd */
+ LOG(log_debug, logtype_cnid, "maybe_start_dbd: cnid_dbd[%d] already serving", up->pid);
if (send_fd(up->control_fd, rqstfd) < 0) {
/* FIXME */
return -1;
return 0;
}
- LOG(log_maxdebug, logtype_cnid, "maybe_start_dbd: no cnid_dbd for that volume yet");
+ LOG(log_debug, logtype_cnid, "maybe_start_dbd: no cnid_dbd serving yet");
time(&t);
if (!up) {
/* find an empty slot (i < maxvol) or the first free slot (i == maxvol)*/
- for (i = 0; i <= maxvol; i++) {
- if (srv[i].vol == NULL && i < MAXVOLS) {
+ for (i = 0; i <= maxvol && i < MAXVOLS; i++) {
+ if (srv[i].v_path == NULL) {
up = &srv[i];
- up->vol = vol;
- vol->v_flags |= AFPVOL_OPEN;
+ if ((up->v_path = strdup(volpath)) == NULL)
+ return -1;
up->tm = t;
up->count = 0;
if (i == maxvol)
return -1;
}
} else {
- /* we have a slot but no process, check for respawn too fast */
- if ( (t < (up->tm + TESTTIME)) /* We're in the respawn time window */
- &&
- (up->count > MAXSPAWN) ) { /* ...and already tried to fork too often */
- LOG(log_maxdebug, logtype_cnid, "maybe_start_dbd: respawn too fast just exiting");
- return -1; /* just exit, dont sleep, because we might have work to do for another client */
- }
- if ( t >= (up->tm + TESTTIME) ) { /* out of respawn too fast windows reset the count */
- LOG(log_maxdebug, logtype_cnid, "maybe_start_dbd: respawn window ended");
- up->tm = t;
- up->count = 0;
+ /* we have a slot but no process */
+ if (up->count > 0) {
+ /* check for respawn too fast */
+ if (t < (up->tm + TESTTIME)) {
+ /* We're in the respawn time window */
+ if (up->count > MAXSPAWN) {
+ /* ...and already tried to fork too often */
+ LOG(log_maxdebug, logtype_cnid, "maybe_start_dbd: respawning too fast");
+ return -1; /* just exit, dont sleep, because we might have work to do for another client */
+ }
+ } else {
+ /* out of respawn too fast windows reset the count */
+ LOG(log_info, logtype_cnid, "maybe_start_dbd: respawn window ended");
+ up->count = 0;
+ }
}
up->count++;
- LOG(log_maxdebug, logtype_cnid, "maybe_start_dbd: respawn count now is: %u", up->count);
+ up->tm = t;
+ LOG(log_maxdebug, logtype_cnid, "maybe_start_dbd: respawn count: %u", up->count);
if (up->count > MAXSPAWN) {
/* We spawned too fast. From now until the first time we tried + TESTTIME seconds
we will just return -1 above */
- LOG(log_maxdebug, logtype_cnid, "maybe_start_dbd: reached MAXSPAWN threshhold");
- }
+ LOG(log_info, logtype_cnid, "maybe_start_dbd: reached MAXSPAWN threshhold");
+ }
}
/*
/* there's a pb with the db inform child, it will delete the db */
LOG(log_warning, logtype_cnid,
"Multiple attempts to start CNID db daemon for \"%s\" failed, wiping the slate clean...",
- up->vol->v_path);
- ret = execlp(dbdpn, dbdpn, "-F", obj->options.configfile, "-p", volpath, "-t", buf1, "-l", buf2, "-d", NULL);
+ up->v_path);
+ ret = execlp(dbdpn, dbdpn,
+ "-F", obj->options.configfile,
+ "-p", volpath,
+ "-t", buf1,
+ "-l", buf2,
+ "-u", username,
+ NULL);
} else {
- ret = execlp(dbdpn, dbdpn, "-F", obj->options.configfile, "-p", volpath, "-t", buf1, "-l", buf2, NULL);
+ ret = execlp(dbdpn, dbdpn,
+ "-F", obj->options.configfile,
+ "-p", volpath,
+ "-t", buf1,
+ "-l", buf2,
+ "-u", username,
+ NULL);
}
/* Yikes! We're still here, so exec failed... */
LOG(log_error, logtype_cnid, "Fatal error in exec: %s", strerror(errno));
}
/* ------------------ */
-static int set_dbdir(char *dbdir)
+static int set_dbdir(const char *dbdir, const char *vpath)
{
- int len;
+ EC_INIT;
struct stat st;
+ bstring oldpath, newpath;
+ char *cmd_argv[4];
- len = strlen(dbdir);
+ LOG(log_debug, logtype_cnid, "set_dbdir: volume: %s, db path: %s", vpath, dbdir);
- if (stat(dbdir, &st) < 0 && mkdir(dbdir, 0755) < 0) {
- LOG(log_error, logtype_cnid, "set_dbdir: mkdir failed for %s", dbdir);
- return -1;
- }
+ EC_NULL_LOG( oldpath = bformat("%s/%s/", vpath, DBHOME) );
+ EC_NULL_LOG( newpath = bformat("%s/%s/", dbdir, DBHOME) );
- if (dbdir[len - 1] != '/') {
- strcat(dbdir, "/");
- len++;
- }
- strcpy(dbdir + len, DBHOME);
- if (stat(dbdir, &st) < 0 && mkdir(dbdir, 0755 ) < 0) {
+ if (lstat(dbdir, &st) < 0 && mkdir(dbdir, 0755) < 0) {
LOG(log_error, logtype_cnid, "set_dbdir: mkdir failed for %s", dbdir);
- return -1;
+ EC_FAIL;
}
- return 0;
-}
-/* ------------------ */
-static uid_t user_to_uid (char *username)
-{
- struct passwd *this_passwd;
-
- /* check for anything */
- if ( !username || strlen ( username ) < 1 ) return 0;
-
- /* grab the /etc/passwd record relating to username */
- this_passwd = getpwnam ( username );
-
- /* return false if there is no structure returned */
- if (this_passwd == NULL) return 0;
-
- /* return proper uid */
- return this_passwd->pw_uid;
-
-}
-
-/* ------------------ */
-static gid_t group_to_gid ( char *group)
-{
- struct group *this_group;
-
- /* check for anything */
- if ( !group || strlen ( group ) < 1 ) return 0;
-
- /* grab the /etc/groups record relating to group */
- this_group = getgrnam ( group );
+ if (lstat(cfrombstr(oldpath), &st) == 0 && lstat(cfrombstr(newpath), &st) != 0 && errno == ENOENT) {
+ /* There's an .AppleDB in the volume root, we move it */
+ cmd_argv[0] = "mv";
+ cmd_argv[1] = bdata(oldpath);
+ cmd_argv[2] = (char *)dbdir;
+ cmd_argv[3] = NULL;
+ if (run_cmd("mv", cmd_argv) != 0) {
+ LOG(log_error, logtype_cnid, "set_dbdir: moving CNID db from \"%s\" to \"%s\" failed",
+ bdata(oldpath), dbdir);
+ EC_FAIL;
+ }
- /* return false if there is no structure returned */
- if (this_group == NULL) return 0;
+ }
- /* return proper gid */
- return this_group->gr_gid;
+ if (lstat(cfrombstr(newpath), &st) < 0 && mkdir(cfrombstr(newpath), 0755 ) < 0) {
+ LOG(log_error, logtype_cnid, "set_dbdir: mkdir failed for %s", bdata(newpath));
+ EC_FAIL;
+ }
+EC_CLEANUP:
+ bdestroy(oldpath);
+ bdestroy(newpath);
+ EC_EXIT;
}
/* ------------------ */
daemon_exit(EXITERR_SYS);
}
- /* Catch SIGTERM */
- sv.sa_handler = sigterm_handler;
+ /* Catch SIGTERM and SIGQUIT */
+ sv.sa_handler = sig_handler;
sigfillset(&sv.sa_mask );
if (sigaction(SIGTERM, &sv, NULL ) < 0 ) {
LOG(log_error, logtype_afpd, "sigaction: %s", strerror(errno) );
daemon_exit(EXITERR_SYS);
}
+ if (sigaction(SIGQUIT, &sv, NULL ) < 0 ) {
+ LOG(log_error, logtype_afpd, "sigaction: %s", strerror(errno) );
+ daemon_exit(EXITERR_SYS);
+ }
/* Ignore the rest */
sv.sa_handler = SIG_IGN;
/* block everywhere but in pselect */
sigemptyset(&set);
sigaddset(&set, SIGCHLD);
- sigprocmask(SIG_BLOCK, &set, NULL);
+ sigprocmask(SIG_SETMASK, &set, NULL);
}
static int setlimits(void)
return 0;
}
+static uid_t uid_from_name(const char *name)
+{
+ struct passwd *pwd;
+
+ pwd = getpwnam(name);
+ if (pwd == NULL)
+ return 0;
+ return pwd->pw_uid;
+}
+
/* ------------------ */
int main(int argc, char *argv[])
{
- char volpath[MAXPATHLEN + 1];
- int len, actual_len;
+ char *volname = NULL;
+ char *volpath = NULL;
+ char *username = NULL;
+ int len[DBD_NUM_OPEN_ARGS], actual_len;
pid_t pid;
int status;
char *dbdpn = _PATH_CNID_DBD;
- char *host = DEFAULTHOST;
- char *port = DEFAULTPORT;
+ char *host;
+ char *port;
int i;
int cc;
uid_t uid = 0;
gid_t gid = 0;
- int err = 0;
int debug = 0;
int ret;
sigset_t set;
AFPObj obj = { 0 };
struct vol *vol;
- while (( cc = getopt( argc, argv, "dF:v")) != -1 ) {
+ while (( cc = getopt( argc, argv, "dF:vV")) != -1 ) {
switch (cc) {
case 'd':
debug = 1;
obj.cmdlineconfigfile = strdup(optarg);
break;
case 'v':
+ case 'V':
printf("cnid_metad (Netatalk %s)\n", VERSION);
return -1;
default:
- printf("cnid_metad [-dv] [-F alternate configfile ]\n");
+ printf("cnid_metad [-dvV] [-F alternate configfile ]\n");
return -1;
}
}
- /* Check for PID lockfile */
- if (check_lockfile("cnid_metad", _PATH_CNID_METAD_LOCK))
- return -1;
-
if (!debug && daemonize(0, 0) != 0)
exit(EXITERR_SYS);
- /* Create PID lockfile */
- if (create_lockfile("cnid_metad", _PATH_CNID_METAD_LOCK))
- return -1;
-
- if (afp_config_parse(&obj) != 0)
- daemon_exit(1);
-
- set_processname("cnid_metad");
- setuplog(obj.options.logconfig, obj.options.logfile);
-
- if (load_volumes(&obj, NULL) != 0)
+ if (afp_config_parse(&obj, "cnid_metad") != 0)
daemon_exit(1);
(void)setlimits();
+ host = atalk_iniparser_getstrdup(obj.iniconfig, INISEC_GLOBAL, "cnid listen", "localhost:4700");
+ if ((port = strrchr(host, ':')))
+ *port++ = 0;
+ else
+ port = DEFAULTPORT;
if ((srvfd = tsockfd_create(host, port, 10)) < 0)
daemon_exit(1);
+ LOG(log_note, logtype_afpd, "CNID Server listening on %s:%s", host, port);
+
/* switch uid/gid */
if (uid || gid) {
LOG(log_debug, logtype_cnid, "Setting uid/gid to %i/%i", uid, gid);
if (srv[i].pid == pid) {
srv[i].pid = 0;
close(srv[i].control_fd);
- srv[i].vol->v_flags &= ~AFPVOL_OPEN;
break;
}
}
if (WIFEXITED(status)) {
LOG(log_info, logtype_cnid, "cnid_dbd[%i] exited with exit code %i",
pid, WEXITSTATUS(status));
+ } else {
+ /* cnid_dbd did a clean exit probably on idle timeout, reset bookkeeping */
+ srv[i].tm = 0;
+ srv[i].count = 0;
}
- else if (WIFSIGNALED(status)) {
+ if (WIFSIGNALED(status)) {
LOG(log_info, logtype_cnid, "cnid_dbd[%i] got signal %i",
pid, WTERMSIG(status));
}
if (rqstfd <= 0)
continue;
- ret = readt(rqstfd, &len, sizeof(int), 1, 4);
+ ret = readt(rqstfd, &len[0], sizeof(int) * DBD_NUM_OPEN_ARGS, 1, 4);
if (!ret) {
/* already close */
LOG(log_severe, logtype_cnid, "error read: %s", strerror(errno));
goto loop_end;
}
- else if (ret != sizeof(int)) {
+ else if (ret != DBD_NUM_OPEN_ARGS * sizeof(int)) {
LOG(log_error, logtype_cnid, "short read: got %d", ret);
goto loop_end;
}
+
/*
* checks for buffer overruns. The client libatalk side does it too
* before handing the dir path over but who trusts clients?
*/
- if (!len || len +DBHOMELEN +2 > MAXPATHLEN) {
- LOG(log_error, logtype_cnid, "wrong len parameter: %d", len);
+ if (!len[0] || !len[1]) {
+ LOG(log_error, logtype_cnid, "wrong len parameter: len[0]: %d, len[1]: %d", len[0], len[1]);
goto loop_end;
}
- actual_len = readt(rqstfd, volpath, len, 1, 5);
- if (actual_len < 0) {
- LOG(log_severe, logtype_cnid, "Read(2) error : %s", strerror(errno));
+ volname = malloc(len[0]);
+ volpath = malloc(len[1]);
+ if (len[2]) {
+ username = malloc(len[2]);
+ }
+ if (!volname || !volpath || (len[2] && !username)) {
+ LOG(log_severe, logtype_cnid, "malloc: %s", strerror(errno));
goto loop_end;
}
- if (actual_len != len) {
- LOG(log_error, logtype_cnid, "error/short read (dir): %s", strerror(errno));
+
+ actual_len = readt(rqstfd, volname, len[0], 1, 5);
+ if (actual_len != len[0]) {
+ LOG(log_severe, logtype_cnid, "readt: %s", strerror(errno));
goto loop_end;
}
- volpath[len] = '\0';
+ actual_len = readt(rqstfd, volpath, len[1], 1, 5);
+ if (actual_len != len[1]) {
+ LOG(log_severe, logtype_cnid, "readt: %s", strerror(errno));
+ goto loop_end;
+ }
- if ((vol = getvolbypath(volpath)) == NULL) {
- LOG(log_severe, logtype_cnid, "getvolbypath(\"%s\"): %s", volpath, strerror(errno));
+ if (len[2]) {
+ actual_len = readt(rqstfd, username, len[2], 1, 5);
+ if (actual_len != len[2]) {
+ LOG(log_severe, logtype_cnid, "readt: %s", strerror(errno));
+ goto loop_end;
+ }
+ strlcpy(obj.username, username, MAXUSERLEN);
+ obj.uid = uid_from_name(username);
+ if (!obj.uid)
+ goto loop_end;
+ } else {
+ obj.username[0] = 0;
+ }
+
+ LOG(log_debug, logtype_cnid, "user: %s, volume %s, path %s",
+ username ? username : "-", volname, volpath);
+
+ if (load_volumes(&obj, lv_all) != 0) {
+ LOG(log_severe, logtype_cnid, "main: error reloading config");
goto loop_end;
}
- if (set_dbdir(vol->v_dbpath) < 0) {
+ if ((vol = getvolbypath(&obj, volpath)) == NULL) {
+ LOG(log_severe, logtype_cnid, "main: no volume for path \"%s\"", volpath);
goto loop_end;
}
- maybe_start_dbd(&obj, dbdpn, vol);
+ LOG(log_maxdebug, logtype_cnid, "main: dbpath: %s", vol->v_dbpath);
+
+ if (set_dbdir(vol->v_dbpath, vol->v_path) < 0) {
+ goto loop_end;
+ }
+
+ maybe_start_dbd(&obj, dbdpn, vol->v_path, username);
+
loop_end:
close(rqstfd);
+ unload_volumes(&obj);
+ SAFE_FREE(volname);
+ SAFE_FREE(volpath);
+ SAFE_FREE(username);
}
}