close stdin/stdout 引发的BUG

本文通过一个生产系统上的程序异常案例,介绍了不当关闭标准输入输出(stdin/stdout)可能导致的问题。当这些流被关闭后,后续打开的文件可能会占用它们的文件描述符,导致原本预期的标准输出被重定向到其他文件中。
close stdin/stdout 引发的BUG

大概是去年初的时候的一件事,客户说有一个生产系统上的程序行为不正常,总是在日志里出现一些乱糟糟的信息,
仔细交流之后,客户说只在配置文件是某种状态时才会出现。看了一下,目标就锁定在那个调用的close函数上了
后来想了一想,猛然想起来了:“ open 一个文件时,它的文件描述符总是最小的可用值"
代码示例如下:


[of@lbaby workspace]$ cat tclose.c
#include <stdio.h>
#include <unistd.h>
int main(void)
{
        close(0);
        close(1);
        close(2);

        fopen("a", "w");
        fopen("b", "w");

        printf("hello world/n");
}
[of@lbaby workspace]$ cc -o tclose tclose.c
[of@lbaby workspace]$ ./tclose
[of@lbaby workspace]$ cat b
hello world
[of@lbaby workspace]$
你没看错,printf 没有正常输出,而是输出到文件b中去了
这是因为 close了0(stdin),1(stdout),2(stderr) 以后,
fopen 的 a ,b 的文件描述符分别为0,1,printf的最终调用其实是对文件描述符1的write调用
因此,写文件时就写到b 中去了。

明白了产生原因之后,就容易改动了:把程序中所有涉及到的调试输出(坏毛病啊,发布版里有调试输出,不是我写的代码)删除,算是暂时解决了这个问题。

教训:不要轻易关闭 stdin/stdout/stderr,如果非要关闭,将0, 1 , 2 dup2 到/dev/null之后再进行

云风的blog 也记录了相关的问题 有兴趣的可以看 这里

#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/wait.h> #include <sys/types.h> #include <signal.h> #define MAX_INPUT_SIZE 1024 #define MAX_ARGS 64 void execute_command(char **args); /* 执行命令 */ int main() { char input[MAX_INPUT_SIZE] = {0}; /* 存储用户输入缓冲区 */ char *args[MAX_ARGS] = {NULL}; /* 命令参数 */ char *token = NULL; /* 分割字符串 */ int arg_count = 0; /* 参数计数器 */ while (1) { printf("myshell > "); fflush(stdout); /* 获取输入, 处理 EOF */ if (fgets(input, sizeof(input), stdin) == NULL) { break; } /* 移除换行符 */ input[strcspn(input, "\n")] = '\0'; /* 处理 exit 命令 */ if (strcmp(input, "exit") == 0) { printf("Exiting shell...\n"); break; } /* 解析命令和参数 */ token = strtok(input, " "); while (token != NULL && arg_count < MAX_ARGS - 1) { args[arg_count++] = token; token = strtok(NULL, " "); } args[arg_count] = NULL; /* 执行命令 */ execute_command(args); } return 0; } /* 执行命令函数 */ void execute_command(char **args) { /* 检查是否为内置命令 */ if (strcmp(args[0], "stop") == 0) { printf("Error: 'stop' is not a command. It should appear in program output.\n"); return; } pid_t pid = 0; int pipefd[2] = {0}; if (pipe(pipefd) == -1) { perror("pipe"); exit(EXIT_FAILURE); } pid = fork(); if (pid == -1) { perror("fork"); exit(EXIT_FAILURE); } else if (pid == 0)/* 子进程 */ { /* 关闭读取端 */ close(pipefd[0]); /* 重定向输出到管道 */ dup2(pipefd[1], STDOUT_FILENO); close(pipefd[1]); /* 执行命令 */ execvp(args[0], args); /* 执行失败 */ fprintf(stderr, "myshell: %s: command not found\n", args[0]); exit(EXIT_FAILURE); } else /* 父进程 */ { close(pipefd[1]); /* 关闭写入端 */ char buffer[MAX_INPUT_SIZE] = {0}; /* 输出缓冲区 */ ssize_t bytes_read = 0; /* 读取字节数 */ ssize_t total_printed = 0; /* 已打印字节数 */ char *stop_pos = strstr(buffer, "stop"); /* 检测输出中是否包含 stop */ /* 读取子进程输出 */ while ((bytes_read = read(pipefd[0], buffer, sizeof(buffer)-1)) > 0) { /* 确保字符串终止 */ buffer[bytes_read] = '\0'; /* 检测输出中是否包含 stop */ if (stop_pos) { /* 1、打印 stop 之前的内容 */ size_t keep_len = stop_pos - buffer; if (keep_len > 0) { printf("%.*s", (int)keep_len, buffer); fflush(stdout); } /* 2、打印stop本身 */ printf("stop"); fflush(stdout); /* 3、终止子进程并打印消息 */ kill(pid, SIGTERM); printf("\nReceived 'stop' in output. Terminating child process.\n"); fflush(stdout); /* 跳过后续处理 */ continue; } else /* 未检测到 stop 时正常打印输出 */ { printf("%s", buffer); fflush(stdout); } } close(pipefd[0]); /* 关闭读取端 */ waitpid(pid, NULL, 0); /* 等待子进程结束 */ } } 这是我的主要功能程序 #include <stdio.h> int main() { for (int i = 0; i < 5; i++) { printf("count: %d\n", i); if (i == 3) { printf("stop"); } } printf("This line should not be printed.\n"); return 0; } 这是我的测试程序 现在运行了./main后,输入以下内容: myshell > ./te myshell: ./te: command not found myshell > ./test count: 0 count: 1 count: 2 count: 3 stop Received 'stop' in output. Terminating child process. myshell > cat test.c cat: cat: No such file or directory cat: cat: No such file or directory #include <stdio.h> int main() { for (int i = 0; i < 5; i++) { printf("count: %d\n", i); if (i == 3) { printf("stop Received 'stop' in output. Terminating child process. myshell > 为什么会输出两个cat再输出test.c中的内容
08-08
GNS3 是一款网络模拟软件,而 IOS(Internetwork Operating System)是思科网络设备运行的操作系统。GNS3 中使用 IOS 镜像进行网络模拟时可能遇到的一些常见 bug 情况如下: ### 启动与加载问题 - **镜像加载失败**:可能由于 IOS 镜像文件损坏、不兼容或者 GNS3 配置中指定的镜像路径错误,导致设备无法正常加载 IOS 镜像启动。例如,当使用了不被 GNS3 版本支持的较新或较旧的 IOS 版本,就容易出现此类问题。 - **启动时间过长或卡住**:在某些情况下,设备启动过程中可能会花费很长时间,甚至卡在某个启动阶段。这可能与模拟设备分配的内存不足有关,当分配的内存无法满足 IOS 正常启动的需求时,就会出现启动缓慢或停滞的现象。 ### 网络连接问题 - **接口无法正常工作**:模拟设备的接口可能显示为关闭状态或者无法正常协商链路速度和双工模式。这可能是由于 IOS 镜像本身的驱动问题,或者在 GNS3 中对接口的配置参数设置错误。 - **网络连通性故障**:即使设备接口状态正常,但网络中仍然存在连通性问题,比如无法 ping 通其他设备。这可能是由于 IOS 的路由配置错误、访问控制列表(ACL)限制或者虚拟网络拓扑中的链路故障等原因造成的。 ### 配置保存与恢复问题 - **配置保存失败**:在设备中进行配置更改后,尝试保存配置时可能会失败。这可能是由于 IOS 系统的文件系统出现问题,或者设备的闪存空间不足,无法保存新的配置文件。 - **配置恢复异常**:当尝试从备份文件恢复设备配置时,可能会出现配置不生效或者部分配置丢失的情况。这可能与备份文件的格式不兼容、IOS 版本差异或者恢复过程中的操作错误有关。 ### 性能与稳定性问题 - **高 CPU 使用率**:模拟设备在运行过程中可能会出现 CPU 使用率过高的情况,导致设备响应缓慢,甚至影响整个模拟网络的性能。这可能是由于 IOS 中的某些功能(如复杂的路由协议计算、频繁的数据包处理等)消耗了大量的 CPU 资源。 - **设备崩溃或重启**:在长时间运行或者进行某些特定操作时,模拟设备可能会突然崩溃或重启。这可能是由于 IOS 中存在的软件漏洞,在特定的条件下触发了系统的异常处理机制,导致设备重新启动。 以下是一个简单的 Python 脚本示例,用于模拟检查 GNS3 中设备接口状态的情况: ```python import paramiko # 设备信息 device_ip = '192.168.1.1' username = 'admin' password = 'password' # 创建 SSH 客户端 ssh = paramiko.SSHClient() ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) try: # 连接设备 ssh.connect(device_ip, username=username, password=password) # 执行命令查看接口状态 stdin, stdout, stderr = ssh.exec_command('show interfaces status') output = stdout.read().decode() print(output) except paramiko.AuthenticationException: print("认证失败,请检查用户名和密码。") except paramiko.SSHException as ssh_ex: print(f"SSH 连接出错: {ssh_ex}") finally: # 关闭 SSH 连接 ssh.close() ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值