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.
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.
17 #endif /* HAVE_CONFIG_H */
24 #include <sys/types.h>
29 #include <atalk/logger.h>
30 #include <atalk/errchk.h>
31 #include <atalk/util.h>
32 #include <atalk/server_child.h>
35 #define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8)
36 #endif /* ! WEXITSTATUS */
38 #define WIFEXITED(stat_val) (((stat_val) & 255) == 0)
39 #endif /* ! WIFEXITED */
41 #define WIFSTOPPED(status) (((status) & 0xff) == 0x7f)
44 #define WIFSIGNALED(status) (!WIFSTOPPED(status) && !WIFEXITED(status))
47 #define WTERMSIG(status) ((status) & 0x7f)
50 /* hash/child functions: hash OR's pid */
51 #define HASH(i) ((((i) >> 8) ^ (i)) & (CHILD_HASHSIZE - 1))
53 static inline void hash_child(afp_child_t **htable, afp_child_t *child)
57 table = &htable[HASH(child->afpch_pid)];
58 if ((child->afpch_next = *table) != NULL)
59 (*table)->afpch_prevp = &child->afpch_next;
61 child->afpch_prevp = table;
64 static inline void unhash_child(afp_child_t *child)
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;
73 static afp_child_t *resolve_child(afp_child_t **table, pid_t pid)
77 for (child = table[HASH(pid)]; child; child = child->afpch_next) {
78 if (child->afpch_pid == pid)
85 /* initialize server_child structure */
86 server_child_t *server_child_alloc(int connections)
88 server_child_t *children;
90 if (!(children = (server_child_t *)calloc(1, sizeof(server_child_t))))
93 children->servch_nsessions = connections;
94 pthread_mutex_init(&children->servch_lock, NULL);
100 * @return pointer to struct server_child_data on success, NULL on error
102 afp_child_t *server_child_add(server_child_t *children, pid_t pid, int ipc_fd)
104 afp_child_t *child = NULL;
106 pthread_mutex_lock(&children->servch_lock);
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);
115 /* if we already have an entry. just return. */
116 if ((child = resolve_child(children->servch_table, pid)))
119 if ((child = calloc(1, sizeof(afp_child_t))) == NULL)
122 child->afpch_pid = pid;
123 child->afpch_ipc_fd = ipc_fd;
125 hash_child(children->servch_table, child);
126 children->servch_count++;
129 pthread_mutex_unlock(&children->servch_lock);
133 /* remove a child and free it */
134 int server_child_remove(server_child_t *children, pid_t pid)
139 if (!(child = resolve_child(children->servch_table, pid)))
143 if (child->afpch_clientid) {
144 free(child->afpch_clientid);
145 child->afpch_clientid = NULL;
148 /* In main:child_handler() we need the fd in order to remove it from the pollfd set */
149 fd = child->afpch_ipc_fd;
154 children->servch_count--;
156 if (children->servch_cleanup)
157 children->servch_cleanup(pid);
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)
166 afp_child_t *child, *tmp;
169 for (j = 0; j < CHILD_HASHSIZE; j++) {
170 child = children->servch_table[j]; /* start at the beginning */
172 tmp = child->afpch_next;
173 close(child->afpch_ipc_fd);
174 if (child->afpch_clientid)
175 free(child->afpch_clientid);
184 /* send signal to all child processes */
185 void server_child_kill(server_child_t *children, int sig)
187 afp_child_t *child, *tmp;
190 for (i = 0; i < CHILD_HASHSIZE; i++) {
191 child = children->servch_table[i];
193 tmp = child->afpch_next;
194 kill(child->afpch_pid, sig);
200 /* send kill to a child processes */
201 static int kill_child(afp_child_t *child)
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;
209 LOG(log_info, logtype_default, "Unresponsive child[%d], sending SIGKILL", child->afpch_pid);
210 kill(child->afpch_pid, SIGKILL);
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
219 int server_child_transfer_session(server_child_t *children,
223 uint16_t DSI_requestID)
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);
234 if (kill(pid, 0) == 0) {
235 LOG(log_error, logtype_default, "Reconnect: killing old session[%u]", pid);
243 if (!child->afpch_valid) {
244 /* hmm, client 'guess' the pid, rogue? */
245 LOG(log_error, logtype_default, "Reconnect: invalidated child[%u]", pid);
247 } else if (child->afpch_uid != uid) {
248 LOG(log_error, logtype_default, "Reconnect: child[%u] not the same user", pid);
252 LOG(log_note, logtype_default, "Reconnect: transfering session to child[%u]", pid);
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);
259 EC_ZERO_LOG(send_fd(child->afpch_ipc_fd, afp_socket));
260 EC_ZERO_LOG(kill(pid, SIGURG));
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)
274 afp_child_t *child, *tmp;
277 for (i = 0; i < CHILD_HASHSIZE; i++) {
278 child = children->servch_table[i];
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) {
287 LOG(log_warning, logtype_default,
288 "Terminated disconnected child[%u], client rebooted.",
291 LOG(log_warning, logtype_default,
292 "Session with different pid[%u]", child->afpch_pid);
295 /* One client with multiple sessions */
296 LOG(log_debug, logtype_default,
297 "Found another session[%u] for client[%u]", child->afpch_pid, pid);
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;
316 /* ---------------------------
317 * reset children signals
319 void server_reset_signal(void)
323 const struct itimerval none = {{0, 0}, {0, 0}};
325 setitimer(ITIMER_REAL, &none, NULL);
326 memset(&sv, 0, sizeof(sv));
327 sv.sa_handler = SIG_DFL;
328 sigemptyset( &sv.sa_mask );
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 );
337 sigaddset(&sigs, SIGALRM);
338 sigaddset(&sigs, SIGHUP);
339 sigaddset(&sigs, SIGUSR1);
340 sigaddset(&sigs, SIGCHLD);
341 pthread_sigmask(SIG_UNBLOCK, &sigs, NULL);