views:

189

answers:

2

My function is being passed a struct containing, among other things, a NULL terminated array of pointers to words making up a command with arguments.

I'm performing a glob match on the list of arguments, to expand them into a full list of files, then I want to replace the passed argument array with the new expanded one.

The globbing is working fine, that is, g.gl_pathv is populated with the list of expected files. However, I am having trouble copying this array into the struct I was given.

#include <glob.h>

struct command {
  char **argv;
  // other fields...
}

void myFunction( struct command * cmd )
{
  char **p = cmd->argv;
  char* program = *p++; // save the program name (e.g 'ls', and increment to the first argument

  glob_t g;
  memset(&g, 0, sizeof(g));
  g.gl_offs = 1;
  int res = glob(*p++, GLOB_DOOFFS, NULL, &g);
  glob_handle_res(res);
  while (*p)
  {
      res = glob(*p, GLOB_DOOFFS | GLOB_APPEND, NULL, &g);
      glob_handle_res(res);
  }

  if( g.gl_pathc <= 0 )
  {
      globfree(&g);
  }

  cmd->argv = malloc((g.gl_pathc + g.gl_offs) * sizeof *cmd->argv);

  if (cmd->argv == NULL) { sys_fatal_error("pattern_expand: malloc failed\n");}
   // copy over the arguments
  size_t i = g.gl_offs;
  for (; i < g.gl_pathc + g.gl_offs; ++i)
      cmd->argv[i] = strdup(g.gl_pathv[i]);

  // insert the original program name
  cmd->argv[0] = strdup(program);
  ** cmd->argv[g.gl_pathc + g.gl_offs] = 0; **
  globfree(&g);
}

void 
command_free(struct esh_command * cmd)
{
    char ** p = cmd->argv;
    while (*p) {
        free(*p++); // Segfaults here, was it already freed?
    }
    free(cmd->argv);
    free(cmd);
}

Edit 1: Also, I realized I need to stick program back in there as cmd->argv[0]
Edit 2: Added call to calloc
Edit 3: Edit mem management with tips from Alok
Edit 4: More tips from alok
Edit 5: Almost working.. the app segfaults when freeing the command struct

Finally: Seems like I was missing the terminating NULL, so adding the line:

cmd->argv[g.gl_pathc + g.gl_offs] = 0;  

seemed to make it work.

A: 

Don't you need to multiple g.gl_pathc by sizeof(char *)?

bmargulies
hm, yes.. but i think part of the problem is cmd->argv is probably smaller than g.gl_pathv.after all if the initial argv is made up of two elements: "ls" and "*.h".. the *.h could be expanded into 100s of filenames
Casey
good point. I just spotted the low-hanging bug.
bmargulies
+1  A: 

argv is an array of pointers of char *. This means that argv has space for argc char * values. If you try to copy more than that many char * values into it, you will end up with an overflow.

Most likely your glob call results in more than argc elements in gl_pathv field (i.e, gl_pathc > argc). This is undefined behavior.

It is similar to the code below:

/* Wrong code */
#include <string.h>

int a[] = { 1, 2, 3 };
int b[] = { 1, 2, 3, 4 };
memcpy(a, b, sizeof b);

Solution: you should either work with the glob_t struct directly, or allocate new space to copy gl_pathv to a new char **:

char **paths = malloc(g.gl_pathc * sizeof *paths);
if (paths == NULL) { /* handle error */ }
for (size_t i=0; i < g.gl_pathc; ++i) {
    /* The following just copies the pointer */
    paths[i] = g.gl_pathv[i];

    /* If you actually want to copy the string, then
       you need to malloc again here.

       Something like:

       paths[i] = malloc(strlen(g.gl_pathv[i] + 1));

       followed by strcpy.
     */
}

/* free all the allocated data when done */

Edit: after your edit:

cmd->argv = calloc(g.gl_pathc, sizeof(char *) *g.gl_pathc);

it should work, but each of argv[1] to argv[g.gl_pathc + g.gl_offs - 1] is a char * that is "owned" by the struct glob. Your memcpy call is only copying the pointers. When you later do globfree(), those pointers don't mean anything anymore. So, you need to do copy the strings for your use:

size_t i;
cmd->argv = malloc((g.gl_pathc+g.gl_offs) * sizeof *cmd->argv);
for (i=g.gl_offs; i < g.gl_pathc + g.gl_offs; ++i)
    cmd->argv[i] = strdup(g.gl_pathv[i]);

This makes sure you now have your own private copies of the strings. Be sure to free them (and argv) once you are done.

There are a few other problems with your code.

  1. You are doing *p++, you should do p++, since you're not using the value of the dereferencing.
  2. You should really check the return value of glob.
  3. Your paths variable needs g.gl_pathc + 1 elements, not g.gl_pathc. (Or more correctly, you need to allocate g.gl_pathc + g.gl_offs times sizeof *paths bytes.)
  4. Your for loop to copy strings should be for (j=1; j < g.gl_pathc + g.gl_offs; ++j).
  5. Make sure you prevent shell from expanding your glob. I.e., call ./a.out '*' instead of ./a.out *.
Alok
the idea behind my function being passed the command struct is that my function is a handler of sorts that is performing some operation on the command string before it is executed. hence, i need to modify the value of argv in the cmd.
Casey
But where does the `argv` element of `cmd` come from? If it is an array, you can't just say `cmd->argv = ...`, which is illegal in C. It is like saying: `int a[] = {1, 2};` followed by `a = malloc(...);`.
Alok
Nevermind, my brain wasn't engaged. I "missed" the definition of `struct cmd`.
Alok
@Casey, see my edit.
Alok
@Alok, thanks.. almost there! See my latest edit.
Casey
@Casey: you are not posting your real code. The `continue;` in the `if` condition doesn't make sense. Also, try replacing: `glob_t g; memset(` by `glob_t g = { 0 };`. Finally, maybe `gl_offs` is only supposed to be written to, not read, so try replacing `gl_offs` by 1 in your code.
Alok