diff --git a/Assignment-06/questions.md b/Assignment-06/questions.md index 43ffc1074f8ea23f1bd68675be2ef81b5b4900b3..1b8c897ec11192d3a011c359a0f2c2ca7ed69dc3 100644 --- a/Assignment-06/questions.md +++ b/Assignment-06/questions.md @@ -1,19 +1,19 @@ 1. How does the remote client determine when a command's output is fully received from the server, and what techniques can be used to handle partial reads or ensure complete message transmission? -_answer here_ +_It determines by specific things, so for this one we use a null terminator or eof to see when a message is over. We can buffer incoming data, or predefined limiters, or length prefixing_ 2. This week's lecture on TCP explains that it is a reliable stream protocol rather than a message-oriented one. Since TCP does not preserve message boundaries, how should a networked shell protocol define and detect the beginning and end of a command sent over a TCP connection? What challenges arise if this is not handled correctly? -_answer here_ +_It should frame the message, so that it has proper lengths, or delimiters, and EOF characters and stuff that like. It needs proper handling so that the commands may be interpreted correctly_ 3. Describe the general differences between stateful and stateless protocols. -_answer here_ +_Stateful protocols maintain information across multiple requests, while stateless protcols don't._ 4. Our lecture this week stated that UDP is "unreliable". If that is the case, why would we ever use it? -_answer here_ +_UDP is low latency and a very fast so it's very useful when you need information quickly._ 5. What interface/abstraction is provided by the operating system to enable applications to use network communications? -_answer here_ \ No newline at end of file +_Operating systems use socket interfaces, and that allows these applications to perform network communications over protocols._ \ No newline at end of file diff --git a/Assignment-06/starter/rsh_server.c b/Assignment-06/starter/rsh_server.c index 29473d50e866f6303e1d51098247976357e6c60b..648d2c7502742e16e36536e1f47f5d2d7a33c5b6 100644 --- a/Assignment-06/starter/rsh_server.c +++ b/Assignment-06/starter/rsh_server.c @@ -74,6 +74,7 @@ int start_server(char *ifaces, int port, int is_threaded){ */ int stop_server(int svr_socket){ return close(svr_socket); + } /* @@ -130,9 +131,14 @@ int boot_server(char *ifaces, int port){ } addr.sin_family = AF_INET; - addr.sin_addr.s_addr = inet_addr(ifaces); + if (inet_pton(AF_INET, ifaces, &addr.sin_addr) <= 0) { + perror("inet_pton"); + close(svr_socket); + return ERR_RDSH_SERVER; + } addr.sin_port = htons(port); + ret = bind(svr_socket, (const struct sockaddr *) &addr, sizeof(struct sockaddr_in)); if (ret == -1 ) { @@ -149,6 +155,7 @@ int boot_server(char *ifaces, int port){ ret = listen(svr_socket, 20); if (ret == -1) { perror("listen"); + close(svr_socket); return ERR_RDSH_COMMUNICATION; } @@ -197,16 +204,58 @@ int boot_server(char *ifaces, int port){ * connections, and negative values terminate the server. * */ -int process_cli_requests(int svr_socket){ - int cli_socket; - int rc = OK; - while(1){ - // TODO use the accept syscall to create cli_socket - // and then exec_client_requests(cli_socket) + int process_cli_requests(int svr_socket) { + int cli_socket; + char buffer[RDSH_COMM_BUFF_SZ]; + ssize_t bytes_received; + int rc = OK; + + while (1) { + // Accept a client connection + cli_socket = accept(svr_socket, NULL, NULL); + if (cli_socket == -1) { + perror("accept"); + rc = ERR_RDSH_COMMUNICATION; + break; + } + + // Receive data from the client + bytes_received = recv(cli_socket, buffer, sizeof(buffer), 0); + if (bytes_received < 0) { + perror("recv"); + close(cli_socket); + rc = ERR_RDSH_COMMUNICATION; + break; + } else if (bytes_received == 0) { + // Client disconnected + close(cli_socket); + continue; + } + + // Check for special commands + if (strncmp(buffer, "stop-server", strlen("stop-server")) == 0) { + printf("Client requested server to stop\n"); + rc = OK_EXIT; + close(cli_socket); + break; + } + + // Handle client requests + rc = exec_client_requests(cli_socket); + if (rc < 0) { + fprintf(stderr, "Error processing client requests: %d\n", rc); + close(cli_socket); + break; + } + + // Close the client socket + close(cli_socket); + printf("Client socket closed properly\n"); } - stop_server(cli_socket); + // Clean up and stop the server + stop_server(svr_socket); return rc; } @@ -251,35 +300,73 @@ int process_cli_requests(int svr_socket){ * ERR_RDSH_COMMUNICATION: A catch all for any socket() related send * or receive errors. */ + int exec_client_requests(int cli_socket) { + char *io_buff; int io_size; command_list_t cmd_list; - int rc; - int cmd_rc; - int last_rc; - char *io_buff; + int rc = OK; + // Allocate a buffer for I/O operations io_buff = malloc(RDSH_COMM_BUFF_SZ); - if (io_buff == NULL){ - return ERR_RDSH_SERVER; + if (io_buff == NULL) { + return ERR_RDSH_SERVER; // Return server error if allocation fails } - while(1) { - // TODO use recv() syscall to get input + while (1) { + // Receive input from the client + io_size = recv(cli_socket, io_buff, RDSH_COMM_BUFF_SZ, 0); + if (io_size < 0) { + perror("recv"); + free(io_buff); + return ERR_RDSH_COMMUNICATION; // Return communication error + } else if (io_size == 0) { + // Client disconnected + free(io_buff); + return OK; // Return OK to accept another client + } - // TODO build up a cmd_list + // Null-terminate the received data + io_buff[io_size] = '\0'; + + // Check for special commands + if (strncmp(io_buff, "exit", strlen("exit")) == 0) { + // Client sent the `exit` command + free(io_buff); + return OK; // Return OK to accept another client + } else if (strncmp(io_buff, "stop-server", strlen("stop-server")) == 0) { + // Client sent the `stop-server` command + free(io_buff); + return OK_EXIT; // Return OK_EXIT to stop the server + } - // TODO rsh_execute_pipeline to run your cmd_list + // Parse the input into a command list + rc = parse_pipeline(io_buff, &cmd_list); + if (rc != OK) { + // Handle parsing error + send_message_string(cli_socket, CMD_ERR_RDSH_EXEC); + send_message_eof(cli_socket); + continue; // Continue to the next iteration + } - // TODO send appropriate respones with send_message_string - // - error constants for failures - // - buffer contents from execute commands - // - etc. + // Execute the command pipeline + rc = rsh_execute_pipeline(cli_socket, &cmd_list); + if (rc < 0) { + // Handle execution error + send_message_string(cli_socket, CMD_ERR_RDSH_EXEC); + send_message_eof(cli_socket); + continue; // Continue to the next iteration + } - // TODO send_message_eof when done + // Free memory for each command's buffer + for (int i = 0; i < cmd_list.num; i++) { + free(cmd_list.commands[i]._cmd_buffer); + } } - return WARN_RDSH_NOT_IMPL; + // Clean up (this will never be reached due to the while(1) loop) + free(io_buff); + return WARN_RDSH_NOT_IMPL; // Default return value } /* @@ -296,14 +383,22 @@ int exec_client_requests(int cli_socket) { * ERR_RDSH_COMMUNICATION: The send() socket call returned an error or if * we were unable to send the EOF character. */ -int send_message_eof(int cli_socket){ - int send_len = (int)sizeof(RDSH_EOF_CHAR); - int sent_len; - sent_len = send(cli_socket, &RDSH_EOF_CHAR, send_len, 0); - if (sent_len != send_len){ +int send_message_string(int cli_socket, char *buff) { + int bytes_sent; + + // Send the message + bytes_sent = send(cli_socket, buff, strlen(buff), 0); + if (bytes_sent < 0) { + perror("send"); return ERR_RDSH_COMMUNICATION; } + + // Send the EOF character + if (send_message_eof(cli_socket) != OK) { + return ERR_RDSH_COMMUNICATION; + } + return OK; } @@ -327,8 +422,15 @@ int send_message_eof(int cli_socket){ * we were unable to send the message followed by the EOF character. */ int send_message_string(int cli_socket, char *buff){ - //TODO implement writing to cli_socket with send() - return WARN_RDSH_NOT_IMPL; + int bytes_sent; + + bytes_sent = send(cli_socket, buff, strlen(buff), 0); + if (bytes_sent < 0) { + perror("send"); + return ERR_RDSH_COMMUNICATION; + } + + return OK; } @@ -370,29 +472,76 @@ int send_message_string(int cli_socket, char *buff){ * macro that we discussed during our fork/exec lecture to * get this value. */ + int rsh_execute_pipeline(int cli_sock, command_list_t *clist) { int pipes[clist->num - 1][2]; // Array of pipes pid_t pids[clist->num]; - int pids_st[clist->num]; // Array to store process IDs - Built_In_Cmds bi_cmd; + int pids_st[clist->num]; // Array to store process statuses int exit_code; // Create all necessary pipes for (int i = 0; i < clist->num - 1; i++) { if (pipe(pipes[i]) == -1) { perror("pipe"); - exit(EXIT_FAILURE); + return ERR_RDSH_COMMUNICATION; } } + // Fork and execute each command in the pipeline for (int i = 0; i < clist->num; i++) { - // TODO this is basically the same as the piped fork/exec assignment, except for where you connect the begin and end of the pipeline (hint: cli_sock) - - // TODO HINT you can dup2(cli_sock with STDIN_FILENO, STDOUT_FILENO, etc. + pids[i] = fork(); + if (pids[i] == -1) { + perror("fork"); + return ERR_RDSH_COMMUNICATION; + } + if (pids[i] == 0) { // Child process + // Redirect input for the first command + if (i == 0) { + if (dup2(cli_sock, STDIN_FILENO) == -1) { + perror("dup2 stdin"); + exit(EXIT_FAILURE); + } + } else { + // Redirect stdin to the read end of the previous pipe + if (dup2(pipes[i - 1][0], STDIN_FILENO) == -1) { + perror("dup2 stdin"); + exit(EXIT_FAILURE); + } + } + + // Redirect output for the last command + if (i == clist->num - 1) { + // Redirect stdout and stderr to the client socket + if (dup2(cli_sock, STDOUT_FILENO) == -1) { + perror("dup2 stdout"); + exit(EXIT_FAILURE); + } + if (dup2(cli_sock, STDERR_FILENO) == -1) { + perror("dup2 stderr"); + exit(EXIT_FAILURE); + } + } else { + // Redirect stdout to the write end of the current pipe + if (dup2(pipes[i][1], STDOUT_FILENO) == -1) { + perror("dup2 stdout"); + exit(EXIT_FAILURE); + } + } + + // Close all pipe ends in the child process + for (int j = 0; j < clist->num - 1; j++) { + close(pipes[j][0]); + close(pipes[j][1]); + } + + // Execute the command + execvp(clist->commands[i].argv[0], clist->commands[i].argv); + perror("execvp"); + exit(EXIT_FAILURE); + } } - // Parent process: close all pipe ends for (int i = 0; i < clist->num - 1; i++) { close(pipes[i][0]); @@ -404,15 +553,15 @@ int rsh_execute_pipeline(int cli_sock, command_list_t *clist) { waitpid(pids[i], &pids_st[i], 0); } - //by default get exit code of last process - //use this as the return value + // Determine the exit code exit_code = WEXITSTATUS(pids_st[clist->num - 1]); for (int i = 0; i < clist->num; i++) { - //if any commands in the pipeline are EXIT_SC - //return that to enable the caller to react - if (WEXITSTATUS(pids_st[i]) == EXIT_SC) + if (WEXITSTATUS(pids_st[i]) == EXIT_SC) { exit_code = EXIT_SC; + break; + } } + return exit_code; }