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