• Home
  • History
  • Annotate
  • Line#
  • Navigate
  • Raw
  • Download
  • only in /netgear-WNDR4500v2-V1.0.0.60_1.0.38/ap/gpl/timemachine/netatalk-2.2.0/libatalk/util/
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
59typedef struct server_child_fork {
60    struct server_child_data *table[CHILD_HASHSIZE];
61    void (*cleanup)(const pid_t);
62} server_child_fork;
63
64int parent_or_child; /* 0: parent, 1: child */
65
66static 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
78static 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
87static 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 */
100server_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 */
124afp_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        goto exit;
140
141    fork = (server_child_fork *) children->fork + forkid;
142
143    /* if we already have an entry. just return. */
144    if (child = resolve_child(fork->table, pid))
145        goto exit;
146
147    if ((child = calloc(1, sizeof(afp_child_t))) == NULL)
148        goto exit;
149
150    child->pid = pid;
151    child->valid = 0;
152    child->killed = 0;
153    child->ipc_fds[0] = ipc_fds[0];
154    child->ipc_fds[1] = ipc_fds[1];
155
156    hash_child(fork->table, child);
157    children->count++;
158
159exit:
160    pthread_sigmask(SIG_SETMASK, &oldsig, NULL);
161    return child;
162}
163
164/* remove a child and free it */
165int server_child_remove(server_child *children, const int forkid, pid_t pid)
166{
167    int fd;
168    server_child_fork *fork;
169    struct server_child_data *child;
170
171    fork = (server_child_fork *) children->fork + forkid;
172    if (!(child = resolve_child(fork->table, pid)))
173        return -1;
174
175    unhash_child(child);
176    if (child->clientid) {
177        free(child->clientid);
178        child->clientid = NULL;
179    }
180
181    /* In main:child_handler() we need the fd in order to remove it from the pollfd set */
182    fd = child->ipc_fds[0];
183    if (child->ipc_fds[0] != -1) {
184        close(child->ipc_fds[0]);
185        child->ipc_fds[0] = -1;
186    }
187    if (child->ipc_fds[1] != -1) {
188        close(child->ipc_fds[1]);
189        child->ipc_fds[1] = -1;
190    }
191
192    free(child);
193    children->count--;
194
195    if (fork->cleanup)
196        fork->cleanup(pid);
197
198    return fd;
199}
200
201/* free everything: by using a hash table, this increases the cost of
202 * this part over a linked list by the size of the hash table */
203void server_child_free(server_child *children)
204{
205    server_child_fork *fork;
206    struct server_child_data *child, *tmp;
207    int i, j;
208
209    for (i = 0; i < children->nforks; i++) {
210        fork = (server_child_fork *) children->fork + i;
211        for (j = 0; j < CHILD_HASHSIZE; j++) {
212            child = fork->table[j]; /* start at the beginning */
213            while (child) {
214                tmp = child->next;
215                if (child->clientid) {
216                    free(child->clientid);
217                }
218                free(child);
219                child = tmp;
220            }
221        }
222    }
223    free(children->fork);
224    free(children);
225}
226
227/* send signal to all child processes */
228void server_child_kill(server_child *children, int forkid, int sig)
229{
230    server_child_fork *fork;
231    struct server_child_data *child, *tmp;
232    int i;
233
234    fork = (server_child_fork *) children->fork + forkid;
235    for (i = 0; i < CHILD_HASHSIZE; i++) {
236        child = fork->table[i];
237        while (child) {
238            tmp = child->next;
239            kill(child->pid, sig);
240            child = tmp;
241        }
242    }
243}
244
245/* send kill to a child processes.
246 * a plain-old linked list
247 * FIXME use resolve_child ?
248 */
249static int kill_child(struct server_child_data *child)
250{
251    if (!child->killed) {
252        kill(child->pid, SIGTERM);
253        /* we don't wait because there's no guarantee that we can really kill it */
254        child->killed = 1;
255        return 1;
256    } else {
257        LOG(log_info, logtype_default, "Unresponsive child[%d], sending SIGKILL", child->pid);
258        kill(child->pid, SIGKILL);
259    }
260    return 1;
261}
262
263/*!
264 * Try to find an old session and pass socket
265 * @returns -1 on error, 0 if no matching session was found, 1 if session was found and socket passed
266 */
267int server_child_transfer_session(server_child *children,
268                                  int forkid,
269                                  pid_t pid,
270                                  uid_t uid,
271                                  int afp_socket,
272                                  uint16_t DSI_requestID)
273{
274    EC_INIT;
275    server_child_fork *fork;
276    struct server_child_data *child;
277    int i;
278
279    fork = (server_child_fork *) children->fork + forkid;
280    if ((child = resolve_child(fork->table, pid)) == NULL) {
281        LOG(log_note, logtype_default, "Reconnect: no child[%u]", pid);
282        if (kill(pid, 0) == 0) {
283            LOG(log_note, logtype_default, "Reconnect: terminating old session[%u]", pid);
284            kill(pid, SIGTERM);
285            sleep(2);
286            if (kill(pid, 0) == 0) {
287                LOG(log_error, logtype_default, "Reconnect: killing old session[%u]", pid);
288                kill(pid, SIGKILL);
289                sleep(2);
290            }
291        }
292        return 0;
293    }
294
295    if (!child->valid) {
296        /* hmm, client 'guess' the pid, rogue? */
297        LOG(log_error, logtype_default, "Reconnect: invalidated child[%u]", pid);
298        return 0;
299    } else if (child->uid != uid) {
300        LOG(log_error, logtype_default, "Reconnect: child[%u] not the same user", pid);
301        return 0;
302    }
303
304    LOG(log_note, logtype_default, "Reconnect: transfering session to child[%u]", pid);
305
306    if (writet(child->ipc_fds[0], &DSI_requestID, 2, 0, 2) != 2) {
307        LOG(log_error, logtype_default, "Reconnect: error sending DSI id to child[%u]", pid);
308        EC_STATUS(-1);
309        goto EC_CLEANUP;
310    }
311    EC_ZERO_LOG(send_fd(child->ipc_fds[0], afp_socket));
312    EC_ZERO_LOG(kill(pid, SIGURG));
313
314    EC_STATUS(1);
315
316EC_CLEANUP:
317    EC_EXIT;
318}
319
320
321/* see if there is a process for the same mac     */
322/* if the times don't match mac has been rebooted */
323void server_child_kill_one_by_id(server_child *children, int forkid, pid_t pid,
324                                 uid_t uid, uint32_t idlen, char *id, uint32_t boottime)
325{
326    server_child_fork *fork;
327    struct server_child_data *child, *tmp;
328    int i;
329
330    fork = (server_child_fork *)children->fork + forkid;
331
332    for (i = 0; i < CHILD_HASHSIZE; i++) {
333        child = fork->table[i];
334        while (child) {
335            tmp = child->next;
336            if ( child->pid != pid) {
337                if (child->idlen == idlen && memcmp(child->clientid, id, idlen) == 0) {
338                    if ( child->time != boottime ) {
339                        /* Client rebooted */
340                        if (uid == child->uid) {
341                            kill_child(child);
342                            LOG(log_warning, logtype_default,
343                                "Terminated disconnected child[%u], client rebooted.",
344                                child->pid);
345                        } else {
346                            LOG(log_warning, logtype_default,
347                                "Session with different pid[%u]", child->pid);
348                        }
349                    } else {
350                        /* One client with multiple sessions */
351                        LOG(log_debug, logtype_default,
352                            "Found another session[%u] for client[%u]", child->pid, pid);
353                    }
354                }
355            } else {
356                /* update childs own slot */
357                child->time = boottime;
358                if (child->clientid)
359                    free(child->clientid);
360                LOG(log_debug, logtype_default, "Setting client ID for %u", child->pid);
361                child->uid = uid;
362                child->valid = 1;
363                child->idlen = idlen;
364                child->clientid = id;
365            }
366            child = tmp;
367        }
368    }
369}
370
371/* for extra cleanup if necessary */
372void server_child_setup(server_child *children, const int forkid,
373                        void (*fcn)(const pid_t))
374{
375    server_child_fork *fork;
376
377    fork = (server_child_fork *) children->fork + forkid;
378    fork->cleanup = fcn;
379}
380
381
382/* ---------------------------
383 * reset children signals
384 */
385void server_reset_signal(void)
386{
387    struct sigaction    sv;
388    sigset_t            sigs;
389    const struct itimerval none = {{0, 0}, {0, 0}};
390
391    setitimer(ITIMER_REAL, &none, NULL);
392    memset(&sv, 0, sizeof(sv));
393    sv.sa_handler =  SIG_DFL;
394    sigemptyset( &sv.sa_mask );
395
396    sigaction(SIGALRM, &sv, NULL );
397    sigaction(SIGHUP,  &sv, NULL );
398    sigaction(SIGTERM, &sv, NULL );
399    sigaction(SIGUSR1, &sv, NULL );
400    sigaction(SIGCHLD, &sv, NULL );
401
402    sigemptyset(&sigs);
403    sigaddset(&sigs, SIGALRM);
404    sigaddset(&sigs, SIGHUP);
405    sigaddset(&sigs, SIGUSR1);
406    sigaddset(&sigs, SIGCHLD);
407    pthread_sigmask(SIG_UNBLOCK, &sigs, NULL);
408
409}
410