#include "esh_main_loop.h" #include "esh_misc.h" #include "esh_history.h" #include "esh_init.h" #include "esh_deinit.h" #include "string_misc.h" void EShRunLoop() { struct termios orig_term_attr; struct termios new_term_attr; /* set the terminal to semi-raw mode */ tcgetattr(fileno(stdin), &orig_term_attr); memcpy(&new_term_attr, &orig_term_attr, sizeof(struct termios)); new_term_attr.c_lflag &= ~(ECHO | ICANON | ISIG | IEXTEN); new_term_attr.c_cc[VTIME] = 0; new_term_attr.c_cc[VMIN] = 1; tcsetattr(fileno(stdin), TCSANOW, &new_term_attr); char* command = malloc(esh_info_global->max_command_length); char* uncomplete_command = malloc(esh_info_global->max_command_length); // Used to restore after moves history for (;;) { EShShowMsg(); memset(command, 0, esh_info_global->max_command_length); int command_length = 0; int current_command_pos = 0; int current_history_step = 0; char input_char; memset(uncomplete_command, 0, esh_info_global->max_command_length); while (input_char = fgetc(stdin)) { if (input_char == 10) { printf("\n"); break; // 'Enter' pressed; } if (input_char == EOF) { continue; } if (input_char == '\033') { // Arrow up/down/left/right sequence fgetc(stdin); switch(fgetc(stdin)) { case 'A': { if (current_history_step == 0) { strcpy(uncomplete_command, command); } const char* new_command = EShReceiveCommandFromHistory(current_history_step); if (new_command != NULL) { ++current_history_step; for (int i = current_command_pos; i < command_length; ++i) { printf(" "); ++current_command_pos; } for (int i = current_command_pos; i > 0; --i) { printf("\b \b"); } strcpy(command, new_command); command_length = strlen(new_command); current_command_pos = command_length; for (int i = 0; i < command_length; ++i) { printf("%c", command[i]); } } break; } case 'B': // Down { const char* new_command; if (current_history_step > 0) { --current_history_step; } if (current_history_step > 0) { new_command = EShReceiveCommandFromHistory(current_history_step - 1); } else { new_command = uncomplete_command; } for (int i = current_command_pos; i < command_length; ++i) { printf(" "); ++current_command_pos; } for (int i = current_command_pos; i > 0; --i) { printf("\b \b"); } strcpy(command, new_command); command_length = strlen(new_command); current_command_pos = command_length; for (int i = 0; i < command_length; ++i) { printf("%c", command[i]); } break; } case 'C': // Right if (current_command_pos < command_length) { printf("%c", command[current_command_pos]); ++current_command_pos; } break; case 'D': // Left if (current_command_pos > 0) { printf("\b"); --current_command_pos; } break; } continue; } if (input_char == 4) { // Ctrl + d tcsetattr(fileno(stdin), TCSANOW, &orig_term_attr); return; } if (input_char == 127) { // Backspace if (current_command_pos > 0) { for (int i = current_command_pos - 1; i < command_length; ++i) { command[i] = command[i + 1]; } command[command_length - 1] = '\0'; printf("\b"); printf("%s", command + current_command_pos - 1); printf(" \b"); --current_command_pos; --command_length; for (int i = command_length - 1; i >= current_command_pos; --i) { printf("\b"); } } continue; } if (input_char == 3) { // Ctrl + c tcsetattr(fileno(stdin), TCSANOW, &orig_term_attr); printf("\n"); return; } if (EShIsShellLetter(input_char)) { for (int i = command_length; i >= current_command_pos; --i) { command[i + 1] = command[i]; } command[current_command_pos] = input_char; printf("%s", command + current_command_pos); ++current_command_pos; ++command_length; for (int i = command_length; i > current_command_pos; --i) { printf("\b"); } continue; } } if (command_length == 0) { printf("\n"); continue; } EShAddCommandToHistory(command); int jobs_num = 0; EShJob* jobs = EShParseCommandIntoJobs(command, &jobs_num); // EShPrintJobsDebugInfo(command, jobs, jobs_num); EShExecuteJobs(jobs_num, jobs); EShFreeJobsList(jobs); } free(command); free(uncomplete_command); /* restore the original terminal attributes */ tcsetattr(fileno(stdin), TCSANOW, &orig_term_attr); } void EShShowMsg() { EShUpdateInviteMessage(); printf("%s", esh_info_global->invite_message); } EShJob* EShParseCommandIntoJobs(char* command, int* jobs_num) { EShJob* jobs = EShMakeJobsList(); *jobs_num = 1; int current_job_cmd_length = 0; for (int i = 0; i < strlen(command); ++i) { ESH_JOB_DELIMITER delimiter = EshIsJobDelimiter(command + i); if (delimiter != NOT_A_DELIMITER) { EShSplitJobCommand(&jobs[*jobs_num - 1], ' '); if (delimiter == SEMICOLON) { jobs[*jobs_num].job_start_condition = NO_CONDITION; } if (delimiter == LOGIC_AND) { jobs[*jobs_num].job_start_condition = PREVIOUS_EXIT_SUCCESS; } if (delimiter == LOGIC_OR) { jobs[*jobs_num].job_start_condition = PREVIOUS_EXIT_FAILED; } if (delimiter == BIT_AND) { printf(ANSI_COLOR_RED "\nESh note: running processes in background is still not available, selected job will be running in foreground :(\n" ANSI_COLOR_RESET); fflush(stdout); } if (delimiter == BIT_OR) { int pipes[2]; if (pipe(pipes) != 0) { printf(ANSI_COLOR_RED "\nESh error: cannot spawn pipe: %s\n" ANSI_COLOR_RESET, strerror(errno)); fflush(stdout); } jobs[*jobs_num - 1].stdout_fd = pipes[1]; jobs[*jobs_num].stdin_fd = pipes[0]; } ++(*jobs_num); current_job_cmd_length = 0; i += EShGetJobDelimiterSize(EshIsJobDelimiter(command + i)) - 1; continue; } jobs[*jobs_num - 1].command[current_job_cmd_length] = command[i]; ++current_job_cmd_length; } EShSplitJobCommand(&jobs[*jobs_num - 1], ' '); return jobs; } void EShSplitJobCommand(EShJob* job, char delim) { int token_status = 0; // 0 - token result[result_size] finished // 1 - token result[result_size] not finished int current_token_length = 0; for (int i = 0; i < strlen(job->command); ++i) { while (job->command[i] == delim && i < strlen(job->command)) { if (token_status == 1) { token_status = 0; } ++i; } if (i == strlen(job->command)) { if (token_status == 1) { ++job->command_tokens_num; } break; } if (token_status == 0) { token_status = 1; ++job->command_tokens_num; current_token_length = 0; } job->command_tokens[job->command_tokens_num - 1][current_token_length] = job->command[i]; ++current_token_length; } for (int i = 0; i < job->command_tokens_num; ++i) { EShSubstituteShellValuesArgument(&job->command_tokens[i]); } } void EShExecuteJobs(int jobs_num, EShJob* jobs_list) { int prev_job_exit_code = 0; for (int i = 0; i < jobs_num; ++i) { if (strlen(jobs_list[i].command) == 0) continue; if (jobs_list[i].job_start_condition == NO_CONDITION || (jobs_list[i].job_start_condition == PREVIOUS_EXIT_SUCCESS && prev_job_exit_code == 0) || (jobs_list[i].job_start_condition == PREVIOUS_EXIT_FAILED && prev_job_exit_code != 0)) { EShRunJob(&jobs_list[i]); } } } void EShSetJobCommandType(EShJob* job) { } int EShRunJob(EShJob* job) { if (EShIsInnerJob(job)) { return EShProcessInnerJob(job); } else { return EShProcessExecJob(job); } // TODO: PIPES INIT & BG PROCESS SUPPORT } int EShIsInnerJob(EShJob* job) { return strcmp(job->command_tokens[0], "cd") == 0 || strcmp(job->command_tokens[0], "exit") == 0; // TODO: jobs, bg, fg (!) } int EShProcessInnerJob(EShJob* job) { if (strcmp(job->command_tokens[0], "cd") == 0) { if (chdir(job->command_tokens[1]) == -1) { printf(ANSI_COLOR_RED "Esh: cannot change directory: %s\n" ANSI_COLOR_RESET, strerror(errno)); fflush(stdout); return -1; } getcwd(esh_info_global->current_working_dir, PATH_MAX); EShOptimiseCurrentWorkingDirectory(); EShUpdateInviteMessage(); return 0; } if (strcmp(job->command_tokens[0], "exit") == 0) { EShExit(0); return 0; // Actually, no need in it. } } int EShProcessExecJob(EShJob* job) { int pid = fork(); if (pid == 0) { if (job->stdin_fd != -1) { dup2(job->stdin_fd, STDIN_FILENO); close(job->stdin_fd); job->stdin_fd = -1; } if (job->stdout_fd != -1) { dup2(job->stdout_fd, STDOUT_FILENO); close(job->stdout_fd); job->stdout_fd = -1; } if (job->stderr_fd != -1) { dup2(job->stderr_fd, STDERR_FILENO); close(job->stderr_fd); job->stderr_fd = -1; } for (int i = job->command_tokens_num; i < esh_info_global->max_command_tokens; ++i) { free(job->command_tokens[i]); job->command_tokens[i] = NULL; } execvp(job->command_tokens[0], job->command_tokens); printf(ANSI_COLOR_RED "Esh: run command %s failed.\n" ANSI_COLOR_RESET, job->command); fflush(stdout); exit(1); // execlp failed } else { if (job->stdin_fd != -1) { close(job->stdin_fd); job->stdin_fd = -1; } if (job->stdout_fd != -1) { close(job->stdout_fd); job->stdout_fd = -1; } if (job->stderr_fd != -1) { close(job->stderr_fd); job->stderr_fd = -1; } job->job_pid = pid; int status; waitpid(pid, &status, 0); if (WIFEXITED(status)) { return WEXITSTATUS(status); } else { return -1; // No exit status received; } } } void EShSubstituteShellValuesArgument(char** arg) { char* new_str = *arg; // Processing tilda new_str = repl_str(new_str, "~", esh_info_global->user_data->pw_dir); // Processing system variables char* envvar_begin = new_str - 1; while ((envvar_begin = strchr(envvar_begin + 1, '%')) != NULL) { char* envvar_end = strchr(envvar_begin + 1, '%'); if (envvar_end == NULL) break; char envvar_name[1024 * 1024]; memset(envvar_name, 0, 1024 * 1024); memcpy(envvar_name, envvar_begin + 1, (envvar_end - envvar_begin - 1)); char* envvar_value = getenv(envvar_name); if (envvar_value == NULL) { continue; } char envvar_name_overed[1024 * 1024]; memset(envvar_name_overed, 0, 1024 * 1024); strcat(envvar_name_overed, "%"); strcat(envvar_name_overed, envvar_name); strcat(envvar_name_overed, "%"); new_str = repl_str(new_str, envvar_name_overed, envvar_value); } free(*arg); *arg = new_str; } void EShPrintJobsDebugInfo(const char* command, EShJob* jobs, int jobs_num) { printf("\nD: Splitted command (%s) into %d jobs:\n", command, jobs_num); for (int i = 0; i < jobs_num; ++i) { printf("===================\n" "Job #%d:\n" "Cmd: %s\n" "Cmd tokens: %d ", i, jobs[i].command, jobs[i].command_tokens_num); for (int j = 0; j < jobs[i].command_tokens_num; ++j) { printf("{%s} ", jobs[i].command_tokens[j]); } printf("\n===================\n"); } printf("\n"); }