X-Git-Url: https://arthur.barton.de/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=libatalk%2Futil%2Fserver_child.c;h=016029f8c4200a34e87896f0eba8460326e02f89;hb=5a338fdcf3a5f33c7bebba55373172cf0bf2e00e;hp=b21bae35f08fe427b4f1f8eed027f0420cb2dd57;hpb=df4786e567fddb4b238f658d43e2662f488f73ba;p=netatalk.git diff --git a/libatalk/util/server_child.c b/libatalk/util/server_child.c index b21bae35..016029f8 100644 --- a/libatalk/util/server_child.c +++ b/libatalk/util/server_child.c @@ -1,15 +1,13 @@ /* - * $Id: server_child.c,v 1.12 2010-01-21 14:14:49 didg Exp $ - * * Copyright (c) 1997 Adrian Sun (asun@zoology.washington.edu) * All rights reserved. See COPYRIGHT. * * - * handle inserting, removing, and freeing of children. + * handle inserting, removing, and freeing of children. * this does it via a hash table. it incurs some overhead over * a linear append/remove in total removal and kills, but it makes * single-entry removals a fast operation. as total removals occur during - * child initialization and kills during server shutdown, this is + * child initialization and kills during server shutdown, this is * probably a win for a lot of connections and unimportant for a small * number of connections. */ @@ -24,7 +22,7 @@ #include #endif /* HAVE_UNISTD_H */ #include -#include +#include /* POSIX.1 sys/wait.h check */ #include @@ -33,6 +31,11 @@ #endif /* HAVE_SYS_WAIT_H */ #include +#include +#include +#include +#include + #ifndef WEXITSTATUS #define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8) #endif /* ! WEXITSTATUS */ @@ -43,368 +46,344 @@ #define WIFSTOPPED(status) (((status) & 0xff) == 0x7f) #endif #ifndef WIFSIGNALED -#define WIFSIGNALED(status) (!WIFSTOPPED(status) && !WIFEXITED(status)) +#define WIFSIGNALED(status) (!WIFSTOPPED(status) && !WIFEXITED(status)) #endif #ifndef WTERMSIG #define WTERMSIG(status) ((status) & 0x7f) #endif -#include - /* hash/child functions: hash OR's pid */ #define CHILD_HASHSIZE 32 #define HASH(i) ((((i) >> 8) ^ (i)) & (CHILD_HASHSIZE - 1)) -struct server_child_data { - pid_t pid; /* afpd worker process pid (from the worker afpd process )*/ - uid_t uid; /* user id of connected client (from the worker afpd process) */ - int valid; /* 1 if we have a clientid */ - u_int32_t time; /* client boot time (from the mac client) */ - int killed; /* 1 if we already tried to kill the client */ - - u_int32_t idlen; /* clientid len (from the Mac client) */ - char *clientid; /* clientid (from the Mac client) */ - struct server_child_data **prevp, *next; -}; - typedef struct server_child_fork { - struct server_child_data *table[CHILD_HASHSIZE]; - void (*cleanup)(const pid_t); + struct server_child_data *table[CHILD_HASHSIZE]; + void (*cleanup)(const pid_t); } server_child_fork; - +int parent_or_child; /* 0: parent, 1: child */ + static inline void hash_child(struct server_child_data **htable, - struct server_child_data *child) + struct server_child_data *child) { - struct server_child_data **table; + struct server_child_data **table; - table = &htable[HASH(child->pid)]; - if ((child->next = *table) != NULL) - (*table)->prevp = &child->next; - *table = child; - child->prevp = table; + table = &htable[HASH(child->pid)]; + if ((child->next = *table) != NULL) + (*table)->prevp = &child->next; + *table = child; + child->prevp = table; } static inline void unhash_child(struct server_child_data *child) { - if (child->prevp) { - if (child->next) - child->next->prevp = child->prevp; - *(child->prevp) = child->next; - } + if (child->prevp) { + if (child->next) + child->next->prevp = child->prevp; + *(child->prevp) = child->next; + } } -static inline struct server_child_data -*resolve_child(struct server_child_data **table, const pid_t pid) +static struct server_child_data *resolve_child(struct server_child_data **table, pid_t pid) { - struct server_child_data *child; + struct server_child_data *child; - for (child = table[HASH(pid)]; child; child = child->next) { - if (child->pid == pid) - break; - } + for (child = table[HASH(pid)]; child; child = child->next) { + if (child->pid == pid) + break; + } - return child; + return child; } /* initialize server_child structure */ server_child *server_child_alloc(const int connections, const int nforks) { - server_child *children; + server_child *children; - children = (server_child *) calloc(1, sizeof(server_child)); - if (!children) - return NULL; + children = (server_child *) calloc(1, sizeof(server_child)); + if (!children) + return NULL; - children->nsessions = connections; - children->nforks = nforks; - children->fork = (void *) calloc(nforks, sizeof(server_child_fork)); - - if (!children->fork) { - free(children); - return NULL; - } + children->nsessions = connections; + children->nforks = nforks; + children->fork = (void *) calloc(nforks, sizeof(server_child_fork)); + + if (!children->fork) { + free(children); + return NULL; + } - return children; + return children; } -/* add a child in. return 0 on success, -1 on serious error, and - * > 0 for a non-serious error. */ -int server_child_add(server_child *children, const int forkid, - const pid_t pid) +/*! + * add a child + * @return pointer to struct server_child_data on success, NULL on error + */ +afp_child_t *server_child_add(server_child *children, int forkid, pid_t pid, uint ipc_fds[2]) { - server_child_fork *fork; - struct server_child_data *child; - sigset_t sig, oldsig; - - /* we need to prevent deletions from occuring before we get a - * chance to add the child in. */ - sigemptyset(&sig); - sigaddset(&sig, SIGCHLD); - pthread_sigmask(SIG_BLOCK, &sig, &oldsig); - - /* it's possible that the child could have already died before the - * pthread_sigmask. we need to check for this. */ - if (kill(pid, 0) < 0) { - pthread_sigmask(SIG_SETMASK, &oldsig, NULL); - return 1; - } + server_child_fork *fork; + afp_child_t *child = NULL; + sigset_t sig, oldsig; + + /* we need to prevent deletions from occuring before we get a + * chance to add the child in. */ + sigemptyset(&sig); + sigaddset(&sig, SIGCHLD); + pthread_sigmask(SIG_BLOCK, &sig, &oldsig); + + /* it's possible that the child could have already died before the + * pthread_sigmask. we need to check for this. */ + if (kill(pid, 0) < 0) { + LOG(log_error, logtype_default, "server_child_add: no such process pid [%d]", pid); + goto exit; + } - fork = (server_child_fork *) children->fork + forkid; + fork = (server_child_fork *) children->fork + forkid; - /* if we already have an entry. just return. */ - if (resolve_child(fork->table, pid)) { - pthread_sigmask(SIG_SETMASK, &oldsig, NULL); - return 0; - } + /* if we already have an entry. just return. */ + if (child = resolve_child(fork->table, pid)) + goto exit; - if ((child = (struct server_child_data *) - calloc(1, sizeof(struct server_child_data))) == NULL) { - pthread_sigmask(SIG_SETMASK, &oldsig, NULL); - return -1; - } + if ((child = calloc(1, sizeof(afp_child_t))) == NULL) + goto exit; + + child->pid = pid; + child->valid = 0; + child->killed = 0; + child->ipc_fds[0] = ipc_fds[0]; + child->ipc_fds[1] = ipc_fds[1]; - child->pid = pid; - child->valid = 0; - child->killed = 0; - hash_child(fork->table, child); - children->count++; - pthread_sigmask(SIG_SETMASK, &oldsig, NULL); + hash_child(fork->table, child); + children->count++; - return 0; +exit: + pthread_sigmask(SIG_SETMASK, &oldsig, NULL); + return child; } /* remove a child and free it */ -int server_child_remove(server_child *children, const int forkid, - const pid_t pid) +int server_child_remove(server_child *children, const int forkid, pid_t pid) { - server_child_fork *fork; - struct server_child_data *child; - - fork = (server_child_fork *) children->fork + forkid; - if (!(child = resolve_child(fork->table, pid))) - return 0; - - unhash_child(child); - if (child->clientid) { - free(child->clientid); - } - free(child); - children->count--; - return 1; + int fd; + server_child_fork *fork; + struct server_child_data *child; + + fork = (server_child_fork *) children->fork + forkid; + if (!(child = resolve_child(fork->table, pid))) + return -1; + + unhash_child(child); + if (child->clientid) { + free(child->clientid); + child->clientid = NULL; + } + + /* In main:child_handler() we need the fd in order to remove it from the pollfd set */ + fd = child->ipc_fds[0]; + if (child->ipc_fds[0] != -1) { + close(child->ipc_fds[0]); + child->ipc_fds[0] = -1; + } + if (child->ipc_fds[1] != -1) { + close(child->ipc_fds[1]); + child->ipc_fds[1] = -1; + } + + free(child); + children->count--; + + if (fork->cleanup) + fork->cleanup(pid); + + return fd; } /* free everything: by using a hash table, this increases the cost of * this part over a linked list by the size of the hash table */ void server_child_free(server_child *children) { - server_child_fork *fork; - struct server_child_data *child, *tmp; - int i, j; - - for (i = 0; i < children->nforks; i++) { - fork = (server_child_fork *) children->fork + i; - for (j = 0; j < CHILD_HASHSIZE; j++) { - child = fork->table[j]; /* start at the beginning */ - while (child) { - tmp = child->next; - if (child->clientid) { - free(child->clientid); + server_child_fork *fork; + struct server_child_data *child, *tmp; + int i, j; + + for (i = 0; i < children->nforks; i++) { + fork = (server_child_fork *) children->fork + i; + for (j = 0; j < CHILD_HASHSIZE; j++) { + child = fork->table[j]; /* start at the beginning */ + while (child) { + tmp = child->next; + if (child->clientid) { + free(child->clientid); + } + free(child); + child = tmp; + } } - free(child); - child = tmp; - } } - } - free(children->fork); - free(children); + free(children->fork); + free(children); } -/* send kill to child processes: this also has an increased cost over - * a plain-old linked list */ -void server_child_kill(server_child *children, const int forkid, - const int sig) +/* send signal to all child processes */ +void server_child_kill(server_child *children, int forkid, int sig) { - server_child_fork *fork; - struct server_child_data *child, *tmp; - int i; - - fork = (server_child_fork *) children->fork + forkid; - for (i = 0; i < CHILD_HASHSIZE; i++) { - child = fork->table[i]; - while (child) { - tmp = child->next; - kill(child->pid, sig); - child = tmp; + server_child_fork *fork; + struct server_child_data *child, *tmp; + int i; + + fork = (server_child_fork *) children->fork + forkid; + for (i = 0; i < CHILD_HASHSIZE; i++) { + child = fork->table[i]; + while (child) { + tmp = child->next; + kill(child->pid, sig); + child = tmp; + } } - } } /* send kill to a child processes. - * a plain-old linked list + * a plain-old linked list * FIXME use resolve_child ? */ static int kill_child(struct server_child_data *child) { - if (!child->killed) { - kill(child->pid, SIGTERM); - /* we don't wait because there's no guarantee that we can really kill it */ - child->killed = 1; - return 1; - } - else { - LOG(log_info, logtype_default, "Already tried to kill (%d) before! Still there?", child->pid); - } - return 0; + if (!child->killed) { + kill(child->pid, SIGTERM); + /* we don't wait because there's no guarantee that we can really kill it */ + child->killed = 1; + return 1; + } else { + LOG(log_info, logtype_default, "Unresponsive child[%d], sending SIGKILL", child->pid); + kill(child->pid, SIGKILL); + } + return 1; } -/* -------------------- */ -void server_child_kill_one(server_child *children, const int forkid, const pid_t pid, const uid_t uid) +/*! + * Try to find an old session and pass socket + * @returns -1 on error, 0 if no matching session was found, 1 if session was found and socket passed + */ +int server_child_transfer_session(server_child *children, + int forkid, + pid_t pid, + uid_t uid, + int afp_socket, + uint16_t DSI_requestID) { - server_child_fork *fork; - struct server_child_data *child, *tmp; - int i; - - fork = (server_child_fork *) children->fork + forkid; - for (i = 0; i < CHILD_HASHSIZE; i++) { - child = fork->table[i]; - while (child) { - tmp = child->next; - if (child->pid == pid) { - if (!child->valid) { - /* hmm, client 'guess' the pid, rogue? */ - LOG(log_info, logtype_default, "Disconnecting old session (%d) no token, bailout!", child->pid); - } - else if (child->uid != uid) { - LOG(log_info, logtype_default, "Disconnecting old session (%d) not the same user, bailout!", child->pid); - } - else { - kill_child(child); - } - } - child = tmp; + EC_INIT; + server_child_fork *fork; + struct server_child_data *child; + int i; + + fork = (server_child_fork *) children->fork + forkid; + if ((child = resolve_child(fork->table, pid)) == NULL) { + LOG(log_note, logtype_default, "Reconnect: no child[%u]", pid); + if (kill(pid, 0) == 0) { + LOG(log_note, logtype_default, "Reconnect: terminating old session[%u]", pid); + kill(pid, SIGTERM); + sleep(2); + if (kill(pid, 0) == 0) { + LOG(log_error, logtype_default, "Reconnect: killing old session[%u]", pid); + kill(pid, SIGKILL); + sleep(2); + } + } + return 0; } - } + + if (!child->valid) { + /* hmm, client 'guess' the pid, rogue? */ + LOG(log_error, logtype_default, "Reconnect: invalidated child[%u]", pid); + return 0; + } else if (child->uid != uid) { + LOG(log_error, logtype_default, "Reconnect: child[%u] not the same user", pid); + return 0; + } + + LOG(log_note, logtype_default, "Reconnect: transfering session to child[%u]", pid); + + if (writet(child->ipc_fds[0], &DSI_requestID, 2, 0, 2) != 2) { + LOG(log_error, logtype_default, "Reconnect: error sending DSI id to child[%u]", pid); + EC_STATUS(-1); + goto EC_CLEANUP; + } + EC_ZERO_LOG(send_fd(child->ipc_fds[0], afp_socket)); + EC_ZERO_LOG(kill(pid, SIGURG)); + + EC_STATUS(1); + +EC_CLEANUP: + EC_EXIT; } /* see if there is a process for the same mac */ /* if the times don't match mac has been rebooted */ -void server_child_kill_one_by_id(server_child *children, const int forkid, const pid_t pid, - const uid_t uid, - const u_int32_t idlen, char *id, u_int32_t boottime) +void server_child_kill_one_by_id(server_child *children, int forkid, pid_t pid, + uid_t uid, uint32_t idlen, char *id, uint32_t boottime) { - server_child_fork *fork; - struct server_child_data *child, *tmp; - int i; - - fork = (server_child_fork *) children->fork + forkid; - for (i = 0; i < CHILD_HASHSIZE; i++) { - child = fork->table[i]; - while (child) { - tmp = child->next; - if ( child->pid != pid) { - if ( child->idlen == idlen && !memcmp(child->clientid, id, idlen)) { - if ( child->time != boottime ) { - if (uid == child->uid) { - if (kill_child(child)) { - LOG(log_info, logtype_default, "Disconnecting old session %d, client rebooted.", child->pid); - } - } - else { - LOG(log_info, logtype_default, "Disconnecting old session not the same uid, bailout!"); - } - } - else if (child->killed) { - /* there's case where a Mac close a connection and restart a new one before - * the first is 'waited' by the master afpd process - */ - LOG(log_info, logtype_default, - "WARNING: connection (%d) killed but still there.", child->pid); - } - else { - LOG(log_info, logtype_default, - "WARNING: 2 connections (%d, %d), boottime identical, don't know if one needs to be disconnected.", - child->pid, pid); - } - - } - } - else - { - child->time = boottime; - /* free old token if any */ - if (child->clientid) { - free(child->clientid); - } - child->uid = uid; - child->valid = 1; - child->idlen = idlen; - child->clientid = id; - LOG(log_debug, logtype_default, "Setting clientid (len %d) for %d, boottime %X", idlen, child->pid, boottime); - } - child = tmp; + server_child_fork *fork; + struct server_child_data *child, *tmp; + int i; + + fork = (server_child_fork *)children->fork + forkid; + + for (i = 0; i < CHILD_HASHSIZE; i++) { + child = fork->table[i]; + while (child) { + tmp = child->next; + if ( child->pid != pid) { + if (child->idlen == idlen && memcmp(child->clientid, id, idlen) == 0) { + if ( child->time != boottime ) { + /* Client rebooted */ + if (uid == child->uid) { + kill_child(child); + LOG(log_warning, logtype_default, + "Terminated disconnected child[%u], client rebooted.", + child->pid); + } else { + LOG(log_warning, logtype_default, + "Session with different pid[%u]", child->pid); + } + } else { + /* One client with multiple sessions */ + LOG(log_debug, logtype_default, + "Found another session[%u] for client[%u]", child->pid, pid); + } + } + } else { + /* update childs own slot */ + child->time = boottime; + if (child->clientid) + free(child->clientid); + LOG(log_debug, logtype_default, "Setting client ID for %u", child->pid); + child->uid = uid; + child->valid = 1; + child->idlen = idlen; + child->clientid = id; + } + child = tmp; + } } - } } /* for extra cleanup if necessary */ void server_child_setup(server_child *children, const int forkid, - void (*fcn)(const pid_t)) + void (*fcn)(const pid_t)) { - server_child_fork *fork; + server_child_fork *fork; - fork = (server_child_fork *) children->fork + forkid; - fork->cleanup = fcn; + fork = (server_child_fork *) children->fork + forkid; + fork->cleanup = fcn; } -/* keep track of children. */ -void server_child_handler(server_child *children) -{ - int status, i; - pid_t pid; - -#ifndef WAIT_ANY -#define WAIT_ANY (-1) -#endif /* ! WAIT_ANY */ - - while ((pid = waitpid(WAIT_ANY, &status, WNOHANG)) > 0) { - for (i = 0; i < children->nforks; i++) { - if (server_child_remove(children, i, pid)) { - server_child_fork *fork; - - fork = (server_child_fork *) children->fork + i; - if (fork->cleanup) - fork->cleanup(pid); - break; - } - } - - if (WIFEXITED(status)) { - if (WEXITSTATUS(status)) { - LOG(log_info, logtype_default, "server_child[%d] %d exited %d", i, pid, - WEXITSTATUS(status)); - } else { - LOG(log_info, logtype_default, "server_child[%d] %d done", i, pid); - } - } else { - if (WIFSIGNALED(status)) - { - LOG(log_info, logtype_default, "server_child[%d] %d killed by signal %d", i, pid, - WTERMSIG (status)); - } - else - { - LOG(log_info, logtype_default, "server_child[%d] %d died", i, pid); - } - } - } -} - -/* --------------------------- +/* --------------------------- * reset children signals -*/ + */ void server_reset_signal(void) { struct sigaction sv; @@ -415,18 +394,18 @@ void server_reset_signal(void) memset(&sv, 0, sizeof(sv)); sv.sa_handler = SIG_DFL; sigemptyset( &sv.sa_mask ); - + sigaction(SIGALRM, &sv, NULL ); sigaction(SIGHUP, &sv, NULL ); sigaction(SIGTERM, &sv, NULL ); sigaction(SIGUSR1, &sv, NULL ); sigaction(SIGCHLD, &sv, NULL ); - + sigemptyset(&sigs); sigaddset(&sigs, SIGALRM); sigaddset(&sigs, SIGHUP); sigaddset(&sigs, SIGUSR1); sigaddset(&sigs, SIGCHLD); pthread_sigmask(SIG_UNBLOCK, &sigs, NULL); - + }