diff options
| author | Aargh Rai <shubh2007rai@gmail.com> | 2026-03-28 19:03:45 +0530 |
|---|---|---|
| committer | Aargh Rai <shubh2007rai@gmail.com> | 2026-03-28 19:03:45 +0530 |
| commit | c7194b9b07a9c80344b52e002d0863fd258f0b38 (patch) | |
| tree | 258e0aae8fdbdce64d8df36e75138a186358cd6b /src | |
basic backup system, which utlizies regex for ignore files
Diffstat (limited to 'src')
| -rw-r--r-- | src/backup.c | 172 | ||||
| -rw-r--r-- | src/config.c | 24 | ||||
| -rw-r--r-- | src/ignore.c | 66 | ||||
| -rwxr-xr-x | src/lib.h | 24 | ||||
| -rwxr-xr-x | src/main.c | 123 |
5 files changed, 409 insertions, 0 deletions
diff --git a/src/backup.c b/src/backup.c new file mode 100644 index 0000000..d90d1d1 --- /dev/null +++ b/src/backup.c @@ -0,0 +1,172 @@ +#include "lib.h" +#include <dirent.h> +#include <regex.h> + +int check_path_type(const char* path) { + struct stat path_stat; + if (stat(path, &path_stat) != 0) { + return path_type_unknown; + } + if (S_ISDIR(path_stat.st_mode)) { + return path_type_directory; + } + return path_type_file; +} + +int follows_ignore_rules(char* path, char* ignore_line) { + char lastbitofthepath[PATH_MAX]; + int k = 0; + for (int i = 0; path[i] != 0; i++) { + if (path[i] == '/') { + lastbitofthepath[k] = 0; + k = 0; + continue; + } + lastbitofthepath[k++] = path[i]; + } + if (k != 0 && lastbitofthepath[k] != 0) { + lastbitofthepath[k] = 0; + } + + regex_t re; + int ret; + + if (regcomp(&re, ignore_line, REG_EXTENDED) != 0) { + return 0; + } + + ret = regexec(&re, lastbitofthepath, (size_t) 0, NULL, 0); + regfree(&re); + + if (ret == 0) { + return 1; + } else { + return 0; + } +} + +int should_ignore(char* path, char* ignore_instructions, int VERBOSE) { + int i = 0, k = 0; + char line[PATH_MAX]; + while (ignore_instructions[i] != 0) { + if (ignore_instructions[i] != '\n') { + line[k++] = ignore_instructions[i]; + i++; + continue; + } + line[k] = 0; + k = 0; + if (!follows_ignore_rules(path, line)) { + i++; + continue; + } + if (VERBOSE) { + printf("Ignoring %s due to ignore rule %s\n", path, line); + } + return 1; + } + return 0; +} + +void copy_to_backup(char* source_path, int VERBOSE) { + char destination_path[PATH_MAX]; + int index_of_first_slash = -1; + for (int i = 0; i < PATH_MAX; i++) { + if (source_path[i] != '/') continue; + index_of_first_slash = i; + break; + } + + if (index_of_first_slash == -1) { + // I DON'T EVEN KNOW HOW TO HANDLE THIS + fprintf(stderr, "Weird path issue encountered, failed to copy to source\nPath encountered: %s", source_path); + return; + } + + int i = 0; + for (; i < index_of_first_slash; i++) { + destination_path[i] = source_path[i]; + } + destination_path[i++] = '.'; + destination_path[i++] = 'b'; + destination_path[i] = 'k'; + while (++i < PATH_MAX) { + destination_path[i] = source_path[i - 3]; + } + + if (VERBOSE) { + printf("%s -> %s\n", source_path, destination_path); + } + + verify_parent_dirs_exist(destination_path, VERBOSE); + + char command[2 * PATH_MAX + 10]; + snprintf(command, 2 * PATH_MAX + 10, "cp \"%s\" \"%s\"", source_path, destination_path); + system(command); + if (VERBOSE) { + printf("Executing: %s\n", command); + } +} + +void push(char* directories, int cursor, const char* to_add) { + int i = 0; + for (; to_add[i] != 0; i++) { + directories[cursor * PATH_MAX + i] = to_add[i]; + } + while (i < PATH_MAX) { + directories[cursor * PATH_MAX + i++] = 0; + } +} + +void backup(char* target, char* ignore_instructions, int VERBOSE) { + int file_type = check_path_type(target); + if (file_type != path_type_directory) { + fprintf(stderr, "Input not a directory\n"); + return; + } + + int cursor = 0; + char* directories = malloc(PATH_MAX * PATH_MAX * sizeof(*directories)); + push(directories, cursor, target); + while (cursor != -1) { + char* path = &directories[(cursor--) * PATH_MAX]; + DIR* dir_ptr = opendir(path); + if (dir_ptr == NULL) { + fprintf(stderr, "Couldn't open the directory: %s\n", path); + continue; + } + + struct dirent* entry_ptr; + char full_path[PATH_MAX]; + int i = 0; + for (; path[i] != 0; i++) { + full_path[i] = path[i]; + } + full_path[i++] = '/'; + while ((entry_ptr = readdir(dir_ptr)) != NULL) { + if ( + strcmp(entry_ptr->d_name, ".") == 0 || + strcmp(entry_ptr->d_name, "..") == 0 + ) continue; + int j = 0; + for (; entry_ptr->d_name[j] != 0; j++) { + full_path[i + j] = entry_ptr->d_name[j]; + } + full_path[i + j] = 0; + + if (VERBOSE) { + printf("Looking at: %s\n", full_path); + } + if (should_ignore(full_path, ignore_instructions, VERBOSE)) { + continue; + } + + if (entry_ptr->d_type == DT_REG) { + copy_to_backup(full_path, VERBOSE); + continue; + } + push(directories, ++cursor, full_path); + } + closedir(dir_ptr); + } +} diff --git a/src/config.c b/src/config.c new file mode 100644 index 0000000..ab0eb05 --- /dev/null +++ b/src/config.c @@ -0,0 +1,24 @@ +#include "lib.h" +#include <stdio.h> +#include <stdlib.h> + +char config[PATH_MAX] = {0}; +char* get_default_config() { + if (config[0] != 0) return config; + + const char* xdg_config_home = getenv("XDG_CONFIG_HOME"); + const char* home = getenv("HOME"); + + if (home == NULL) { + fprintf(stderr, "HOME environment variable not set, failed to determine the config path"); + return NULL; + } + + if (xdg_config_home != NULL && xdg_config_home[0] != 0) { + snprintf(config, PATH_MAX, "%s/backbencher/.backupignore", xdg_config_home); + } else { + snprintf(config, PATH_MAX, "%s/.config/backbencher/.backupignore", home); + } + + return config; +} diff --git a/src/ignore.c b/src/ignore.c new file mode 100644 index 0000000..fbfb8ed --- /dev/null +++ b/src/ignore.c @@ -0,0 +1,66 @@ +#include "lib.h" + +int _; + +void mkdir_if_not_exist(char* path, int VERBOSE) { + if (mkdir(path, S_IRWXU) < 0) return; + if (VERBOSE) { + printf("(Created to create directory %s)", path); + } +} + +void verify_parent_dirs_exist(char* file_path, int VERBOSE) { + for (int i = 0; file_path[i] != 0; i++) { + if (file_path[i] == '/') file_path[i] = 0; + if (file_path[i] != 0) continue; + mkdir_if_not_exist(file_path, VERBOSE); + file_path[i] = '/'; + } +} + +char* get_ignore_file(char* ignore_file, int VERBOSE) { + verify_parent_dirs_exist(ignore_file, VERBOSE); + FILE* file = fopen(ignore_file, "r"); + char* default_config; + + if (file != 0) { + float current_pointer = ftell(file); + fseek(file, 0, SEEK_END); + long int file_size = ftell(file); + fseek(file, current_pointer, SEEK_SET); + + char* contents = malloc(file_size * sizeof(*contents)); + _ = fread(contents, 1, file_size, file); + fclose(file); + + return contents; + } + + if (strcmp(ignore_file, (default_config = get_default_config())) != 0) { + return NULL; + } + + // mainly used for first time running + if (default_config == NULL) { + return NULL; + } + + file = fopen(default_config, "w"); + if (file == 0) { + return NULL; + } + + int content_length = strlen(DEFAULT_CONFIG_CONTENTS); + _ = fwrite(DEFAULT_CONFIG_CONTENTS, 1, content_length, file); + fclose(file); + if (VERBOSE) { + printf("Creating %s\n", default_config); + } + + char* contents = malloc(content_length * sizeof(char)); + for (int i = 0; i < content_length; i++) { + contents[i] = DEFAULT_CONFIG_CONTENTS[i]; + } + return contents; +} + diff --git a/src/lib.h b/src/lib.h new file mode 100755 index 0000000..999e7d7 --- /dev/null +++ b/src/lib.h @@ -0,0 +1,24 @@ +#include <unistd.h> +#include <string.h> +#include <stdio.h> +#include <sys/stat.h> +#include <dirent.h> +#include <stdio.h> +#include <stdlib.h> + +#define NULL_STR (char*)0 +#define DEFAULT_CONFIG_CONTENTS "^node_modules$\n^target$\n^build$\n^dist$\n^\\.next$\n^\\.output$\n^\\.vercel$\n^\\.wrangler$\n^\\.venv$\n^\\.astro$\n^\\.devenv$\n^__pycache__$" + +typedef enum { + path_type_unknown, + path_type_directory, + path_type_file +} path_type; + +char* get_default_config(); +char* shift(int* argc, char*** argv); +char* get_ignore_file(char* ignore_file, int VERBOSE); +void mkdir_if_not_exist(char* path, int VERBOSE); +void verify_parent_dirs_exist(char* file_path, int VERBOSE); + +void backup(char* target, char* ignore_instructions, int VERBOSE); diff --git a/src/main.c b/src/main.c new file mode 100755 index 0000000..7e6e1ee --- /dev/null +++ b/src/main.c @@ -0,0 +1,123 @@ +#include "lib.h" + +#define VERSION "1.0" + +void help() { + printf("Backup your files\n"); + printf("Usage: Backbencher [OPTIONS] [input]\n"); + printf("Arguments:\n"); + printf("\t[input]... The path of the folder to backup\n"); + printf("Options:\n"); + printf("\t-i --ignore-file <filename> Specify which files to exclude\n\t\t(default = ~/.config/backbencher/.backupignore)\n"); + printf("\t-h --help \tPrint help\n"); + printf("\t-v --verbose \tVerbose logging \n"); + printf("\t-V --version \tPrint version\n"); +} + +void assign(char* to, char* from) { + int i = 0; + for (; from[i] != 0; i++) { + to[i] = from[i]; + } + to[i] = 0; +} + +int source_main(int argc, char** argv) { + shift(&argc, &argv); + + char* ignore_file = get_default_config(); + char* target = NULL_STR; + int VERBOSE = 0; + + while (1) { + char* segment = shift(&argc, &argv); + if (segment == NULL_STR) { + break; + } + else if (segment[0] == '-' && segment[1] == '-') { + if (strcmp(&segment[2], "ignore-file") == 0) { + assign(&ignore_file[0], shift(&argc, &argv)); + if (ignore_file == NULL_STR) { + fprintf(stderr, "Missing <filename> for --ignore-file\n"); + return 1; + } + } + else if (strcmp(&segment[2], "help") == 0) { + help(); + return 0; + } + else if (strcmp(&segment[2], "verbose") == 0) { + VERBOSE = 1; + } + else if (strcmp(&segment[2], "version") == 0) { + printf(VERSION); + printf("\n"); + return 0; + } + } else if (segment[0] == '-') { + if (segment[1] == 'i') { + assign(&ignore_file[0], shift(&argc, &argv)); + if (ignore_file == NULL_STR) { + fprintf(stderr, "Missing <filename> for --ignore-file\n"); + return 1; + } + } + else if (segment[1] == 'h') { + help(); + return 0; + } + else if (segment[1] == 'v') { + VERBOSE = 1; + } + else if (segment[1] == 'V') { + printf(VERSION); + printf("\n"); + return 0; + } + } else { + target = segment; + } + } + + if (target == NULL_STR) { + fprintf(stderr, "Missing [input] - The folder to backup\n"); + return 1; + } + + if (VERBOSE) { + printf("Target folder: %s\n", target); + printf("Using ignore file: %s\n", ignore_file); + printf("Using verbosity: %d\n", VERBOSE); + } + + char* ignore_instructions = get_ignore_file(ignore_file, VERBOSE); + if (ignore_instructions == NULL) { + fprintf(stderr, "I don't have permissions to read the ignore file\n"); + return 1; + } + if (VERBOSE) { + printf("Ignoring rules:\n%s\n", ignore_instructions); + } + + backup(target, ignore_instructions, VERBOSE); + + free(ignore_instructions); + return 0; +} + +int main(int argc, char** argv) { + int result = source_main(argc, argv); + if (result != 0) { + printf("\n"); + help(); + } + return result; +} + +char* shift(int* argc, char*** argv) { + if (argc == 0) return NULL_STR; + --(*argc); + char* output = **argv; + ++(*argv); + return output; +} |
