]> arthur.barton.de Git - netatalk.git/blob - libatalk/util/server_child.c
replace remaining %m printf glibc extension with %s strerror(errno)
[netatalk.git] / libatalk / util / server_child.c
1 /*
2  * $Id: server_child.c,v 1.7.4.1.2.4.2.1 2005-03-31 00:25:55 didg Exp $
3  *
4  * Copyright (c) 1997 Adrian Sun (asun@zoology.washington.edu)
5  * All rights reserved. See COPYRIGHT.
6  *
7  *
8  * handle inserting, removing, and freeing of children. 
9  * this does it via a hash table. it incurs some overhead over
10  * a linear append/remove in total removal and kills, but it makes
11  * single-entry removals a fast operation. as total removals occur during
12  * child initialization and kills during server shutdown, this is 
13  * probably a win for a lot of connections and unimportant for a small
14  * number of connections.
15  */
16
17 #ifdef HAVE_CONFIG_H
18 #include "config.h"
19 #endif /* HAVE_CONFIG_H */
20
21 #include <stdlib.h>
22 #include <string.h>
23 #ifdef HAVE_UNISTD_H
24 #include <unistd.h>
25 #endif /* HAVE_UNISTD_H */
26 #include <signal.h>
27 #include <atalk/logger.h>
28
29 /* POSIX.1 sys/wait.h check */
30 #include <sys/types.h>
31 #ifdef HAVE_SYS_WAIT_H
32 #include <sys/wait.h>
33 #endif /* HAVE_SYS_WAIT_H */
34 #include <sys/time.h>
35
36 #ifndef WEXITSTATUS
37 #define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8)
38 #endif /* ! WEXITSTATUS */
39 #ifndef WIFEXITED
40 #define WIFEXITED(stat_val) (((stat_val) & 255) == 0)
41 #endif /* ! WIFEXITED */
42 #ifndef WIFSTOPPED
43 #define WIFSTOPPED(status) (((status) & 0xff) == 0x7f)
44 #endif
45 #ifndef WIFSIGNALED
46 #define WIFSIGNALED(status) (!WIFSTOPPED(status) && !WIFEXITED(status)) 
47 #endif
48 #ifndef WTERMSIG
49 #define WTERMSIG(status)      ((status) & 0x7f)
50 #endif
51
52 #include <atalk/server_child.h>
53
54 #ifndef __inline__
55 #define __inline__
56 #endif /* ! __inline__ */
57
58 /* hash/child functions: hash OR's pid */
59 #define CHILD_HASHSIZE 32
60 #define HASH(i) ((((i) >> 8) ^ (i)) & (CHILD_HASHSIZE - 1))
61
62 struct server_child_data {
63   pid_t     pid;                /* afpd worker process pid (from the worker afpd process )*/
64   uid_t     uid;                /* user id of connected client (from the worker afpd process) */
65   int       valid;              /* 1 if we have a clientid */
66   u_int32_t time;               /* client boot time (from the mac client) */
67   int       killed;             /* 1 if we already tried to kill the client */
68
69   u_int32_t idlen;              /* clientid len (from the Mac client) */
70   char *clientid;               /* clientid (from the Mac client) */
71   struct server_child_data **prevp, *next;
72 };
73
74 typedef struct server_child_fork {
75   struct server_child_data *table[CHILD_HASHSIZE];
76   void (*cleanup)(const pid_t);
77 } server_child_fork;
78
79  
80 static __inline__ void hash_child(struct server_child_data **htable,
81                                   struct server_child_data *child)
82 {
83   struct server_child_data **table;
84
85   table = &htable[HASH(child->pid)];
86   if ((child->next = *table) != NULL)
87     (*table)->prevp = &child->next;
88   *table = child;
89   child->prevp = table;
90 }
91
92 static __inline__ void unhash_child(struct server_child_data *child)
93 {
94   if (child->prevp) {
95     if (child->next)
96       child->next->prevp = child->prevp;
97     *(child->prevp) = child->next;
98   }
99 }
100
101 static __inline__ struct server_child_data 
102 *resolve_child(struct server_child_data **table, const pid_t pid)
103 {
104   struct server_child_data *child;
105
106   for (child = table[HASH(pid)]; child; child = child->next) {
107     if (child->pid == pid)
108       break;
109   }
110
111   return child;
112 }
113
114 /* initialize server_child structure */
115 server_child *server_child_alloc(const int connections, const int nforks)
116 {
117   server_child *children;
118
119   children = (server_child *) calloc(1, sizeof(server_child));
120   if (!children)
121     return NULL;
122
123   children->nsessions = connections;
124   children->nforks = nforks;
125   children->fork = (void *) calloc(nforks, sizeof(server_child_fork));
126   
127   if (!children->fork) {
128     free(children);
129     return NULL;
130   } 
131
132   return children;
133 }
134
135 /* add a child in. return 0 on success, -1 on serious error, and
136  * > 0 for a non-serious error. */
137 int server_child_add(server_child *children, const int forkid,
138                      const pid_t pid)
139 {
140   server_child_fork *fork;
141   struct server_child_data *child;
142   sigset_t sig, oldsig;
143
144   /* we need to prevent deletions from occuring before we get a 
145    * chance to add the child in. */
146   sigemptyset(&sig);
147   sigaddset(&sig, SIGCHLD);
148   sigprocmask(SIG_BLOCK, &sig, &oldsig);
149
150   /* it's possible that the child could have already died before the
151    * sigprocmask. we need to check for this. */
152   if (kill(pid, 0) < 0) {
153     sigprocmask(SIG_SETMASK, &oldsig, NULL);
154     return 1;
155   }
156
157   fork = (server_child_fork *) children->fork + forkid;
158
159   /* if we already have an entry. just return. */
160   if (resolve_child(fork->table, pid)) {
161     sigprocmask(SIG_SETMASK, &oldsig, NULL);
162     return 0;
163   }
164
165   if ((child = (struct server_child_data *) 
166        calloc(1, sizeof(struct server_child_data))) == NULL) {
167     sigprocmask(SIG_SETMASK, &oldsig, NULL);
168     return -1;
169   }
170
171   child->pid = pid;
172   child->valid = 0;
173   child->killed = 0;
174   hash_child(fork->table, child);
175   children->count++;
176   sigprocmask(SIG_SETMASK, &oldsig, NULL);
177
178   return 0;
179 }
180
181 /* remove a child and free it */
182 int server_child_remove(server_child *children, const int forkid,
183                         const pid_t pid)
184 {
185   server_child_fork *fork;
186   struct server_child_data *child;
187
188   fork = (server_child_fork *) children->fork + forkid;
189   if (!(child = resolve_child(fork->table, pid)))
190     return 0;
191   
192   unhash_child(child);
193   if (child->clientid) {
194       free(child->clientid);
195   }
196   free(child);
197   children->count--;
198   return 1;
199 }
200
201 /* free everything: by using a hash table, this increases the cost of
202  * this part over a linked list by the size of the hash table */
203 void server_child_free(server_child *children)
204 {
205   server_child_fork *fork;
206   struct server_child_data *child, *tmp;
207   int i, j;
208
209   for (i = 0; i < children->nforks; i++) {
210     fork = (server_child_fork *) children->fork + i;
211     for (j = 0; j < CHILD_HASHSIZE; j++) {
212       child = fork->table[j]; /* start at the beginning */
213       while (child) {
214         tmp = child->next;
215         if (child->clientid) {
216             free(child->clientid);
217         }
218         free(child);
219         child = tmp;
220       }
221     }
222   }
223   free(children->fork);
224   free(children);
225 }
226
227 /* send kill to child processes: this also has an increased cost over
228  * a plain-old linked list */
229 void server_child_kill(server_child *children, const int forkid,
230                        const int sig)
231 {
232   server_child_fork *fork;
233   struct server_child_data *child, *tmp;
234   int i;
235
236   fork = (server_child_fork *) children->fork + forkid;
237   for (i = 0; i < CHILD_HASHSIZE; i++) {
238     child = fork->table[i];
239     while (child) {
240       tmp = child->next;
241       kill(child->pid, sig);
242       child = tmp;
243     }
244   }
245 }
246
247 /* send kill to a child processes.
248  * a plain-old linked list 
249  * FIXME use resolve_child ?
250  */
251 static int kill_child(struct server_child_data *child)
252 {
253   if (!child->killed) {
254      kill(child->pid, SIGTERM);
255      /* we don't wait because there's no guarantee that we can really kill it */
256      child->killed = 1;
257      return 1;
258   }
259   else {
260      LOG(log_info, logtype_default, "Already tried to kill (%d) before! Still there?",  child->pid);
261   }
262   return 0;
263 }
264
265 /* -------------------- */
266 void server_child_kill_one(server_child *children, const int forkid, const pid_t pid, const uid_t uid)
267 {
268   server_child_fork *fork;
269   struct server_child_data *child, *tmp;
270   int i;
271
272   fork = (server_child_fork *) children->fork + forkid;
273   for (i = 0; i < CHILD_HASHSIZE; i++) {
274     child = fork->table[i];
275     while (child) {
276       tmp = child->next;
277       if (child->pid == pid) {
278           if (!child->valid) {
279              /* hmm, client 'guess' the pid, rogue? */
280              LOG(log_info, logtype_default, "Disconnecting old session (%d) no token, bailout!",  child->pid);
281           }
282           else if (child->uid != uid) {
283              LOG(log_info, logtype_default, "Disconnecting old session (%d) not the same user, bailout!",  child->pid);
284           }
285           else {
286               kill_child(child);
287           }
288       }
289       child = tmp;
290     }
291   }
292 }
293
294
295 /* see if there is a process for the same mac     */
296 /* if the times don't match mac has been rebooted */
297 void server_child_kill_one_by_id(server_child *children, const int forkid, const pid_t pid,
298           const uid_t uid, 
299           const u_int32_t idlen, char *id, u_int32_t boottime)
300 {
301   server_child_fork *fork;
302   struct server_child_data *child, *tmp;
303   int i;
304
305   fork = (server_child_fork *) children->fork + forkid;
306   for (i = 0; i < CHILD_HASHSIZE; i++) {
307     child = fork->table[i];
308     while (child) {
309       tmp = child->next;
310       if ( child->pid != pid) {
311           if ( child->idlen == idlen && !memcmp(child->clientid, id, idlen)) {
312              if ( child->time != boottime ) {
313                   if (uid == child->uid) {
314                       if (kill_child(child)) {
315                           LOG(log_info, logtype_default, "Disconnecting old session %d, client rebooted.",  child->pid);
316                       }
317                   }
318                   else {
319                       LOG(log_info, logtype_default, "Disconnecting old session not the same uid, bailout!");
320                   }
321              }
322              else if (child->killed) {
323                   /* there's case where a Mac close a connection and restart a new one before
324                    * the first is 'waited' by the master afpd process
325                   */
326                   LOG(log_info, logtype_default, 
327                       "WARNING: connection (%d) killed but still there.", child->pid);
328              }
329              else {
330                   LOG(log_info, logtype_default, 
331                       "WARNING: 2 connections (%d, %d), boottime identical, don't know if one needs to be disconnected.",
332                        child->pid, pid);
333              } 
334                 
335           }
336       }
337       else 
338       {
339           child->time = boottime;
340           /* free old token if any */
341           if (child->clientid) {
342               free(child->clientid);
343           }
344           child->uid = uid; 
345           child->valid = 1;
346           child->idlen = idlen;
347           child->clientid = id;
348           LOG(log_info, logtype_default, "Setting clientid (len %d) for %d, boottime %X", idlen, child->pid, boottime);
349       }
350       child = tmp;
351     }
352   }
353 }
354
355 /* for extra cleanup if necessary */
356 void server_child_setup(server_child *children, const int forkid,
357                           void (*fcn)(const pid_t))
358 {
359   server_child_fork *fork;
360
361   fork = (server_child_fork *) children->fork + forkid;
362   fork->cleanup = fcn;
363 }
364
365
366 /* keep track of children. */
367 void server_child_handler(server_child *children)
368 {
369   int status, i;
370   pid_t pid;
371   
372 #ifndef WAIT_ANY
373 #define WAIT_ANY (-1)
374 #endif /* ! WAIT_ANY */
375
376   while ((pid = waitpid(WAIT_ANY, &status, WNOHANG)) > 0) {
377     for (i = 0; i < children->nforks; i++) {
378       if (server_child_remove(children, i, pid)) {
379         server_child_fork *fork;
380         
381         fork = (server_child_fork *) children->fork + i;
382         if (fork->cleanup)
383           fork->cleanup(pid);
384         break;
385       }
386     }
387
388     if (WIFEXITED(status)) {
389       if (WEXITSTATUS(status)) {
390         LOG(log_info, logtype_default, "server_child[%d] %d exited %d", i, pid, 
391                WEXITSTATUS(status));
392       } else {
393         LOG(log_info, logtype_default, "server_child[%d] %d done", i, pid);
394       }
395     } else {
396       if (WIFSIGNALED(status))
397       { 
398         LOG(log_info, logtype_default, "server_child[%d] %d killed by signal %d", i, pid,  
399                WTERMSIG (status));
400       }
401       else
402       {
403         LOG(log_info, logtype_default, "server_child[%d] %d died", i, pid);
404       }
405     }
406   }
407 }
408
409 /* --------------------------- 
410  * reset children signals
411 */
412 void server_reset_signal(void)
413 {
414     struct sigaction    sv;
415     sigset_t            sigs;
416     const struct itimerval none = {{0, 0}, {0, 0}};
417
418     setitimer(ITIMER_REAL, &none, NULL);
419     memset(&sv, 0, sizeof(sv));
420     sv.sa_handler =  SIG_DFL;
421     sigemptyset( &sv.sa_mask );
422     
423     sigaction(SIGALRM, &sv, 0 );
424     sigaction(SIGHUP,  &sv, 0 );
425     sigaction(SIGTERM, &sv, 0 );
426     sigaction(SIGUSR1, &sv, 0 );
427     sigaction(SIGCHLD, &sv, 0 );
428     
429     sigemptyset(&sigs);
430     sigaddset(&sigs, SIGALRM);
431     sigaddset(&sigs, SIGHUP);
432     sigaddset(&sigs, SIGUSR1);
433     sigaddset(&sigs, SIGCHLD);
434     sigprocmask(SIG_UNBLOCK, &sigs, NULL);
435         
436 }