先看示例

以下示例将标准输出重定向到一块内存区域,结束时,可以获取这块内容,方便单元测试。

void *m = fd_redirect_to_memory_begin(STDOUT_FILENO); // STDOUT_FILENO等于1
printf("123");
puts("456");
char *result = fd_redirect_to_memory_end(m);
// result 这里就等于"123456\n"
free(result);

也可以将标准输出重定向到文件,这样,程序启动时设置一下,就可以将所有屏幕日志导向文件。

FILE *fp = fopen(LOG_FILE_NAME, "w+");
void *m = fd_redirect_to_file_begin(STDOUT_FILENO, fp);
printf("123");
puts("456");
fd_redirect_to_file_end(m);
// 文件中的内容就等于"123456\n"
fclose(fp);

如何实现

标准输出的重定向主要使用dup和dup2函数:

int dup(int oldfd);
int dup2(int oldfd, int newfd);

dup函数将创建一个新的文件描述符,指向与oldfd相同的文件。

dup2函数会让newfd指向与oldfd相同的文件。

所以,可以先将STDOUT_FILENO通过dup函数复制一份,再使用dup2让STDOUT_FILENO指向新的位置。

核心代码如下:

// 1就是STDOUT_FILENO
// 开始重定向
int backupfd = dup(1); // backupfd会等于(比如)3,指向与stdout相同的位置
dup2(your_own_fd, 1);  // 将1号fd重定向到别的文件
// 结束重定向
dup2(backupfd, 1);     // 将1号fd恢复
close(backupfd)        // 别忘了关闭复制出来的fd

完整代码

fd_redirect_to_file

重定向到文件的代码比较简单,需要注意fileno(fp)不一定是有效的fd就可以了。

struct redirect_file_st
{
    int fno;
    int dup;
};

void *fd_redirect_to_file_begin(int __fileno, FILE *fp)
{
    struct redirect_file_st *__st = malloc(sizeof(struct redirect_file_st));
    __st->fno = __fileno;
    __st->dup = dup(__fileno);
    if (__st->dup < 0)
    {
        free(__st);
        return NULL;
    }
    if (dup2(fileno(fp), __fileno) < 0) // fileno(fp) may not a fd
    {
        close(__st->dup);
        free(__st);
        return NULL;
    }
    return __st;
}

void fd_redirect_to_file_end(void *p)
{
    if (p == NULL)
    {
        fprintf(stderr, "fd_redirect_to_file_end: input ptr cannot be NULL.");
        abort();
    }
    struct redirect_file_st *__st = p;
    dup2(__st->dup, __st->fno);
    close(__st->dup);
    free(__st);
}

fd_redirect_to_memory

重定向到内存的代码略有些不一样,是因为open_memstream打开的文件流,虽然也是FILE结构,但其实是没有fd这个东西的(因为没有真实的打开文件),所以需要借助pipe来读取。

struct redirect_memory_st
{
    int fno;
    int dup;
    int fd[2];
};

void *fd_redirect_to_memory_begin(int __fileno)
{
    struct redirect_memory_st *__st = malloc(sizeof(struct redirect_memory_st));
    __st->fno = __fileno;
    __st->dup = dup(__fileno);
    if (__st->dup < 0)
    {
        free(__st);
        return NULL;
    }
    if (pipe(__st->fd) < 0)
    {
        close(__st->dup);
        free(__st);
        return NULL;
    }
    dup2(__st->fd[1], __fileno);
    return __st;
}

char *fd_redirect_to_memory_end(void *p)
{
    if (p == NULL)
    {
        fprintf(stderr, "fd_redirect_to_memory_end: input ptr cannot be NULL.");
        abort();
    }
    struct redirect_memory_st *__st = p;
    char *result;
    size_t len;
    FILE *fp = open_memstream(&result, &len);

    char buf[128];
    ssize_t n;
    fcntl(__st->fd[0], F_SETFL, fcntl(__st->fd[0], F_GETFL) | O_NONBLOCK);
    while ((n = read(__st->fd[0], buf, 128)) > 0)
    {
        fwrite(buf, 1, n, fp);
    }

    close(__st->fd[0]);
    close(__st->fd[1]);
    dup2(__st->dup, __st->fno);
    close(__st->dup);

    fclose(fp);
    free(__st);
    return result;
}