cfilesystemssystem-calls

Directory entries not being sorted in custom ls implementation


I've been trying to implement the 'ls' command using sys calls in C, but I've come upon a slight problem. No matter what I try, I just can't seem to sort the directory entries.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <dirent.h>
#include <sys/types.h>
#define BUFF_MAX 1024


int entry_cmp(const void* a, const void* b){
    struct dirent* entry_a = (struct dirent*) a;
    struct dirent* entry_b = (struct dirent*) b;
    return strcasecmp(entry_a->d_name, entry_b->d_name);
}

void ls(const char* dirname){
    DIR* dir = opendir(dirname);

    struct dirent* entry;
    struct dirent* entries[BUFF_MAX];
    unsigned entry_count = 0;

    while((entry = readdir(dir)) != NULL){

        if(entry->d_name[0] == '.') 
            continue;

        entries[entry_count++] = entry;

    }

    qsort(entries, entry_count, sizeof(struct dirent*), &entry_cmp);
    
    for(unsigned i = 0; i < entry_count; i++){
        entry = entries[i];
        printf("%s ", entry->d_name);
    }

    printf("\n");
    closedir(dir);
}

I've tried to analyze what's going on under the hood using gdb, but it seems to give me gibberish for the directory entry names.

entry_cmp (a=0x7fffffffb160, b=0x7fffffffb168) at Ex-2/myls.c:89
89          struct dirent* entry_a = (struct dirent*) a;
(gdb) step
90          struct dirent* entry_b = (struct dirent*) b;
(gdb) step
91          return strcasecmp(entry_a->d_name, entry_b->d_name);
(gdb) p entry_a->d_name
$3 = "UUU\000\000\250\223UUUU\000\000\300\223UUUU\000\000ؓUUUU\000\000\360\223UUUU\000\000\b\224UUUU\000\000 \224UUUU\000\000\070\224UUUU\000\000P\224UUUU", '\000' <repeats 188 times>
(gdb) p entry_b->d_name
$4 = "UUU\000\000\300\223UUUU\000\000ؓUUUU\000\000\360\223UUUU\000\000\b\224UUUU\000\000 \224UUUU\000\000\070\224UUUU\000\000P\224UUUU", '\000' <repeats 196 times>

What is the reason for the sorting not occuring?


Solution

  • From the readdir manual page on the returned pointer:

    On success, readdir() returns a pointer to a dirent structure. (This structure may be statically allocated; do not attempt to free(3) it.)

    The important note here is the one about "statically allocated". That means the readdir function might have only one single dirent structure, and it continuously return a pointer to this same object. This could be easily checked in the debugger by printing the value of all pointers.

    That means you need to copy the structure, not the pointer.

    The solution is to change entries to be an array of structure object, not pointers, and then copy the returned structure:

    // ...
    
    struct dirent entries[BUFF_MAX];  // Array of objects, not pointers
    
    // ...
    
    entries[entry_count++] = *entry;  // Copy the object, not the pointer
    
    // ...