0

我有一个使用 FIFO 重现服务器的 C 程序。该程序从输入 FIFO 中读取两行——一个数字n和一个字符串str——并写入输出 FIFOn行,每行都是str. 我写了以下代码。

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

#define MAX_SIZE 256

char *readline(int fd, char *buffer) {
    char c;
    int i = 0;
    while (read(fd, &c, 1) != 0) {
        if (c == '\n')
            break;
        buffer[i++] = c;
    }
    return buffer;
}

int main(int  argc, char** argv) {
    mkfifo(argv[1], 0666);
    mkfifo(argv[2], 0666);
    int in = open(argv[1], O_RDONLY);
    int out = open(argv[2] ,O_WRONLY);

    char line[MAX_SIZE];
    memset(line, 0, MAX_SIZE);
    int n, i;
    while (1) {
        strcpy(line, readline(in, line));
        sscanf(line, "%d", &n);
        strcpy(line, readline(in, line));

        for (i = 0; i < n; i++) {
            write(out, line, strlen(line));
            write(out, "\n", 1);
        }
    }
    close(in);
    close(out);

    return 0;
}

该程序编译并运行没有错误,但它在每次执行时输出不同数量的字符串。例如,如果输入 FIFO 中的两条输入行是5\nhello,那么它会在每次运行时打印出 1 到 25 次出现的hello(频率似乎是完全随机的)。

我已经坚持了两天了。请给我一些帮助。

4

3 回答 3

2

我没有声称或保证我什至知道你的程序了什么,因为我已经有 20 年没有迫切需要在系统级别使用 FIFO 了。但有一点很清楚。两天的时间是很长的时间来处理一些事情,而无需在调试器中运行它,这样做会暴露出许多问题。

首先,readline()永远不要终止它传递的字符串。这在第一次时并不像第二次及以后那样重要,因为输入行中可能存在较短的数据。此外,read()可能会失败,并且这样做不会返回0,这是循环中唯一会中断的条件。该失败应该打破循环反映在返回结果中。因为您返回缓冲区指针,所以合理的失败结果可能是 NULL:

考虑这样的事情:

char *readline(int fd, char *buffer)
{
    ssize_t res = 0;
    char c = 0;
    int i = 0;
    for(;;)
    {
        res = read(fd, &c, 1);
        if (res < 0)
            return NULL;
        else if (res == 0 || c == '\n')
            break;
        buffer[i++] = c;

    };
    buffer[i] = 0;
    return buffer;
}

有人可能会争辩说,如果缓冲区为空,它应该返回 NULL,因为您不能将长度为 0 的数据包放在 FIFO 上。我把它留给你来决定,但可以肯定的是,这是你算法中的一个潜在漏洞。


接下来,如果提交的缓冲区重叠,则该strcpy()函数具有未定义的行为。由于readline()返回传入的缓冲区,并且由于所述相同缓冲区也是相同缓冲区的目标,strcpy()因此的程序正在执行 UB。从我所看到的一切来看,strcpy()首先在这个程序中是没有用的,甚至根本不应该在那里

这显然是错误的:

strcpy(line, readline(in, line));
sscanf(line, "%d", &n);
strcpy(line, readline(in, line));

for (i = 0; i < n; i++) {
    write(out, line, strlen(line));
    write(out, "\n", 1);
}

上面应该是这样的:

if (readline(in, line))
{
    if (sscanf(line, "%d", &n) == 1)
    {
        if (readline(in, line))
        {
            for (i = 0; i < n; i++) 
            {
                write(out, line, strlen(line));
                write(out, "\n", 1);
            }
        }
    }
}

readline()假设按照规定进行了更改。这些可以组合成一个单独的三个表达式 if 子句,但如上所述,它至少是可调试的。换句话说,通过短路评估,这样做应该没有问题:

if (readline(in, line) &&
    sscanf(line, "%d", &n) == 1 &&
    readline(in, line))
{
    for (i = 0; i < n; i++) 
    {
        write(out, line, strlen(line));
        write(out, "\n", 1);
    }
}

但我建议您保留前者,直到您彻底调试为止。

最后,请注意,这readline()仍然是等待发生的缓冲区溢出。您确实应该将 max-len 传递给该函数并限制这种潜在的可能性,或者动态管理缓冲区。

于 2013-11-10T14:09:05.363 回答
1

代码不会line为每次迭代初始化,因此如果readline()不读取任何内容,它会保持line's 的内容不变。

并且您不测试是否sscanf()失败,代码无法识别 alsn保持不变,并且 get 的最后一个值line被打印出来n,一切重新开始......


readline()错过检查是否read()失败。


要从这个练习中学习,它总是测试(系统)调用的结果,无论它是否失败。

于 2013-11-10T13:59:26.417 回答
-1
int readline(int fd, char *buf, int nbytes) {
   int numread = 0;
   int value; /* read fonksiyonu sonunda okunan sayı degerini gore islem yapar */
   /* Controls  */
   while (numread < nbytes - 1) {
      value = read(fd, buf + numread, 1);
      if ((value == -1) && (errno == EINTR))
         continue;
      if ( (value == 0) && (numread == 0) )
         return 0;
      if (value == 0)
         break;
      if (value == -1)
         return -1;
      numread++;

      /* realocating for expand the buffer ...*/
      if( numread == allocSize-2 ){
         allocSize*=2;   /* allocSize yeterli olmadıgı zaman buf ı genisletmemizi saglayarak
         memory leak i onler  */
         buf=realloc(buf,allocSize);

         if( buf == NULL ){
           fprintf(stderr,"Failed to reallocate!\n");
             return -1; 
         }
      }
      /* Eger gelen karakter \n ise return okudugu karakter sayısı */
      if (buf[numread-1] == '\n') {
          buf[numread] = '\0';
          return numread; 
      }  
   }
   errno = EINVAL;
   return -1;   
}
于 2014-04-08T07:44:32.130 回答