/*- * Copyright (c) 2026, Mr. Goblin * * SPDX-License-Identifier: BSD-2-Clause * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL * THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY * OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include "cliflags.h" #include "codes.h" #define BTASKTD_HELP_STR \ "Copyright (c) 2026, Mr. Goblin\n" \ "btasktd - task tracking TCP-server\n" \ "Usage: btasktd [-Vh] [-p port] [-u users]\n" \ "For more info read the program's man page\n" #define BTASKTD_VERSION_STR \ "0.0.0" #ifndef BTASKTD_QUEUE_LEN # define BTASKTD_QUEUE_LEN 16 #endif #ifndef BTASKTD_CLIENT_BUF_SIZE # define BTASKTD_CLIENT_BUF_SIZE 4096 #endif #ifndef BTASKTD_MAX_RESPONSE_BUF_SIZE # define BTASKTD_MAX_RESPONSE_BUF_SIZE 8192 #endif #ifndef BTASKTD_RESPONSE_BUF_SIZE # define BTASKTD_RESPONSE_BUF_SIZE \ ((BTASKTD_MAX_RESPONSE_BUF_SIZE) - 256) #endif struct user { int fd; char buf[BTASKTD_CLIENT_BUF_SIZE]; int bi; /* buf index */ }; /* BODY SHOULD BE EMPTY OR END WITH CRLF (\r\n, 13 10) */ struct response { int code; const char *body; /* for empty use NULL or "" */ }; struct btasktd { int fd; int users_cnt; struct user *users; }; static void fprint_help(FILE *f) { fprintf(f, "%s", BTASKTD_HELP_STR); } static void set_nonblock(int fd) { int fflags = fcntl(fd, F_GETFL); fcntl(fd, F_SETFL, fflags|O_NONBLOCK); } static int find_free_user(const struct user *arr, int count) { int i; for (i = 0; i < count; i++) { if (-1 == arr[i].fd) return i; } return -1; } static void reset_user_buf(struct user *pu) { pu->bi = 0; memset(pu->buf, '\0', sizeof(pu->buf)); } static void reset_user(struct user *pu) { pu->fd = -1; reset_user_buf(pu); } static void send_to_client(int fd, const char *s) { int len = strlen(s); int code = send(fd, s, len, 0); if (-1 == code) perror("send"); } static void send_response(int fd, struct response r) { char buf[BTASKTD_MAX_RESPONSE_BUF_SIZE] = {0}; if (NULL == r.body) r.body = ""; snprintf(buf, sizeof(buf), "%s%s.\r\n", code_to_str(r.code), r.body); send_to_client(fd, buf); } static void send_response_code(int fd, int code) { struct response r = { CODE_200_OK, NULL }; r.code = code; send_response(fd, r); } static void handle_accept(struct btasktd *this) { int idx; struct sockaddr_in user_addr = {0}; socklen_t addrlen = sizeof(user_addr); int fd = accept(this->fd, (struct sockaddr*)&user_addr, &addrlen); if (-1 == fd) { perror("accept"); return; } set_nonblock(fd); idx = find_free_user(this->users, this->users_cnt); if (-1 == idx) { send_response_code(fd, CODE_503_SERVICE_UNAVAILABLE); close(fd); } this->users[idx].fd = fd; this->users[idx].bi = 0; } static void handle_timeout(struct btasktd *this) { } static void handle_buf(struct btasktd *this, int id, int should_send_response) { struct response resp; struct user *pu = &(this->users[id]); char buf[BTASKTD_RESPONSE_BUF_SIZE] = {0}; /* dummy yet */ resp.code = CODE_200_OK; resp.body = ""; if (should_send_response) { snprintf(buf, sizeof(buf), "%s", pu->buf); #if 1 send_to_client(pu->fd, buf); #else resp.body = buf; send_response(pu->fd, resp); #endif } reset_user_buf(pu); } static void handle_user(struct btasktd *this, int id) { int valread; struct user *pu = &(this->users[id]); unsigned sz = sizeof(pu->buf) - (pu->bi) - 1; valread = recv(pu->fd, pu->buf + pu->bi, sz, 0); if (0 == valread) { if (pu->bi > 0) { handle_buf(this, id, 0); } close(pu->fd); reset_user(pu); return; } else if (-1 == valread) { perror("recv"); close(pu->fd); reset_user(pu); return; } handle_buf(this, id, 1); } int main(int argc, char **argv) { struct btasktd server; int server_fd, opt = 1, res, port, passed_flags = 1, users_cnt; int max_fd; struct user *users = NULL; int i; struct sockaddr_in addr = {0}; struct int_flag_val int_res; struct bool_flag_val bool_res; fd_set readfds; bool_res = extract_bool_flag('V', argc, argv); if (fvr_ok == bool_res.code && bool_res.data) { printf("btasktd version %s\n", BTASKTD_VERSION_STR); return 0; } bool_res = extract_bool_flag('h', argc, argv); if (fvr_ok == bool_res.code && bool_res.data) { fprint_help(stdout); return 0; } int_res = extract_int_flag('p', argc, argv); switch (int_res.code) { case fvr_not_found: # ifdef BTASKTD_DEFAULT_PORT port = BTASKTD_DEFAULT_PORT; # else fprintf(stderr, "No port specified!\n"); passed_flags = 0; # endif break; case fvr_ok: port = int_res.data; if (port > 0) break; /* fallthrough */ case fvr_invalid: fprintf(stderr, "Invalid port!\n"); return 1; break; } int_res = extract_int_flag('u', argc, argv); switch (int_res.code) { case fvr_not_found: # ifdef BTASKTD_DEFAULT_USERS_CNT users_cnt = BTASKTD_DEFAULT_USERS_CNT; # else fprintf(stderr, "No users count specified!\n"); passed_flags = 0; # endif break; case fvr_ok: users_cnt = int_res.data; if (users_cnt > 0) break; /* fallthrough */ case fvr_invalid: fprintf(stderr, "Invalid users count!\n"); return 1; break; } if (!passed_flags) { fprintf(stderr, "-\n"); fprint_help(stderr); return 1; } users = (struct user*)calloc(users_cnt, sizeof(struct user)); if (NULL == users) { fprintf(stderr, "No memory!\n"); return 1; } for (i = 0; i < users_cnt; i++) { users[i].fd = -1; } server_fd = socket(AF_INET, SOCK_STREAM, 0); if (server_fd < 0) { free(users); perror("socket"); return 1; } addr.sin_family = AF_INET; addr.sin_port = htons(port); addr.sin_addr.s_addr = htonl(INADDR_ANY); res = setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); if (0 != res) { close(server_fd); free(users); perror("setsockopt"); return 1; } res = bind(server_fd, (struct sockaddr*)&addr, sizeof(addr)); if (0 != res) { close(server_fd); free(users); perror("bind"); return 1; } res = listen(server_fd, BTASKTD_QUEUE_LEN); if (0 != res) { close(server_fd); free(users); perror("listen"); return 1; } set_nonblock(server_fd); server.fd = server_fd; server.users_cnt = users_cnt; server.users = users; printf("btaskt server btasktd started on port %d\n", port); for (;;) { FD_ZERO(&readfds); FD_SET(server_fd, &readfds); max_fd = server_fd; for (i = 0; i < users_cnt; i++) { if (-1 == users[i].fd) continue; FD_SET(users[i].fd, &readfds); if (users[i].fd > max_fd) max_fd = users[i].fd; } res = select(max_fd+1, &readfds, NULL, NULL, NULL); if (-1 == res) { if (EINTR == errno) { /* signal handler called */ } else { perror("select"); } continue; } else if (0 == res) { handle_timeout(&server); continue; } if (FD_ISSET(server_fd, &readfds)) handle_accept(&server); for (i = 0; i < users_cnt; i++) { if (-1 == users[i].fd) continue; if (FD_ISSET(users[i].fd, &readfds)) handle_user(&server, i); } } printf("btaskt server btasktd stopped\n"); close(server_fd); free(users); return 0; }