]> arthur.barton.de Git - netatalk.git/blobdiff - libatalk/util/server_child.c
More robust IPC reconnect error handling
[netatalk.git] / libatalk / util / server_child.c
index b21bae35f08fe427b4f1f8eed027f0420cb2dd57..016029f8c4200a34e87896f0eba8460326e02f89 100644 (file)
@@ -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 <unistd.h>
 #endif /* HAVE_UNISTD_H */
 #include <signal.h>
-#include <atalk/logger.h>
+#include <errno.h>
 
 /* POSIX.1 sys/wait.h check */
 #include <sys/types.h>
 #endif /* HAVE_SYS_WAIT_H */
 #include <sys/time.h>
 
+#include <atalk/logger.h>
+#include <atalk/errchk.h>
+#include <atalk/util.h>
+#include <atalk/server_child.h>
+
 #ifndef WEXITSTATUS
 #define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8)
 #endif /* ! WEXITSTATUS */
 #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 <atalk/server_child.h>
-
 /* 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);
-        
+
 }