cstringpathstrtokgetenv

C re-initializing a variable after using strtok


I'm trying to write a function to search in the PATH directories. I used getenv("PATH") to get the path string, then used strtok() to split it.

The second time I call my function it does not reinitialize the path string. it searches only in the first part of the path.

I know that strtok() modifies the original string terminating each token with a \0 . but in my case I don't understand why calling the function again doesn't create a new string with the normal delimiters ":".

Here is my code:

bool str_in_path(char *target, char *result) {
  char *path = getenv("PATH");
  char *token;
  char tokens[10][100];
  int count = 0;
  
  token = strtok(path, ":");
  strcpy(tokens[0], token);
  count++;

  for (int i = 1; token && i < 10; i++) {
    token = strtok(NULL, delimiter);
    if (token) {
      strcpy(tokens[i], token);
      count++;
    }
  }

  for (int i = 0; i < count; i++) {
    struct dirent *entry = NULL
    DIR *dir = opendir(tokens[i]);
    if (dir == NULL) {
      fprintf(stderr, "can't open directory %s\n", tokens[i]);
      continue;
    }   
    while ((entry = readdir(dir)) != NULL) {
      if (!strcmp(target, entry->d_name)) {
        strcpy(result, tokens[i]);
        strcat(result, "/");
        strcat(result, entry->d_name);
        closedir(dir);
        return true;
      }   
    }
    closedir(dir);
  }
  return false;
}

The first time I call the function it behaves as I expect. the second time it searches only the first part of the path. although it calls char *path = getenv("PATH"); to initialize the path string. why is that and how can I solve it.


Solution

  • As @TedLyngmo and @robertklep pointed in their comments, the string returned by getenv() function shouldn't be changed by the application. So, the problem was solved by just copying the returned string into a new string and tokenizing the new one, leaving the original string untouched.

    Here is the new working code refactored as @xing suggested in his answer to keep parsing and searching logic separated.

    bool tokenize(char *str, char tokens[][100], int max_tokens, int *count, const char *delimiter) {
      char *token;                                                                   
      int num_tokens = 0;                                                            
      token = strtok(str, delimiter);                                                
      if (!token)                                                                    
        return false;                                                                
      strcpy(tokens[0], token);                                                      
      num_tokens++;                                                                  
      for (int i = 1; token && i < max_tokens; i++) {                                        
        token = strtok(NULL, delimiter);                                             
        if(token) {                                                                  
          strcpy(tokens[i], token);                                                  
          num_tokens++;                                                              
        }                                                                            
      }                                                                              
      *count = num_tokens;                                                           
      return true;                                                                   
    }          
    
    bool str_in_path(char *target, char *result) {
      const char *path = getenv("PATH");
      char path_copy[strlen(path) + 1];
      strcpy(path_copy, path);  // copying the path to avoid manipulating the original string returned by getenv()
      char dirs[20][100] = {};
      int num_dirs;
      tokenize(path_copy, dirs, 20, &num_dirs, ":"); // tokenizing the copied path instead of the original one.
    
      for (int i = 0; i < num_dirs; i++) {
        struct dirent *entry = NULL;
        DIR *dir = opendir(dirs[i]);
        if (dir == NULL) {
          fprintf(stderr, "can't open directory %s\n", dirs[i]);
          continue;
        }                                                                                                                                                              
        while ((entry = readdir(dir)) != NULL) {
          if (!strcmp(target, entry->d_name)) {
            strcpy(result, dirs[i]);
            strcat(result, "/");
            strcat(result, entry->d_name);
            closedir(dir);
            return true;
          }
        }
        closedir(dir);
      }
      return false;
    }