-2

I'm working on a c programming mimicking UNIX shell and I wonder if there's a way to find an executable file(command file) in a specific directory

alsrb17
  • 33
  • 4
  • 3
    If you are targeting a machine with POSIX extensions, [opendir](https://pubs.opengroup.org/onlinepubs/009695399/functions/opendir.html), _ect_ and [fstat](https://pubs.opengroup.org/onlinepubs/007908799/xsh/fstat.html)? – Neil Jun 03 '21 at 22:01

1 Answers1

3

By executable file, I am assuming you are looking for any file that is not a directory that has the execution flag set.
One way you could go about achieving your goal is to:

  1. Get a directory stream corresponding to the directory by using the opendir.
  2. Iterate through the directory stream using readdir .
  3. At each iteration, find the absolute path of the file, which can be computed by concatenating the directory path and file name (can be obtained by accessing the d_name of the dirent struct), pass this path to stat, and use bit mask specified in inode documentation to check for the execution permission.

Code:

#include <stdio.h>
#include <sys/types.h> //opendir, stat
#include <dirent.h> //opendir
#include <errno.h>
#include <string.h>
#include <sys/stat.h> //stat
#include <stdlib.h> //free, malloc

int main(int argc, char** argv) {
    if (argc < 2) {
        fprintf(stderr, "Usage: ./a.out path_to_a_directory \n");
        return 1;
    }
    char* path_to_directory = argv[1];
    int path_length = strlen(path_to_directory);
    int modified = 0;
    //Modify path so that a dash is at the end.
    if (path_to_directory[path_length - 1] != '/') {
        char* modified_path = (char *) malloc(sizeof(char) * (strlen(path_to_directory) + 2));
        //Copies the null character as well.
        strcpy(modified_path, path_to_directory);
        modified_path[path_length] = '/';
        modified_path[path_length + 1] = '\0';
        path_to_directory = modified_path;
        //Set flag to true so that the dynamically allocated memory is freed later.
        modified = 1;
    }

    //Get the directory stream corresponding to the directory. 
    DIR* in_directory_stream = opendir(path_to_directory);
    if (in_directory_stream == NULL) {
        fprintf(stderr, "Error: the specified directory cannot be found or opened. \n", errno);
        if (modified) free(path_to_directory);
        return 1;
    }
    dirent* entry = NULL; 
    printf("Files that are executable by at least one of the permission classes (owner, group, others) are: \n");
    while ((entry = readdir(in_directory_stream)) != NULL) {
        //All directories contain . and .., which corresponds to current and parent directory respectively,
        //in unix systems. Since we are looking for only executable files, we can ignore them. 
        if (!strcmp(".", entry->d_name)) {
            continue;
        }
        if (!strcmp("..", entry->d_name)) {
            continue;
        }
        //Get file information. 
        struct stat entry_info;
        /* Create the absolute path of the entry.
         * Without it, as mentioned by Shawn below, 
         * stat will look for a file with the entry's name in current working directory 
         * instead of the specified directory. 
         */
        char* entry_absolute_path  = (char *) malloc(sizeof(char) * (strlen(path_to_directory) 
                    + strlen(entry->d_name) + 1));
        strcat(entry_absolute_path, path_to_directory);
        strcat(entry_absolute_path, entry->d_name);       
       if (stat(entry_absolute_path, &entry_info) == -1) {
            fprintf(stderr, "Error in obtaining file information about %s\n", entry->d_name);
       } else {
           // Check if the file is not a directory and 
           // is executable by one of the permission classes (owner, group, others). 
           if (((entry_info.st_mode & S_IFMT) != S_IFDIR) && 
                   ((entry_info.st_mode & S_IXUSR) || (entry_info.st_mode & S_IXGRP) 
                   || (entry_info.st_mode & S_IXOTH))) {
               printf("%s\n", entry->d_name);
           }
       } 
           free(entry_absolute_path);
    }
    //Close directory stream.
    closedir(in_directory_stream);    
    if (modified) free(path_to_directory);
    return 0;


}

EDIT: Corrected the program to pass the absolute path of the file to stat. Prior to this edit, the stat was given only the file name resulting in the program searching only the current working directory. Error identified by Shawn

phuclv
  • 37,963
  • 15
  • 156
  • 475
David
  • 98
  • 5
  • Gotta take into account the filename in the `dirent` struct doesn't include directory. Need that too for `stat()`. – Shawn Jun 03 '21 at 23:10
  • What do you mean by the "file name in the `dirent` struct doesn't include directory "? My understanding is that if a directory contained a subdirectory, we would get a `dirent` struct corresponding to that subdirectory when we iterate through the directory stream. – David Jun 04 '21 at 00:50
  • 1
    If you `opendir()` "/foo/bar/", the file names don't have `/foo/bar/` in front, so unless you're scanning your current working directory, `stat()` will fail unless a file with the same name just happens to be present in the cwd - and it'll be looking at the wrong file in that case. – Shawn Jun 04 '21 at 01:25
  • See [this question and answers](https://stackoverflow.com/questions/5125919/stat-error-no-such-file-or-directory-when-file-name-is-returned-by-readdir) for more information. – Shawn Jun 04 '21 at 02:02
  • Thank you for identifying that error. I have corrected the program above. – David Jun 04 '21 at 03:02