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