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:
and this is what we get from our own custom 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!.