int main(int argc, char *argv[]){ char *service = SERVER_PORT; if (daemon_init() == -1){ printf("can't fork self\n"); exit(0); } if(is_already_running(argv[0]) != 0){ fprintf(stdout, "%s process is already exists.\n", argv[0]); exit(-1); } int servSock = SetupTcpServerSocket(service); if(servSock < 0){ fprintf(stderr, "SetupTCPServerSocket() failed"); } for(; ;){ int clntSock = AcceptTcpConnection(servSock); HandleTcpClient(clntSock); close(clntSock); } }
static bool is_already_running() { int fd = -1; const char *file_path = "/var/run/educ_noip.pid"; const mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; /* -rw-r--r-- */ struct flock lock_ctx = { .l_type = F_WRLCK, .l_whence = SEEK_SET, .l_start = 0, .l_len = 0, .l_pid = -1, }; const int OBTAIN_LOCK_ERR = -1; int errno_save = 0; if ((fd = open(file_path, O_RDWR | O_CREAT, mode)) == -1) log_die(errno, "is_already_running: can't open %s", file_path); if (fcntl(fd, F_SETLK, &lock_ctx) == OBTAIN_LOCK_ERR) { errno_save = errno; close(fd); if (errno_save == EACCES || errno_save == EAGAIN) return (true); else log_die(errno_save, "is_already_running: can't lock %s", file_path); } ftruncate(fd, 0); dprintf(fd, "%ld\n", (long int) getpid()); g_lockfile_fd = fd; return (false); } /** * @brief Run in the background * @return void * * Detach the program from the controlling terminal and continue * execution... */ void Daemonize() { switch (fork()) { case FORK_FAILED: log_die(errno, "Daemonize: Cannot fork"); case VALUE_CHILD_PROCESS: if (setsid() == -1) log_die(errno, "Daemonize: Trouble in becoming the session leader"); break; default: _exit(0); } log_init(); /* Calls to the log functions before this log to stderr/stdout. */ switch (redirect_standard_streams()) { case REDIR_STDERR_FAIL: log_warn(0, "Daemonize: Error redirecting stderr"); break; case REDIR_STDIN_FAIL: log_warn(0, "Daemonize: Error redirecting stdin"); break; case REDIR_STDOUT_FAIL: log_warn(0, "Daemonize: Error redirecting stdout"); break; default: case REDIR_OK: log_debug("Daemonize: All standard IO-streams successfully redirected"); break; } if (is_already_running()) log_die(0, "Daemonize: FATAL: A copy of the daemon is already running!"); }
/* * Respond to commands requests and perform the commands: * For a list of commands see command_process(). * * Will fork() for longer running operations such as fullcompare and quickcompare. * * Will load all PPMs into RAM before listening for commands. * * Args: * * orig_socketfh : A file handle where text is sent, typically for any operational errors. * sql_info : A string with SQL connection information. * portno : Which network port to listen on. * compare_size : The height (and width) of the PPMs. * maxerr : For images to be considered similar the difference must be below this amount. * * Return: non-zero on error. */ int server_loop(FILE *log_fh, char *sql_info, int portno, int compare_size, unsigned int maxerr) { if(is_already_running(log_fh, portno)){ error(log_fh, "Quitting."); return 3; } // Setup IPv4 and/or IPv6 ports to listen on. // Ports below first_real_client_index are for listening for new connections. int first_real_client_index = 0; int listening_socket = create_port_listen_v4(log_fh, portno); if (listening_socket > 0) { global_client_detail[first_real_client_index].fd = listening_socket; first_real_client_index++; } // Setup a IPV6 network socket to listen on listening_socket = create_port_listen_v6(log_fh, portno); if (listening_socket > 0) { global_client_detail[first_real_client_index].fd = listening_socket; first_real_client_index++; } if (first_real_client_index == 0) { error(log_fh, "Failed to start listening on network. Quitting."); return 2; } // Done setting up IPv4 and/or IPv6 listening ports. // Connect to SQL database PGconn *psql = ppm_sql_connect(log_fh, sql_info); if (!psql) { error(log_fh, "libpq error: PQconnectdb returned NULL.\nSQL details: %s", sql_info); return 1; } // All PPMs in RAM. Loaded from SQL. PicInfo *picinfo_list = NULL; // Load all PPMs from SQL into RAM. int rc = load(log_fh, psql, &picinfo_list); if (rc) { error(log_fh, "LOAD failed with code %d", rc); ppm_sql_disconnect(log_fh, psql); return 1; } // Setup an array of incoming file descriptors. int index; for (index = first_real_client_index; index < CLIENT_MAX; index++) { global_client_detail[index].fd = CLIENT_SLOT_FREE; global_client_detail[index].command_buffer[0] = 0; global_client_detail[index].pid = 0; // TODO global_client_detail[index].connection_timeout = TODO ; } global_active_connection_count = first_real_client_index; // Listening Sockets have been created. int server_loop = 1; // Setup a timeout waiting for commands. // We time out so that we can reap children without needing to wait for commands. fd_set my_fd_set; struct timeval timeout; // listen for commands while (server_loop) { /* Initialize the file descriptor set we will block on. */ FD_ZERO(&my_fd_set); // Setup FD set to block on. Also maximum FD. int fd_max = 0; for (index = 0; index < CLIENT_MAX; index++) { // Don't add closed FDs. if (global_client_detail[index].fd == CLIENT_SLOT_FREE) continue; // Stop listening for new connections if we have too many. if ((index < first_real_client_index) && (global_active_connection_count >= CLIENT_MAX)) continue; FD_SET(global_client_detail[index].fd, &my_fd_set); fd_max = max(fd_max, global_client_detail[index].fd); } /* Initialize the timeout data structure. */ // We do timeout so we can do housekeeping without need to wait for client input to trigger the loop. timeout.tv_sec = COMMAND_LISTEN_TIMEOUT; timeout.tv_usec = 0; /* select() returns the count of FDs with waiting input, or -1 if error. */ int fd_count_with_input = TEMP_FAILURE_RETRY( select(fd_max + 1, &my_fd_set, NULL, NULL, &timeout)); if (fd_count_with_input < 0) { error(log_fh, "select() failed. errno=%d, error=%s", errno, strerror(errno)); continue; } // Housekeeping // Reaper: Clean up any child processes which have exited. pid_t late_pid; while ((late_pid = waitpid(-1, NULL, WNOHANG)) > 0) { global_child_process_count--; // Find the late client by pid then record it as dead. for (index = first_real_client_index; index < CLIENT_MAX; index++){ if ((global_client_detail[index].fd != CLIENT_SLOT_FREE) && (global_client_detail[index].pid == late_pid)) { global_client_detail[index].pid = 0; break; } } } // Housekeeping // TODO check for any stale client connections // TODO add a mechanism so that we expire connections (other than socked for new v4 and v6) // if they wait too long to send data. // Don't expire connections if the server is the one that hasn't responded. // There was a select() timeout, no FDs with input available. if (fd_count_with_input == 0) continue; // Read data from FDs. for (index = 0; index < CLIENT_MAX; index++) { // Check for valid FD. if (global_client_detail[index].fd == CLIENT_SLOT_FREE) continue; // Check for waiting data on FD. int fd = global_client_detail[index].fd; if (!FD_ISSET(fd, &my_fd_set)) continue; // We have a new IPv4 or IPv6 connection. if (index < first_real_client_index) { int new_sockfd = accept(fd, NULL, NULL); if (new_sockfd < 0) { error(log_fh, "accept()"); continue; } int free_slot = first_real_client_index; // lower connections are for new IPv4 and IPv6 connections. for (; free_slot < CLIENT_MAX; free_slot++) if (global_client_detail[free_slot].fd == CLIENT_SLOT_FREE) break; // Are we are out of free slots? if (free_slot >= CLIENT_MAX) { error(log_fh, "Internal Error: we somehow got more connections than we can handle."); char *mesg = "BUSY: Please come back later"; int write_rc = write(new_sockfd, mesg, strlen(mesg)); if (write_rc == -1) { error(log_fh, "Failed to tell client to go away"); } close(new_sockfd); } // Register new connection. else { global_client_detail[free_slot].fd = new_sockfd; global_client_detail[free_slot].pid = 0; bzero(global_client_detail[free_slot].command_buffer, BUFFER_SIZE); global_client_detail[free_slot].cmd_offset = 0; global_active_connection_count++; // TODO anything else? // TODO set timeout. } } else { // We have data on existing connection that needs to be read. int client_fd = global_client_detail[index].fd; char *cmd_buffer = global_client_detail[index].command_buffer; int cmd_offset = global_client_detail[index].cmd_offset; // Read data. int read_bytes = read(client_fd, &cmd_buffer[cmd_offset], BUFFER_SIZE - cmd_offset); if (read_bytes < 0) { // kill the connection as client has most likely gone away. error(log_fh, "Failed to read from client, closing the FD."); close(client_fd); global_client_detail[index].fd = CLIENT_SLOT_FREE; continue; } global_client_detail[index].cmd_offset += read_bytes; // TODO update timeout. // Check if a command has been completed. int command_end = strcspn(cmd_buffer, "\r\n"); if (command_end > 0) { cmd_buffer[command_end] = 0; // Strip trailing LF, CR, CRLF, LFCR, ... command_process(client_fd, cmd_buffer, &picinfo_list, psql, &server_loop, compare_size, maxerr); close(client_fd); global_client_detail[index].fd = CLIENT_SLOT_FREE; global_active_connection_count--; } } } } if (picinfo_list) { unload(&picinfo_list); } // close all sockets, including for new IPv4 and IPv6 connections. for (index = 0; index < CLIENT_MAX; index++) if (global_client_detail[index].fd != CLIENT_SLOT_FREE) close(global_client_detail[index].fd); // End of server loop ppm_sql_disconnect(log_fh, psql); return 0; }