使stdin以安全和便携的方式可写

时间:2014-11-02 06:55:33

标签: c stdin portability

我试图运行两个程序。

Case 1

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main()
{
        int n;
        int k = 10;
        int ret_val = 0;
        ret_val = write (0, &k, sizeof(int));
        if (-1 == ret_val)
        {
                printf ("Failed to write");
                exit (EXIT_FAILURE);
        }
        scanf ("%d", &n);
        printf ("Integer read is %d \n", n);
        return 0;
}

然后我尝试了下一个。

Case 2

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main()
{
        int n;
        int k = 10;
        int ret_val = 0;

        /* Open file from which content shall be inserted to stdin_buffer */
        int source_fd = open ("file_in.txt", O_CREAT | O_RDWR, S_IRWXU);
        if (-1 == source_fd)
        {
                printf ("Failed to open file for reading");
                exit (EXIT_FAILURE);
        }
        int stdin_fd;

        /* Close STDIN_FILENO */
        close(0);
        /* dup the source */
        stdin_fd = dup (source_fd);
        if (-1 == stdin_fd)
        {
                printf ("Failed to dup");
                exit (EXIT_FAILURE);
        }

        /* write to stdin_buffer (content will be taken from file_in.txt) */
        ret_val = write (stdin_fd, &k, sizeof(int));
        if (-1 == ret_val)
        {
                printf ("Failed to write to stdin_buffer");
                exit (EXIT_FAILURE);
        }

        scanf ("%d", &n);
        printf ("Integer read is %d \n", n);

        close(source_fd);
        return 0;
}

现在在第一种情况下,我无法写入标准输入。在第二种情况下,我能够从文件“file_in.txt”获取输入,并将内容发送到标准输入缓冲区。

我无法得到一个很好的解释,为什么我的第一个案例没有成功。谁能解释一下?

stdin应该像任何其他文件一样吗?如果是写保护,那很好。但是当我重定向输入时(在第二种情况下),没有“权限被拒绝”的问题。这段代码似乎是不可移植的。是否有一种可移植且安全的方法来重定向文件中的stdin?


在完成评论后,我想出了一个更好的工作代码。我想对此代码提供一些反馈

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>

#define LEN 100

int main()
{
        int n;
        char buffer[LEN];
        memset (buffer, '\0', LEN);
        int ret_val = 0;

        /* Open file from which content shall be inserted to stdin_buffer */
        int source_fd = open ("file_in.txt", O_CREAT | O_RDONLY, S_IRWXU);
        if (-1 == source_fd)
        {
                perror ("Failed to open file for reading");
                exit (EXIT_FAILURE);
        }
        /* Temp stdin_buffer */
        int temp_fd = open ("temp_in.txt", O_CREAT | O_RDWR, S_IRWXU);
        if (-1 == temp_fd)
        {
                perror ("Failed to open temp stdin");
                exit (EXIT_FAILURE);
        }

        int stdin_fd;
        /* Close STDIN_FILENO */
        close(0);
        /* dup the source */
        stdin_fd = dup (temp_fd);
        if (-1 == stdin_fd)
        {
                perror ("Failed to dup");
                exit (EXIT_FAILURE);
        }


        ret_val = read (source_fd, buffer, LEN);
        if (-1 == ret_val)
        {
                perror ("Failed to read from source");
                exit (EXIT_FAILURE);
        }
        else
        {
                printf ("%s read from Source file\n", buffer);
        }

        /* write to stdin_buffer (content taken from file_in.txt) */
        ret_val = write (stdin_fd, buffer, LEN);
        if (-1 == ret_val)
        {
                perror ("Failed to write to stdin_buffer");
                exit (EXIT_FAILURE);
        }


        ret_val = lseek (stdin_fd, 0, SEEK_SET);
        if (-1 == ret_val)
        {
                perror ("Failed lseek");
                exit (EXIT_FAILURE);
        }

        ret_val = scanf ("%d", &n);
        if (-1 == ret_val)
        {
                perror ("Failed to read stdin_buffer");
                exit (EXIT_FAILURE);
        }
        printf ("Integer read is %d \n", n);

        close(source_fd);
        return 0;
}

2 个答案:

答案 0 :(得分:2)

更新前

在第一个程序中,3个空字节和一个换行符(可能)写入屏幕(不一定按顺序);程序然后尝试从键盘读取(假设命令行上没有I / O重定向)。写入标准输入不会加载输入缓冲区。您经常可以写入标准输入(并从标准输出和标准错误中读取),因为经典技术打开带有O_RDWR的文件描述符,然后将其连接到标准I / O通道。但是,无法保证您可以这样做。 (顺便说一句,第一个程序需要<unistd.h>。)

第二个程序有很多未定义的行为,很难分析。 open()调用需要三个参数,因为它包含O_CREAT;第三个参数是文件的模式(例如0644)。你没有检查open()是否成功。你没有检查写入是否成功;它不会,因为文件描述符被打开O_RDONLY(或者,source_fd被打开O_RDONLY,而dup()会将该模式复制到文件描述符0),这意味着write()将失败。不检查输入操作(您不确保scanf()成功)。 (第二个程序并不需要<sys/types.h><sys/stat.h>。)

基本上,由于您未检查任何关键功能调用,因此您不了解所发生的事情。

更新后1

请注意,错误消息应写入标准错误,并应以换行符终止。

我得到了第一个按照规定工作的程序(Mac OS X 10.10 Yosemite,GCC 4.8.1),虽然很难证明空字节已写入标准输入(但是在那里写了换行符)。然后我可以键入10(或20,或100,或......)加上返回,然后打印该整数。

第二个程序在scanf()上失败,因为当您尝试读取时,文件指针位于文件的末尾。你可以看到你的程序的这个变种:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>

int main(void)
{    
    /* Open file from which content shall be inserted to stdin_buffer */
    int source_fd = open ("file_in.txt", O_CREAT | O_RDWR, S_IRWXU);
    if (-1 == source_fd)
    {
        printf ("Failed to open file for reading\n");
        exit (EXIT_FAILURE);
    }

    close(0);
    int stdin_fd = dup (source_fd);
    if (-1 == stdin_fd)
    {
        printf("Failed to dup\n");
        exit(EXIT_FAILURE);
    }

    int k = 10;
    int ret_val = write(stdin_fd, &k, sizeof(int));
    if (-1 == ret_val)
    {
        printf("Failed to write to stdin_buffer\n");
        exit(EXIT_FAILURE);
    }

    int rc;
    int n;
    if ((rc = scanf("%d", &n)) != 1)
        printf("Failed to read from standard input: rc = %d\n", rc);
    else
        printf("Integer read is %d (0x%08x)\n", n, n);

    close(source_fd);
    return 0;
}

它产生:

Failed to read from standard input: rc = -1

如果在阅读前回放文件,则会返回0;写入文件的二进制数据不是整数的有效字符串表示形式。

更新后2

我编写了一个小函数err_exit(),因为它允许代码在页面上更小。我已经在几个地方修改了您的代码,以报告上一个函数的返回值。缺少输入不是错误。当你读取0字节时,这不是一个错误;这是EOF。当有数据要读取但是它不是整数值的文本格式时,不会发生转换,但这不是错误。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>

#define LEN 100

static void err_exit(const char *msg)
{
    perror(msg);
    exit(EXIT_FAILURE);
}

int main(void)
{
        int n = 99;
        char buffer[LEN];
        memset(buffer, '\0', LEN);
        int ret_val = 0;

        /* Open file from which content shall be inserted to stdin_buffer */
        int source_fd = open("file_in.txt", O_CREAT | O_RDONLY, S_IRWXU);
        if (-1 == source_fd)
                err_exit("Failed to open file for reading");
        /* Temp stdin_buffer */
        int temp_fd = open("temp_in.txt", O_CREAT | O_RDWR, S_IRWXU);
        if (-1 == temp_fd)
                err_exit("Failed to open temp stdin");

        /* Close STDIN_FILENO */
        close(0);
        /* dup the source */
        int stdin_fd = dup(temp_fd);
        if (-1 == stdin_fd)
                err_exit("Failed to dup");

        ret_val = read(source_fd, buffer, LEN);
        if (-1 == ret_val)
                err_exit("Failed to read from source");
        else
                printf("(%d bytes) <<%s>> read from Source file\n", ret_val, buffer);

        /* write to stdin_buffer (content taken from file_in.txt) */
        ret_val = write(stdin_fd, buffer, LEN);
        if (-1 == ret_val)
                err_exit("Failed to write to stdin_buffer");

        ret_val = lseek(stdin_fd, 0, SEEK_SET);
        if (-1 == ret_val)
                err_exit("Failed lseek");

        ret_val = scanf("%d", &n);
        if (-1 == ret_val)
                err_exit("Failed to read stdin_buffer");
        printf("Integer read is %d (ret_val = %d)\n", n, ret_val);

        close(source_fd);
        return 0;
}

输出:

(0 bytes) <<>> read from Source file
Integer read is 99 (ret_val = 0)

scanf()无法读取值时,它(通常)不会将任何内容写入相应的变量。这就是为什么99幸存下来的原因。如果您希望scanf()可以读取整数数据,则需要:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>

#define LEN 100

static void err_exit(const char *msg)
{
    perror(msg);
    exit(EXIT_FAILURE);
}

int main(void)
{
        int n = 99;
        char buffer[LEN] = "";
        int ret_val = 0;

        /* Open file from which content shall be inserted to stdin */
        int source_fd = open("file_in.txt", O_CREAT | O_RDONLY, S_IRWXU);
        if (-1 == source_fd)
                err_exit("Failed to open file for reading");
        /* Temp stdin */
        int temp_fd = open("temp_in.txt", O_CREAT | O_RDWR, S_IRWXU);
        if (-1 == temp_fd)
                err_exit("Failed to open temp stdin");

        /* Close STDIN_FILENO */
        close(0);
        /* dup the source */
        int stdin_fd = dup(temp_fd);
        if (-1 == stdin_fd)
                err_exit("Failed to dup");

        ret_val = read(source_fd, buffer, LEN);
        if (-1 == ret_val)
                err_exit("Failed to read from source");
        else
                printf("(%d bytes) <<%s>> read from Source file\n", ret_val, buffer);

        /* write to stdin (content taken from file_in.txt) */
        ret_val = write(stdin_fd, "10\n", sizeof("10\n")-1);
        if (-1 == ret_val)
                err_exit("Failed to write to stdin");

        ret_val = lseek(stdin_fd, 0, SEEK_SET);
        if (-1 == ret_val)
                err_exit("Failed lseek");

        ret_val = scanf("%d", &n);
        if (-1 == ret_val)
                err_exit("Failed to read stdin");
        printf("Integer read is %d (ret_val = %d)\n", n, ret_val);

        close(source_fd);
        return 0;
}

输出:

(0 bytes) <<>> read from Source file
Integer read is 10 (ret_val = 1)

答案 1 :(得分:0)

让我们看看你的三个节目在做什么......

计划1

第一个程序写入filedescriptor 0.默认情况下,这是stdout,因此宏STDOUT_FILENO具有该值。由于stdout是一个运行到进程中的单向流,因此写入失败。

计划2

在此程序中,您打开一个文件,该文件可能获得FD(filedescriptor)3(标准流的0-2之后)。然后,你用FD 0关闭stdout。然后,你dup() FD 3,因为第一个开放点位于索引0,那就是文件的新FD。由于scanf()只使用(未更改的)宏STDIN_FILENO,它将在那里获取该文件的内容。

计划3

在程序3中,您执行的操作与程序2完全相同,只是您打开文件两次。对上述情况的解释几乎遵循上述两点。

当你说'#34;更好的工作代码&#34;时,问题就是你的意思。关键是&#34;更好&#34;除非你指定你真正想要的东西,否则不可能说。根据您的主题和评论猜测,您希望远程控制第二个进程,就像在bash中使用管道符号一样。

为了做到这一点,你将不得不求助于特定于操作系统的方法,只需搜索&#34;输入输出重定向&#34;你应该能够找到一些信息。既然你已经提到了execl(),那么超出POSIX的可移植性(例如到win32)似乎不是一个问题,为此你应该找到很多示例代码,比我在这里写的要好得多。

无论如何,它归结为大致这些步骤:

  • pipe()创建一对FD
  • fork()创建新流程
  • child:dup2()将输入FD从pipe()重新分配给stdin
  • child:execl()新进程
  • parent:写入输出FD以生成子进程的输入

此外,您应关闭未使用的流或使用fcntl(FD_CLOEXEC)对其进行配置,以便自动关闭它们。重要的一点是,两个FD保持连接到同一个(!)通道,尽管两个进程都有结束。关闭每一端的一端会在两个进程之间留下单向通道。