Django Freeman
Django Freeman

Reputation: 81

C popen() changes the string

I'm using popen() to run a shell command in my Ubuntu machine (16.04 LTS) and for some reason, popen changes the command string and then it runs the changed command string, resulting in an error, because there's no such command (it adds /021/001 to the end of the string).

I run the same code on an equivalent machine with a different Ubuntu version (14.04 LTS) and popen doesn't change the command string and runs perfectly.

Any idea why this happens?

The code I'm running:

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;
}

The command:

"/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;
}

Upvotes: 0

Views: 954

Answers (2)

wildplasser
wildplasser

Reputation: 44240

Note: you can avoid all this string handling by using getc() in stead of fgets() . All your chunksizes will become one, and you wont need strlen() anymore:

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;
}

Upvotes: 0

Jean-Fran&#231;ois Fabre
Jean-Fran&#231;ois Fabre

Reputation: 140168

length = strlen("/sbin/iwlist ") + strlen(device) + strlen(" scan");
command = (char*) calloc(length, sizeof(char));

As suspected, your command buffer is too small (length++ would be an improvement). You're missing the string termination. So the string termination is overwritten at some point when allocating some more memory in your popen loop and your command "leaks" since the null char char isn't where it should be anymore.

the fact that it works on some other machine is just luck. The memory allocation is different. But your code is still incorrect. That's why it's called undefined behaviour.

Upvotes: 3

Related Questions