X-Git-Url: https://arthur.barton.de/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=libatalk%2Futil%2Fserver_child.c;h=59c8c7e5de657d2b0b84524e4ffc1133fb924cdc;hb=d525573d235fbbba664d69e90450c7b82eb05835;hp=7e26eb0332715f875ca64fda77d7dac9bc26aefe;hpb=458fcac4e6aee7eb54ba744f57169f70cce12505;p=netatalk.git diff --git a/libatalk/util/server_child.c b/libatalk/util/server_child.c index 7e26eb03..59c8c7e5 100644 --- a/libatalk/util/server_child.c +++ b/libatalk/util/server_child.c @@ -3,251 +3,401 @@ * 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. */ #ifdef HAVE_CONFIG_H #include "config.h" -#endif +#endif /* HAVE_CONFIG_H */ #include #include #include #include -#include +#include #include #include +#include +#include +#include +#include #include -#ifndef __inline__ -#define __inline__ +#ifndef WEXITSTATUS +#define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8) +#endif /* ! WEXITSTATUS */ +#ifndef WIFEXITED +#define WIFEXITED(stat_val) (((stat_val) & 255) == 0) +#endif /* ! WIFEXITED */ +#ifndef WIFSTOPPED +#define WIFSTOPPED(status) (((status) & 0xff) == 0x7f) +#endif +#ifndef WIFSIGNALED +#define WIFSIGNALED(status) (!WIFSTOPPED(status) && !WIFEXITED(status)) +#endif +#ifndef WTERMSIG +#define WTERMSIG(status) ((status) & 0x7f) #endif /* 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; - 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; - -static __inline__ void hash_child(struct server_child_data **htable, - struct server_child_data *child) +static inline void hash_child(struct server_child_data **htable, + 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) +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)); - return children; + if (!children->fork) { + free(children); + return NULL; + } + + 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; - - /* we need to prevent deletions from occuring before we get a - * chance to add the child in. */ - sigemptyset(&sig); - sigaddset(&sig, SIGCHLD); - sigprocmask(SIG_BLOCK, &sig, NULL); - - /* it's possible that the child could have already died before the - * sigprocmask. we need to check for this. */ - if (kill(pid, 0) < 0) { - sigprocmask(SIG_UNBLOCK, &sig, 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 (child = resolve_child(fork->table, pid)) + goto exit; - /* if we already have an entry. just return. */ - if (resolve_child(fork->table, pid)) { - sigprocmask(SIG_UNBLOCK, &sig, NULL); - return 0; - } + if ((child = calloc(1, sizeof(afp_child_t))) == NULL) + goto exit; - if ((child = (struct server_child_data *) - calloc(1, sizeof(struct server_child_data))) == NULL) { - sigprocmask(SIG_UNBLOCK, &sig, NULL); - return -1; - } + 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; - hash_child(fork->table, child); - children->count++; - sigprocmask(SIG_UNBLOCK, &sig, 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); - 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; - free(child); - child = tmp; - } + 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(children->fork); + free(children); +} + +/* 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; + } } - } - 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 kill to a child processes. + * a plain-old linked list + * FIXME use resolve_child ? + */ +static int kill_child(struct server_child_data *child) { - 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; + 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; +} + +/*! + * 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) +{ + 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, 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) == 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) +/* --------------------------- + * reset children signals + */ +void server_reset_signal(void) { - server_child_fork *fork; - int status, i; - pid_t pid; - -#ifndef WAIT_ANY -#define WAIT_ANY (-1) -#endif + struct sigaction sv; + sigset_t sigs; + const struct itimerval none = {{0, 0}, {0, 0}}; + + setitimer(ITIMER_REAL, &none, NULL); + 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); - while ((pid = waitpid(WAIT_ANY, &status, WNOHANG)) > 0) { - for (i = 0; i < children->nforks; i++) { - if (server_child_remove(children, i, pid)) { - fork = (server_child_fork *) children->fork + i; - if (fork->cleanup) - fork->cleanup(pid); - break; - } - } - - if (WIFEXITED(status)) { - if (WEXITSTATUS(status)) { - syslog(LOG_INFO, "server_child[%d] %d exited %d", i, pid, - WEXITSTATUS(status)); - } else { - syslog(LOG_INFO, "server_child[%d] %d done", i, pid); - } - } else { - if (WIFSIGNALED(status)) - syslog(LOG_INFO, "server_child[%d] %d killed", i, pid); - else - syslog(LOG_INFO, "server_child[%d] %d died", i, pid); - } - } }