【问题标题】:C popen() changes the stringC popen() 改变字符串
【发布时间】:2017-05-03 11:38:00
【问题描述】:

我正在使用popen() 在我的 Ubuntu 机器(16.04 LTS)中运行 shell 命令,出于某种原因,popen 更改了命令字符串,然后运行更改后的命令字符串,导致错误,因为没有这样的命令(它将/021/001 添加到字符串的末尾)。

我在具有不同 Ubuntu 版本 (14.04 LTS) 的等效机器上运行相同的代码,popen 不会更改命令字符串并且可以完美运行。

知道为什么会这样吗?

我正在运行的代码:

char* runShellReadCmd(char* command) {
FILE *fp;
int outputCurrentSize = 0, outputArrSize = READ_INITIAL_SIZE;
char readChunk[READ_CHUNK], *tmpOutputStrArr = NULL, *data = NULL;
fp = popen(command, "r");
data = (char*)calloc(READ_INITIAL_SIZE, sizeof(char));
while (fgets(readChunk, READ_CHUNK, fp) != NULL)
{
    //  If string size equals to the array size, need to expand the array.
    if (outputCurrentSize >= outputArrSize) {
        outputArrSize *= 2;
        //  Re allocate array, save it in a temporary array.
        tmpOutputStrArr = realloc(data, outputArrSize * sizeof(char));
        //  If re-allocationg failed, stop and return NULL to indicate that a problem has occured.
        if (!tmpOutputStrArr) {
            free(tmpOutputStrArr);
            free(data);
            return NULL;
        }
        data = tmpOutputStrArr;
    }
    //  Concatenate the recently read 100 chars to the data arr.
    strcat(data, readChunk);
    //  Save current string length.
    outputCurrentSize += READ_CHUNK;
}
pclose(fp);
return data;
}

命令:

"/sbin/iwlist wlp2s0 scan"

funcs.c:

//  Runs a shell command using given command string and returns the output.
char* runShellReadCmd(char* command) {
FILE *fp;
int outputCurrentSize = 0, outputArrSize = READ_INITIAL_SIZE, elementsRead = 0;
char readChunk[READ_CHUNK], *tmpOutputStrArr = NULL, *data = NULL;
printf("%s", command);
fflush(stdout);
fp = popen(command, "r");
data = (char*)calloc(READ_INITIAL_SIZE, sizeof(char));
// while (fgets(readChunk, READ_CHUNK, fp) != NULL)
while (elementsRead = fread(readChunk, sizeof(char), READ_CHUNK, fp) != READ_CHUNK)
{
    //  If string size equals to the array size, need to expand the array.
    if (outputCurrentSize >= outputArrSize) {
        outputArrSize *= 2;
        //  Re allocate array, save it in a temporary array.
        tmpOutputStrArr = realloc(data, outputArrSize * sizeof(char));
        //  If re-allocationg failed, stop and return NULL to indicate that a problem has occured.
        if (!tmpOutputStrArr) {
            free(tmpOutputStrArr);
            free(data);
            return NULL;
        }
        data = tmpOutputStrArr;
    }
    //  Concatenate the recently read 100 chars to the data arr.
    strcat(data, readChunk);
    //  Save current string length.
    outputCurrentSize += READ_CHUNK;
}
pclose(fp);
return data;
}

//  Returns the number of times the given substring appears in the given string.
int getNumOfMatches(char* string, char* substring) {
int count = 0;
char* temp = string;
while ((temp = strstr(temp, substring))) {
    count++;
    temp++;
}
return count;
}

//  Returns the first wlan device name thats available in the system.
char* getWlanDeviceName() {
int size = 0, i;
char* device, *tmpPointer, *deviceNameStartPointer;
char* str = runShellReadCmd("/sbin/ifconfig");
deviceNameStartPointer = strstr(str, WIFI_DEVICE_PREFIX);
tmpPointer = deviceNameStartPointer;
while (*tmpPointer != ' ') {
    size++;
    tmpPointer++;
}
device = (char*)calloc(size, sizeof(char));
tmpPointer = deviceNameStartPointer;
for(i = 0; i < size; i++) {
    device[i] = *tmpPointer;
    tmpPointer++;
}
free(str);
return device;
}

test_funcs.c:

//  Tests whether the wifi device can find wifi networks (at least 1).
int testWifi() {
int testOk = 0, length;
char* command, *output, *device = getWlanDeviceName();
length = strlen("/sbin/iwlist ") + strlen(device) + strlen(" scan");
command = (char*) calloc(length, sizeof(char));
strncpy(command, "/sbin/iwlist ", strlen("/sbin/iwlist "));
//strcat(command, "/sbin/iwlist ");
strcat(command, device);
strcat(command, " scan");
output = runShellReadCmd(command);
printf("%s\n", output);
testOk = getNumOfMatches(output, WIFI_NETWORK_START_STR) > 0;
//printf("%d", getNumOfMatches(WIFI_NETWORK_START_STR, output));
free(device);
return testOk;
}

【问题讨论】:

  • outputCurrentSize += READ_CHUNK; 这个增量可能太大了。
  • aside:if (!tmpOutputStrArr) { free(tmpOutputStrArr);:你不需要那个free
  • READ_CHUNKREAD_INITIAL_SIZE 是什么?无论实际大小如何,您总是添加行的最大大小。也许你最好在这里使用fread 而不是fgets
  • 可能您的命令缓冲区过小,并被您用于输出的后续mallocrealloc 覆盖。我非常怀疑popen 有这样的错误。向我们展示调用您的例程的代码。
  • 当你拼凑command 时,你的length 需要一个额外的字节作为空终止符。可能值得考虑asprintf 而不是重复的strcats 加上手动分配。

标签: c linux ubuntu popen


【解决方案1】:
length = strlen("/sbin/iwlist ") + strlen(device) + strlen(" scan");
command = (char*) calloc(length, sizeof(char));

正如怀疑的那样,您的command 缓冲区太小(length++ 将是一个改进)。你错过了字符串终止。因此,当您在 popen 循环中分配更多内存并且您的命令“泄漏”时,字符串终止会在某些时候被覆盖,因为 null char char 不再应该在哪里了。

它可以在其他机器上运行的事实只是运气。内存分配不同。但是您的代码仍然不正确。这就是为什么它被称为未定义的行为

【讨论】:

  • 你的意思是“字符串终止”,而不是“行终止”,对吧?
【解决方案2】:

注意:您可以通过使用 getc() 而不是 fgets() 来避免所有这些字符串处理。你所有的 chunksize 都将合而为一,你将不再需要 strlen()

char* runShellReadCmd(char* command) {
FILE *fp;
size_t size, used;
char *data = NULL;
int ch;

fp = popen(command, "r");
if (!fp) return NULL;

for(size=used=0; ;) {
        if (used >= size) {
                size = size ? 2*size : 100;
                data = realloc(data, size); // TODO : check return
                }
        ch = getc(fp) ;
        if (ch == EOF) break;
        data[used++] = ch;
    }
data[used] = 0;

pclose(fp);

// maybe a final resize here:
// data = realloc(data, used+1);
return data;
}

【讨论】:

  • 不需要used+1 进行尺寸检查。三元应使用?,如size = size ? 2*size : 100
  • 是的,确实如此。对于最终的 '\0',总是 是必需的。注意:我稍微更新了订单。
  • 重新考虑(并在编辑后):你是对的。不需要+1,每个读取的字符都会在缓冲区中添加一个字符(如果您认为 EOF 是一个字符)
猜你喜欢
  • 2012-05-26
  • 1970-01-01
  • 1970-01-01
  • 2017-07-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-04-10
  • 1970-01-01
相关资源
最近更新 更多