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 53static 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 64static 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 73afp_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 */ 86server_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 */ 102afp_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 129exit: 130 pthread_mutex_unlock(&children->servch_lock); 131 return child; 132} 133 134/* remove a child and free it */ 135int 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 */ 166void 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 */ 189void 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 */ 205static 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 */ 223int 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 268EC_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 */ 275void 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 */ 327void 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