From 14e3c369033eefe05b462621441962a1e2255e13 Mon Sep 17 00:00:00 2001 From: Ziheng Chen <zc328@dragons.drexel.edu> Date: Mon, 3 Mar 2025 04:02:25 +0000 Subject: [PATCH] Upload New File --- w8/rsh_cli.c | 259 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 259 insertions(+) create mode 100644 w8/rsh_cli.c diff --git a/w8/rsh_cli.c b/w8/rsh_cli.c new file mode 100644 index 0000000..49073f2 --- /dev/null +++ b/w8/rsh_cli.c @@ -0,0 +1,259 @@ + +#include <sys/socket.h> +#include <arpa/inet.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/un.h> +#include <fcntl.h> + +#include "dshlib.h" +#include "rshlib.h" + + + + +/* + * exec_remote_cmd_loop(server_ip, port) + * server_ip: a string in ip address format, indicating the servers IP + * address. Note 127.0.0.1 is the default meaning the server + * is running on the same machine as the client + * + * port: The port the server will use. Note the constant + * RDSH_DEF_PORT which is 1234 in rshlib.h. If you are using + * tux you may need to change this to your own default, or even + * better use the command line override -c implemented in dsh_cli.c + * For example ./dsh -c 10.50.241.18:5678 where 5678 is the new port + * number and the server address is 10.50.241.18 + * + * This function basically implements the network version of + * exec_local_cmd_loop() from the last assignemnt. It will: + * + * 1. Allocate buffers for sending and receiving data over the + * network + * 2. Create a network connection to the server, getting an active + * socket by calling the start_client(server_ip, port) function. + * 2. Go into an infinite while(1) loop prompting the user for + * input commands. + * + * a. Accept a command from the user via fgets() + * b. Send that command to the server using send() - it should + * be a null terminated string + * c. Go into a loop and receive client requests. Note each + * receive might not be a C string so you need to print it + * out using: + * printf("%.*s", (int)bytes_received, rsp_buff); + * this version of printf() uses the "%.*s" flag that indicates + * that the rsp_buff might be a null terminated string, or + * it might not be, if its not, print exactly bytes_received + * bytes. + * d. In the recv() loop described above. Each time you receive + * data from the server, see if the last byte received is the + * EOF character. This indicates the server is done and you can + * send another command by going to the top of the loop. The + * best way to do this is as follows assuming you are receiving + * data into a buffer called recv_buff, and you received + * recv_bytes in the call to recv: + * + * recv_bytes = recv(sock, recv_buff, recv_buff_sz, 0) + * + * if recv_bytes: + * <negative_number>: communication error + * 0: Didn't receive anything, likely server down + * > 0: Got some data. Check if the last byte is EOF + * is_eof = (recv_buff[recv_bytes-1] == RDSH_EOF_CHAR) ? 1 : 0; + * if is_eof is true, this is the last part of the transmission + * from the server and you can break out of the recv() loop. + * + * returns: + * OK: The client executed all of its commands and is exiting + * either by the `exit` command that terminates the client + * or the `stop-server` command that terminates both the + * client and the server. + * ERR_MEMORY: If this function cannot allocate memory via + * malloc for the send and receive buffers + * ERR_RDSH_CLIENT: If the client cannot connect to the server. + * AKA the call to start_client() fails. + * ERR_RDSH_COMMUNICATION: If there is a communication error, AKA + * any failures from send() or recv(). + * + * NOTE: Since there are several exit points and each exit point must + * call free() on the buffers allocated, close the socket, and + * return an appropriate error code. Its suggested you use the + * helper function client_cleanup() for these purposes. For example: + * + * return client_cleanup(cli_socket, request_buff, resp_buff, ERR_RDSH_COMMUNICATION); + * return client_cleanup(cli_socket, request_buff, resp_buff, OK); + * + * The above will return ERR_RDSH_COMMUNICATION and OK respectively to the main() + * function after cleaning things up. See the documentation for client_cleanup() + * + */ +int exec_remote_cmd_loop(char *address, int port) +{ + + int cli_socket; + char *cmd_buff, *rsp_buff; + ssize_t recv_size; + + // Allocate buffers for command input and response storage + cmd_buff = (char *)malloc(SH_CMD_MAX); + rsp_buff = (char *)malloc(RDSH_COMM_BUFF_SZ); + + if (!cmd_buff || !rsp_buff) { + fprintf(stderr, "Error: Memory allocation failed\n"); + return client_cleanup(-1, cmd_buff, rsp_buff, ERR_MEMORY); + } + + // Start the client (connect to the server) + cli_socket = start_client(address, port); + if (cli_socket < 0) { + return client_cleanup(cli_socket, cmd_buff, rsp_buff, ERR_RDSH_CLIENT); + } + + // Main client loop + while (1) { + // Print prompt and read user input + printf("%s", SH_PROMPT); + if (fgets(cmd_buff, SH_CMD_MAX, stdin) == NULL) { + printf("\n"); + break; // Exit loop on EOF (Ctrl+D) + } + + // Remove trailing newline + cmd_buff[strcspn(cmd_buff, "\n")] = '\0'; + + // Ignore empty input + if (strlen(cmd_buff) == 0) continue; + + // Send command to the server (including null terminator) + if (send(cli_socket, cmd_buff, strlen(cmd_buff) + 1, 0) < 0) { + perror("Error sending command to server"); + return client_cleanup(cli_socket, cmd_buff, rsp_buff, ERR_RDSH_COMMUNICATION); + } + + // Receive response from server + while ((recv_size = recv(cli_socket, rsp_buff, RDSH_COMM_BUFF_SZ, 0)) > 0) { + // Check if last byte is the EOF character + int is_last_chunk = (rsp_buff[recv_size - 1] == RDSH_EOF_CHAR) ? 1 : 0; + + // Replace EOF character with null terminator + if (is_last_chunk) { + rsp_buff[recv_size - 1] = '\0'; + } + + // Print response (handles both raw data and null-terminated strings) + printf("%.*s", (int)recv_size, rsp_buff); + + // If EOF character received, stop receiving + if (is_last_chunk) break; + } + + if (recv_size < 0) { + perror("Error receiving response from server"); + return client_cleanup(cli_socket, cmd_buff, rsp_buff, ERR_RDSH_COMMUNICATION); + } + } + + // Cleanup before exiting + return client_cleanup(cli_socket, cmd_buff, rsp_buff, OK); + + //return WARN_RDSH_NOT_IMPL; +} + +/* + * start_client(server_ip, port) + * server_ip: a string in ip address format, indicating the servers IP + * address. Note 127.0.0.1 is the default meaning the server + * is running on the same machine as the client + * + * port: The port the server will use. Note the constant + * RDSH_DEF_PORT which is 1234 in rshlib.h. If you are using + * tux you may need to change this to your own default, or even + * better use the command line override -c implemented in dsh_cli.c + * For example ./dsh -c 10.50.241.18:5678 where 5678 is the new port + * number and the server address is 10.50.241.18 + * + * This function basically runs the client by: + * 1. Creating the client socket via socket() + * 2. Calling connect() + * 3. Returning the client socket after connecting to the server + * + * returns: + * client_socket: The file descriptor fd of the client socket + * ERR_RDSH_CLIENT: If socket() or connect() fail + * + */ +int start_client(char *server_ip, int port) { + int client_socket; + struct sockaddr_in server_addr; + + // Step 1: Create the client socket + client_socket = socket(AF_INET, SOCK_STREAM, 0); + if (client_socket < 0) { + perror("Error creating client socket"); + return ERR_RDSH_CLIENT; + } + + // Step 2: Set up the server address struct + memset(&server_addr, 0, sizeof(server_addr)); + server_addr.sin_family = AF_INET; + server_addr.sin_port = htons(port); // Convert port to network byte order + + // Step 3: Convert and assign the server IP address + if (inet_pton(AF_INET, server_ip, &server_addr.sin_addr) <= 0) { + perror("Invalid server IP address"); + close(client_socket); + return ERR_RDSH_CLIENT; + } + + // Step 4: Connect to the server + if (connect(client_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) { + perror("Error connecting to the server"); + close(client_socket); + return ERR_RDSH_CLIENT; + } + + printf("Connected to server at %s:%d\n", server_ip, port); + return client_socket; // Return the connected socket descriptor +} + + +/* + * client_cleanup(int cli_socket, char *cmd_buff, char *rsp_buff, int rc) + * cli_socket: The client socket + * cmd_buff: The buffer that will hold commands to send to server + * rsp_buff: The buffer that will hld server responses + * + * This function does the following: + * 1. If cli_socket > 0 it calls close(cli_socket) to close the socket + * 2. It calls free() on cmd_buff and rsp_buff + * 3. It returns the value passed as rc + * + * Note this function is intended to be helper to manage exit conditions + * from the exec_remote_cmd_loop() function given there are several + * cleanup steps. We provide it to you fully implemented as a helper. + * You do not have to use it if you want to develop an alternative + * strategy for cleaning things up in your exec_remote_cmd_loop() + * implementation. + * + * returns: + * rc: This function just returns the value passed as the + * rc parameter back to the caller. This way the caller + * can just write return client_cleanup(...) + * + */ +int client_cleanup(int cli_socket, char *cmd_buff, char *rsp_buff, int rc){ + //If a valid socket number close it. + if(cli_socket > 0){ + close(cli_socket); + } + + //Free up the buffers + free(cmd_buff); + free(rsp_buff); + + //Echo the return value that was passed as a parameter + return rc; +} \ No newline at end of file -- GitLab