]> arthur.barton.de Git - netatalk.git/blob - libatalk/util/server_child.c
AFP statistics via dbus IPC
[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 afp_child_t *server_child_resolve(server_child_t *childs, id_t pid)
74 {
75     afp_child_t *child;
76
77     for (child = childs->servch_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 = server_child_resolve(children, 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     child->afpch_logintime = time(NULL);
125
126     hash_child(children->servch_table, child);
127     children->servch_count++;
128
129 exit:
130     pthread_mutex_unlock(&children->servch_lock);
131     return child;
132 }
133
134 /* remove a child and free it */
135 int server_child_remove(server_child_t *children, pid_t pid)
136 {
137     int fd;
138     afp_child_t *child;
139
140     if (!(child = server_child_resolve(children, pid)))
141         return -1;
142
143     pthread_mutex_lock(&children->servch_lock);
144
145     unhash_child(child);
146     if (child->afpch_clientid) {
147         free(child->afpch_clientid);
148         child->afpch_clientid = NULL;
149     }
150
151     /* In main:child_handler() we need the fd in order to remove it from the pollfd set */
152     fd = child->afpch_ipc_fd;
153     if (fd != -1)
154         close(fd);
155
156     free(child);
157     children->servch_count--;
158
159     pthread_mutex_unlock(&children->servch_lock);
160
161     return fd;
162 }
163
164 /* free everything: by using a hash table, this increases the cost of
165  * this part over a linked list by the size of the hash table */
166 void server_child_free(server_child_t *children)
167 {
168     afp_child_t *child, *tmp;
169     int j;
170
171     for (j = 0; j < CHILD_HASHSIZE; j++) {
172         child = children->servch_table[j]; /* start at the beginning */
173         while (child) {
174             tmp = child->afpch_next;
175             close(child->afpch_ipc_fd);
176             if (child->afpch_clientid)
177                 free(child->afpch_clientid);
178             if (child->afpch_volumes)
179                 free(child->afpch_volumes);
180             free(child);
181             child = tmp;
182         }
183     }
184
185     free(children);
186 }
187
188 /* send signal to all child processes */
189 void server_child_kill(server_child_t *children, int sig)
190 {
191     afp_child_t *child, *tmp;
192     int i;
193
194     for (i = 0; i < CHILD_HASHSIZE; i++) {
195         child = children->servch_table[i];
196         while (child) {
197             tmp = child->afpch_next;
198             kill(child->afpch_pid, sig);
199             child = tmp;
200         }
201     }
202 }
203
204 /* send kill to a child processes */
205 static int kill_child(afp_child_t *child)
206 {
207     if (!child->afpch_killed) {
208         kill(child->afpch_pid, SIGTERM);
209         /* we don't wait because there's no guarantee that we can really kill it */
210         child->afpch_killed = 1;
211         return 1;
212     } else {
213         LOG(log_info, logtype_default, "Unresponsive child[%d], sending SIGKILL", child->afpch_pid);
214         kill(child->afpch_pid, SIGKILL);
215     }
216     return 1;
217 }
218
219 /*!
220  * Try to find an old session and pass socket
221  * @returns -1 on error, 0 if no matching session was found, 1 if session was found and socket passed
222  */
223 int server_child_transfer_session(server_child_t *children,
224                                   pid_t pid,
225                                   uid_t uid,
226                                   int afp_socket,
227                                   uint16_t DSI_requestID)
228 {
229     EC_INIT;
230     afp_child_t *child;
231
232     if ((child = server_child_resolve(children, pid)) == NULL) {
233         LOG(log_note, logtype_default, "Reconnect: no child[%u]", pid);
234         if (kill(pid, 0) == 0) {
235             LOG(log_note, logtype_default, "Reconnect: terminating old session[%u]", pid);
236             kill(pid, SIGTERM);
237             sleep(2);
238             if (kill(pid, 0) == 0) {
239                 LOG(log_error, logtype_default, "Reconnect: killing old session[%u]", pid);
240                 kill(pid, SIGKILL);
241                 sleep(2);
242             }
243         }
244         return 0;
245     }
246
247     if (!child->afpch_valid) {
248         /* hmm, client 'guess' the pid, rogue? */
249         LOG(log_error, logtype_default, "Reconnect: invalidated child[%u]", pid);
250         return 0;
251     } else if (child->afpch_uid != uid) {
252         LOG(log_error, logtype_default, "Reconnect: child[%u] not the same user", pid);
253         return 0;
254     }
255
256     LOG(log_note, logtype_default, "Reconnect: transfering session to child[%u]", pid);
257     
258     if (writet(child->afpch_ipc_fd, &DSI_requestID, 2, 0, 2) != 2) {
259         LOG(log_error, logtype_default, "Reconnect: error sending DSI id to child[%u]", pid);
260         EC_STATUS(-1);
261         goto EC_CLEANUP;
262     }
263     EC_ZERO_LOG(send_fd(child->afpch_ipc_fd, afp_socket));
264     EC_ZERO_LOG(kill(pid, SIGURG));
265
266     EC_STATUS(1);
267
268 EC_CLEANUP:
269     EC_EXIT;
270 }
271
272
273 /* see if there is a process for the same mac     */
274 /* if the times don't match mac has been rebooted */
275 void server_child_kill_one_by_id(server_child_t *children, pid_t pid,
276                                  uid_t uid, uint32_t idlen, char *id, uint32_t boottime)
277 {
278     afp_child_t *child, *tmp;
279     int i;
280
281     pthread_mutex_lock(&children->servch_lock);
282     
283     for (i = 0; i < CHILD_HASHSIZE; i++) {
284         child = children->servch_table[i];
285         while (child) {
286             tmp = child->afpch_next;
287             if (child->afpch_pid != pid) {
288                 if (child->afpch_idlen == idlen && memcmp(child->afpch_clientid, id, idlen) == 0) {
289                     if ( child->afpch_boottime != boottime ) {
290                         /* Client rebooted */
291                         if (uid == child->afpch_uid) {
292                             kill_child(child);
293                             LOG(log_warning, logtype_default,
294                                 "Terminated disconnected child[%u], client rebooted.",
295                                 child->afpch_pid);
296                         } else {
297                             LOG(log_warning, logtype_default,
298                                 "Session with different pid[%u]", child->afpch_pid);
299                         }
300                     } else {
301                         /* One client with multiple sessions */
302                         LOG(log_debug, logtype_default,
303                             "Found another session[%u] for client[%u]", child->afpch_pid, pid);
304                     }
305                 }
306             } else {
307                 /* update childs own slot */
308                 child->afpch_boottime = boottime;
309                 if (child->afpch_clientid)
310                     free(child->afpch_clientid);
311                 LOG(log_debug, logtype_default, "Setting client ID for %u", child->afpch_pid);
312                 child->afpch_uid = uid;
313                 child->afpch_valid = 1;
314                 child->afpch_idlen = idlen;
315                 child->afpch_clientid = id;
316             }
317             child = tmp;
318         }
319     }
320
321     pthread_mutex_unlock(&children->servch_lock);
322 }
323
324 /* ---------------------------
325  * reset children signals
326  */
327 void server_reset_signal(void)
328 {
329     struct sigaction    sv;
330     sigset_t            sigs;
331     const struct itimerval none = {{0, 0}, {0, 0}};
332
333     setitimer(ITIMER_REAL, &none, NULL);
334     memset(&sv, 0, sizeof(sv));
335     sv.sa_handler =  SIG_DFL;
336     sigemptyset( &sv.sa_mask );
337
338     sigaction(SIGALRM, &sv, NULL );
339     sigaction(SIGHUP,  &sv, NULL );
340     sigaction(SIGTERM, &sv, NULL );
341     sigaction(SIGUSR1, &sv, NULL );
342     sigaction(SIGCHLD, &sv, NULL );
343
344     sigemptyset(&sigs);
345     sigaddset(&sigs, SIGALRM);
346     sigaddset(&sigs, SIGHUP);
347     sigaddset(&sigs, SIGUSR1);
348     sigaddset(&sigs, SIGCHLD);
349     pthread_sigmask(SIG_UNBLOCK, &sigs, NULL);
350
351 }