Learning in Public: Coding ‘ls’ in C (Part 2)

Muro
4 min readJun 14, 2024

--

Welcome back to my journey of learning C by building the ls command from scratch. In the previous post, we explored the basics of directory streams and took a peek inside using gdb. Now let’s move on to reading from the directory and printing its contents.

To recap, in Part 1 we wrote this piece of code to open a directory and we obtained a pointer to a DIR

//myls.c

#include <dirent.h>
#include <stdio.h>

int main(int argc, char **argv) {
// define a pointer to DIR
DIR *dir_stream;

// open the current directory (".")
dir_stream = opendir(".");

// opendir returns NULL on error, so we handle the error...
if (dir_stream == NULL) {
perror("Error while trying to open directory \n");
return 1;
}

return 0;
}

We used gdb to inspect the contents of dir_stream, which gave us a detailed view of the internal structure. Now, let's move forward and actually read from the directory.

To read the contents of a directory, we use the readdir function, which is part of the C standard library (included in dirent.h) . Let’s look at the documentation:

man readir

So, the readdir function takes a DIR pointer (which is our dir_stream in the code) and returns a pointer to a dirent struct. This struct represents the next entry in dir_stream. To read all entries, we just keep calling readdir in a loop until it returns NULL.

The documentation also tell us what the dirent struct looks like:

struct dirent{
ino_t d_ino;
off_t d_off;
unsigned short d_reclen;
unsigned char d_type;
char d_name[256];
};

Here’s the cool part: this struct gives us some handy info about each file in the directory:

  • d_name: The filename.
  • d_reclen: The size of the directory entry.

So, if we iterate through dir_stream, call readdir on each entry, and print out these three values, we’ll have a basic version of our very own ls function.

We also need to remember to close our directory when we are done. For that we will use the closedir function from the C standard library (also included in dirent.h ). Here is it’s man page:

So, to close the directory, we just need to pass a pointer to our dictionary stream dir_stream and it will return 0 if the directory was closed successfully and -1 if there is an error.

Let’s get to it!

// myls.c
//
//
#include <dirent.h>
#include <stdio.h>

int main(int argc, char **argv) {
// define a pointer to DIR
DIR *dir_stream;
// define a pointer to the dirent struct
struct dirent *dir_read;

// open the current directory (".")
dir_stream = opendir(".");

// opendir returns NULL on error, so we handle the error...
if (dir_stream == NULL) {
perror("Error while trying to open directory\n");
return 1;
}

// now let's read the contents:
while ((dir_read = readdir(dir_stream)) != NULL) {
printf("%s\t%d bytes\n", dir_read->d_name, dir_read->d_reclen);
}

// we also need to close the directory when we are done
if (closedir(dir_stream) == -1) {
perror("Can't close the dir \n");
return -1;
}

return 0;
}

We can now compile and see the results:

gcc -Wall -Werror -g myls.c -o myls

This is what we expect from the built-in ls function:

This is what we expect from the ls function

and this is what we get from our own custom myls function.

This is what we get with our myls function.

It’s kind of satisfying to see our code listing the directory contents just like the built-in ls command, right? The built-in ls function offers so much more functionality, but I think we’re in a great position to start adding more features ourselves. That will be part of future posts.

Thanks for following along in this journey. Happy coding and see you next time!.

--

--