4
\$\begingroup\$

Hi guys how you doing??

I just finished creating an rsync helper program that reads its configuration file using a program that I also created and it deletes some cache directories that I included in the config file plus a few commands for cleaning the system, then it creates a directory on my hard drive with the date of today and starts backing up the system.

For this tool I used C. I know that it may sound strange choice for a wrapper program to be written in C, but the truth I like creating tools in C, so I don't know what about you guys but I think the results aren't bad.

So here is the code:

  1. errors.h
#ifndef ERRORS_H
#define ERRORS_H
void handle_files_error(const char *, char *);
int check_input_size(int, int);
void handle_general_error(const char *);
void handle_strstr_error(const char *);
#endif
  1. errors.c
/*
*This header is for handling errors 
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "errors.h"
/*
*This function will handle errors that occurres 
*while managing files and prints the right message.
*/
void handle_files_error(const char *message, char *file_path) {
 char full_message[300];
 if(check_input_size(sizeof(full_message), strlen(message)+strlen(file_path))) { //Check the final full_message size
 fprintf(stderr, "The size of the inserted string in errors.c -> handle_files_error is invalid.\n"); 
 exit(EXIT_FAILURE);
 }
 strcpy(full_message, message);
 strcat(full_message, file_path);
 perror(full_message);
 exit(EXIT_FAILURE);
}
/*
*This function will check if the size of the input is 
*valid and can fit in the array without overflowing it.
*0 for valid, 1 otherwise.
*/
int check_input_size(int valid_size, int input_size) {
 if(input_size < valid_size)
 return 0;
 else 
 return 1;
}
/*
*This function will handle general errors and display 
*the relevant message.
*/
void handle_general_error(const char *message) {
 fprintf(stderr, "%s\n", message);
 exit(EXIT_FAILURE);
}
/*
*This function will handle errors for the strstr() function 
*and it will display the write message if a string wasnt found.
*/
void handle_strstr_error(const char *serched_str) {
 fprintf(stderr, "Error occurred while serching for unit : %s.\n", serched_str);
}
  1. config_man.h
#ifndef CONFIG_MAN_H
#define CONFIG_MAN_H
int check_file_existence(char *);
void create_config_file(char *, const char*);
void write_config_unit(char *, const char *, const char *);
char *read_config_unit(char *, const char *);
int check_unit_existence(const char *, char *);
void read_reg_syntax(char *, char *, int);
int read_list_syntax(char *, char *, int);
#endif
  1. config_man.c
/*
*This header contains all required functions to manage configuration files.
*/ 
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "errors.h" //Header for error handling
#include "config_man.h"
/*
*The function will check if the given file in the 
*file path exists. It retruns 1 for yes and 0 for no.
*/
int check_file_existence(char *file_path) {
 FILE *fp;
 fp = fopen(file_path, "r");
 if(fp == NULL) //if file doesnt exists 
 return 0;
 fclose(fp);
 return 1;
}
/*
*A function to create a configuration file with the given path.
*Then it'll write a comentted discription and instruction for this file.
*/ 
void create_config_file(char *file_path, const char *description) {
 FILE *fp;
 const char *config_instruct = {
 "################################################################\n"
 "#--------------------------------------------------------------#\n"
 "# [Instructions] #\n"
 "#--------------------------------------------------------------#\n"
 "################################################################\n"
 "# Please it's very important to make sure there are 3 charact- #\n"
 "# ers between the unit name and its configurations. #\n"
 "# For example: #\n"
 "# #\n"
 "# UnitName = configurations #\n"
 "# #\n"
 "# As you can see there are 3 characters between the unit name #\n"
 "# and the configurations (2 spaces and an equal sign). Be awa- #\n"
 "# re that each new line terminates the unit's configurations. #\n"
 "# If the unit's configurations are too long you can put it in- #\n"
 "# side a pair of braces and make it a list. For example: #\n"
 "# #\n"
 "# VeryLongUnit = { #\n"
 "# config_1 #\n"
 "# config_2 #\n"
 "# config_3 #\n"
 "# config_4 #\n"
 "# } <- Don't forget to close the list #\n"
 "# #\n"
 "# If the list is group of commands you can add '&&' to the end #\n"
 "# of each line. If the command requires root privileges you #\n"
 "# can just add sudo to the command. For example: #\n"
 "# #\n"
 "# CommandsUnit = { #\n"
 "# command 1 && #\n"
 "# sudo root_command 2 && #\n"
 "# command 3 && #\n"
 "# sudo root_command 3 && #\n"
 "# command 4 #\n"
 "# } #\n"
 "# #\n"
 "# Be aware that every new line in the list syntax is converted #\n"
 "# into a space, and '}' terminates the list. #\n"
 "# Note: Please if you want to use indention for the list, only #\n"
 "# use tabs, becuase they are ignored while reading a list. #\n"
 "# Lastly as you can see hashes ('#') are ignored. #\n"
 "# By the way, sorry for the hard syntax I really tried to make #\n"
 "# as easy as I can and that's the result. I hope you like it :)#\n"
 "################################################################\n"
 };
 fp = fopen(file_path, "w");
 if(fp == NULL) //Check if error occurred
 handle_files_error("An error occurred in create_config_file() while creating ", file_path);
 fprintf(fp, "%s\n", description);
 fprintf(fp, "%s\n", config_instruct);
 fclose(fp);
}
/*
*This function takes a unit name and a description then it'll 
*write the description to it so youll know how to configure it.
*/
void write_config_unit(char *file_path, const char *unit_name, const char *unit_desc) {
 FILE *fp;
 fp = fopen(file_path, "a");
 if(fp == NULL) //Check if error occurred
 handle_files_error("An error occurred in write_config_unit() while opening ", file_path);
 fprintf(fp, "%s", unit_name);
 fprintf(fp, "%s", " = ");
 fprintf(fp, "%s\n", unit_desc);
 fclose(fp); 
}
/*
*This function will read all the config file, it'll
*ignore every commented line (with '#'), it will search 
*for the desired unit's confgis. If the unit exists it will 
*return a pointer to it, otherwise it will return NULL.
*/
char *read_config_unit(char *file_path, const char *unit_name) {
 FILE *fp;
 char unit_buffer[400]; //Buffer for the unit name and its configs 
 char *configs_beginning; //Pointer to the targeted unit's configs beginning
 static char read_configs[500]; //Array for the read configs
 unsigned int config_status = 0; //1 = list, 0 = one line configurations
 fp = fopen(file_path, "r");
 if(fp == NULL) //Check if error occurred
 handle_files_error("An error occurred in read_config_file() while reading ", file_path);
 memset(read_configs, '0円', sizeof(read_configs)); //Make sure the static array is empty 
 //Loop in the file's content 
 while(fgets(unit_buffer, 400, fp) != NULL) { 
 //If the beginning of a line is commented or empty
 if(*unit_buffer=='#' || *unit_buffer=='\n' || *unit_buffer=='0円') 
 continue; //Ignore and read next line
 if(config_status) { //If list was found
 if(read_list_syntax(unit_buffer, read_configs, sizeof(read_configs))) //If reading list
 continue; //read the next line of the list
 fclose(fp);
 return read_configs;
 }
 else {
 if(check_unit_existence(unit_name, unit_buffer)) //If unit wasnt found in line
 continue; //Read the next one 
 /*The address of the beginning of the unit's configurations =
 beginning of the line + len of the unit name + 3 bytes (2 spaces and '=' sign)*/
 configs_beginning = unit_buffer + strlen(unit_name) + 3;
 if(*(configs_beginning) == '{') { //If unit configs is beginning of a list
 config_status = 1;
 continue; 
 }
 else {
 read_reg_syntax(configs_beginning, read_configs, sizeof(read_configs));
 fclose(fp);
 return read_configs;
 }
 }
 }
 fclose(fp);
 return NULL;
}
/*
*This function reads the regular configurations syntax,
*Then passes it to the config_buffer.
*/
void read_reg_syntax(char *config_beginning, char *configs_buffer, int buffer_size) {
 int i = 0;
 while(1) 
 switch(config_beginning[i]) {
 case '\n':
 configs_buffer[i] = '0円'; //teminate line
 return; //exit
 case '0円': //Line is terminated
 return; //exit
 default:
 //Check for overflow
 if(check_input_size(buffer_size, i+1)) {
 fprintf(stderr, "The size of the inserted string in config_man.c -> read_reg_syntax is invalid.\n");
 exit(EXIT_FAILURE);
 }
 //insert char into the configs_buffer
 configs_buffer[i] = config_beginning[i];
 i++;
 break;
 }
}
/*
*This function will read the syntax of a list, if finished reading itll 
*return 0, if still reading it will return 1 to read the next line.
*/
int read_list_syntax(char *line_beginning, char *configs_buffer, int buffer_size) {
 int char_cnt, i;
 char_cnt = strlen(configs_buffer); //number of characters in configs_buffer
 i = 0; 
 while(1) { //Loop and read line until reaching '\n' or ';'
 switch(line_beginning[i]) {
 case '}': //If end of list
 configs_buffer[char_cnt] = '0円'; //terminate line
 return 0; //Finished reading list
 case '\t': //Ignore indention 
 break;
 case '\n': //Convert to one space 
 //Make sure there is no overflow
 if(check_input_size(buffer_size, char_cnt+1)) {
 fprintf(stderr, "The size of the inserted string in config_man.c -> read_list_syntax is invalid.\n");
 exit(EXIT_FAILURE);
 }
 configs_buffer[char_cnt] = ' ';
 return 1; //Read next line
 default:
 //Make sure there is no overflow
 if(check_input_size(buffer_size, char_cnt+1)) {
 fprintf(stderr, "The size of the inserted string in config_man.c -> read_list_syntax is invalid.\n");
 exit(EXIT_FAILURE);
 }
 configs_buffer[char_cnt] = line_beginning[i];
 char_cnt++;
 break;
 }
 i++;
 }
}
/*
*This function takes a unit name and pointer to the beginning
*of the unit's line and it then it finds the unit's name which is
*the first word then it checks if the founded unit and the passed 
*one are equal and the same.
*0 = equal, 1 = not equal
*/
int check_unit_existence(const char *unit_name, char *line_begin) {
 unsigned int i;
 char unit_to_check[50]; //Array for the unit name that needs to be checked
 //Get unit name
 for(i=0; line_begin[i]!='\n' && line_begin[i]!='\t' && line_begin[i]!=' ' && line_begin[i]!='0円'; i++) {
 //Make sure there is no overflow
 if(check_input_size(sizeof(unit_to_check), i+1)) {
 fprintf(stderr, "The size of the inserted string in config_man.c -> check_unit_existence is invalid.\n");
 exit(EXIT_FAILURE);
 } 
 unit_to_check[i] = line_begin[i];
 }
 if(strcmp(unit_name, unit_to_check) == 0) //If equal 
 return 0;
 else //Not equal
 return 1;
}
  1. sys_backup.h
#ifndef SYS_BACKUP_H
#define SYS_BACKUP_H
void delete_dirs(char *);
void get_date(int *);
char *make_backup_dir(char *, int *);
void backup_sys(const char *, char *);
#endif
  1. sys_backup.c
/*
*This program will make some cleaning that you regularly do 
*before the full system backup. And then itll create new dir 
*in your storage device with date, to make the system backup in
*it useng rsync. The program will use system() to connect all the
*command line tools together and automate this process. Lastly 
*its good to note that tho program reads all the commands and 
*your customaized cleaning process from a config file with the 
*following path: ~/.config/sys_backup
*/
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <string.h>
#include "config_man.h" //For config files managment 
#include "errors.h" //Error handling header
#include "sys_backup.h"
int main() {
 const char *config_desc = {
 "################################################################\n"
 "# This configuration file purposes are to provide to the #\n"
 "# sys_backup.c program the right cleaning process and storage #\n"
 "# device path, customized to your own needs and preferences. #\n"
 "################################################################"
 };
 const char *units_list[] = {
 "DirsToClean", "CleaningCommands", "DevicePath", 
 "RsyncCommand", "0円"
 };
 const char *units_desc[] = {
 "Dirs path you regularly clean, like some cache dirs",
 "{\n"
 "\tCommands for cleaning your sysem, like:\n"
 "\tsudo pacman -Sc (for deleting uninstalled packages\n"
 "\tfrom the cache in arch based distros)\n"
 "}", 
 "Your storage device path hdd, usb or whatever you use", 
 "sudo rsync -aAXHv --exclude={\"/dev/*\",\"/proc/*\",\"/sys/*\""
 ",\"/tmp/*\",\"/run/*\",\"/mnt/*\",\"/media/*\",\"/lost+found\"} / ", 
 "0円"
 };
 char *config_path = "/.config/sys_backup";
 char config_full_path[60];
 char *home, *configurations, *backup_path;
 int date[3]; //Array for the date of today
 unsigned int i;
 /*Get configuration's file path*/
 home = getenv("HOME"); //Get home path 
 //Check for overflow 
 if(check_input_size(sizeof(config_full_path), strlen(home)+strlen(config_path))) {
 fprintf(stderr, "The size of the inserted string in sys_backup.c -> main() is invalid.\n");
 exit(EXIT_FAILURE);
 }
 strcpy(config_full_path, home);
 strcat(config_full_path, config_path);
 
 if(check_file_existence(config_full_path) == 0) { //Check if config file exists
 create_config_file(config_full_path, config_desc);
 for(i=0; *units_desc[i]!='0円'; i++)
 write_config_unit(config_full_path, *(units_list+i), *(units_desc+i));
 printf("Please configure the prorgram's config file in the following path: %s.\n", config_full_path);
 return 0;
 }
 /*Read the configured units*/
 if((configurations=read_config_unit(config_full_path, units_list[0])) == NULL) { //Unit wasnt found
 handle_strstr_error(units_list[0]);
 exit(EXIT_FAILURE);
 }
 delete_dirs(configurations);
 
 if((configurations=read_config_unit(config_full_path, units_list[1])) == NULL) { //Unit wasnt found
 handle_strstr_error(units_list[1]);
 exit(EXIT_FAILURE);
 }
 system(configurations);
 
 if((configurations=read_config_unit(config_full_path, units_list[2])) == NULL) { //Unit wasnt found
 handle_strstr_error(units_list[2]);
 exit(EXIT_FAILURE);
 }
 get_date(date); //Get the date of today and pass it to the date array
 backup_path = make_backup_dir(configurations, date); //Create backup dir and get its path
 
 if((configurations=read_config_unit(config_full_path, units_list[3])) == NULL) { //Unit wasnt found
 handle_strstr_error(units_list[3]);
 exit(EXIT_FAILURE);
 }
 backup_sys(configurations, backup_path); //Backup system in the created dir
 return 0;
}
/*
*This function will delete the given argument dirs.
*/
void delete_dirs(char *dirs_path) {
 char full_command[700];
 const char *command = "rm -rf ";
 const char *danger_message = {
 "Nice try, but we wont let you destroy your system ;)\n"
 "Please make sure that DirsToClean unit is configured properly "
 "and there is no sign of standalone root tree (\"/\")."
 };
 unsigned int i;
 //Loop in dirs_path yo make sure no one removing the root tree by mistake
 for(i=0; i<strlen(dirs_path); i++)
 if(dirs_path[i]==' ' && dirs_path[i+1]=='/' && dirs_path[i+2]==' ') {
 fprintf(stderr, "%s\n", danger_message);
 exit(EXIT_FAILURE);
 }
 //Check for overflow
 if(check_input_size(sizeof(full_command), strlen(dirs_path)+strlen(command))) {
 fprintf(stderr, "The size of the inserted string in sys_backup.c -> delete_dirs is invalid.\n");
 exit(EXIT_FAILURE);
 }
 //Copy command to buffer (rm -rf dirs_path) to delete dirs.
 strcpy(full_command, command);
 strcat(full_command, dirs_path);
 system(full_command); //Execute command
}
/*
*This function will get the date of today, and it'll insert it to the passed date array.
*/
void get_date(int *date) {
 long int sec_since_epoch;
 struct tm current_time, *time_ptr;
 sec_since_epoch = time(0); 
 time_ptr = &current_time; //Set time pointer to the current_time struct
 localtime_r(&sec_since_epoch, time_ptr);
 //Pass today's date to the array 
 *date = time_ptr->tm_mday;
 *(date+1) = time_ptr->tm_mon + 1; //+1 because months range from 0 - 11
 *(date+2) = time_ptr->tm_year - 100; //-100 because tm_year is number of passed years since 1900
}
/*
*A function that gets pointer to int array that contains the
*date of today and create a backup dir in the passed path 
*passed date. Then it will return the full path of the created dir. 
*/
char *make_backup_dir(char *device_path, int *date_array) {
 char dir_name[10], full_command[200];
 const char *command = "mkdir ";
 static char backup_path[150]; //The returned full backup path 
 //Convert the date_array to a string so will use it to name the dir in the device path
 sprintf(dir_name, "%02d", *date_array);
 sprintf((dir_name+3), "%02d", *(date_array+1));
 sprintf(dir_name, "%02d", *date_array);
 sprintf(dir_name+6, "%d", *(date_array+2));
 dir_name[2] = dir_name[5] = '-';
 //Check for overflow
 if(check_input_size(sizeof(backup_path), strlen(device_path)+strlen(dir_name))) {
 fprintf(stderr, "The size of the inserted string in sys_backup.c -> make_backup_dir is invalid.\n");
 exit(EXIT_FAILURE);
 }
 strcpy(backup_path, device_path);
 strcat(backup_path, dir_name); //Complete the full dir path 
 //Check for overflow
 if(check_input_size(sizeof(full_command), strlen(command)+strlen(backup_path))) {
 fprintf(stderr, "The size of the inserted string in sys_backup.c -> make_backup_dir is invalid.\n");
 exit(EXIT_FAILURE);
 }
 strcpy(full_command, command); //Insert command to the full_command
 strcat(full_command, backup_path); //Complete the command
 
 system(full_command); //Execute the command 
 return backup_path;
}
/*
*This function will make the full system backup using rsync to the passed dir path.
*/
void backup_sys(const char *command, char *backup_path) {
 char full_command[500];
 /*Prepare command*/
 //Check for overflow
 if(check_input_size(sizeof(full_command), strlen(command)+strlen(backup_path)+sizeof("/"))) {
 fprintf(stderr, "The size of the inserted string in sys_backup.c -> backup_sys is invalid.\n");
 exit(EXIT_FAILURE);
 }
 strcpy(full_command, command);
 strcat(full_command, backup_path);
 strcat(full_command, "/");
 
 system(full_command); //Execute the command 
}
  1. Here is a config example
################################################################
# This configuration file purposes are to provide to the #
# sys_backup.c program the right cleaning process and storage #
# device path, customized to your own needs and preferences. #
################################################################
################################################################
#--------------------------------------------------------------#
# [Instructions] #
#--------------------------------------------------------------#
################################################################
# Please it's very important to make sure there are 3 charact- #
# ers between the unit name and its configurations. #
# For example: #
# #
# UnitName = configurations #
# #
# As you can see there are 3 characters between the unit name #
# and the configurations (2 spaces and an equal sign). Be awa- #
# re that each new line terminates the unit's configurations. #
# If the unit's configurations are too long you can put it in- #
# side a pair of braces and make it a list. For example: #
# #
# VeryLongUnit = { #
# config_1 #
# config_2 #
# config_3 #
# config_4 #
# } <- Don't forget to close the list #
# #
# If the list is group of commands you can add '&&' to the end #
# of each line. If the command requires root privileges you #
# can just add sudo to the command. For example: #
# #
# CommandsUnit = { #
# command 1 && #
# sudo root_command 2 && #
# command 3 && #
# sudo root_command 3 && #
# command 4 #
# } #
# #
# Be aware that every new line in the list syntax is converted #
# into a space, and '}' terminates the list. #
# Note: Please if you want to use indention for the list, only #
# use tabs, becuase they are ignored while reading a list. #
# Lastly as you can see hashes ('#') are ignored. #
# By the way, sorry for the hard syntax I really tried to make #
# as easy as I can and that's the result. I hope you like it :)#
################################################################
DirsToClean = {
 ~/.cache/spotify
 ~/.cache/mozilla/firefox/9jizeht4.default-release/thumbnails
 ~/.cache/tracker3
 ~/.cache/mozilla/firefox/9jizeht4.default-release/cache2
 ~/.cache/zoom
 ~/.local/share/Trash/files/*
 ~/.cache/torbrowser/download/*
 ~/.cache/yarn
}
CleaningCommands = {
 sudo pacman -Sc &&
 sudo paccache -rk2 &&
 sudo journalctl --vacuum-time=1weeks
}
DevicePath = /run/media/yan/HDD/
#This is the default command which backups all the necessary directories.
#You can of course change it to your needs.
RsyncCommand = sudo rsync -aAXHv --exclude={"/dev/*","/proc/*","/sys/*","/tmp/*","/run/*","/mnt/*","/media/*","/lost+found"} / 

I would like honest reviews and feedback that would help me to improve the code and more important my coding skills. Here is a link to the program's repo if its easier for you to read it there: https://github.com/yankh764/full-system-backup

Thanks for any review.

ferada
11.4k25 silver badges65 bronze badges
asked Mar 15, 2021 at 15:49
\$\endgroup\$
2
  • \$\begingroup\$ This is not by any stretch of the imagination functional programming. \$\endgroup\$ Commented Mar 16, 2021 at 15:40
  • \$\begingroup\$ You may have meant procedural programming, but there's no tag for that, and anyway that's implied enough by the c tag. \$\endgroup\$ Commented Mar 16, 2021 at 15:43

1 Answer 1

3
\$\begingroup\$

Language

I know that it may sound strange choice for a wrapper program to be written in C

Yeah, about that. For practice, fine; but overall C isn't the right tool for this job. A more abstract scripting language such as Python will buy you shorter code with more memory safety, more operating system portability, and built-in support for better handling of alternate character encodings, locales, etc., etc., etc.

Typo

erros.h -> errors.h

serching -> searching

prorgram -> program

Constant parameters

void handle_files_error(const char *message, char *file_path) {

should have const before file_path as well.

Truncation instead of failure

handle_files_error is a nasty consequence of using non-memory-managed language, and would be comparatively trivial in a memory-managed language. Even if you were to stick to C, aborting on a message that's too long is the least friendly of your options. Instead, truncate to the length of your buffer; or better yet don't have a buffer at all and simply fprintf to stderr.

Utility functions

check_input_size should not exist and makes the code less legible than simply doing the length comparison in place.

Default configuration

This whole default-config-if-missing mechanism:

 if(check_file_existence(config_full_path) == 0) { //Check if config file exists
 create_config_file(config_full_path, config_desc);
 for(i=0; *units_desc[i]!='0円'; i++)
 write_config_unit(config_full_path, *(units_list+i), *(units_desc+i));
 printf("Please configure the prorgram's config file in the following path: %s.\n", config_full_path);
 return 0;
 }

can go away. The typical thing for Linux programs to do is require that a configuration file exist, and if it doesn't, fail outright; or (somewhat more complex) adopt a set of in-memory defaults. Writing a default configuration file if none exists is surprising behaviour.

Implicit array-to-pointer

This syntax is a little spooky:

const char *danger_message = {
 "Nice try, but we wont let you destroy your system ;)\n"
 "Please make sure that DirsToClean unit is configured properly "
 "and there is no sign of standalone root tree (\"/\")."
};

You're using an array literal but then dumping the array information and only keeping a pointer. For that reason, drop your braces - the string literal on its own is enough.

Dates by reference

get_date is problematic. You're assuming that int *date is actually an array of three integers. Why not just pass three separate pointers? The intent is clearer and more explicit that way.

Even if you kept your current pointer style - which you shouldn't - your array assignments

*date = time_ptr->tm_mday;
*(date+1) = time_ptr->tm_mon + 1; //+1 because months range from 0 - 11
*(date+2) = time_ptr->tm_year - 100; //-100 because tm_year is number of passed years since 1900

should turn into

date[0] = time_ptr->tm_mday;
date[1] = time_ptr->tm_mon + 1; //+1 because months range from 0 - 11
date[2] = time_ptr->tm_year - 100; //-100 because tm_year is number of passed years since 1900

But again, this will be a one-liner in Python.

Repeated sprintf

You need to avoid your manual offsets here:

sprintf(dir_name, "%02d", *date_array);
sprintf((dir_name+3), "%02d", *(date_array+1));
sprintf(dir_name, "%02d", *date_array);
sprintf(dir_name+6, "%d", *(date_array+2));));
dir_name[2] = dir_name[5] = '-';

Instead,

sprintf(dir_name, "%02d-%02d-%d", date_array[0], date_array[1], date_array[2]);

Security

There is ample opportunity for this program to be abused. You're forming command line strings using a fragile manual-memory-offset style in fixed-size buffers and then passing that off to system. Read this for flavour. This is a recipe for disaster.

Rather than shelling into mkdir for instance, just call it from C.

answered Mar 16, 2021 at 16:28
\$\endgroup\$
2
  • \$\begingroup\$ Thank you for your honest feedback and this full review I really appreciate it @Reinderien and im sorry that I don't know all these things but im kind of beginner so im trying to learn from books, documents, reviews and feedbacks from you guys. So thank you for trying to help, ill of course modify the code, improve it and try to make it as best as i can and even maybe ill do onother version of it in python or bash script \$\endgroup\$ Commented Mar 16, 2021 at 18:05
  • 1
    \$\begingroup\$ Great! If you do implement the above improvements, submit a second question with your new code. Thanks! \$\endgroup\$ Commented Mar 16, 2021 at 20:48

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.