Привет, ребята, как дела ?? Я только что закончил создание вспомогательной программы rsync, которая считывает его файл конфигурации с помощью программы, которую я также создал, и удаляет некоторые каталоги кеша, которые я включил в файл конфигурации, а также несколько команд для очистки системы, затем он создает каталог на моем жестком диске с дата сегодня и начинает резервное копирование системы. Для этого инструмента я использовал C, я знаю, что это может показаться странным выбором для программы-оболочки, написанной на C, но, по правде говоря, мне нравится создавать инструменты на C, поэтому я не знаю, что насчет вас, ребята, но я думаю, что результаты не Плохо. Итак, вот код:
- 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
- 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, "%sn", 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);
}
- 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
- 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 ("https://codereview.stackexchange.com/questions/257190/#") 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, "%sn", description);
fprintf(fp, "%sn", 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, "%sn", unit_desc);
fclose(fp);
}
/*
*This function will read all the config file, it'll
*ignore every commented line (with "https://codereview.stackexchange.com/questions/257190/#"), 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, ' ', 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=="https://codereview.stackexchange.com/questions/257190/#" || *unit_buffer=='n' || *unit_buffer==' ')
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] = ' '; //teminate line
return; //exit
case ' ': //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] = ' '; //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]!=' '; 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;
}
- 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
- 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", " "
};
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 packagesn"
"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"} / ",
" "
};
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]!=' '; 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]=="https://codereview.stackexchange.com/" && dirs_path[i+2]==' ') {
fprintf(stderr, "%sn", 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 = ¤t_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
}
- Вот пример конфигурации
################################################################
# 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 ("https://codereview.stackexchange.com/questions/257190/#") 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"} /
Я хотел бы получить честные отзывы и отзывы, которые помогут мне улучшить код и, что более важно, мои навыки программирования. Вот ссылка на репозиторий программы, если вам легче ее там прочитать:
https://github.com/yankh764/full-system-backup
Спасибо за любой обзор.
1 ответ
Язык
Я знаю, что выбор программы-оболочки, написанной на C, может показаться странным.
Ага, об этом. Для практики нормально; но в целом C не подходит для этой работы. Более абстрактный язык сценариев, такой как Python, купит вам более короткий код с большей безопасностью памяти, большей переносимостью операционной системы и встроенной поддержкой для лучшей обработки альтернативных кодировок символов, локалей и т. Д. И т. Д. И т. Д.
Опечатка
erros.h
-> errors.h
serching
-> searching
prorgram
-> program
Постоянные параметры
void handle_files_error(const char *message, char *file_path) {
должен иметь const
перед file_path
также.
Усечение вместо отказа
handle_files_error
является неприятным последствием использования языка без управления памятью и будет сравнительно тривиальным для языка с управлением памятью. Даже если вы будете придерживаться C, прерывание слишком длинного сообщения будет наименее удобным из ваших вариантов. Вместо этого обрежьте до длины вашего буфера; или еще лучше вообще не иметь буфера и просто fprintf
к stderr
.
Служебные функции
check_input_size
не должно существовать и делает код менее разборчивым, чем простое сравнение длины на месте.
Конфигурация по умолчанию
Весь этот механизм default-config-if-missing:
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]!=' '; 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;
}
может уйти. Обычно программы Linux требуют наличия конфигурационного файла, а если его нет, то сразу выходят из строя; или (несколько более сложный) принять набор настроек по умолчанию в памяти. Запись файла конфигурации по умолчанию, если таковой не существует, вызывает удивление.
Неявный указатель на массив
Этот синтаксис немного пугает:
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 ("/")."
};
Вы используете литерал массива, но затем выгружаете информацию о массиве и сохраняете только указатель. По этой причине снимите фигурные скобки — достаточно одного строкового литерала.
Даты по ссылке
get_date
проблематично. Вы предполагаете, что int *date
на самом деле представляет собой массив из трех целых чисел. Почему бы просто не передать три отдельных указателя? Таким образом, намерение становится более ясным и явным.
Даже если вы сохранили текущий стиль указателя — чего не следует — назначения массива
*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
должен превратиться в
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
Но опять же, это будет однострочный текст на Python.
Повторяется sprintf
Здесь вам нужно избегать ручных смещений:
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] = '-';
Вместо,
sprintf(dir_name, "%02d-%02d-%d", date_array[0], date_array[1], date_array[2]);
Безопасность
Существует множество возможностей злоупотребления этой программой. Вы формируете строки командной строки, используя хрупкий стиль ручного смещения памяти в буферах фиксированного размера, а затем передаете это в system
. Читать это для аромата. Это рецепт катастрофы.
Вместо того, чтобы обстреливать mkdir
например, просто назови это с C.
Спасибо за ваш честный отзыв и этот полный обзор. Я очень ценю его @Reinderien, и мне очень жаль, что я не знаю всего этого, но я вроде как новичок, поэтому я пытаюсь учиться на книгах, документах, обзорах и отзывах от вас, ребята. Так что спасибо за попытку помочь, я, конечно, модифицировал код, улучшил его и попытался сделать его как можно лучше, и даже, возможно, плохо сделаю другую его версию в скрипте python или bash.
– yan_kh
Большой! Если вы все же реализуете вышеуказанные улучшения, отправьте второй вопрос с новым кодом. Спасибо!
— Райндериен