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