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:
Implementation of search that executes the UNIX command with optional arguments for each matching file using fork/exec/wait
Implementation of search that lists the files and directories in the specified format when executed with multiple options (combination of
-L,-sand-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.