To implement a search program in C program using system calls for files and directories

49 views Asked by At

I designed a C code to implement a search program in C program using system calls for files and directories and add additional features to the search program implemented.

/*
Name:
BlazerId:
Project #:
To compile: gcc -o search search.c
To run: ./search [-v] [-L size_limit] [-s pattern] [-d max_depth] [-t file_type] [-e command] [-E command] [directory]
*/

#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
#include <string.h>
#include <dirent.h>
#include <sys/stat.h>
#include <time.h>
#include <limits.h>
#include <unistd.h>
#include <sys/types.h>
#include <pwd.h>
#include <grp.h>
#include <errno.h>
#include <sys/wait.h>

/*
    Function: file_permissions
    Description: Prints the permissions of a file.
    Parameters:
        - mode: File mode
    Returns: None
*/
void file_permissions(mode_t mode) {
    char permissions[10];
    permissions[0] = (S_ISDIR(mode)) ? 'd' : '-';
    permissions[1] = (mode & S_IRUSR) ? 'r' : '-';
    permissions[2] = (mode & S_IWUSR) ? 'w' : '-';
    permissions[3] = (mode & S_IXUSR) ? 'x' : '-';
    permissions[4] = (mode & S_IRGRP) ? 'r' : '-';
    permissions[5] = (mode & S_IWGRP) ? 'w' : '-';
    permissions[6] = (mode & S_IXGRP) ? 'x' : '-';
    permissions[7] = (mode & S_IROTH) ? 'r' : '-';
    permissions[8] = (mode & S_IWOTH) ? 'w' : '-';
    permissions[9] = (mode & S_IXOTH) ? 'x' : '-';
    for (int i = 0; i < sizeof(permissions); i++) {
        putchar(permissions[i]);
    }
}

/*
    Function: last_modify
    Description: Prints the last modification time of a file.
    Parameters:
        - last_time: Last modification time
    Returns: None
*/
void last_modify(time_t last_time) {
    char start_time[30];
    struct tm *time_info = localtime(&last_time);
    strftime(start_time, sizeof(start_time), "%b %d %H:%M", time_info);
    printf(" Last modified: %s\n", start_time);
}

/*
    Function: file_size
    Description: Prints the size of a file.
    Parameters:
        - file_size: File size
    Returns: None
*/
void file_size(off_t file_size) {
    printf(" Size: %lld bytes\n", (long long) file_size);
}

/*
    Function: symlink_path
    Description: Prints the target path of a symbolic link.
    Parameters:
        - filepath: Path of the symbolic link
    Returns: None
*/
void symlink_path(const char *filepath) {
    char link_target[PATH_MAX];
    ssize_t len_symlnk = readlink(filepath, link_target, sizeof(link_target) - 1);
    if (len_symlnk == -1) {
        perror("readlink");
        return;
    }
    link_target[len_symlnk] = '\0';
    printf("%s->%s\n", filepath, link_target);
}

/*
    Function: file_info
    Description: Prints information about a file, including permissions, size, last modified time, and symbolic link target.
                 Executes a specified Unix command if provided.
    Parameters:
        - path: Path of the file
        - file_meta: File metadata
        - verbose: Verbose mode flag
        - unix_command: Unix command to execute
    Returns: None
*/
void file_info(const char *path, struct stat *file_meta, int verbose, const char *unix_command) {
    if (verbose) {
        if (S_ISLNK(file_meta->st_mode)) {
            symlink_path(path);
        } else {
            printf("%s\n", path);
        }
        file_size(file_meta->st_size);
        last_modify(file_meta->st_mtime);
        file_permissions(file_meta->st_mode);
    } else {
        if (S_ISLNK(file_meta->st_mode)) {
            symlink_path(path);
        } else {
            printf("%s\n", path);
        }
    }

    // Execute specified Unix command using fork/exec/wait
    if (unix_command != NULL) {
        char *cmd_copy = strdup(unix_command);
        char *token = strtok(cmd_copy, " ");
        char *tar_file_name = NULL;
        while (token != NULL) {
            tar_file_name = token;
            token = strtok(NULL, " ");
        }

        pid_t pid = fork();
        if (pid == -1) {
            perror("fork");
            exit(EXIT_FAILURE);
        } else if (pid == 0) {  // Child process
            execlp("tar", "tar", "rvf", tar_file_name, path, NULL);
            perror("execlp");
            exit(EXIT_FAILURE);
        } else {  // Parent process
            int status;
            waitpid(pid, &status, 0);
            if (WIFEXITED(status) && WEXITSTATUS(status) != 0) {
                fprintf(stderr, "tar command failed for file: %s\n", path);
            }
        }

        free(cmd_copy);
    }
}

/*
    Function: list_filedir
    Description: Recursively lists files and directories in a specified directory hierarchy.
    Parameters:
        - path: Directory path
        - current_level: Current level of directory hierarchy
        - verbose: Verbose mode flag
        - file_size_threshold: Minimum file size threshold
        - pattern: Pattern to match filenames
        - depth_limit: Maximum directory traversal depth
        - file_type: File type filter (0: all files, 1: regular files, 2: directories)
        - unix_command: Unix command to execute
        - unix_command_args: Arguments for the Unix command
    Returns: None
*/
void list_filedir(const char *path, int current_level, int verbose, long file_size_threshold, const char *pattern, int depth_limit, int file_type, const char *unix_command, char * const* unix_command_args) {
    DIR *dir = opendir(path);
    if (dir == NULL) {
        perror("opendir");
        return;
    }
    struct dirent *ent;
    struct stat ent_st;
    char full_path[PATH_MAX];
    while ((ent = readdir(dir)) != NULL) {
        if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0)
            continue;
        snprintf(full_path, sizeof(full_path), "%s/%s", path, ent->d_name);
        if (lstat(full_path, &ent_st) == -1) {
            perror("lstat");
            continue;
        }
        int meets_size_threshold = (file_size_threshold == -1 || ent_st.st_size >= file_size_threshold);
        int matches_pattern = (pattern == NULL || strstr(ent->d_name, pattern) != NULL);
        int is_regular_file = S_ISREG(ent_st.st_mode);
        int is_directory = S_ISDIR(ent_st.st_mode);

        if (file_type == 1 && !is_regular_file) { // List regular files only
            continue;
        } else if (file_type == 2 && !is_directory) { // List directories only
            continue;
        }

        if (meets_size_threshold && matches_pattern) {
            printf("%*s", current_level * 4, "");
            file_info(full_path, &ent_st, verbose, unix_command);
        }

        if (is_directory && current_level < depth_limit) {
            list_filedir(full_path, current_level + 1, verbose, file_size_threshold, pattern, depth_limit, file_type, unix_command, unix_command_args);
        }
    }
    closedir(dir);
}


int main(int argc, char *argv[]) {
    int verbose_flag = 0;
    long size_limit = -1;
    int max_depth = INT_MAX;
    char *pattern = NULL;
    int file_type = 0; // 0: all files, 1: regular files, 2: directories
    const char *command = NULL;
    char * const *command_args = NULL;

    int option;
    while ((option = getopt(argc, argv, "vL:s:d:t:e:E:")) != -1) {
        switch (option) {
            case 'v':
                verbose_flag = 1;
                break;
            case 'L':
                size_limit = strtol(optarg, NULL, 10);
                break;
            case 's':
                pattern = optarg;
                break;
            case 'd':
                max_depth = strtol(optarg, NULL, 10);
                break;
            case 't':
                if (strcmp(optarg, "f") == 0) {
                    file_type = 1; // List regular files only
                } else if (strcmp(optarg, "d") == 0) {
                    file_type = 2; // List directories only
                } else {
                    fprintf(stderr, "Invalid argument for -t option\n");
                    exit(EXIT_FAILURE);
                }
                break;
            case 'e':
                command = optarg;
                break;
            case 'E':
                command = optarg;
                // Parse additional arguments for the command
                command_args = &argv[optind];
                break;
            default:
                fprintf(stderr, "Usage: %s [-v] [-L size_limit] [-s pattern] [-d max_depth] [-t file_type] [-e command] [-E command] [directory]\n", argv[0]);
                exit(EXIT_FAILURE);
        }
    }

    char *base_path = (optind < argc) ? argv[optind] : ".";
    list_filedir(base_path, 0, verbose_flag, size_limit, pattern, max_depth, file_type, command, command_args);
    return 0;
}

Here is the readme.md file of the program:

# Search Program

This program allows you to search for files and directories in a specified directory hierarchy. Only for Unix operating systems.

# Install require packages

The very first step is to update the packages, run this command:
```bash
ssudo apt update
```
Install **make** package using the following commnad:
```bash
sudo apt install make
```
Install **gcc** using the following commnad:
```bash
sudo apt install build-essential
```
# Compilation

To compile the C source file into an executable, follow the below steps:
- Place search.c and the Makefile in the same directory.
- Open a terminal and navigate to the directory containing the files.
- Run the following command in terminal:
```bash
gcc -o search search.c
```
#  Execution

After compilation, you can run the executable program using the following command:
```bash
./search [-v] [-L size_limit] [-s pattern] [-d max_depth] [-t file_type] [-e command] [-E command] [directory]
```
Options
-v: Verbose mode (optional).
-L size_limit: Limit the size of files to be listed (optional).
-s pattern: Search for files matching a specific pattern (optional).
-d max_depth: Specify the maximum depth of directory traversal (optional).
-t file_type: Specify the type of files to list (0 for all files, 1 for regular files, 2 for directories) (optional).
-e command: Execute the specified Unix command for each file (optional).
-E command: Execute the specified Unix command using all file names as arguments (optional).

# Citation

https://www.geeksforgeeks.org/command-line-arguments-in-c-cpp/
https://www3.ntu.edu.sg/home/ehchua/programming/cpp/gcc_make.html
https://www.javatpoint.com/command-line-arguments-in-c
https://www.tutorialspoint.com/cprogramming/c_command_line_arguments.htm
https://www.upgrad.com/tutorials/software-engineering/c-tutorial/command-line-arguments-in-c/

My C code successfully implement a search program using system calls for files and directories. But this code failed to execute Unix-command. I tried to include this features in code. But this not work.

Additional features need to include in the C code:

  1. Implementation of search that executes the UNIX command with optional arguments for each matching file using fork/exec/wait

  2. Implementation of search that lists the files and directories in the specified format when executed with multiple options (combination of -L, -s and -e/-E)

-e "<unix-command with arguments>" For each file that matches the search criteria the UNIX command specified with arguments must be executed.

-E "<unix-command with arguments>" The list of files that matches the search criteria must be provided as an argument to the UNIX command specified.

Note that with the “-e” option, the UNIX command is executed for each file whereas with the “-E” option the UNIX command is executed only once but uses all the file names as arguments. You must use fork/exec/wait to create a new process to execute the UNIX command. The UNIX command and any optional arguments are enclosed within double quotes. The program should support -e or -E options in combination with -L and -s options. You can assume that the -e or -E options appear after the -L and -s options.

Here are some examples of commands with description:

$./search -L 1024 -e "ls -l" List all files with size >= 1024 bytes in the current directory, and execute the command "ls -l" on each file (ignore directories)

$./search -s jpg 3 -E "tar cvf jpg.tar" List all files that have the substring “jpg” in their filename or directory name with depth <=3 relative to the current directory, and creates a tar file named jpg.tar that contains these files

$./serch -L 1024 -s jpg 3 -e "wc -l" List all files that have the substring “jpg” in their filename with depth <=3 relative to the current directory and size >= 1024, and execute the command "wc -l" on each file (ignore directories)

The above commands are just examples. This code must run with all kinds unix-command with arguments with -e and -E option.

0

There are 0 answers