cpointersrecursiondirent.h

C Recursive directory fetcher


I was trying to make a little script to get me started on handling pointers, and allocating and deallocating memory in C.

In this script I'm trying to recursively fetch folders and files using the package dirent.h.

This is the script:

// Packages
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <dirent.h>

// Constants
#define root "/"
#define currentDir "."
#define prevDir ".."
#define binDir "bin"
#define gitDir ".git"

// Structs
struct filesArray {
  int currentArrayDirectoryElements;
  int currentArrayFileElements;
  const char** arrayDirectoriesPtr;
  const char** arrayFilesPtr;
};

/*
  concat concatenates two referenced strings,
  returns concatenated string or exit
*/
char* concat(const char* s1, const char* s2)
{
    char* result = malloc(strlen(s1) + strlen(s2) + 1); // null term

    if (result == NULL) {
        fprintf(
          stderr,
          "Fatal: failed to concatenate strings: %s and %s",
          s1, s2
        );

        exit(0);
    }

    strcpy(result, s1);
    strcat(result, s2);
    return result;
}

/*
  readDirAddFile recursively reads the passed in directory and adds each file to
  the filesArray struct passed as a reference.
*/
void readDirAddFile(const char* dirPath, struct filesArray* filesArray)
{
  printf("************ Getting out ************\n");
  printf("Current path passed in: %s\n", dirPath);
  printf("Current path pointer passed in: %p\n", dirPath);
  printf("*************************************\n");
  // Fetch files and add their local dir path to array
  DIR* dir = opendir(dirPath);
  if (dir == NULL) {
    return;
  }

  // Read current directory
  struct dirent* entity = readdir(dir);

  // Create and allocate a string path to the current dir
  const char* pathToDir = concat(dirPath, (const char *) &root);

  printf("************ Starting looping ************\n");
  printf("Current directory: %s\n", pathToDir);
  printf("Current directory pointer: %p\n", pathToDir);
  printf("*************************************\n");
  // Iterate through all directory elements
  while (entity != NULL) {
    printf("\nType: %hhd, Name: %s\n", entity->d_type, entity->d_name);

     // File
    printf("File: %d\n", entity->d_type == DT_REG);
    if (entity->d_type == DT_REG) {
      // Add file path to array
      const char* filePath = concat(pathToDir, (const char *) &entity->d_name);

      // Alloctae Element
      printf("Filepath String: %s\n", filePath);
      printf("Array files ptr: %p, %d\n", filesArray->arrayFilesPtr, filesArray->currentArrayFileElements);
      printf("Element ptr: %p\n", filePath);

      filesArray->arrayFilesPtr[filesArray->currentArrayFileElements] = filePath;

      printf("Element allocated: %s\n", filesArray->arrayFilesPtr[filesArray->currentArrayFileElements]);


      // Bk
      filesArray->currentArrayFileElements += 1;
    }

    // Directory
    printf("Directory: %d, current dir: %d, prev dir: %d, Bin dir: %d\n", entity->d_type == DT_DIR, strcmp(entity->d_name, currentDir) != 0, strcmp(entity->d_name, prevDir) != 0, strcmp(entity->d_name, binDir) != 0);
    if (
      entity->d_type == DT_DIR
      && strcmp(entity->d_name, currentDir) != 0
      && strcmp(entity->d_name, prevDir)    != 0
      && strcmp(entity->d_name, binDir)     != 0
      && strcmp(entity->d_name, gitDir)     != 0
    ) {
      // Generate path
      const char* newDir = concat(pathToDir, (const char *) &entity->d_name);


      printf("************ Passing in ************\n");
      printf("Recursing directory Path: %s\n", newDir);
      printf("Recursing directory Pointer: %p\n", newDir);
      printf("*************************************\n");
      readDirAddFile(newDir, filesArray);

      // Recurse fetch
      filesArray->arrayDirectoriesPtr[filesArray->currentArrayDirectoryElements] = newDir;

      // Bk
      filesArray->currentArrayDirectoryElements += 1;
    }

    entity = readdir(dir);
  };

  // Close read and path to dir
  printf("\nClosing directory at path: %s\n\n", pathToDir);
  closedir(dir);
  free((void *) pathToDir);

  return;
}

/*
  fetchFiles runs through the whole directory and stores all files' path
  into an array of strings pointers.
  returns a pointer to the array, its max elements and current present elements,
  namely a filesArray.
*/
void fetchFiles(struct filesArray* filesArray)
{
  // Recursively read from dir root
  readDirAddFile((const char *) &currentDir, filesArray);

  // Return the filesArray struct
  return;
}


/*
  main execution
*/
int main()
{
  // Track execution
  clock_t begin = clock();
  // printf() displays the string inside quotation
  printf("Scanning for invalid URLs and Local Paths\n\n");

  // Init filesStuct
  struct filesArray filesArray = {
    .currentArrayDirectoryElements = 0,
    .currentArrayFileElements = 0,
    .arrayFilesPtr = malloc(sizeof(char*)),
    .arrayDirectoriesPtr = malloc(sizeof(char*)),
  };

  printf("Current directory: before fetchfiles\n\n");
  fetchFiles(&filesArray);

  printf("\nArray Pointer: %p \nCurrent Elements: %d \n",
   filesArray.arrayFilesPtr,
   filesArray.currentArrayFileElements
  );

  // Bk
  // Files
  for (int i = 0; i < filesArray.currentArrayFileElements; ++i) {
    printf("\n\n");
    printf("\nFreeing File Element: %s, At pointer: %p\n", filesArray.arrayFilesPtr[i], filesArray.arrayFilesPtr[i]);
    free((void *) filesArray.arrayFilesPtr[i]);
  };
  free((void *) filesArray.arrayFilesPtr);
  // Paths
  for (int i = 0; i < filesArray.currentArrayDirectoryElements; ++i) {
    printf("\n\n");
    printf("\nFreeing File Element: %s, At pointer: %p\n", filesArray.arrayDirectoriesPtr[i], filesArray.arrayDirectoriesPtr[i]);
    free((void *) filesArray.arrayDirectoriesPtr[i]);
  };
  free((void *) filesArray.arrayDirectoriesPtr);


  // Calculate execution Time
  clock_t end = clock();
  double time_spent = (double)(end - begin) / CLOCKS_PER_SEC;
  printf("\n\nScript took: %fs\n", time_spent);
  // End
  return 0;
}

You can also find it in a relpit here https://replit.com/@EliaRiva/FolderFetcher?v=1

The problem is that I deallocate a pointer that is pointing to an empty memory address. I can spot that the problem of the script is probably related to when it goes back from the deepest directory to continue the fetch, and it is concatenating a string with a pointer that is not pointing to the correct string anymore.

Still, I can find what is causing this. Thank you for your help and time.

Tried running the script logging out each passage; I may have found the problem but I can't resolve it.


Solution

  • As suggested from Jonathan Leffler, I'm posting a self-response for people looking for an implemented solution.

    The error occurs because the pointer pointing to an array of char pointers (char** PointingToAnArrayOfCharPointers) had an allocation of memory of just one pointer element, thus having undefined behaviour for any exceeding element. Note that the error wasn't happening shortly after allocating an extra element but after a few.

    The solution is simple, check wether you have enough allocated space for elements and if not, increase the space size by allocating new memory for the array and coping over old values. Don't forget to deallocate the old array (free) and update the new pointer.

    Implementation:

    /*
      increaseArraySize increases the size of a dynamic char pointer to pointers array
    */
    void increaseArraySize(
      const char** arrayPtr,
      int* currentMaxElementsPtr,
      int newMaxElements
    )
    {
      // Store
      int totalNewMaxElements = *currentMaxElementsPtr + newMaxElements;
    
      // Allocate space for enough elements
      const char** newSizeArray = malloc(*currentMaxElementsPtr * sizeof(char*));
    
      if (newSizeArray == NULL) {
        fprintf(
          stderr,
          "Fatal: failed to allocate new array space"
        );
    
        exit(0);
      }
    
      // Copy old array values into new array
      memcpy(newSizeArray, arrayPtr, (totalNewMaxElements - newMaxElements) * sizeof(char*));
    
      // Update struct array pointer, max elements and free old array
      *currentMaxElementsPtr = totalNewMaxElements;
      free(arrayPtr);
      arrayPtr = newSizeArray;
    }