gpt4 book ai didi

c - 如何设计一个信号安全的 shell 解释器

转载 作者:行者123 更新时间:2023-12-04 10:52:16 30 4
gpt4 key购买 nike

我现在的代码向标准输出发送提示,然后从标准输入读取一行。在任何时候接收到 SIGINT 都会中断执行并退出程序。我不确定我应该在哪里捕获 SIGINT,而且我知道当我的当前代码接收到信号时我无法启动新提示。是否有适当的方法来实现这一点(最终目标是让它像大多数 shell 一样运行(SIGINT 取消当前提示并启动一个新提示))?

此代码将在 Linux 上运行,但平台独立性越低越好。

get_line 从 stdin 读取一行到缓冲区并生成一个 char[],它被分配给 line。
split_args 获取一行并将其转换为 char[] 数组,按空格拆分。
is_command_valid 确定用户是否输入了已知的内部命令。无法执行外部程序。

static int run_interactive(char *user)
{
int done = 0;

do
{
char *line, **args;
int (*fn)(char *, char **);

fprintf(stderr, "gitorium (%s)> ", user);
get_line(&line);

if (line[0] == '\0')
{
free(line);
break;
}

split_args(&args, line);

if (!strcmp(args[0], "quit") || !strcmp(args[0], "exit") ||
!strcmp(args[0], "logout") || !strcmp(args[0], "bye"))
done = 1;
else if (NULL == args[0] ||
(!strcmp("help", args[0]) && NULL == args[1]))
interactive_help();
else if ((fn = is_command_valid(args)) != NULL)
(*fn)(user, args);
else
error("The command does not exist.");

free(line);
free(args);
}
while (!done);

return 0;
}

这里有两个最重要的辅助函数

static int split_args(char ***args, char *str)
{
char **res = NULL, *p = strtok(str, " ");
int n_spaces = 0, i = 0;

while (p)
{
res = realloc(res, sizeof (char*) * ++n_spaces);

if (res == NULL)
return GITORIUM_MEM_ALLOC;

res[n_spaces-1] = p;
i++;
p = strtok(NULL, " ");
}

res = realloc(res, sizeof(char*) * (n_spaces+1));
if (res == NULL)
return GITORIUM_MEM_ALLOC;

res[n_spaces] = 0;
*args = res;

return i;
}

static int get_line(char **linep)
{
char *line = malloc(LINE_BUFFER_SIZE);
int len = LINE_BUFFER_SIZE, c;
*linep = line;

if(line == NULL)
return GITORIUM_MEM_ALLOC;

for(;;)
{
c = fgetc(stdin);
if(c == EOF || c == '\n')
break;

if(--len == 0)
{
char *linen = realloc(*linep, sizeof *linep + LINE_BUFFER_SIZE);
if(linen == NULL)
return GITORIUM_MEM_ALLOC;

len = LINE_BUFFER_SIZE;
line = linen + (line - *linep);
*linep = linen;
}

*line++ = c;
}

*line = 0;
return 0;
}

最佳答案

如果我没理解错的话,您想知道如何处理信号以及收到信号后该怎么做。

建立信号处理程序的方法是使用 sigaction() .你没有说明你使用的平台,所以我假设是 Linux,尽管 sigaction()由 POSIX 标准定义,应该可以在大多数其他平台上使用。

有多种方法可以做到这一点。一种方法是建立一个信号处理程序,它只需将全局变量设置为 1,表示信号已被捕获。然后在你的getline()功能你建立一个检查,看看是否SIGINT被抓到,如果被抓到,则返回 NULL并允许 run_interactive()再次运行。

以下是您将如何捕获信号:

#include <signal.h>

static int sigint_caught = 0;

static void sigint_handler(int sig) {
sigint_caught = 1;
}

struct sigaction sa;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0; // or SA_RESTART if you want to automatically restart system calls interrupted by the signal
sa.sa_handler = sigint_handler;

if (sigaction(SIGINT, &sa, NULL) == -1) {
printf("could not establish handler\n");
exit(-1); // or something
}

然后也许在 getline() ,在无限循环中,您将建立检查以查看是否 SIGINT已被抓到:

for (;;) {
if (sigint_caught) {
return NULL;
}

// ...

然后在你的 run_interactive()调用你可以检查返回值检查是否SIGINT被捕获了:

// ...

get_line(&line);

if (line == NULL && sigint_caught) {
sigint_caught = 0; // allow it to be caught again
free(line);
continue; // or something; basically go to the next iteration of this loop
} else if (line[0] == '\0') {
free(line);
break;
} else {
// rest of code

没有测试它所以我不能保证它会工作,因为你的问题很宽泛(必须查看更多你的代码等),但希望它能让你对什么有足够的想法你可以在你的情况下做你。这可能是一个非常幼稚的解决方案,但它可能会满足您的需求。对于更健壮的东西,也许可以查看 bash 或 zsh 等流行 shell 的源代码。

例如,可能发生的一件事是 fgetc()可能会阻塞,因为标准输入中没有新数据,这可能是在发送信号时。 fgetc()会被打断并且errno将是 EINTR ,因此您可以在 getline() 中为此添加一个检查:

c = fgetc(stdin);

// make sure to #include <errno.h>
if (errno == EINTR && sigint_caught)
return NULL;

这只有在您设置sa_flags 时才会发生至 SA_RESTART .如果你这样做,那么 fgetc应该自动重启并继续阻塞,直到收到新的输入,这可能是你想要的,也可能不是你想要的。

关于c - 如何设计一个信号安全的 shell 解释器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15890145/

30 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com