Developing Linux Utility like 'ls' in C

Discussion in 'C' started by poornaMoksha, Dec 24, 2011.

  1. poornaMoksha

    poornaMoksha New Member

    Joined:
    Jan 29, 2011
    Messages:
    150
    Likes Received:
    33
    Trophy Points:
    0
    Occupation:
    Software developer
    Location:
    India
    Most of the people working on Linux must have used the basic command 'ls'. I use it many times a day. It is a very useful command when it comes to displaying the contents of a directory and their properties. For those few who have still not used 'ls' its high time now, go to its man page, study it and start using it. Anyways, coming back to the article, this article does not describe the usage of 'ls' command but rather here I try to develop my own 'ls' equivalent 'my_ls'. I'll use C language and will try to develop a full fledged 'ls' command.

    The article will be divided in several small articles which would collectively cover the whole effort. This article becomes the part-I of the series.

    Properties of 'ls' covered in this part



    In this article, we will just cover the basic property where in the contents of the directory are displayed when 'my_ls' is executed.
    For example:

    Code:
    $ my_ls 
     article_function_pointer.txt  Desktop    Downloads  Music       Pictures  Public     testdisk.log  test.py             Videos  worm 
     chmodOctal.txt                Documents  google     output.log  practice  Templates  testfile      Unsaved Document 1  vlc

    The code



    The following code is the first draft of the implementation :

    Code:
    #include<stdio.h> 
     #include<stdlib.h> 
     #include <sys/types.h> 
     #include <dirent.h> 
      
     int main(void) 
     { 
        char *curr_dir = NULL; 
        DIR *dp = NULL; 
        struct dirent *dptr = NULL; 
        unsigned int count = 0; 
      
        // Get the value of environment variable PWD 
        curr_dir = getenv("PWD"); 
        if(NULL == curr_dir) 
        { 
            printf("\n ERROR : Could not get the working directory\n"); 
            return -1; 
        } 
      
        // Open the current directory 
        dp = opendir((const char*)curr_dir); 
        if(NULL == dp) 
        { 
            printf("\n ERROR : Could not open the working directory\n"); 
            return -1; 
        } 
      
        printf("\n"); 
        // Go through and display all the names (files or folders) 
        // Contained in the directory. 
        for(count = 0; NULL != (dptr = readdir(dp)); count++) 
        { 
            printf("%s  ",dptr->d_name); 
        } 
        printf("\n %u", count); 
      
        return 0; 
     }
    • In the above code, we first fetch the current working directory through the environment variable PWD
    • Since now we get the working directory, we open it and iterate over its contents by reading the directory.
    • With each file/folder encountered, we display the name of it.

    The Output and the Loopholes



    The out put of the above code is something like :

    Code:
    $ ./my_ls  
      
     .config  .gnome2_private  .cache  .gstreamer-0.10  worm  Unsaved Document 1  testfile  testdisk.log  article_function_pointer.txt  .compiz  .macromedia  .xsession-errors.old  .icons  .pulse-cookie  Desktop  .gtk-bookmarks  .bash_logout  vlc  .lesshst  .linuxmint  test.py  .thumbnails  .gnome2  .sudo_as_admin_successful  chmodOctal.txt  output.log  .dmrc  .mozilla-thunderbird  .ICEauthority  .xchm  .recently-used.xbel  Pictures  .gksu.lock  Templates  .ssh  google  .mplayer  .kde  .xchat2  .gvfs  .xsession-errors  .viminfo  .themes  .teamviewer  .profile  .openoffice.org  my_ls  ..  Public  Documents  .kchmviewer  .adobe  Music  .vim  .esd_auth  .fontconfig  .java Downloads  .nautilus  .bash_history  .  .mozilla  .gimp-2.6  .gegl-0.0  .gconf  .thunderbird  .gconfd  .Skype  .pulse  .local  Videos  practice  .dbus
    While the output of the standard ls command is :

    Code:
    $ ls 
     article_function_pointer.txt  Desktop    Downloads  Music  output.log  practice  Templates     testfile  Unsaved Document 1  vlc 
     chmodOctal.txt                Documents  google     my_ls  Pictures    Public    testdisk.log  test.py   Videos              worm
    We see that there is a clear difference between the two outputs. The difference visible are :
    1. Our utility 'my_ls' shows many files which begin with '.' while ls does not.
    2. The output from 'my_ls' is not formatted correctly (For example, executables should be in green, Directories should be in blue etc).
    3. The output from 'my_ls' is not in alphabetical order and the display is not in an order.
    So we see that there are above mentioned 3 short comings.

    Lets correct them one by one(The first two will be covered in this part while the last one will be covered in part-II).


    Omitting the files from output that begin with '.'



    Here is the piece of code :

    Code:
    #include<stdio.h> 
     #include<stdlib.h> 
     #include <sys/types.h> 
     #include <dirent.h> 
      
     int main(void) 
     { 
        char *curr_dir = NULL; 
        DIR *dp = NULL; 
        struct dirent *dptr = NULL; 
        unsigned int count = 0; 
      
        curr_dir = getenv("PWD"); 
        if(NULL == curr_dir) 
        { 
            printf("\n ERROR : Could not get the working directory\n"); 
            return -1; 
        } 
      
        dp = opendir((const char*)curr_dir); 
        if(NULL == dp) 
        { 
            printf("\n ERROR : Could not open the working directory\n"); 
            return -1; 
        } 
      
        printf("\n"); 
        for(count = 0; NULL != (dptr = readdir(dp)); count++) 
        { 
            // Check if the name of the file/folder begins with '.' 
            // If yes, then do not display it. 
            if(dptr->d_name[0] != '.') 
                printf("%s  ",dptr->d_name); 
        } 
      
        return 0; 
     }
    • In the above code, we first fetch the current working directory through the environment variable PWD.
    • Since now we get the working directory, we open it and iterate over its contents by reading the directory.
    • If the name of the file/folder begins with a '.', then just ignore it as the standard 'ls' also does not display the file beginning with '.' if its executed without any flags.
    • With each file/folder (whose name does not begin with '.') encountered, we display the name of it on stdout.
    Now lets compare the output :

    Code:
    $ ./my_ls  
      
     worm  Unsaved Document 1  testfile  testdisk.log  article_function_pointer.txt  Desktop  vlc  test.py  chmodOctal.txt  output.log  Pictures  Templates  google  my_ls  Public  Documents  Music  Downloads  Videos  practice   
      
     $ ls 
     article_function_pointer.txt  Desktop    Downloads  Music  output.log  practice  Templates     testfile  Unsaved Document 1  vlc 
     chmodOctal.txt                Documents  google     my_ls  Pictures    Public    testdisk.log  test.py   Videos              worm
    We see that the output of 'my_ls' is closer to 'ls' as now there is no file that begins with '.'

    Getting the Color formatting correct



    Here is the code :

    Code:
    #include<stdio.h> 
     #include<stdlib.h> 
     #include <sys/types.h> 
     #include <dirent.h> 
     #include <sys/stat.h> 
     #include <unistd.h> 
     #include <fcntl.h> 
     #include <sys/ioctl.h> 
      
      
     // Define the color codes as macros 
     #define RESET_COLOR "\e[m" 
     #define MAKE_GREEN "\e[32m" 
     #define MAKE_BLUE "\e[36m" 
      
      
     int main(void) 
     { 
        char *curr_dir = NULL; 
        DIR *dp = NULL; 
        struct dirent *dptr = NULL; 
        unsigned int count = 0; 
      
        // Find the column width of terminal 
        // We will make use of this in part-II  
        // Of this article. 
        struct winsize w; 
        ioctl(STDOUT_FILENO, TIOCGWINSZ, &w); 
      
        curr_dir = getenv("PWD"); 
        if(NULL == curr_dir) 
        { 
            printf("\n ERROR : Could not get the working directory\n"); 
            return -1; 
        } 
         
        dp = opendir((const char*)curr_dir);    
        if(NULL == dp) 
        { 
            printf("\n ERROR : Could not open the working directory\n"); 
            return -1; 
        } 
       
        for(count = 0; NULL != (dptr = readdir(dp)); count++) 
        { 
            if(dptr->d_name[0] != '.') 
            { 
                // Check if the file is executable 
                if(!access(dptr->d_name,X_OK)) 
                { 
                    int fd = -1; 
                    struct stat st; 
      
                    fd = open(dptr->d_name, O_RDONLY, 0); 
                    if(-1 == fd) 
                    { 
                        printf("\n Opening file/Directory failed\n"); 
                        return -1; 
                    } 
                     
                    fstat(fd, &st); 
                    // Check if it actaully was a directory with execute 
                    // permissions on it. 
                    if(S_ISDIR(st.st_mode)) 
                    { 
                        // If it was a directory, print it in Blue 
                        printf(MAKE_BLUE"%s     "RESET_COLOR,dptr->d_name); 
                    } 
                    else 
                    {                                   
                        // If it was a normal executable 
                        // Print it in green 
                        printf(MAKE_GREEN"%s     "RESET_COLOR,dptr->d_name); 
                    } 
                    close(fd); 
                } 
                else 
                { 
                    // No executable flag ON 
                    // Print it in black(default) 
                    printf("%s     ",dptr->d_name); 
                } 
            } 
        } 
        printf("\n"); 
      
        return 0; 
     }
    • In the above code, we first fetch the current working directory through the environment variable PWD
    • Since now we get the working directory, we open it and iterate over its contents by reading the directory.
    • If the name of the file/folder begins with a '.', then just ignore it as the standard 'ls' also does not display the file beginning with '.' if its executed without any flags.
    • Then for every valid file/folder name we see that whether it has execute permissions on it.
    • If execute permissions are there, then for folders we print the name in blue while for files we print the name in green.
    • For all other regular files whose execute permissions are not set, the name is printed in black
    • All these names are printed on stdout.
    The output of the following code is :

    Code:
    $ ./my_ls  
     worm     Unsaved Document 1     testfile     testdisk.log     article_function_pointer.txt     Desktop     vlc     test.py     chmodOctal.txt     output.log     Pictures     Templates     google     my_ls     Public     Documents     Music     Downloads     my_ls.c     Videos     practice
    If you run the above piece on your machine, you will see that the color formatting is present in the output.

    Other formatting



    We will take up the following formatting in Part-II.
    • Displaying the output in alphabetical order.
    • Making the display formatting better.

    Conclusion



    To Conclude, In this article we studied how to develop a simple utility that works like the standard 'ls' utility. We divided the development in different parts and in this part we learn to display simply the contents of the directory and got their color formatting correct. Will be covering the rest in the coming parts.

    Stay tuned for more!!!
     

Share This Page

  1. This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
    By continuing to use this site, you are consenting to our use of cookies.
    Dismiss Notice