C popen() 改变字符串

C popen() changes the string

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

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

知道为什么会这样吗?

我的代码是 运行ning:

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;
}
length = strlen("/sbin/iwlist ") + strlen(device) + strlen(" scan");
command = (char*) calloc(length, sizeof(char));

正如所怀疑的那样,您的 command 缓冲区太小(length++ 会有所改进)。您缺少字符串终止符。因此,当在 popen 循环和命令 "leaks" 中分配更多内存时,字符串终止在某个时候被覆盖,因为 null char char 不在它应该在的位置了。

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

注意:您可以使用 getc() 代替 fgets() 来避免所有这些字符串处理。你所有的块大小都将变成一个,你不再需要 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;
}