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