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