Skip to content
Advertisement

Why are the values in my dynamic 2D char array being overwritten?

I’m trying to build a process logger in C on Linux, but having trouble getting it right. I’d like it to have 3 colums: USER, PID, COMMAND. I’m using the output of ps aux and trying to dynamically append it to an array. That is, for every line ps aux outputs, I want to add a row to my array.

This is my code. (To keep the output short, I only grep for sublime. But this could be anything.)

#define _BSD_SOURCE
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main()
{
    char** processes = NULL;
    char* substr = NULL;
    int n_spaces = 0;
    int columns = 1;

    char line[1024];
    FILE *p;
    p = popen("ps -eo user,pid,command --sort %cpu | grep sublime", "r");
    if(!p)
    {
        fprintf(stderr, "Error");
        exit(1);
    }

    while(fgets(line, sizeof(line) - 1, p))
    {
        puts(line);
        substr = strtok(line, " ");
        while(substr != NULL)
        {
            processes = realloc(processes, sizeof(char*) * ++n_spaces);

            if(processes == NULL)
                exit(-1);

            processes[n_spaces - 1] = substr;

            // first column user, second PID, third all the rest
            if(columns < 2)//if user and PID are already in array, don't split anymore
            {
                substr = strtok(NULL, " ");
                columns++;
            }
            else
            {
                substr = strtok(NULL, "");
            }
        }
        columns = 1;
    }
    pclose(p);

    for(int i = 0; i < (n_spaces); i++)
        printf("processes[%d] = %sn", i, processes[i]);

    free(processes);

    return 0;

}

The output of the for loop at the end looks like this.

processes[0] = user
processes[1] = 7194
processes[2] = /opt/sublime_text/plugin_host 27184

processes[3] = user
processes[4] = 7194
processes[5] = /opt/sublime_text/plugin_host 27184

processes[6] = user
processes[7] = 27194
processes[8] = /opt/sublime_text/plugin_host 27184

processes[9] = user
processes[10] = 27194
processes[11] = /opt/sublime_text/plugin_host 27184

But, from the puts(line) I get that the array should actually contain this:

user       5016 sh -c ps -eo user,pid,command --sort %cpu | grep sublime
user       5018 grep sublime
user      27184 /opt/sublime_text/sublime_text
user      27194 /opt/sublime_text/plugin_host 27184

So, apparently all the values are being overwritten and I can’t figure out why… (Also, I don’t get where the value 7194 in processes[0] = 7194 and processes[4] = 7194 comes from).

What am I doing wrong here? Is it somehow possible to make the output look like the output of puts(line)?

Any help would be appreciated!

Advertisement

Answer

The return value of strtok is a pointer into the string that you tokenise. (The token has been made null-terminated by overwriting the first separator after it with ''.)

When you read a new line with fgets, you overwrite the contents of this line and all tokens, not just the ones from the last parsing, point to the actual content of the line. (The pointers to the previous tokens remain valid, but the content at these locations changes.)

The are several ways to fix this.

  • You could make the tokens you save arrays of chars and strcpy the parsed contents.
  • You could duplicate the parsed tokens with (the non-standard) strdup, which allocates memory for the strings on the heap.
  • You could read an array of lines, so that the tokens are really unique.
User contributions licensed under: CC BY-SA
10 People found this is helpful
Advertisement