]> arthur.barton.de Git - netatalk.git/blob - libatalk/util/server_child.c
Merge remote branch 'sf/product-2-2' into develop
[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 /* HAVE_CONFIG_H */
18
19 #include <stdlib.h>
20 #include <string.h>
21 #include <unistd.h>
22 #include <signal.h>
23 #include <errno.h>
24 #include <sys/types.h>
25 #include <sys/wait.h>
26 #include <sys/time.h>
27
28 #include <atalk/logger.h>
29 #include <atalk/errchk.h>
30 #include <atalk/util.h>
31 #include <atalk/server_child.h>
32
33 #ifndef WEXITSTATUS
34 #define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8)
35 #endif /* ! WEXITSTATUS */
36 #ifndef WIFEXITED
37 #define WIFEXITED(stat_val) (((stat_val) & 255) == 0)
38 #endif /* ! WIFEXITED */
39 #ifndef WIFSTOPPED
40 #define WIFSTOPPED(status) (((status) & 0xff) == 0x7f)
41 #endif
42 #ifndef WIFSIGNALED
43 #define WIFSIGNALED(status) (!WIFSTOPPED(status) && !WIFEXITED(status))
44 #endif
45 #ifndef WTERMSIG
46 #define WTERMSIG(status)      ((status) & 0x7f)
47 #endif
48
49 /* hash/child functions: hash OR's pid */
50 #define CHILD_HASHSIZE 32
51 #define HASH(i) ((((i) >> 8) ^ (i)) & (CHILD_HASHSIZE - 1))
52
53 typedef struct server_child_fork {
54     struct server_child_data *table[CHILD_HASHSIZE];
55     void (*cleanup)(const pid_t);
56 } server_child_fork;
57
58 static inline void hash_child(struct server_child_data **htable,
59                               struct server_child_data *child)
60 {
61     struct server_child_data **table;
62
63     table = &htable[HASH(child->pid)];
64     if ((child->next = *table) != NULL)
65         (*table)->prevp = &child->next;
66     *table = child;
67     child->prevp = table;
68 }
69
70 static inline void unhash_child(struct server_child_data *child)
71 {
72     if (child->prevp) {
73         if (child->next)
74             child->next->prevp = child->prevp;
75         *(child->prevp) = child->next;
76     }
77 }
78
79 static struct server_child_data *resolve_child(struct server_child_data **table, pid_t pid)
80 {
81     struct server_child_data *child;
82
83     for (child = table[HASH(pid)]; child; child = child->next) {
84         if (child->pid == pid)
85             break;
86     }
87
88     return child;
89 }
90
91 /* initialize server_child structure */
92 server_child *server_child_alloc(const int connections, const int nforks)
93 {
94     server_child *children;
95
96     children = (server_child *) calloc(1, sizeof(server_child));
97     if (!children)
98         return NULL;
99
100     children->nsessions = connections;
101     children->nforks = nforks;
102     children->fork = (void *) calloc(nforks, sizeof(server_child_fork));
103
104     if (!children->fork) {
105         free(children);
106         return NULL;
107     }
108
109     return children;
110 }
111
112 /*!
113  * add a child
114  * @return pointer to struct server_child_data on success, NULL on error
115  */
116 afp_child_t *server_child_add(server_child *children, int forkid, pid_t pid, int ipc_fd)
117 {
118     server_child_fork *fork;
119     afp_child_t *child = NULL;
120     sigset_t sig, oldsig;
121
122     /* we need to prevent deletions from occuring before we get a
123      * chance to add the child in. */
124     sigemptyset(&sig);
125     sigaddset(&sig, SIGCHLD);
126     pthread_sigmask(SIG_BLOCK, &sig, &oldsig);
127
128     /* it's possible that the child could have already died before the
129      * pthread_sigmask. we need to check for this. */
130     if (kill(pid, 0) < 0) {
131         LOG(log_error, logtype_default, "server_child_add: no such process pid [%d]", pid);
132         goto exit;
133     }
134
135     fork = (server_child_fork *) children->fork + forkid;
136
137     /* if we already have an entry. just return. */
138     if (child = resolve_child(fork->table, pid))
139         goto exit;
140
141     if ((child = calloc(1, sizeof(afp_child_t))) == NULL)
142         goto exit;
143
144     child->pid = pid;
145     child->valid = 0;
146     child->killed = 0;
147     child->ipc_fd = ipc_fd;
148
149     hash_child(fork->table, child);
150     children->count++;
151
152 exit:
153     pthread_sigmask(SIG_SETMASK, &oldsig, NULL);
154     return child;
155 }
156
157 /* remove a child and free it */
158 int server_child_remove(server_child *children, const int forkid, pid_t pid)
159 {
160     int fd;
161     server_child_fork *fork;
162     struct server_child_data *child;
163
164     fork = (server_child_fork *) children->fork + forkid;
165     if (!(child = resolve_child(fork->table, pid)))
166         return -1;
167
168     unhash_child(child);
169     if (child->clientid) {
170         free(child->clientid);
171         child->clientid = NULL;
172     }
173
174     /* In main:child_handler() we need the fd in order to remove it from the pollfd set */
175     fd = child->ipc_fd;
176     if (fd != -1)
177         close(fd);
178
179     free(child);
180     children->count--;
181
182     if (fork->cleanup)
183         fork->cleanup(pid);
184
185     return fd;
186 }
187
188 /* free everything: by using a hash table, this increases the cost of
189  * this part over a linked list by the size of the hash table */
190 void server_child_free(server_child *children)
191 {
192     server_child_fork *fork;
193     struct server_child_data *child, *tmp;
194     int i, j;
195
196     for (i = 0; i < children->nforks; i++) {
197         fork = (server_child_fork *) children->fork + i;
198         for (j = 0; j < CHILD_HASHSIZE; j++) {
199             child = fork->table[j]; /* start at the beginning */
200             while (child) {
201                 tmp = child->next;
202                 close(child->ipc_fd);
203                 if (child->clientid) {
204                     free(child->clientid);
205                 }
206                 free(child);
207                 child = tmp;
208             }
209         }
210     }
211     free(children->fork);
212     free(children);
213 }
214
215 /* send signal to all child processes */
216 void server_child_kill(server_child *children, int forkid, int sig)
217 {
218     server_child_fork *fork;
219     struct server_child_data *child, *tmp;
220     int i;
221
222     fork = (server_child_fork *) children->fork + forkid;
223     for (i = 0; i < CHILD_HASHSIZE; i++) {
224         child = fork->table[i];
225         while (child) {
226             tmp = child->next;
227             kill(child->pid, sig);
228             child = tmp;
229         }
230     }
231 }
232
233 /* send kill to a child processes.
234  * a plain-old linked list
235  * FIXME use resolve_child ?
236  */
237 static int kill_child(struct server_child_data *child)
238 {
239     if (!child->killed) {
240         kill(child->pid, SIGTERM);
241         /* we don't wait because there's no guarantee that we can really kill it */
242         child->killed = 1;
243         return 1;
244     } else {
245         LOG(log_info, logtype_default, "Unresponsive child[%d], sending SIGKILL", child->pid);
246         kill(child->pid, SIGKILL);
247     }
248     return 1;
249 }
250
251 /*!
252  * Try to find an old session and pass socket
253  * @returns -1 on error, 0 if no matching session was found, 1 if session was found and socket passed
254  */
255 int server_child_transfer_session(server_child *children,
256                                   int forkid,
257                                   pid_t pid,
258                                   uid_t uid,
259                                   int afp_socket,
260                                   uint16_t DSI_requestID)
261 {
262     EC_INIT;
263     server_child_fork *fork;
264     struct server_child_data *child;
265     int i;
266
267     fork = (server_child_fork *) children->fork + forkid;
268     if ((child = resolve_child(fork->table, pid)) == NULL) {
269         LOG(log_note, logtype_default, "Reconnect: no child[%u]", pid);
270         if (kill(pid, 0) == 0) {
271             LOG(log_note, logtype_default, "Reconnect: terminating old session[%u]", pid);
272             kill(pid, SIGTERM);
273             sleep(2);
274             if (kill(pid, 0) == 0) {
275                 LOG(log_error, logtype_default, "Reconnect: killing old session[%u]", pid);
276                 kill(pid, SIGKILL);
277                 sleep(2);
278             }
279         }
280         return 0;
281     }
282
283     if (!child->valid) {
284         /* hmm, client 'guess' the pid, rogue? */
285         LOG(log_error, logtype_default, "Reconnect: invalidated child[%u]", pid);
286         return 0;
287     } else if (child->uid != uid) {
288         LOG(log_error, logtype_default, "Reconnect: child[%u] not the same user", pid);
289         return 0;
290     }
291
292     LOG(log_note, logtype_default, "Reconnect: transfering session to child[%u]", pid);
293     
294     if (writet(child->ipc_fd, &DSI_requestID, 2, 0, 2) != 2) {
295         LOG(log_error, logtype_default, "Reconnect: error sending DSI id to child[%u]", pid);
296         EC_STATUS(-1);
297         goto EC_CLEANUP;
298     }
299     EC_ZERO_LOG(send_fd(child->ipc_fd, afp_socket));
300     EC_ZERO_LOG(kill(pid, SIGURG));
301
302     EC_STATUS(1);
303
304 EC_CLEANUP:
305     EC_EXIT;
306 }
307
308
309 /* see if there is a process for the same mac     */
310 /* if the times don't match mac has been rebooted */
311 void server_child_kill_one_by_id(server_child *children, int forkid, pid_t pid,
312                                  uid_t uid, uint32_t idlen, char *id, uint32_t boottime)
313 {
314     server_child_fork *fork;
315     struct server_child_data *child, *tmp;
316     int i;
317
318     fork = (server_child_fork *)children->fork + forkid;
319
320     for (i = 0; i < CHILD_HASHSIZE; i++) {
321         child = fork->table[i];
322         while (child) {
323             tmp = child->next;
324             if ( child->pid != pid) {
325                 if (child->idlen == idlen && memcmp(child->clientid, id, idlen) == 0) {
326                     if ( child->time != boottime ) {
327                         /* Client rebooted */
328                         if (uid == child->uid) {
329                             kill_child(child);
330                             LOG(log_warning, logtype_default,
331                                 "Terminated disconnected child[%u], client rebooted.",
332                                 child->pid);
333                         } else {
334                             LOG(log_warning, logtype_default,
335                                 "Session with different pid[%u]", child->pid);
336                         }
337                     } else {
338                         /* One client with multiple sessions */
339                         LOG(log_debug, logtype_default,
340                             "Found another session[%u] for client[%u]", child->pid, pid);
341                     }
342                 }
343             } else {
344                 /* update childs own slot */
345                 child->time = boottime;
346                 if (child->clientid)
347                     free(child->clientid);
348                 LOG(log_debug, logtype_default, "Setting client ID for %u", child->pid);
349                 child->uid = uid;
350                 child->valid = 1;
351                 child->idlen = idlen;
352                 child->clientid = id;
353             }
354             child = tmp;
355         }
356     }
357 }
358
359 /* for extra cleanup if necessary */
360 void server_child_setup(server_child *children, const int forkid,
361                         void (*fcn)(const pid_t))
362 {
363     server_child_fork *fork;
364
365     fork = (server_child_fork *) children->fork + forkid;
366     fork->cleanup = fcn;
367 }
368
369
370 /* ---------------------------
371  * reset children signals
372  */
373 void server_reset_signal(void)
374 {
375     struct sigaction    sv;
376     sigset_t            sigs;
377     const struct itimerval none = {{0, 0}, {0, 0}};
378
379     setitimer(ITIMER_REAL, &none, NULL);
380     memset(&sv, 0, sizeof(sv));
381     sv.sa_handler =  SIG_DFL;
382     sigemptyset( &sv.sa_mask );
383
384     sigaction(SIGALRM, &sv, NULL );
385     sigaction(SIGHUP,  &sv, NULL );
386     sigaction(SIGTERM, &sv, NULL );
387     sigaction(SIGUSR1, &sv, NULL );
388     sigaction(SIGCHLD, &sv, NULL );
389
390     sigemptyset(&sigs);
391     sigaddset(&sigs, SIGALRM);
392     sigaddset(&sigs, SIGHUP);
393     sigaddset(&sigs, SIGUSR1);
394     sigaddset(&sigs, SIGCHLD);
395     pthread_sigmask(SIG_UNBLOCK, &sigs, NULL);
396
397 }