]> arthur.barton.de Git - netatalk.git/blob - libatalk/util/server_child.c
7e26eb0332715f875ca64fda77d7dac9bc26aefe
[netatalk.git] / libatalk / util / server_child.c
1 /*
2  * Copyright (c) 1997 Adrian Sun (asun@zoology.washington.edu)
3  * All rights reserved. See COPYRIGHT.
4  *
5  *
6  * handle inserting, removing, and freeing of children. 
7  * this does it via a hash table. it incurs some overhead over
8  * a linear append/remove in total removal and kills, but it makes
9  * single-entry removals a fast operation. as total removals occur during
10  * child initialization and kills during server shutdown, this is 
11  * probably a win for a lot of connections and unimportant for a small
12  * number of connections.
13  */
14
15 #ifdef HAVE_CONFIG_H
16 #include "config.h"
17 #endif
18
19 #include <stdlib.h>
20 #include <string.h>
21 #include <unistd.h>
22 #include <signal.h>
23 #include <sys/syslog.h>
24 #include <sys/types.h>
25 #include <sys/wait.h>
26
27 #include <atalk/server_child.h>
28
29 #ifndef __inline__
30 #define __inline__
31 #endif
32
33 /* hash/child functions: hash OR's pid */
34 #define CHILD_HASHSIZE 32
35 #define HASH(i) ((((i) >> 8) ^ (i)) & (CHILD_HASHSIZE - 1))
36
37 struct server_child_data {
38   pid_t pid; 
39   struct server_child_data **prevp, *next;
40 };
41
42 typedef struct server_child_fork {
43   struct server_child_data *table[CHILD_HASHSIZE];
44   void (*cleanup)(const pid_t);
45 } server_child_fork;
46
47  
48 static __inline__ void hash_child(struct server_child_data **htable,
49                                   struct server_child_data *child)
50 {
51   struct server_child_data **table;
52
53   table = &htable[HASH(child->pid)];
54   if ((child->next = *table) != NULL)
55     (*table)->prevp = &child->next;
56   *table = child;
57   child->prevp = table;
58 }
59
60 static __inline__ void unhash_child(struct server_child_data *child)
61 {
62   if (child->prevp) {
63     if (child->next)
64       child->next->prevp = child->prevp;
65     *(child->prevp) = child->next;
66   }
67 }
68
69 static __inline__ struct server_child_data 
70 *resolve_child(struct server_child_data **table, const pid_t pid)
71 {
72   struct server_child_data *child;
73
74   for (child = table[HASH(pid)]; child; child = child->next) {
75     if (child->pid == pid)
76       break;
77   }
78
79   return child;
80 }
81
82 /* initialize server_child structure */
83 server_child *server_child_alloc(const int connections, const int nforks)
84 {
85   server_child *children;
86
87   children = (server_child *) calloc(1, sizeof(server_child));
88   if (!children)
89     return NULL;
90
91   children->nsessions = connections;
92   children->nforks = nforks;
93   children->fork = (void *) calloc(nforks, sizeof(server_child_fork));
94   
95   if (!children->fork) {
96     free(children);
97     return NULL;
98   } 
99
100   return children;
101 }
102
103 /* add a child in. return 0 on success, -1 on serious error, and
104  * > 0 for a non-serious error. */
105 int server_child_add(server_child *children, const int forkid,
106                      const pid_t pid)
107 {
108   server_child_fork *fork;
109   struct server_child_data *child;
110   sigset_t sig;
111
112   /* we need to prevent deletions from occuring before we get a 
113    * chance to add the child in. */
114   sigemptyset(&sig);
115   sigaddset(&sig, SIGCHLD);
116   sigprocmask(SIG_BLOCK, &sig, NULL);
117
118   /* it's possible that the child could have already died before the
119    * sigprocmask. we need to check for this. */
120   if (kill(pid, 0) < 0) {
121     sigprocmask(SIG_UNBLOCK, &sig, NULL);
122     return 1;
123   }
124
125   fork = (server_child_fork *) children->fork + forkid;
126
127   /* if we already have an entry. just return. */
128   if (resolve_child(fork->table, pid)) {
129     sigprocmask(SIG_UNBLOCK, &sig, NULL);
130     return 0;
131   }
132
133   if ((child = (struct server_child_data *) 
134        calloc(1, sizeof(struct server_child_data))) == NULL) {
135     sigprocmask(SIG_UNBLOCK, &sig, NULL);
136     return -1;
137   }
138
139   child->pid = pid;
140   hash_child(fork->table, child);
141   children->count++;
142   sigprocmask(SIG_UNBLOCK, &sig, NULL);
143
144   return 0;
145 }
146
147 /* remove a child and free it */
148 int server_child_remove(server_child *children, const int forkid,
149                         const pid_t pid)
150 {
151   server_child_fork *fork;
152   struct server_child_data *child;
153
154   fork = (server_child_fork *) children->fork + forkid;
155   if (!(child = resolve_child(fork->table, pid)))
156     return 0;
157   
158   unhash_child(child);
159   free(child);
160   children->count--;
161   return 1;
162 }
163
164 /* free everything: by using a hash table, this increases the cost of
165  * this part over a linked list by the size of the hash table */
166 void server_child_free(server_child *children)
167 {
168   server_child_fork *fork;
169   struct server_child_data *child, *tmp;
170   int i, j;
171
172   for (i = 0; i < children->nforks; i++) {
173     fork = (server_child_fork *) children->fork + i;
174     for (j = 0; j < CHILD_HASHSIZE; j++) {
175       child = fork->table[j]; /* start at the beginning */
176       while (child) {
177         tmp = child->next;
178         free(child);
179         child = tmp;
180       }
181     }
182   }
183   free(children->fork);
184   free(children);
185 }
186
187 /* send kill to child processes: this also has an increased cost over
188  * a plain-old linked list */
189 void server_child_kill(server_child *children, const int forkid,
190                        const int sig)
191 {
192   server_child_fork *fork;
193   struct server_child_data *child, *tmp;
194   int i;
195
196   fork = (server_child_fork *) children->fork + forkid;
197   for (i = 0; i < CHILD_HASHSIZE; i++) {
198     child = fork->table[i];
199     while (child) {
200       tmp = child->next;
201       kill(child->pid, sig);
202       child = tmp;
203     }
204   }
205 }
206
207 /* for extra cleanup if necessary */
208 void server_child_setup(server_child *children, const int forkid,
209                           void (*fcn)(const pid_t))
210 {
211   server_child_fork *fork;
212
213   fork = (server_child_fork *) children->fork + forkid;
214   fork->cleanup = fcn;
215 }
216
217
218 /* keep track of children. */
219 void server_child_handler(server_child *children)
220 {
221   server_child_fork *fork;
222   int status, i;
223   pid_t pid;
224   
225 #ifndef WAIT_ANY
226 #define WAIT_ANY (-1)
227 #endif
228
229   while ((pid = waitpid(WAIT_ANY, &status, WNOHANG)) > 0) {
230     for (i = 0; i < children->nforks; i++) {
231       if (server_child_remove(children, i, pid)) {
232         fork = (server_child_fork *) children->fork + i;
233         if (fork->cleanup)
234           fork->cleanup(pid);
235         break;
236       }
237     }
238     
239     if (WIFEXITED(status)) {
240       if (WEXITSTATUS(status)) {
241         syslog(LOG_INFO, "server_child[%d] %d exited %d", i, pid, 
242                WEXITSTATUS(status));
243       } else {
244         syslog(LOG_INFO, "server_child[%d] %d done", i, pid);
245       }
246     } else {
247       if (WIFSIGNALED(status)) 
248         syslog(LOG_INFO, "server_child[%d] %d killed", i, pid);
249       else
250         syslog(LOG_INFO, "server_child[%d] %d died", i, pid);
251     }
252   }
253 }