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
hash_child(afp_child_t ** htable,afp_child_t * child)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
unhash_child(afp_child_t * child)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
server_child_resolve(server_child_t * childs,id_t pid)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 */
server_child_alloc(int connections)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 */
server_child_add(server_child_t * children,pid_t pid,int ipc_fd)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 */
server_child_remove(server_child_t * children,pid_t pid)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 */
server_child_free(server_child_t * children)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 */
server_child_kill(server_child_t * children,int sig)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 */
kill_child(afp_child_t * child)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 */
server_child_transfer_session(server_child_t * children,pid_t pid,uid_t uid,int afp_socket,uint16_t DSI_requestID)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: transferring 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 */
server_child_kill_one_by_id(server_child_t * children,pid_t pid,uid_t uid,uint32_t idlen,char * id,uint32_t boottime)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 */
server_reset_signal(void)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 }
352