static int do_dup(int fd) { int rval = -1; fdinfo_t *info = NULL; if( fd < 0 ) { errno = EINVAL; return -1; } DEBUG("do_dup(%d, ...) ...", fd); L(); info = fd_lookup(fd); if( info == NULL ) { U(); rval = libc.dup(fd); DEBUG("do_dup(%d) => %d (no info)", fd, rval); return rval; } rval = libc.dup(fd); if( rval >= 0 ) { inc_ref(info); fd_save(rval, info); } U(); DEBUG("do_dup(%d) => %d (with info)", fd, rval); return rval; }
static int do_epoll_create1(int flags) { int rval = libc.epoll_create1(flags); if( rval >= 0 ) { fdinfo_t* info = alloc_info(EPOLL); fd_save(rval, info); } return rval; }
static int do_dup3(int fd, int fd2, int flags) { int rval = -1; fdinfo_t *info = NULL; fdinfo_t *info2 = NULL; if( fd < 0 || fd2 < 0 ) { errno = EINVAL; return -1; } DEBUG("do_dup3(%d, %d, ...) ...", fd, fd2); L(); if( fd == fd2 ) { U(); DEBUG("do_dup3(%d, %d, ...) => 0", fd, fd2); return fd2; } info = fd_lookup(fd); info2 = fd_lookup(fd2); if( info2 != NULL ) { rval = info_close(fd2, info2); if( rval < 0 ) { U(); DEBUG("do_dup3(%d, %d, ...) => %d (close failed)", fd, fd2, rval); return rval; } } rval = libc.dup3(fd, fd2, flags); if( rval < 0 ) { U(); DEBUG("do_dup3(%d, %d, ...) => %d (dup3 failed)", fd, fd2, rval); return rval; } if( info != NULL ) { inc_ref(info); fd_save(fd2, info); } U(); DEBUG("do_dup3(%d, %d, ...) => %d", fd, fd2, rval); return rval; }
/** Handle open request. * @ingroup fakehost * * @param f Pointer to io_device structure. * @param name pathname. * @param mode open mode. * @return Status (as for fileio open). */ int fd_open( iop_io_file_t *f, const char *name, int mode) { char nameBuffer[ 250 ]; int fd; #ifdef DEBUG_T printf( "fakehost: open %i %s %s\n", f->unit, name ,base); #endif fd = open( fd_name( nameBuffer, name), mode, 0 ); if ( fd < 0 ) return fd; return fd_save( fd, f ); }
static int impl_dummy_server(void) { int dummy_server = -1; /* Create our dummy sock. */ struct sockaddr_un dummy_addr; char *socket_path = tempnam("/tmp", ".huptime"); memset(&dummy_addr, 0, sizeof(struct sockaddr_un)); dummy_addr.sun_family = AF_UNIX; strncpy(dummy_addr.sun_path, socket_path, sizeof(dummy_addr.sun_path)-1); /* Create a dummy server. */ dummy_server = socket(AF_UNIX, SOCK_STREAM, 0); if( dummy_server < 0 ) { fprintf(stderr, "Unable to create unix socket?"); return -1; } if( fcntl(dummy_server, F_SETFD, FD_CLOEXEC) < 0 ) { close(dummy_server); fprintf(stderr, "Unable to set cloexec?"); return -1; } if( libc.bind( dummy_server, (struct sockaddr*)&dummy_addr, sizeof(struct sockaddr_un)) < 0 ) { close(dummy_server); fprintf(stderr, "Unable to bind unix socket?"); return -1; } if( libc.listen(dummy_server, 1) < 0 ) { close(dummy_server); fprintf(stderr, "Unable to listen on unix socket?"); return -1; } /* Connect a dummy client. */ int dummy_client = socket(AF_UNIX, SOCK_STREAM, 0); if( dummy_client < 0 ) { close(dummy_server); fprintf(stderr, "Unable to create unix socket?"); return -1; } if( fcntl(dummy_client, F_SETFD, FD_CLOEXEC) < 0 ) { close(dummy_server); close(dummy_client); fprintf(stderr, "Unable to set cloexec?"); return -1; } if( connect( dummy_client, (struct sockaddr*)&dummy_addr, sizeof(struct sockaddr_un)) < 0 ) { close(dummy_server); close(dummy_client); fprintf(stderr, "Unable to connect dummy client?"); return -1; } /* Put the client into an error state. */ int dummy_fd = libc.accept(dummy_server, NULL, 0); if( dummy_fd < 0 ) { fprintf(stderr, "Unable to accept internal client?"); close(dummy_server); close(dummy_client); return -1; } close(dummy_fd); /* Save the dummy info. */ fdinfo_t* dummy_info = alloc_info(DUMMY); if( dummy_info == NULL ) { fprintf(stderr, "Unable to allocate dummy info?"); return -1; } dummy_info->dummy.client = dummy_client; fd_save(dummy_server, dummy_info); inc_ref(dummy_info); fd_save(dummy_client, dummy_info); /* Ensure that it's unlinked. */ unlink(socket_path); free(socket_path); return dummy_server; }
void impl_init(void) { const char* mode_env = getenv("HUPTIME_MODE"); const char* multi_env = getenv("HUPTIME_MULTI"); const char* revive_env = getenv("HUPTIME_REVIVE"); const char* debug_env = getenv("HUPTIME_DEBUG"); const char* pipe_env = getenv("HUPTIME_PIPE"); const char* wait_env = getenv("HUPTIME_WAIT"); if( debug_env != NULL && strlen(debug_env) > 0 ) { debug_enabled = !strcasecmp(debug_env, "true") ? TRUE: FALSE; } DEBUG("Initializing..."); /* Initialize our lock. */ impl_init_lock(); /* Save this pid as our master pid. * This is done to handle processes that use * process pools. We remember the master pid and * will do the full fork()/exec() only when we are * the master. Otherwise, we will simply shutdown * gracefully, and all the master to restart. */ master_pid = getpid(); /* Grab our exit strategy. */ if( mode_env != NULL && strlen(mode_env) > 0 ) { if( !strcasecmp(mode_env, "fork") ) { exit_strategy = FORK; DEBUG("Exit strategy is fork."); } else if( !strcasecmp(mode_env, "exec") ) { exit_strategy = EXEC; DEBUG("Exit strategy is exec."); } else { fprintf(stderr, "Unknown exit strategy."); libc.exit(1); } } /* Check if we have something to unlink. */ to_unlink = getenv("HUPTIME_UNLINK"); if( to_unlink != NULL && strlen(to_unlink) > 0 ) { DEBUG("Unlink is '%s'.", to_unlink); } /* Clear up any outstanding child processes. * Because we may have exited before the process * could do appropriate waitpid()'s, we try to * clean up children here. Note that we may have * some zombies that hang around during the life * of the program, but at every restart they will * be cleaned up (so at least they won't grow * without bound). */ int status = 0; while( waitpid((pid_t)-1, &status, WNOHANG) > 0 ); /* Check if we're in multi mode. */ if( multi_env != NULL && strlen(multi_env) > 0 ) { multi_mode = !strcasecmp(multi_env, "true") ? TRUE: FALSE; } #ifndef SO_REUSEPORT if( multi_mode == TRUE ) { fprintf(stderr, "WARNING: Multi mode not supported.\n"); fprintf(stderr, "(Requires at least Linux 3.9 and recent headers).\n"); } #endif /* Check if we're in revive mode. */ if( revive_env != NULL && strlen(revive_env) > 0 ) { revive_mode = !strcasecmp(revive_env, "true") ? TRUE : FALSE; } /* Check if we are in wait mode. */ if( wait_env != NULL && strlen(wait_env) > 0 ) { wait_mode = !strcasecmp(wait_env, "true") ? TRUE : FALSE; } /* Check if we're a respawn. */ if( pipe_env != NULL && strlen(pipe_env) > 0 ) { int fd = -1; fdinfo_t *info = NULL; int pipefd = strtol(pipe_env, NULL, 10); DEBUG("Loading all file descriptors."); /* Decode all passed information. */ while( !info_decode(pipefd, &fd, &info) ) { fd_save(fd, info); DEBUG("Decoded fd %d (type %d).", fd, info->type); info = NULL; } if( info != NULL ) { dec_ref(info); } /* Finished with the pipe. */ libc.close(pipefd); unsetenv("HUPTIME_PIPE"); DEBUG("Finished decoding."); /* Close all non-encoded descriptors. */ for( fd = 0; fd < fd_max(); fd += 1 ) { info = fd_lookup(fd); if( info == NULL ) { DEBUG("Closing fd %d.", fd); libc.close(fd); } } /* Restore all given file descriptors. */ for( fd = 0; fd < fd_limit(); fd += 1 ) { info = fd_lookup(fd); if( info != NULL && info->type == SAVED ) { fdinfo_t *orig_info = fd_lookup(info->saved.fd); if( orig_info != NULL ) { /* Uh-oh, conflict. Move the original (best effort). */ do_dup(info->saved.fd); do_close(info->saved.fd); } /* Return the offset (ignore failure). */ if( info->saved.offset != (off_t)-1 ) { lseek(fd, info->saved.offset, SEEK_SET); } /* Move the SAVED fd back. */ libc.dup2(fd, info->saved.fd); DEBUG("Restored fd %d.", info->saved.fd); } } } else { DEBUG("Saving all initial file descriptors."); /* Save all of our initial files. These are used * for re-execing the process. These are persisted * effectively forever, and on restarts we close * everything that is not a BOUND socket or a SAVED * file descriptor. */ for( int fd = 0; fd < fd_max(); fd += 1 ) { fdinfo_t *info = fd_lookup(fd); if( info != NULL ) { /* Encoded earlier. */ continue; } /* Make a new SAVED FD. */ int newfd = libc.dup(fd); if( newfd >= 0 ) { fdinfo_t *saved_info = alloc_info(SAVED); if( saved_info != NULL ) { saved_info->saved.fd = fd; saved_info->saved.offset = lseek(fd, 0, SEEK_CUR); fd_save(newfd, saved_info); DEBUG("Saved fd %d (offset %lld).", fd, (long long int)saved_info->saved.offset); } } } } /* Save the environment. * * NOTE: We reserve extra space in the environment * for our special start-up parameters, which will be added * in impl_exec() below. (The encoded BOUND/SAVED sockets). * * We also filter out the special variables above that were * used to pass in information about sockets that were bound. */ free(environ_copy); environ_copy = (char**)read_nul_sep("/proc/self/environ"); DEBUG("Saved environment."); /* Save the arguments. */ free(args_copy); args_copy = (char**)read_nul_sep("/proc/self/cmdline"); DEBUG("Saved args."); for( int i = 0; args_copy[i] != NULL; i += 1 ) { DEBUG(" arg%d=%s", i, args_copy[i]); } /* Save the cwd & exe. */ free(cwd_copy); cwd_copy = (char*)read_link("/proc/self/cwd"); DEBUG("Saved cwd."); free(exe_copy); exe_copy = (char*)read_link("/proc/self/exe"); DEBUG("Saved exe."); /* Install our signal handlers. */ impl_install_sighandlers(); /* Initialize our thread. */ impl_init_thread(); /* Unblock our signals. * Note that we have specifically masked the * signals prior to the exec() below, to cover * the race between program start and having * installed the appropriate handlers. */ sigset_t set; sigemptyset(&set); sigaddset(&set, SIGHUP); sigprocmask(SIG_UNBLOCK, &set, NULL); /* Done. */ DEBUG("Initialization complete."); }
static int do_accept4(int sockfd, struct sockaddr *addr, socklen_t *addrlen, int flags) { int rval = -1; fdinfo_t *info = NULL; if( sockfd < 0 ) { errno = EINVAL; return -1; } DEBUG("do_accept4(%d, ...) ...", sockfd); L(); info = fd_lookup(sockfd); if( info == NULL || (info->type != BOUND && info->type != DUMMY) ) { U(); /* Should return an error. */ rval = libc.accept4(sockfd, addr, addrlen, flags); DEBUG("do_accept4(%d, ...) => %d (no info)", sockfd, rval); return rval; } /* Check that they've called listen. */ if( info->type == BOUND && !info->bound.stub_listened ) { U(); DEBUG("do_accept4(%d, ...) => -1 (not listened)", sockfd); errno = EINVAL; return -1; } /* Check if this is a dummy. * There's no way that they should be calling accept(). * The dummy FD will never trigger a poll, select, epoll, * etc. So we just act as a socket with no clients does -- * either return immediately or block forever. NOTE: We * still return in case of EINTR or other suitable errors. */ if( info->type == DUMMY && info->dummy.client >= 0 ) { rval = info->dummy.client; info->dummy.client = -1; U(); DEBUG("do_accept4(%d, ...) => %d (dummy client)", sockfd, rval); return rval; } U(); if( !(flags & SOCK_NONBLOCK) ) { /* Wait for activity on the socket. */ struct pollfd poll_info; poll_info.fd = sockfd; poll_info.events = POLLIN; poll_info.revents = 0; if( poll(&poll_info, 1, -1) < 0 ) { return -1; } } L(); /* Check our status. */ if( is_exiting == TRUE ) { /* We've transitioned from not exiting * to exiting in this period. This will * circle around a return a dummy descriptor. */ U(); DEBUG("do_accept4(%d, ...) => -1 (interrupted)", sockfd); errno = flags & SOCK_NONBLOCK ? EAGAIN : EINTR; return -1; } /* Do the accept for real. */ fdinfo_t *new_info = alloc_info(TRACKED); if( new_info == NULL ) { U(); DEBUG("do_accept4(%d, ...) => -1 (alloc error?)", sockfd); return -1; } inc_ref(info); new_info->tracked.bound = info; rval = libc.accept4(sockfd, addr, addrlen, flags); if( rval >= 0 ) { /* Save the reference to the socket. */ fd_save(rval, new_info); } else { /* An error occured, nothing to track. */ dec_ref(new_info); } U(); DEBUG("do_accept4(%d, ...) => %d (tracked %d) %s", sockfd, rval, total_tracked, rval == -1 ? strerror(errno) : ""); return rval; }
static int do_bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen) { fdinfo_t *info = NULL; int rval = -1; if( sockfd < 0 ) { errno = EINVAL; return -1; } /* At this point, we can reasonably assume * the program has started up and has installed * whatever signal handlers it wants. We check * that our own signal handler is installed. * If the user doesn't want us to override the * built-in signal handlers, they shouldn't use * huptime. */ impl_install_sighandlers(); DEBUG("do_bind(%d, ...) ...", sockfd); L(); /* See if this socket already exists. */ for( int fd = 0; fd < fd_limit(); fd += 1 ) { fdinfo_t *info = fd_lookup(fd); if( info != NULL && info->type == BOUND && info->bound.addrlen == addrlen && !memcmp(addr, (void*)info->bound.addr, addrlen) ) { DEBUG("Found ghost %d, cloning...", fd); /* Give back a duplicate of this one. */ int rval = do_dup2(fd, sockfd); if( rval < 0 ) { /* Dup2 failed? */ DEBUG("Failed."); continue; } if( info->bound.is_ghost ) { /* Close the original (not needed). */ info->bound.is_ghost = 0; do_close(fd); } /* Success. */ U(); DEBUG("do_bind(%d, ...) => 0 (ghosted)", sockfd); return 0; } } #ifdef SO_REUSEPORT /* Multi mode? Set socket options. */ if( multi_mode == TRUE ) { int optval = 1; if( setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT, &optval, sizeof(optval)) < 0 ) { U(); DEBUG("do_bind(%d, ...) => -1 (no multi?)", sockfd); return -1; } DEBUG("Multi mode enabled."); } #endif /* Try a real bind. */ info = alloc_info(BOUND); if( info == NULL ) { U(); DEBUG("do_bind(%d, ...) => -1 (alloc error?)", sockfd); return -1; } rval = libc.bind(sockfd, addr, addrlen); if( rval < 0 ) { dec_ref(info); U(); DEBUG("do_bind(%d, ...) => %d (error)", sockfd, rval); return rval; } /* Ensure that this socket is non-blocking, * this is because we override the behavior * for accept() and we require non-blocking * behavior. We deal with the consequences. */ rval = fcntl(sockfd, F_SETFL, O_NONBLOCK); if( rval < 0 ) { dec_ref(info); U(); DEBUG("do_bind(%d, ...) => %d (fcntl error)", sockfd, rval); return -1; } /* Save a refresh bound socket info. */ info->bound.stub_listened = 0; info->bound.real_listened = 0; info->bound.addr = (struct sockaddr*)malloc(addrlen); info->bound.addrlen = addrlen; memcpy((void*)info->bound.addr, (void*)addr, addrlen); fd_save(sockfd, info); /* Success. */ U(); DEBUG("do_bind(%d, ...) => %d", sockfd, rval); return rval; }