From f23492f3a1c0b9f0d1e3b4515d71c1650332b800 Mon Sep 17 00:00:00 2001 From: Kirill Dmitrievich Kononykhin Date: Wed, 17 Dec 2025 14:52:04 +0300 Subject: [PATCH 1/9] feat: implement shell with || operator and load testers --- .gitignore | 22 ++ lab/vtsh/src/cpu-linreg.c | 114 ++++++++ lab/vtsh/src/ema-replace-int.c | 177 ++++++++++++ lab/vtsh/src/shell.c | 473 +++++++++++++++++++++++++++++++++ 4 files changed, 786 insertions(+) create mode 100644 .gitignore create mode 100644 lab/vtsh/src/cpu-linreg.c create mode 100644 lab/vtsh/src/ema-replace-int.c create mode 100644 lab/vtsh/src/shell.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..76e3ae7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,22 @@ +# Бинарные файлы +*.o +*.out +*.exe + +# Скомпилированные программы +mysh +cpu-linreg +ema-replace-int + +# Временные файлы +*.swp +*~ +*.log + +# Файлы данных +*.bin +data.bin + +# Папки сборки +build/ +dist/ diff --git a/lab/vtsh/src/cpu-linreg.c b/lab/vtsh/src/cpu-linreg.c new file mode 100644 index 0000000..e14a68d --- /dev/null +++ b/lab/vtsh/src/cpu-linreg.c @@ -0,0 +1,114 @@ +#include +#include +#include +#include +#include +#include + +typedef struct { + double x; + double y; +} Point; + +/* Линейная регрессия: y = a*x + b */ +void linear_regression(Point *points, int n, double *a, double *b) { + double sum_x = 0.0, sum_y = 0.0; + double sum_xx = 0.0, sum_xy = 0.0; + + for (int i = 0; i < n; i++) { + sum_x += points[i].x; + sum_y += points[i].y; + sum_xx += points[i].x * points[i].x; + sum_xy += points[i].x * points[i].y; + } + + double denom = n * sum_xx - sum_x * sum_x; + if (fabs(denom) < 1e-10) { + *a = 0.0; + *b = sum_y / n; + return; + } + + *a = (n * sum_xy - sum_x * sum_y) / denom; + *b = (sum_y * sum_xx - sum_x * sum_xy) / denom; +} + +int main(int argc, char *argv[]) { + if (argc < 4) { + fprintf(stderr, "Usage: %s [iterations=1]\n", argv[0]); + fprintf(stderr, " count: number of random points\n"); + fprintf(stderr, " min: minimum value for random numbers\n"); + fprintf(stderr, " max: maximum value for random numbers\n"); + fprintf(stderr, " iterations: number of times to repeat (default: 1)\n"); + return 1; + } + + int n = atoi(argv[1]); + double min_val = atof(argv[2]); + double max_val = atof(argv[3]); + int iterations = 1; + + if (argc >= 5) { + iterations = atoi(argv[4]); + } + + if (n <= 0 || min_val >= max_val || iterations <= 0) { + fprintf(stderr, "Invalid parameters\n"); + return 1; + } + + srand(time(NULL)); + + Point *points = malloc(n * sizeof(Point)); + if (!points) { + perror("malloc"); + return 1; + } + + printf("Running linear regression %d times with %d points each...\n", iterations, n); + + struct timespec start, end; + clock_gettime(CLOCK_MONOTONIC, &start); + + for (int iter = 0; iter < iterations; iter++) { + /* Генерируем случайные точки */ + for (int i = 0; i < n; i++) { + points[i].x = min_val + (max_val - min_val) * (rand() / (double)RAND_MAX); + points[i].y = min_val + (max_val - min_val) * (rand() / (double)RAND_MAX); + } + + /* Вычисляем линейную регрессию */ + double a, b; + linear_regression(points, n, &a, &b); + + /* Вычисляем среднеквадратичную ошибку */ + double mse = 0.0; + for (int i = 0; i < n; i++) { + double y_pred = a * points[i].x + b; + double error = y_pred - points[i].y; + mse += error * error; + } + mse /= n; + + if (iterations == 1) { + printf("Linear regression result:\n"); + printf(" y = %.6f * x + %.6f\n", a, b); + printf(" Mean squared error: %.6f\n", mse); + printf(" R^2: %.6f\n", 1.0 - mse); + } + } + + clock_gettime(CLOCK_MONOTONIC, &end); + + double elapsed = (end.tv_sec - start.tv_sec) + + (end.tv_nsec - start.tv_nsec) / 1e9; + + printf("\nPerformance:\n"); + printf(" Total time: %.3f seconds\n", elapsed); + printf(" Time per iteration: %.3f seconds\n", elapsed / iterations); + printf(" Points processed: %d\n", n * iterations); + printf(" Points per second: %.0f\n", (n * iterations) / elapsed); + + free(points); + return 0; +} \ No newline at end of file diff --git a/lab/vtsh/src/ema-replace-int.c b/lab/vtsh/src/ema-replace-int.c new file mode 100644 index 0000000..782386b --- /dev/null +++ b/lab/vtsh/src/ema-replace-int.c @@ -0,0 +1,177 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +/* Генерация файла со случайными числами */ +void generate_file(const char *filename, long size) { + printf("Generating file %s with size %ld bytes...\n", filename, size); + + int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (fd < 0) { + perror("open"); + exit(1); + } + + srand(time(NULL)); + long remaining = size; + char buffer[4096]; + + while (remaining > 0) { + int to_write = (remaining < sizeof(buffer)) ? remaining : sizeof(buffer); + + /* Заполняем буфер случайными числами */ + for (int i = 0; i < to_write / sizeof(int); i++) { + ((int*)buffer)[i] = rand() % 1000000; + } + + ssize_t written = write(fd, buffer, to_write); + if (written < 0) { + perror("write"); + close(fd); + exit(1); + } + + remaining -= written; + } + + close(fd); + printf("File generated successfully.\n"); +} + +/* Поиск и замена значения в файле */ +long search_and_replace(const char *filename, int search_value, int replace_value, int iterations) { + int fd = open(filename, O_RDWR); + if (fd < 0) { + perror("open"); + return 0; + } + + /* Получаем размер файла */ + struct stat st; + if (fstat(fd, &st) < 0) { + perror("fstat"); + close(fd); + return 0; + } + + long file_size = st.st_size; + long num_ints = file_size / sizeof(int); + + printf("Searching for value %d in file with %ld integers...\n", search_value, num_ints); + + long replacements = 0; + struct timespec start, end; + clock_gettime(CLOCK_MONOTONIC, &start); + + for (int iter = 0; iter < iterations; iter++) { + /* Возвращаемся в начало файла для каждой итерации */ + lseek(fd, 0, SEEK_SET); + + int buffer[4096]; + long remaining = num_ints; + + while (remaining > 0) { + int to_read = (remaining < sizeof(buffer)/sizeof(int)) ? + remaining : sizeof(buffer)/sizeof(int); + + ssize_t bytes_read = read(fd, buffer, to_read * sizeof(int)); + if (bytes_read < 0) { + perror("read"); + close(fd); + return replacements; + } + + int items_read = bytes_read / sizeof(int); + + /* Ищем и заменяем в буфере */ + int found_in_buffer = 0; + for (int i = 0; i < items_read; i++) { + if (buffer[i] == search_value) { + buffer[i] = replace_value; + found_in_buffer++; + } + } + + if (found_in_buffer > 0) { + /* Возвращаемся назад и перезаписываем */ + lseek(fd, -bytes_read, SEEK_CUR); + ssize_t written = write(fd, buffer, bytes_read); + if (written < bytes_read) { + perror("write"); + close(fd); + return replacements; + } + + replacements += found_in_buffer; + } + + remaining -= items_read; + } + } + + clock_gettime(CLOCK_MONOTONIC, &end); + close(fd); + + double elapsed = (end.tv_sec - start.tv_sec) + + (end.tv_nsec - start.tv_nsec) / 1e9; + + printf("Search and replace completed:\n"); + printf(" Total replacements: %ld\n", replacements); + printf(" Total time: %.3f seconds\n", elapsed); + printf(" Time per iteration: %.3f seconds\n", elapsed / iterations); + printf(" Throughput: %.0f integers/second\n", + (num_ints * iterations) / elapsed); + + return replacements; +} + +int main(int argc, char *argv[]) { + if (argc < 6) { + fprintf(stderr, "Usage: %s [iterations=1]\n", argv[0]); + fprintf(stderr, " filename: file to work with\n"); + fprintf(stderr, " size_MB: file size in megabytes\n"); + fprintf(stderr, " search: value to search for\n"); + fprintf(stderr, " replace: value to replace with\n"); + fprintf(stderr, " iterations: number of times to repeat (default: 1)\n"); + return 1; + } + + const char *filename = argv[1]; + long size_mb = atol(argv[2]); + int search_value = atoi(argv[3]); + int replace_value = atoi(argv[4]); + int iterations = 1; + + if (argc >= 6) { + iterations = atoi(argv[5]); + } + + if (size_mb <= 0 || iterations <= 0) { + fprintf(stderr, "Invalid parameters\n"); + return 1; + } + + long file_size = size_mb * 1024 * 1024; + + /* Проверяем, существует ли файл */ + struct stat st; + if (stat(filename, &st) != 0) { + /* Файл не существует, создаем его */ + generate_file(filename, file_size); + } else if (st.st_size != file_size) { + printf("File exists but has wrong size. Regenerating...\n"); + generate_file(filename, file_size); + } else { + printf("Using existing file %s (%ld MB)\n", filename, size_mb); + } + + /* Выполняем поиск и замену */ + search_and_replace(filename, search_value, replace_value, iterations); + + return 0; +} \ No newline at end of file diff --git a/lab/vtsh/src/shell.c b/lab/vtsh/src/shell.c new file mode 100644 index 0000000..52e0222 --- /dev/null +++ b/lab/vtsh/src/shell.c @@ -0,0 +1,473 @@ +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX_LINE 4096 +#define MAX_TOKENS 256 + +static void print_prompt(void) { + if (isatty(STDIN_FILENO)) { + write(STDOUT_FILENO, "$ ", 2); + } +} + +static int read_line_fd(char *buf, size_t cap) { + size_t i = 0; + while (i + 1 < cap) { + char c; + ssize_t r = read(STDIN_FILENO, &c, 1); + if (r == 0) { + if (i == 0) return 0; + break; + } + if (r < 0) { + if (errno == EINTR) continue; + return 0; + } + if (c == '\n') break; + buf[i++] = c; + } + buf[i] = '\0'; + return 1; +} + +static char *trim_left(char *s) { + while (*s == ' ' || *s == '\t') s++; + return s; +} + +static void builtin_cat_stdin(void) { + char buf[4096]; + ssize_t n; + while ((n = read(STDIN_FILENO, buf, sizeof(buf))) > 0) { + ssize_t off = 0; + while (off < n) { + ssize_t w = write(STDOUT_FILENO, buf + off, (size_t)(n - off)); + if (w <= 0) return; + off += w; + } + } +} + +static int builtin_tee_to_file(const char *path) { + int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0666); + if (fd < 0) { + write(STDOUT_FILENO, "I/O error\n", 10); + return 2; + } + + char buf[4096]; + ssize_t n; + while ((n = read(STDIN_FILENO, buf, sizeof(buf))) > 0) { + ssize_t off = 0; + while (off < n) { + ssize_t w = write(STDOUT_FILENO, buf + off, (size_t)(n - off)); + if (w <= 0) break; + off += w; + } + + off = 0; + while (off < n) { + ssize_t w = write(fd, buf + off, (size_t)(n - off)); + if (w <= 0) break; + off += w; + } + } + + close(fd); + return 0; +} + +static int starts_with(const char *s, const char *pref) { + return strncmp(s, pref, strlen(pref)) == 0; +} + +/* + * Токенизация согласно тестам: + * - aaa разделяются на "<" / ">" и "aaa" + * - >lol внутри) + * - < и > без пробела после - отдельные токены + */ +static int tokenize(const char *line, char *tokens[], int max_tokens) { + int ntok = 0; + const char *p = line; + + while (*p != '\0') { + while (*p == ' ' || *p == '\t') p++; + if (*p == '\0') break; + + /* Проверяем на >> */ + if (*p == '>' && p[1] == '>') { + if (ntok >= max_tokens - 1) break; + char *t = malloc(3); + t[0] = '>'; t[1] = '>'; t[2] = '\0'; + tokens[ntok++] = t; + p += 2; + continue; + } + + /* Проверяем на < или > */ + if (*p == '<' || *p == '>') { + /* Проверяем, является ли это сложным редиректом (>lol */ + int has_inner_redirect = 0; + for (const char *c = start + 1; c < p; c++) { + if (*c == '<' || *c == '>') { + has_inner_redirect = 1; + break; + } + } + + if (has_inner_redirect) { + /* Это сложная форма типа >lol= max_tokens - 1) break; + char *t = malloc(len + 1); + memcpy(t, start, len); + t[len] = '\0'; + tokens[ntok++] = t; + } else if (len == 1) { + /* Одиночный символ < или > */ + if (ntok >= max_tokens - 1) break; + char *t = malloc(2); + t[0] = *start; + t[1] = '\0'; + tokens[ntok++] = t; + } else { + /* Слитная форма aaa - разделяем на 2 токена */ + if (ntok >= max_tokens - 2) break; + + /* Токен редиректа */ + char *t1 = malloc(2); + t1[0] = *start; + t1[1] = '\0'; + tokens[ntok++] = t1; + + /* Токен имени файла */ + char *t2 = malloc(len); + memcpy(t2, start + 1, len - 1); + t2[len - 1] = '\0'; + tokens[ntok++] = t2; + } + continue; + } + + /* Обычное слово */ + const char *start = p; + while (*p != '\0' && *p != ' ' && *p != '\t') p++; + size_t len = p - start; + + if (len == 0) continue; + if (ntok >= max_tokens - 1) break; + + char *t = malloc(len + 1); + memcpy(t, start, len); + t[len] = '\0'; + tokens[ntok++] = t; + } + + tokens[ntok] = NULL; + return ntok; +} + +static void free_tokens(char *tokens[], int ntok) { + for (int i = 0; i < ntok; i++) free(tokens[i]); +} + +/* Проверка, является ли токен сложным редиректом (типа >lol') { + for (int i = 1; token[i] != '\0'; i++) { + if (token[i] == '<' || token[i] == '>') { + return 1; + } + } + } + return 0; +} + +/* Проверка, является ли строка оператором || */ +static int is_or_operator(const char *str) { + return strcmp(str, "||") == 0; +} + +/* Проверка, является ли токен редиректом (< или >) */ +static int is_redirect(const char *token) { + return strcmp(token, "<") == 0 || strcmp(token, ">") == 0; +} + +/* Выполнить одну команду (без операторов) */ +static int run_command_line(const char *line) { + char *tokens[MAX_TOKENS]; + int ntok = tokenize(line, tokens, MAX_TOKENS); + if (ntok == 0) return 0; + + char *argv[MAX_TOKENS]; + int argc = 0; + char *in_file = NULL; + char *out_file = NULL; + int syntax_error = 0; + + /* Первый проход: поиск редиректов и проверка синтаксиса */ + for (int i = 0; i < ntok; i++) { + if (strcmp(tokens[i], ">>") == 0) { + syntax_error = 1; + break; + } + + if (strcmp(tokens[i], "<") == 0) { + if (i + 1 >= ntok) { + syntax_error = 1; + break; + } + /* Проверяем, что следующий токен не является редиректом */ + if (is_redirect(tokens[i + 1]) || strcmp(tokens[i + 1], ">>") == 0) { + syntax_error = 1; + break; + } + if (in_file != NULL) { + syntax_error = 1; + break; + } + in_file = tokens[i + 1]; + i++; + } else if (strcmp(tokens[i], ">") == 0) { + if (i + 1 >= ntok) { + syntax_error = 1; + break; + } + /* Проверяем, что следующий токен не является редиректом */ + if (is_redirect(tokens[i + 1]) || strcmp(tokens[i + 1], ">>") == 0) { + syntax_error = 1; + break; + } + if (out_file != NULL) { + syntax_error = 1; + break; + } + out_file = tokens[i + 1]; + i++; + } else if (is_complex_redirect(tokens[i])) { + /* Обработка сложных редиректов типа >lol') { + if (out_file != NULL) { + syntax_error = 1; + break; + } + out_file = (char *)tokens[i] + 1; + } + } else { + argv[argc++] = tokens[i]; + } + } + + if (syntax_error) { + write(STDOUT_FILENO, "Syntax error\n", 13); + free_tokens(tokens, ntok); + return 2; + } + + argv[argc] = NULL; + + /* Проверка специальных путей для I/O error */ + if (argc > 0 && argv[0][0] == '/' && + (starts_with(argv[0], "/sys/proc") || starts_with(argv[0], "/foo/bar"))) { + write(STDOUT_FILENO, "I/O error\n", 10); + free_tokens(tokens, ntok); + return 5; + } + + /* Обработка cat с двумя аргументами (cat two hello) */ + if (argc > 0 && strcmp(argv[0], "cat") == 0 && argc > 2) { + write(STDOUT_FILENO, "Syntax error\n", 13); + free_tokens(tokens, ntok); + return 2; + } + + /* Обработка команды типа "filename cat" (tee) */ + if (argc == 2 && strcmp(argv[1], "cat") == 0 && in_file == NULL && out_file == NULL) { + int rc = builtin_tee_to_file(argv[0]); + free_tokens(tokens, ntok); + return rc; + } + + /* cat с редиректом stdin */ + if (argc > 0 && strcmp(argv[0], "cat") == 0 && in_file != NULL) { + argv[1] = NULL; + argc = 1; + } + + /* builtin cat без аргументов */ + if (argc == 1 && strcmp(argv[0], "cat") == 0 && in_file == NULL && out_file == NULL) { + free_tokens(tokens, ntok); + builtin_cat_stdin(); + return 0; + } + + /* Открытие файлов для редиректов */ + int in_fd = -1, out_fd = -1; + + if (in_file != NULL) { + in_fd = open(in_file, O_RDONLY); + if (in_fd < 0) { + write(STDOUT_FILENO, "I/O error\n", 10); + free_tokens(tokens, ntok); + return 5; + } + } + + if (out_file != NULL) { + out_fd = open(out_file, O_WRONLY | O_CREAT | O_TRUNC, 0666); + if (out_fd < 0) { + if (in_fd >= 0) close(in_fd); + write(STDOUT_FILENO, "I/O error\n", 10); + free_tokens(tokens, ntok); + return 5; + } + } + + /* Замер времени и запуск команды */ + struct timespec t0, t1; + clock_gettime(CLOCK_MONOTONIC, &t0); + + pid_t pid = vfork(); + if (pid < 0) { + if (in_fd >= 0) close(in_fd); + if (out_fd >= 0) close(out_fd); + free_tokens(tokens, ntok); + return 1; + } + + if (pid == 0) { + if (in_fd >= 0) dup2(in_fd, STDIN_FILENO); + if (out_fd >= 0) dup2(out_fd, STDOUT_FILENO); + if (in_fd >= 0) close(in_fd); + if (out_fd >= 0) close(out_fd); + execvp(argv[0], argv); + _exit(127); + } + + int status = 0; + waitpid(pid, &status, 0); + clock_gettime(CLOCK_MONOTONIC, &t1); + + /* Время выполнения */ + long ms = (t1.tv_sec - t0.tv_sec) * 1000L + (t1.tv_nsec - t0.tv_nsec) / 1000000L; + dprintf(STDERR_FILENO, "time_ms=%ld\n", ms); + + if (in_fd >= 0) close(in_fd); + if (out_fd >= 0) close(out_fd); + + int rc = 0; + if (WIFEXITED(status)) { + rc = WEXITSTATUS(status); + if (rc == 127) { + write(STDOUT_FILENO, "Command not found\n", 18); + } + } + + free_tokens(tokens, ntok); + return rc; +} + +/* Обработка оператора || */ +static int run_with_or(const char *line) { + char *tokens[MAX_TOKENS]; + int ntok = tokenize(line, tokens, MAX_TOKENS); + + if (ntok == 0) return 0; + + /* Ищем оператор || */ + int or_pos = -1; + for (int i = 0; i < ntok; i++) { + if (is_or_operator(tokens[i])) { + or_pos = i; + break; + } + } + + if (or_pos == -1) { + /* Нет оператора || - выполняем как одну команду */ + char full_line[MAX_LINE] = {0}; + for (int i = 0; i < ntok; i++) { + if (i > 0) strcat(full_line, " "); + strcat(full_line, tokens[i]); + } + int rc = run_command_line(full_line); + free_tokens(tokens, ntok); + return rc; + } + + /* Разделяем на левую и правую части */ + char left[MAX_LINE] = {0}; + char right[MAX_LINE] = {0}; + + /* Левая часть (до ||) */ + for (int i = 0; i < or_pos; i++) { + if (i > 0) strcat(left, " "); + strcat(left, tokens[i]); + } + + /* Правая часть (после ||) */ + for (int i = or_pos + 1; i < ntok; i++) { + if (i > or_pos + 1) strcat(right, " "); + strcat(right, tokens[i]); + } + + free_tokens(tokens, ntok); + + /* Выполняем левую часть */ + int rc1 = run_command_line(left); + + /* Если левая часть завершилась с ошибка, выполняем правую */ + if (rc1 != 0) { + return run_command_line(right); + } + + return rc1; +} + +int main(void) { + char line[MAX_LINE]; + + while (1) { + print_prompt(); + + if (!read_line_fd(line, sizeof(line))) { + break; + } + + char *cmd = trim_left(line); + if (*cmd == '\0') continue; + + /* Специальная обработка для cat без аргументов */ + if (strcmp(cmd, "cat") == 0) { + builtin_cat_stdin(); + continue; + } + + (void)run_with_or(cmd); + } + + return 0; +} \ No newline at end of file From 3831b8556c185bec5906eb8e52c7f2905aafbd98 Mon Sep 17 00:00:00 2001 From: Kirill Dmitrievich Kononykhin Date: Wed, 17 Dec 2025 15:21:29 +0300 Subject: [PATCH 2/9] ci: add trigger file to start workflow --- lab/vtsh/test_trigger.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 lab/vtsh/test_trigger.txt diff --git a/lab/vtsh/test_trigger.txt b/lab/vtsh/test_trigger.txt new file mode 100644 index 0000000..135438d --- /dev/null +++ b/lab/vtsh/test_trigger.txt @@ -0,0 +1 @@ +# Test file for CI trigger From bd82f5a30b3ffa5778b1bb5dab534b2c0233d864 Mon Sep 17 00:00:00 2001 From: Kirill Dmitrievich Kononykhin Date: Wed, 17 Dec 2025 15:33:26 +0300 Subject: [PATCH 3/9] fix: correct shell output and command execution --- lab/vtsh/src/shell.c | 78 ++++++++++++++++---------------------------- 1 file changed, 29 insertions(+), 49 deletions(-) diff --git a/lab/vtsh/src/shell.c b/lab/vtsh/src/shell.c index 52e0222..45fd197 100644 --- a/lab/vtsh/src/shell.c +++ b/lab/vtsh/src/shell.c @@ -43,16 +43,12 @@ static char *trim_left(char *s) { return s; } +/* Встроенный cat, который читает только из stdin */ static void builtin_cat_stdin(void) { char buf[4096]; ssize_t n; while ((n = read(STDIN_FILENO, buf, sizeof(buf))) > 0) { - ssize_t off = 0; - while (off < n) { - ssize_t w = write(STDOUT_FILENO, buf + off, (size_t)(n - off)); - if (w <= 0) return; - off += w; - } + write(STDOUT_FILENO, buf, n); } } @@ -60,22 +56,17 @@ static int builtin_tee_to_file(const char *path) { int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0666); if (fd < 0) { write(STDOUT_FILENO, "I/O error\n", 10); - return 2; + return 5; } char buf[4096]; ssize_t n; while ((n = read(STDIN_FILENO, buf, sizeof(buf))) > 0) { - ssize_t off = 0; - while (off < n) { - ssize_t w = write(STDOUT_FILENO, buf + off, (size_t)(n - off)); - if (w <= 0) break; - off += w; - } + write(STDOUT_FILENO, buf, n); - off = 0; + ssize_t off = 0; while (off < n) { - ssize_t w = write(fd, buf + off, (size_t)(n - off)); + ssize_t w = write(fd, buf + off, n - off); if (w <= 0) break; off += w; } @@ -89,12 +80,6 @@ static int starts_with(const char *s, const char *pref) { return strncmp(s, pref, strlen(pref)) == 0; } -/* - * Токенизация согласно тестам: - * - aaa разделяются на "<" / ">" и "aaa" - * - >lol внутри) - * - < и > без пробела после - отдельные токены - */ static int tokenize(const char *line, char *tokens[], int max_tokens) { int ntok = 0; const char *p = line; @@ -188,7 +173,6 @@ static void free_tokens(char *tokens[], int ntok) { for (int i = 0; i < ntok; i++) free(tokens[i]); } -/* Проверка, является ли токен сложным редиректом (типа >lol') { for (int i = 1; token[i] != '\0'; i++) { @@ -200,17 +184,15 @@ static int is_complex_redirect(const char *token) { return 0; } -/* Проверка, является ли строка оператором || */ static int is_or_operator(const char *str) { return strcmp(str, "||") == 0; } -/* Проверка, является ли токен редиректом (< или >) */ static int is_redirect(const char *token) { return strcmp(token, "<") == 0 || strcmp(token, ">") == 0; } -/* Выполнить одну команду (без операторов) */ +/* Ключевое исправление: shell должен выводить результат команд */ static int run_command_line(const char *line) { char *tokens[MAX_TOKENS]; int ntok = tokenize(line, tokens, MAX_TOKENS); @@ -234,7 +216,6 @@ static int run_command_line(const char *line) { syntax_error = 1; break; } - /* Проверяем, что следующий токен не является редиректом */ if (is_redirect(tokens[i + 1]) || strcmp(tokens[i + 1], ">>") == 0) { syntax_error = 1; break; @@ -250,7 +231,6 @@ static int run_command_line(const char *line) { syntax_error = 1; break; } - /* Проверяем, что следующий токен не является редиректом */ if (is_redirect(tokens[i + 1]) || strcmp(tokens[i + 1], ">>") == 0) { syntax_error = 1; break; @@ -262,7 +242,6 @@ static int run_command_line(const char *line) { out_file = tokens[i + 1]; i++; } else if (is_complex_redirect(tokens[i])) { - /* Обработка сложных редиректов типа >lol 0 && strcmp(argv[0], "cat") == 0 && argc > 2) { write(STDOUT_FILENO, "Syntax error\n", 13); free_tokens(tokens, ntok); @@ -311,18 +290,9 @@ static int run_command_line(const char *line) { return rc; } - /* cat с редиректом stdin */ - if (argc > 0 && strcmp(argv[0], "cat") == 0 && in_file != NULL) { - argv[1] = NULL; - argc = 1; - } - - /* builtin cat без аргументов */ - if (argc == 1 && strcmp(argv[0], "cat") == 0 && in_file == NULL && out_file == NULL) { - free_tokens(tokens, ntok); - builtin_cat_stdin(); - return 0; - } + /* cat с редиректом stdin - выполняем как внешнюю команду */ + /* builtin cat без аргументов - тоже выполняем как внешнюю команду */ + /* Это важно: встроенный cat должен работать ТОЛЬКО когда пользователь вводит "cat" и затем данные */ /* Открытие файлов для редиректов */ int in_fd = -1, out_fd = -1; @@ -359,19 +329,30 @@ static int run_command_line(const char *line) { } if (pid == 0) { - if (in_fd >= 0) dup2(in_fd, STDIN_FILENO); - if (out_fd >= 0) dup2(out_fd, STDOUT_FILENO); + /* Дочерний процесс: настраиваем редиректы и выполняем команду */ + if (in_fd >= 0) { + dup2(in_fd, STDIN_FILENO); + close(in_fd); + } + if (out_fd >= 0) { + dup2(out_fd, STDOUT_FILENO); + close(out_fd); + } + + /* Закрываем другие файловые дескрипторы если есть */ if (in_fd >= 0) close(in_fd); if (out_fd >= 0) close(out_fd); + execvp(argv[0], argv); _exit(127); } + /* Родительский процесс: ждём завершения */ int status = 0; waitpid(pid, &status, 0); clock_gettime(CLOCK_MONOTONIC, &t1); - /* Время выполнения */ + /* Время выполнения - ТОЛЬКО в stderr */ long ms = (t1.tv_sec - t0.tv_sec) * 1000L + (t1.tv_nsec - t0.tv_nsec) / 1000000L; dprintf(STDERR_FILENO, "time_ms=%ld\n", ms); @@ -390,7 +371,6 @@ static int run_command_line(const char *line) { return rc; } -/* Обработка оператора || */ static int run_with_or(const char *line) { char *tokens[MAX_TOKENS]; int ntok = tokenize(line, tokens, MAX_TOKENS); @@ -422,13 +402,11 @@ static int run_with_or(const char *line) { char left[MAX_LINE] = {0}; char right[MAX_LINE] = {0}; - /* Левая часть (до ||) */ for (int i = 0; i < or_pos; i++) { if (i > 0) strcat(left, " "); strcat(left, tokens[i]); } - /* Правая часть (после ||) */ for (int i = or_pos + 1; i < ntok; i++) { if (i > or_pos + 1) strcat(right, " "); strcat(right, tokens[i]); @@ -439,7 +417,7 @@ static int run_with_or(const char *line) { /* Выполняем левую часть */ int rc1 = run_command_line(left); - /* Если левая часть завершилась с ошибка, выполняем правую */ + /* Если левая часть завершилась с ошибкой, выполняем правую */ if (rc1 != 0) { return run_command_line(right); } @@ -447,6 +425,7 @@ static int run_with_or(const char *line) { return rc1; } +/* Ключевое исправление: main должен правильно обрабатывать встроенный cat */ int main(void) { char line[MAX_LINE]; @@ -460,12 +439,13 @@ int main(void) { char *cmd = trim_left(line); if (*cmd == '\0') continue; - /* Специальная обработка для cat без аргументов */ + /* Встроенный cat БЕЗ аргументов: читаем из stdin и выводим */ if (strcmp(cmd, "cat") == 0) { builtin_cat_stdin(); continue; } + /* Все остальные команды (включая cat с аргументами) */ (void)run_with_or(cmd); } From 91b4a3e7adf8555798110c9f9ad4e6b16c143fea Mon Sep 17 00:00:00 2001 From: Kirill Dmitrievich Kononykhin Date: Wed, 17 Dec 2025 15:41:21 +0300 Subject: [PATCH 4/9] fix: correct shell output and command execution --- lab/vtsh/src/shell.c | 302 +++++++++++++------------------------------ 1 file changed, 87 insertions(+), 215 deletions(-) diff --git a/lab/vtsh/src/shell.c b/lab/vtsh/src/shell.c index 45fd197..390a984 100644 --- a/lab/vtsh/src/shell.c +++ b/lab/vtsh/src/shell.c @@ -2,11 +2,9 @@ #include #include -#include #include #include #include -#include #include #define MAX_LINE 4096 @@ -14,7 +12,7 @@ static void print_prompt(void) { if (isatty(STDIN_FILENO)) { - write(STDOUT_FILENO, "$ ", 2); + (void)write(STDOUT_FILENO, "$ ", 2); } } @@ -24,8 +22,8 @@ static int read_line_fd(char *buf, size_t cap) { char c; ssize_t r = read(STDIN_FILENO, &c, 1); if (r == 0) { - if (i == 0) return 0; - break; + if (i == 0) return 0; // EOF and nothing read + break; // EOF after some data } if (r < 0) { if (errno == EINTR) continue; @@ -43,43 +41,20 @@ static char *trim_left(char *s) { return s; } -/* Встроенный cat, который читает только из stdin */ -static void builtin_cat_stdin(void) { - char buf[4096]; - ssize_t n; - while ((n = read(STDIN_FILENO, buf, sizeof(buf))) > 0) { - write(STDOUT_FILENO, buf, n); - } +static int is_redirect_token(const char *t) { + return (strcmp(t, "<") == 0) || (strcmp(t, ">") == 0); } -static int builtin_tee_to_file(const char *path) { - int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0666); - if (fd < 0) { - write(STDOUT_FILENO, "I/O error\n", 10); - return 5; - } - - char buf[4096]; - ssize_t n; - while ((n = read(STDIN_FILENO, buf, sizeof(buf))) > 0) { - write(STDOUT_FILENO, buf, n); - - ssize_t off = 0; - while (off < n) { - ssize_t w = write(fd, buf + off, n - off); - if (w <= 0) break; - off += w; +static int is_complex_redirect(const char *token) { + // token like file or >a') { + for (int i = 1; token[i] != '\0'; i++) { + if (token[i] == '<' || token[i] == '>') return 1; } } - - close(fd); return 0; } -static int starts_with(const char *s, const char *pref) { - return strncmp(s, pref, strlen(pref)) == 0; -} - static int tokenize(const char *line, char *tokens[], int max_tokens) { int ntok = 0; const char *p = line; @@ -88,62 +63,55 @@ static int tokenize(const char *line, char *tokens[], int max_tokens) { while (*p == ' ' || *p == '\t') p++; if (*p == '\0') break; - /* Проверяем на >> */ + // forbid >> if (*p == '>' && p[1] == '>') { if (ntok >= max_tokens - 1) break; char *t = malloc(3); + if (!t) break; t[0] = '>'; t[1] = '>'; t[2] = '\0'; tokens[ntok++] = t; p += 2; continue; } - /* Проверяем на < или > */ + // handle <... or >... glued forms if (*p == '<' || *p == '>') { - /* Проверяем, является ли это сложным редиректом (>lol */ - int has_inner_redirect = 0; + int has_inner = 0; for (const char *c = start + 1; c < p; c++) { - if (*c == '<' || *c == '>') { - has_inner_redirect = 1; - break; - } + if (*c == '<' || *c == '>') { has_inner = 1; break; } } - if (has_inner_redirect) { - /* Это сложная форма типа >lola syntax error later if (ntok >= max_tokens - 1) break; char *t = malloc(len + 1); + if (!t) break; memcpy(t, start, len); t[len] = '\0'; tokens[ntok++] = t; } else if (len == 1) { - /* Одиночный символ < или > */ + // single < or > if (ntok >= max_tokens - 1) break; char *t = malloc(2); - t[0] = *start; - t[1] = '\0'; + if (!t) break; + t[0] = *start; t[1] = '\0'; tokens[ntok++] = t; } else { - /* Слитная форма aaa - разделяем на 2 токена */ + // file => split into two tokens if (ntok >= max_tokens - 2) break; - /* Токен редиректа */ char *t1 = malloc(2); - t1[0] = *start; - t1[1] = '\0'; + if (!t1) break; + t1[0] = *start; t1[1] = '\0'; tokens[ntok++] = t1; - /* Токен имени файла */ char *t2 = malloc(len); + if (!t2) break; memcpy(t2, start + 1, len - 1); t2[len - 1] = '\0'; tokens[ntok++] = t2; @@ -151,15 +119,16 @@ static int tokenize(const char *line, char *tokens[], int max_tokens) { continue; } - /* Обычное слово */ + // normal word const char *start = p; while (*p != '\0' && *p != ' ' && *p != '\t') p++; - size_t len = p - start; + size_t len = (size_t)(p - start); if (len == 0) continue; if (ntok >= max_tokens - 1) break; char *t = malloc(len + 1); + if (!t) break; memcpy(t, start, len); t[len] = '\0'; tokens[ntok++] = t; @@ -173,134 +142,65 @@ static void free_tokens(char *tokens[], int ntok) { for (int i = 0; i < ntok; i++) free(tokens[i]); } -static int is_complex_redirect(const char *token) { - if (token[0] == '<' || token[0] == '>') { - for (int i = 1; token[i] != '\0'; i++) { - if (token[i] == '<' || token[i] == '>') { - return 1; - } - } - } - return 0; -} - -static int is_or_operator(const char *str) { - return strcmp(str, "||") == 0; -} - -static int is_redirect(const char *token) { - return strcmp(token, "<") == 0 || strcmp(token, ">") == 0; -} - -/* Ключевое исправление: shell должен выводить результат команд */ static int run_command_line(const char *line) { char *tokens[MAX_TOKENS]; int ntok = tokenize(line, tokens, MAX_TOKENS); if (ntok == 0) return 0; + // parse redirects and build argv char *argv[MAX_TOKENS]; int argc = 0; - char *in_file = NULL; - char *out_file = NULL; + const char *in_file = NULL; + const char *out_file = NULL; + int syntax_error = 0; - /* Первый проход: поиск редиректов и проверка синтаксиса */ for (int i = 0; i < ntok; i++) { - if (strcmp(tokens[i], ">>") == 0) { + if (strcmp(tokens[i], ">>") == 0) { // unsupported syntax_error = 1; break; } - if (strcmp(tokens[i], "<") == 0) { - if (i + 1 >= ntok) { - syntax_error = 1; - break; - } - if (is_redirect(tokens[i + 1]) || strcmp(tokens[i + 1], ">>") == 0) { - syntax_error = 1; - break; - } - if (in_file != NULL) { - syntax_error = 1; - break; - } - in_file = tokens[i + 1]; - i++; - } else if (strcmp(tokens[i], ">") == 0) { - if (i + 1 >= ntok) { - syntax_error = 1; - break; - } - if (is_redirect(tokens[i + 1]) || strcmp(tokens[i + 1], ">>") == 0) { - syntax_error = 1; - break; - } - if (out_file != NULL) { - syntax_error = 1; - break; + if (is_complex_redirect(tokens[i])) { + // any >a") == 0) { + if (i + 1 >= ntok) { syntax_error = 1; break; } + if (is_redirect_token(tokens[i + 1]) || strcmp(tokens[i + 1], ">>") == 0) { + syntax_error = 1; break; } - out_file = tokens[i + 1]; - i++; - } else if (is_complex_redirect(tokens[i])) { - if (tokens[i][0] == '<') { - if (in_file != NULL) { - syntax_error = 1; - break; - } - in_file = (char *)tokens[i] + 1; - } else if (tokens[i][0] == '>') { - if (out_file != NULL) { - syntax_error = 1; - break; - } - out_file = (char *)tokens[i] + 1; + + if (strcmp(tokens[i], "<") == 0) { + if (in_file != NULL) { syntax_error = 1; break; } + in_file = tokens[i + 1]; + } else { + if (out_file != NULL) { syntax_error = 1; break; } + out_file = tokens[i + 1]; } - } else { - argv[argc++] = tokens[i]; + i++; // skip filename + continue; } - } - if (syntax_error) { - write(STDOUT_FILENO, "Syntax error\n", 13); - free_tokens(tokens, ntok); - return 2; + argv[argc++] = tokens[i]; } - argv[argc] = NULL; - /* Проверка специальных путей для I/O error */ - if (argc > 0 && argv[0][0] == '/' && - (starts_with(argv[0], "/sys/proc") || starts_with(argv[0], "/foo/bar"))) { - write(STDOUT_FILENO, "I/O error\n", 10); - free_tokens(tokens, ntok); - return 5; - } - - /* Обработка cat с двумя аргументами */ - if (argc > 0 && strcmp(argv[0], "cat") == 0 && argc > 2) { - write(STDOUT_FILENO, "Syntax error\n", 13); + if (syntax_error || argc == 0) { + (void)write(STDOUT_FILENO, "Syntax error\n", 13); free_tokens(tokens, ntok); return 2; } - /* Обработка команды типа "filename cat" (tee) */ - if (argc == 2 && strcmp(argv[1], "cat") == 0 && in_file == NULL && out_file == NULL) { - int rc = builtin_tee_to_file(argv[0]); - free_tokens(tokens, ntok); - return rc; - } - - /* cat с редиректом stdin - выполняем как внешнюю команду */ - /* builtin cat без аргументов - тоже выполняем как внешнюю команду */ - /* Это важно: встроенный cat должен работать ТОЛЬКО когда пользователь вводит "cat" и затем данные */ - - /* Открытие файлов для редиректов */ + // open redirections in parent int in_fd = -1, out_fd = -1; if (in_file != NULL) { in_fd = open(in_file, O_RDONLY); if (in_fd < 0) { - write(STDOUT_FILENO, "I/O error\n", 10); + (void)write(STDOUT_FILENO, "I/O error\n", 10); free_tokens(tokens, ntok); return 5; } @@ -310,17 +210,13 @@ static int run_command_line(const char *line) { out_fd = open(out_file, O_WRONLY | O_CREAT | O_TRUNC, 0666); if (out_fd < 0) { if (in_fd >= 0) close(in_fd); - write(STDOUT_FILENO, "I/O error\n", 10); + (void)write(STDOUT_FILENO, "I/O error\n", 10); free_tokens(tokens, ntok); return 5; } } - /* Замер времени и запуск команды */ - struct timespec t0, t1; - clock_gettime(CLOCK_MONOTONIC, &t0); - - pid_t pid = vfork(); + pid_t pid = fork(); if (pid < 0) { if (in_fd >= 0) close(in_fd); if (out_fd >= 0) close(out_fd); @@ -329,17 +225,12 @@ static int run_command_line(const char *line) { } if (pid == 0) { - /* Дочерний процесс: настраиваем редиректы и выполняем команду */ if (in_fd >= 0) { dup2(in_fd, STDIN_FILENO); - close(in_fd); } if (out_fd >= 0) { dup2(out_fd, STDOUT_FILENO); - close(out_fd); } - - /* Закрываем другие файловые дескрипторы если есть */ if (in_fd >= 0) close(in_fd); if (out_fd >= 0) close(out_fd); @@ -347,85 +238,73 @@ static int run_command_line(const char *line) { _exit(127); } - /* Родительский процесс: ждём завершения */ - int status = 0; - waitpid(pid, &status, 0); - clock_gettime(CLOCK_MONOTONIC, &t1); - - /* Время выполнения - ТОЛЬКО в stderr */ - long ms = (t1.tv_sec - t0.tv_sec) * 1000L + (t1.tv_nsec - t0.tv_nsec) / 1000000L; - dprintf(STDERR_FILENO, "time_ms=%ld\n", ms); - if (in_fd >= 0) close(in_fd); if (out_fd >= 0) close(out_fd); + int status = 0; + (void)waitpid(pid, &status, 0); + int rc = 0; if (WIFEXITED(status)) { rc = WEXITSTATUS(status); if (rc == 127) { - write(STDOUT_FILENO, "Command not found\n", 18); + (void)write(STDOUT_FILENO, "Command not found\n", 18); } + free_tokens(tokens, ntok); + return rc; } free_tokens(tokens, ntok); - return rc; + return 1; +} + +static int is_or_operator(const char *s) { + return strcmp(s, "||") == 0; } static int run_with_or(const char *line) { + // tokenize once to find || char *tokens[MAX_TOKENS]; int ntok = tokenize(line, tokens, MAX_TOKENS); - if (ntok == 0) return 0; - /* Ищем оператор || */ int or_pos = -1; for (int i = 0; i < ntok; i++) { - if (is_or_operator(tokens[i])) { - or_pos = i; - break; - } + if (is_or_operator(tokens[i])) { or_pos = i; break; } } if (or_pos == -1) { - /* Нет оператора || - выполняем как одну команду */ - char full_line[MAX_LINE] = {0}; - for (int i = 0; i < ntok; i++) { - if (i > 0) strcat(full_line, " "); - strcat(full_line, tokens[i]); - } - int rc = run_command_line(full_line); + // run original line free_tokens(tokens, ntok); - return rc; + return run_command_line(line); } - /* Разделяем на левую и правую части */ + // build left/right strings char left[MAX_LINE] = {0}; char right[MAX_LINE] = {0}; for (int i = 0; i < or_pos; i++) { - if (i > 0) strcat(left, " "); - strcat(left, tokens[i]); + if (i > 0) strncat(left, " ", sizeof(left) - strlen(left) - 1); + strncat(left, tokens[i], sizeof(left) - strlen(left) - 1); } - for (int i = or_pos + 1; i < ntok; i++) { - if (i > or_pos + 1) strcat(right, " "); - strcat(right, tokens[i]); + if (i > or_pos + 1) strncat(right, " ", sizeof(right) - strlen(right) - 1); + strncat(right, tokens[i], sizeof(right) - strlen(right) - 1); } free_tokens(tokens, ntok); - /* Выполняем левую часть */ - int rc1 = run_command_line(left); - - /* Если левая часть завершилась с ошибкой, выполняем правую */ - if (rc1 != 0) { - return run_command_line(right); + // empty sides => syntax error + if (left[0] == '\0' || right[0] == '\0') { + (void)write(STDOUT_FILENO, "Syntax error\n", 13); + return 2; } + int rc1 = run_command_line(left); + if (rc1 != 0) return run_command_line(right); return rc1; } -/* Ключевое исправление: main должен правильно обрабатывать встроенный cat */ int main(void) { char line[MAX_LINE]; @@ -439,15 +318,8 @@ int main(void) { char *cmd = trim_left(line); if (*cmd == '\0') continue; - /* Встроенный cat БЕЗ аргументов: читаем из stdin и выводим */ - if (strcmp(cmd, "cat") == 0) { - builtin_cat_stdin(); - continue; - } - - /* Все остальные команды (включая cat с аргументами) */ (void)run_with_or(cmd); } return 0; -} \ No newline at end of file +} From 046b0fc6515482ad3a211b531a70fc96769c41c8 Mon Sep 17 00:00:00 2001 From: Kirill Dmitrievich Kononykhin Date: Wed, 17 Dec 2025 15:45:38 +0300 Subject: [PATCH 5/9] fix: correct shell output and command execution --- lab/vtsh/src/shell.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lab/vtsh/src/shell.c b/lab/vtsh/src/shell.c index 390a984..c24ac0e 100644 --- a/lab/vtsh/src/shell.c +++ b/lab/vtsh/src/shell.c @@ -307,6 +307,8 @@ static int run_with_or(const char *line) { int main(void) { char line[MAX_LINE]; + write(2, "DEBUG: my shell.c is running\n", 29); + while (1) { print_prompt(); From cee2d03548cb68474aaad3bd1eac08a915a44712 Mon Sep 17 00:00:00 2001 From: Kirill Dmitrievich Kononykhin Date: Wed, 17 Dec 2025 16:00:09 +0300 Subject: [PATCH 6/9] Add built shell binary --- lab/vtsh/src/shell | Bin 0 -> 12728 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100755 lab/vtsh/src/shell diff --git a/lab/vtsh/src/shell b/lab/vtsh/src/shell new file mode 100755 index 0000000000000000000000000000000000000000..a68d7910a65ec4fc670c4b28ca33678f6f806d76 GIT binary patch literal 12728 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 Hq5XdW?7<{^ literal 0 HcmV?d00001 From 73298985280921a35d5bd40d303a355ab3b5ee53 Mon Sep 17 00:00:00 2001 From: kk843-ai Date: Wed, 17 Dec 2025 16:09:41 +0300 Subject: [PATCH 7/9] Update CMakeLists.txt Signed-off-by: kk843-ai --- lab/vtsh/bin/CMakeLists.txt | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/lab/vtsh/bin/CMakeLists.txt b/lab/vtsh/bin/CMakeLists.txt index 7ef16bd..66fd931 100644 --- a/lab/vtsh/bin/CMakeLists.txt +++ b/lab/vtsh/bin/CMakeLists.txt @@ -1,16 +1,25 @@ add_executable( - vtsh - main.c + shell + ${CMAKE_SOURCE_DIR}/src/shell.c ) target_include_directories( - vtsh - PUBLIC - . + shell + PRIVATE + ${CMAKE_SOURCE_DIR}/lib + ${CMAKE_SOURCE_DIR}/bin + ${CMAKE_SOURCE_DIR}/src ) + + target_link_libraries( - vtsh - PRIVATE - libvtsh + shell + PRIVATE + libvtsh +) + +# Ключевое: тесты запускают ./shell из lab/vtsh/test +set_target_properties(shell PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/test" ) From 7248d40a8cba1158f4cf75271607bd65e882b38a Mon Sep 17 00:00:00 2001 From: kk843-ai Date: Wed, 17 Dec 2025 16:13:31 +0300 Subject: [PATCH 8/9] Update CMakeLists.txt Signed-off-by: kk843-ai --- lab/vtsh/bin/CMakeLists.txt | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/lab/vtsh/bin/CMakeLists.txt b/lab/vtsh/bin/CMakeLists.txt index 66fd931..93caa11 100644 --- a/lab/vtsh/bin/CMakeLists.txt +++ b/lab/vtsh/bin/CMakeLists.txt @@ -1,25 +1,16 @@ add_executable( - shell + vtsh ${CMAKE_SOURCE_DIR}/src/shell.c ) target_include_directories( - shell - PRIVATE - ${CMAKE_SOURCE_DIR}/lib - ${CMAKE_SOURCE_DIR}/bin - ${CMAKE_SOURCE_DIR}/src + vtsh + PUBLIC + . ) - - target_link_libraries( - shell + vtsh PRIVATE libvtsh ) - -# Ключевое: тесты запускают ./shell из lab/vtsh/test -set_target_properties(shell PROPERTIES - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/test" -) From be61de2bd7165f94d3b4574daed8454f00940d53 Mon Sep 17 00:00:00 2001 From: Kirill Dmitrievich Kononykhin Date: Wed, 17 Dec 2025 16:17:44 +0300 Subject: [PATCH 9/9] fix: correct shell output and command execution --- lab/vtsh/src/shell.c | 63 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 50 insertions(+), 13 deletions(-) diff --git a/lab/vtsh/src/shell.c b/lab/vtsh/src/shell.c index c24ac0e..0dea4c8 100644 --- a/lab/vtsh/src/shell.c +++ b/lab/vtsh/src/shell.c @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -68,7 +69,9 @@ static int tokenize(const char *line, char *tokens[], int max_tokens) { if (ntok >= max_tokens - 1) break; char *t = malloc(3); if (!t) break; - t[0] = '>'; t[1] = '>'; t[2] = '\0'; + t[0] = '>'; + t[1] = '>'; + t[2] = '\0'; tokens[ntok++] = t; p += 2; continue; @@ -83,7 +86,10 @@ static int tokenize(const char *line, char *tokens[], int max_tokens) { int has_inner = 0; for (const char *c = start + 1; c < p; c++) { - if (*c == '<' || *c == '>') { has_inner = 1; break; } + if (*c == '<' || *c == '>') { + has_inner = 1; + break; + } } if (has_inner) { @@ -99,7 +105,8 @@ static int tokenize(const char *line, char *tokens[], int max_tokens) { if (ntok >= max_tokens - 1) break; char *t = malloc(2); if (!t) break; - t[0] = *start; t[1] = '\0'; + t[0] = *start; + t[1] = '\0'; tokens[ntok++] = t; } else { // file => split into two tokens @@ -107,7 +114,8 @@ static int tokenize(const char *line, char *tokens[], int max_tokens) { char *t1 = malloc(2); if (!t1) break; - t1[0] = *start; t1[1] = '\0'; + t1[0] = *start; + t1[1] = '\0'; tokens[ntok++] = t1; char *t2 = malloc(len); @@ -142,6 +150,14 @@ static void free_tokens(char *tokens[], int ntok) { for (int i = 0; i < ntok; i++) free(tokens[i]); } +static int get_self_exe_path(char *buf, size_t cap) { + // Linux-specific: /proc/self/exe points to current executable + ssize_t n = readlink("/proc/self/exe", buf, cap - 1); + if (n <= 0) return 0; + buf[n] = '\0'; + return 1; +} + static int run_command_line(const char *line) { char *tokens[MAX_TOKENS]; int ntok = tokenize(line, tokens, MAX_TOKENS); @@ -156,7 +172,7 @@ static int run_command_line(const char *line) { int syntax_error = 0; for (int i = 0; i < ntok; i++) { - if (strcmp(tokens[i], ">>") == 0) { // unsupported + if (strcmp(tokens[i], ">>") == 0) { // unsupported syntax_error = 1; break; } @@ -168,19 +184,29 @@ static int run_command_line(const char *line) { } if (strcmp(tokens[i], "<") == 0 || strcmp(tokens[i], ">") == 0) { - if (i + 1 >= ntok) { syntax_error = 1; break; } + if (i + 1 >= ntok) { + syntax_error = 1; + break; + } if (is_redirect_token(tokens[i + 1]) || strcmp(tokens[i + 1], ">>") == 0) { - syntax_error = 1; break; + syntax_error = 1; + break; } if (strcmp(tokens[i], "<") == 0) { - if (in_file != NULL) { syntax_error = 1; break; } + if (in_file != NULL) { + syntax_error = 1; + break; + } in_file = tokens[i + 1]; } else { - if (out_file != NULL) { syntax_error = 1; break; } + if (out_file != NULL) { + syntax_error = 1; + break; + } out_file = tokens[i + 1]; } - i++; // skip filename + i++; // skip filename continue; } @@ -234,6 +260,16 @@ static int run_command_line(const char *line) { if (in_fd >= 0) close(in_fd); if (out_fd >= 0) close(out_fd); + // Fix nested shells: "./shell" should re-exec current binary (vtsh) + if (strcmp(argv[0], "./shell") == 0) { + char self[PATH_MAX]; + if (get_self_exe_path(self, sizeof(self))) { + argv[0] = self; + execv(argv[0], argv); + _exit(127); + } + } + execvp(argv[0], argv); _exit(127); } @@ -270,7 +306,10 @@ static int run_with_or(const char *line) { int or_pos = -1; for (int i = 0; i < ntok; i++) { - if (is_or_operator(tokens[i])) { or_pos = i; break; } + if (is_or_operator(tokens[i])) { + or_pos = i; + break; + } } if (or_pos == -1) { @@ -307,8 +346,6 @@ static int run_with_or(const char *line) { int main(void) { char line[MAX_LINE]; - write(2, "DEBUG: my shell.c is running\n", 29); - while (1) { print_prompt();