summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.backupignore68
-rw-r--r--.clangd3
-rwxr-xr-x.envrc12
-rw-r--r--[-rwxr-xr-x].gitignore11
-rwxr-xr-xCMakeLists.txt14
-rwxr-xr-xLICENSE21
-rwxr-xr-xREADME.md23
-rwxr-xr-xbackbencherbin0 -> 18840 bytes
-rw-r--r--build.c126
-rwxr-xr-xdevenv.lock103
-rwxr-xr-xdevenv.nix8
-rwxr-xr-xdevenv.yaml15
-rw-r--r--src/backup.c173
-rw-r--r--src/backup_streamer.c54
-rw-r--r--src/config.c24
-rw-r--r--src/ignore.c66
-rw-r--r--[-rwxr-xr-x]src/lib.h61
-rw-r--r--[-rwxr-xr-x]src/main.c143
-rw-r--r--src/recursive_reader.c21
-rw-r--r--src/tmp_repo.c72
20 files changed, 417 insertions, 601 deletions
diff --git a/.backupignore b/.backupignore
new file mode 100644
index 0000000..15aa790
--- /dev/null
+++ b/.backupignore
@@ -0,0 +1,68 @@
+node_modules/
+__pycache__/
+target/
+.next/
+dist/
+build/
+*.iso
+
+.authinfo
+backp/
+.bash_history
+.bash_logout
+.bun
+.cache/
+.cargo/
+.config/
+# Desktop
+# dev
+.dmrc
+.docker
+# Documents
+# Downloads
+.face
+.face.icon
+# .gitconfig
+# .gitconfig-local
+# .gitconfig-org
+.gnupg
+go
+.ICEauthority
+.lesshst
+.local
+# Mail
+# .mbsyncrc
+# Music
+.node_repl_history
+.npm
+.nv
+.nvidia-settings-rc
+.nvm
+.omp
+opt
+# orgs
+# .password-store
+# Pictures
+.pki
+prep
+.profile
+Public
+.pyenv
+.python_history
+.rustup
+.sentry-native
+# .ssh
+.steam
+!.steam/debian-installation/steamapps/common/assettocorsa/content/cars
+!.steam/debian-installation/steamapps/common/assettocorsa/content/tracks
+.steampath
+.steampid
+.sudo_as_admin_successful
+Templates
+.var
+# Videos
+.wget-hsts
+.Xauthority
+.xsession
+.xsession-errors
+.xsession-errors.old
diff --git a/.clangd b/.clangd
new file mode 100644
index 0000000..01b6d82
--- /dev/null
+++ b/.clangd
@@ -0,0 +1,3 @@
+CompileFlags:
+ Add:
+ - -lgit2
diff --git a/.envrc b/.envrc
deleted file mode 100755
index cc5c18b..0000000
--- a/.envrc
+++ /dev/null
@@ -1,12 +0,0 @@
-#!/usr/bin/env bash
-
-export DIRENV_WARN_TIMEOUT=20s
-
-eval "$(devenv direnvrc)"
-
-# `use devenv` supports the same options as the `devenv shell` command.
-#
-# To silence all output, use `--quiet`.
-#
-# Example usage: use devenv --quiet --impure --option services.postgres.enable:bool true
-use devenv
diff --git a/.gitignore b/.gitignore
index 60bb522..378eac2 100755..100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,12 +1 @@
-# Devenv
-.devenv*
-devenv.local.nix
-devenv.local.yaml
-
-# direnv
-.direnv
-
-# pre-commit
-.pre-commit-config.yaml
-
build
diff --git a/CMakeLists.txt b/CMakeLists.txt
deleted file mode 100755
index 0125655..0000000
--- a/CMakeLists.txt
+++ /dev/null
@@ -1,14 +0,0 @@
-cmake_minimum_required(VERSION 3.10)
-project(backbencher)
-
-file(GLOB SOURCES "src/**/*.c")
-file(GLOB SRC_FILES "src/*.c")
-set(SOURCES ${SOURCES} ${SRC_FILES})
-
-if(SOURCES)
- add_executable(backbencher ${SOURCES})
-else()
- message(FATAL_ERROR "No .c files found in source")
-endif()
-
-target_compile_options(backbencher PRIVATE -Ofast)
diff --git a/LICENSE b/LICENSE
deleted file mode 100755
index 1b40a2e..0000000
--- a/LICENSE
+++ /dev/null
@@ -1,21 +0,0 @@
-MIT License
-
-Copyright (c) 2025 Aargh Rai
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
diff --git a/README.md b/README.md
deleted file mode 100755
index 35e8b23..0000000
--- a/README.md
+++ /dev/null
@@ -1,23 +0,0 @@
-## Backbencher
-
-Backup your files
-Currently only UNIX compliant
-
-### Usage
-```bash
-backbencher [OPTIONS] [input]
-```
-
-### Arguments
-- `[input]...`
- The path of the folder to backup
-
-### Options
-```bash
--i, --ignore-file <filename> Specify which files to exclude
- (default: ~/.config/backbencher/.backupignore)
-
--h, --help Print help
--v, --verbose Verbose logging
--V, --version Print version
-```
diff --git a/backbencher b/backbencher
new file mode 100755
index 0000000..308bea5
--- /dev/null
+++ b/backbencher
Binary files differ
diff --git a/build.c b/build.c
new file mode 100644
index 0000000..8fc29be
--- /dev/null
+++ b/build.c
@@ -0,0 +1,126 @@
+#include <sys/wait.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <linux/limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <time.h>
+#include <string.h>
+
+#define RELEASE_MODE
+
+#ifdef RELEASE_MODE
+#define CC "gcc", "-O3"
+#else
+#define CC "gcc", "-fsanitize=address", "-g"
+#endif
+
+#define EXE_NAME "backbencher"
+
+int status;
+
+#define RUN_CMD(...) do { \
+ if (fork() == 0) { \
+ char* args[] = __VA_ARGS__; \
+ print_cmd(args); \
+ execvp(args[0], args); \
+ return 0; \
+ } else { \
+ wait(&status); \
+ } \
+} while (0);
+
+void print_cmd(char** args) {
+ int i = 0;
+ while (args[i] != NULL) {
+ printf("%s ", args[i++]);
+ }
+ printf("\n");
+}
+
+// https://github.com/tsoding/nob.h/blob/0a08926d8094fc4ae678155c5d73ae21d1f96f3f/nob.h#L2413
+int needs_rebuild(char* bin_file, char* source_file) {
+ struct stat statbuf = {0};
+ if (stat(bin_file, &statbuf) < 0) {
+ if (errno == ENOENT) return 1;
+ return -1;
+ }
+ time_t output_path_time = statbuf.st_mtime;
+
+ if (stat(source_file, &statbuf) < 0) {
+ fprintf(stderr, "could not stat %s: %s", source_file, strerror(errno));
+ return -1;
+ }
+
+ time_t input_path_time = statbuf.st_mtime;
+ return input_path_time > output_path_time;
+}
+
+void getlibgit2(char *output, int output_max_size) {
+ int link[2];
+ pid_t pid;
+
+ pipe(link);
+ pid = fork();
+
+ if (pid == 0) {
+ dup2(link[1], STDOUT_FILENO);
+ close(link[0]);
+ close(link[1]);
+ execl("/usr/bin/pkg-config", "--cflags", "--libs", "libgit2", (char*)0);
+ exit(0);
+ }
+
+ close(link[1]);
+ int nbytes = read(link[0], output, output_max_size);
+ wait(NULL);
+
+ int i = -1;
+ while (++i < nbytes) {
+ if (output[i] >= 32) continue;
+ nbytes = i - 1;
+ break;
+ }
+ output[nbytes] = 0;
+ return;
+}
+
+int main(int argc, char** argv) {
+ if (needs_rebuild(argv[0], __FILE__)) {
+ RUN_CMD({CC, __FILE__, "-o", argv[0], NULL});
+ if (status != 0) {
+ // cc error
+ return 0;
+ }
+ print_cmd(argv);
+ execvp(argv[0], argv);
+ }
+
+ if (argc >= 2 && strcmp(argv[1], "install") == 0) {
+ printf("install -m 755 " EXE_NAME " /usr/local/bin/" EXE_NAME);
+ char dest[PATH_MAX];
+ snprintf(dest, sizeof(dest), "/usr/local/bin/%s", EXE_NAME);
+ char *install_args[] = {"install", "-m", "755", EXE_NAME, dest, NULL};
+ execvp("install", install_args);
+ perror("execvp");
+ return 0;
+ }
+ if (argc >= 2 && strcmp(argv[1], "release") == 0) {
+ char libgit[4096];
+ getlibgit2(libgit, 4096);
+ RUN_CMD({CC, "src/main.c", libgit, "-o", EXE_NAME, "-O3", NULL});
+ return 0;
+ }
+
+ char libgit[4096];
+ getlibgit2(libgit, 4096);
+ RUN_CMD({CC, "src/main.c", libgit, "-o", EXE_NAME, NULL});
+
+ if (status == 0 && argc >= 2) {
+ argv[1] = malloc(1024);
+ strncpy(argv[1], "./" EXE_NAME, 1024);
+ print_cmd(argv + 1);
+ execvp(argv[1], argv + 1);
+ }
+}
diff --git a/devenv.lock b/devenv.lock
deleted file mode 100755
index 1dcb611..0000000
--- a/devenv.lock
+++ /dev/null
@@ -1,103 +0,0 @@
-{
- "nodes": {
- "devenv": {
- "locked": {
- "dir": "src/modules",
- "lastModified": 1765463314,
- "owner": "cachix",
- "repo": "devenv",
- "rev": "ec805a5ad3e3ffb6d47d05c078a60db2e636fac4",
- "type": "github"
- },
- "original": {
- "dir": "src/modules",
- "owner": "cachix",
- "repo": "devenv",
- "type": "github"
- }
- },
- "flake-compat": {
- "flake": false,
- "locked": {
- "lastModified": 1765121682,
- "owner": "edolstra",
- "repo": "flake-compat",
- "rev": "65f23138d8d09a92e30f1e5c87611b23ef451bf3",
- "type": "github"
- },
- "original": {
- "owner": "edolstra",
- "repo": "flake-compat",
- "type": "github"
- }
- },
- "git-hooks": {
- "inputs": {
- "flake-compat": "flake-compat",
- "gitignore": "gitignore",
- "nixpkgs": [
- "nixpkgs"
- ]
- },
- "locked": {
- "lastModified": 1765464257,
- "owner": "cachix",
- "repo": "git-hooks.nix",
- "rev": "09e45f2598e1a8499c3594fe11ec2943f34fe509",
- "type": "github"
- },
- "original": {
- "owner": "cachix",
- "repo": "git-hooks.nix",
- "type": "github"
- }
- },
- "gitignore": {
- "inputs": {
- "nixpkgs": [
- "git-hooks",
- "nixpkgs"
- ]
- },
- "locked": {
- "lastModified": 1762808025,
- "owner": "hercules-ci",
- "repo": "gitignore.nix",
- "rev": "cb5e3fdca1de58ccbc3ef53de65bd372b48f567c",
- "type": "github"
- },
- "original": {
- "owner": "hercules-ci",
- "repo": "gitignore.nix",
- "type": "github"
- }
- },
- "nixpkgs": {
- "locked": {
- "lastModified": 1764580874,
- "owner": "cachix",
- "repo": "devenv-nixpkgs",
- "rev": "dcf61356c3ab25f1362b4a4428a6d871e84f1d1d",
- "type": "github"
- },
- "original": {
- "owner": "cachix",
- "ref": "rolling",
- "repo": "devenv-nixpkgs",
- "type": "github"
- }
- },
- "root": {
- "inputs": {
- "devenv": "devenv",
- "git-hooks": "git-hooks",
- "nixpkgs": "nixpkgs",
- "pre-commit-hooks": [
- "git-hooks"
- ]
- }
- }
- },
- "root": "root",
- "version": 7
-}
diff --git a/devenv.nix b/devenv.nix
deleted file mode 100755
index b8babeb..0000000
--- a/devenv.nix
+++ /dev/null
@@ -1,8 +0,0 @@
-{ pkgs, lib, config, inputs, ... }:
-
-{
- packages = with pkgs; [ git gcc gnumake cmake ];
- enterShell = ''
- git --version # Use packages
- '';
-}
diff --git a/devenv.yaml b/devenv.yaml
deleted file mode 100755
index 116a2ad..0000000
--- a/devenv.yaml
+++ /dev/null
@@ -1,15 +0,0 @@
-# yaml-language-server: $schema=https://devenv.sh/devenv.schema.json
-inputs:
- nixpkgs:
- url: github:cachix/devenv-nixpkgs/rolling
-
-# If you're using non-OSS software, you can set allowUnfree to true.
-# allowUnfree: true
-
-# If you're willing to use a package that's vulnerable
-# permittedInsecurePackages:
-# - "openssl-1.1.1w"
-
-# If you have more than one devenv you can merge them
-#imports:
-# - ./backend
diff --git a/src/backup.c b/src/backup.c
deleted file mode 100644
index 060777c..0000000
--- a/src/backup.c
+++ /dev/null
@@ -1,173 +0,0 @@
-#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);
- }
- free(directories);
-}
diff --git a/src/backup_streamer.c b/src/backup_streamer.c
new file mode 100644
index 0000000..3b5929b
--- /dev/null
+++ b/src/backup_streamer.c
@@ -0,0 +1,54 @@
+#include <unistd.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+#include <assert.h>
+#include <string.h>
+
+#include "lib.h"
+
+struct backup_streamer backup_streamer_init(char *cwd) {
+ struct backup_streamer output;
+ output.cwd_length = strnlen(cwd, PATH_MAX);
+ char *end = stpncpy(output.to_prefix, cwd, PATH_MAX - 3);
+ *(end++) = '.';
+ *(end++) = 'b';
+ *(end++) = 'k';
+ *(end++) = 0;
+ return output;
+}
+
+void get_backup_destination(struct backup_streamer *streamer, char* from) {
+ char *cursor = stpncpy(streamer->cursor, streamer->to_prefix, PATH_MAX);
+ from += streamer->cwd_length;
+ int n = (cursor - streamer->cursor) - 2;
+ strncpy(cursor, from, PATH_MAX - n);
+}
+
+int is_dir(char *path) {
+ struct stat info;
+ if (stat(path, &info) != 0) return -1;
+ return S_ISDIR(info.st_mode);
+}
+
+void backup_file(char *from, char *to) {
+ ensure_parents_exist(to);
+ pid_t pid;
+ if ((pid = fork()) == 0) {
+ char *argv[] = {"/usr/bin/cp", from, to, NULL};
+ execv(argv[0], argv);
+ }
+ assert(waitpid(pid, NULL, 0) != -1);
+}
+
+void ensure_parents_exist(char *path) {
+ int n = strnlen(path, PATH_MAX);
+ for (int i = 1; i < n; i++) {
+ if (path[i] != '/') continue;
+ path[i] = 0;
+ if (is_dir(path) == -1) {
+ debugf("mkdir %s\n", path);
+ assert(mkdir(path, 0755) != -1);
+ }
+ path[i] = '/';
+ }
+}
diff --git a/src/config.c b/src/config.c
deleted file mode 100644
index ab0eb05..0000000
--- a/src/config.c
+++ /dev/null
@@ -1,24 +0,0 @@
-#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
deleted file mode 100644
index fbfb8ed..0000000
--- a/src/ignore.c
+++ /dev/null
@@ -1,66 +0,0 @@
-#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
index 999e7d7..6c098c7 100755..100644
--- a/src/lib.h
+++ b/src/lib.h
@@ -1,24 +1,47 @@
-#include <unistd.h>
-#include <string.h>
-#include <stdio.h>
-#include <sys/stat.h>
-#include <dirent.h>
+#ifndef LIB_H_
+#define LIB_H_
+
+#include <git2.h>
+#include <linux/limits.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__$"
+#define debugf(...) if (debug) (void)printf("[debug] " __VA_ARGS__)
+
+int is_git_metadata_dir(char *path);
+
+struct tmp_repo {
+ git_repository* repo;
+ char path[70];
+};
+struct tmp_repo tmp_repo_init();
+void tmp_repo_deinit(struct tmp_repo repo);
+
+int check_for_backupignore();
+void copy_over_backupignore(struct tmp_repo repo);
+
+int is_ignored(struct tmp_repo repo, char *path);
+
+struct recursive_reader {
+ FILE *pfile;
+ char *line;
+ size_t capacity;
+};
+struct recursive_reader recursive_reader_init();
+int recursive_reader_read(struct recursive_reader *reader);
+void recursive_reader_deinit(struct recursive_reader reader);
+
+struct backup_streamer {
+ int cwd_length;
+ char to_prefix[PATH_MAX];
+ char cursor[PATH_MAX];
+};
+struct backup_streamer backup_streamer_init(char *cwd);
+void get_backup_destination(struct backup_streamer *streamer, char* from);
-typedef enum {
- path_type_unknown,
- path_type_directory,
- path_type_file
-} path_type;
+int is_dir(char *path);
+void backup_file(char *from, char *to);
+void ensure_parents_exist(char *path);
-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);
+int debug;
-void backup(char* target, char* ignore_instructions, int VERBOSE);
+#endif // LIB_H_
diff --git a/src/main.c b/src/main.c
index 7d33850..1d419fa 100755..100644
--- a/src/main.c
+++ b/src/main.c
@@ -1,125 +1,44 @@
-#include "lib.h"
+#include <unistd.h>
+#include <assert.h>
-#define VERSION "1.0"
+#include "lib.h"
-void help() {
- printf(
-"Backup your files\n"
-"Usage: Backbencher [OPTIONS] [input]\n"
-"Arguments:\n"
-"\t[input]... The path of the folder to backup\n"
-"Options:\n"
-"\t-i --ignore-file <filename> Specify which files to exclude\n\t\t(default = ~/.config/backbencher/.backupignore)\n"
-"\t-h --help \tPrint help\n"
-"\t-v --verbose \tVerbose logging\n"
-"\t-V --version \tPrint version\n"
- );
-}
+int main(int argc, char **argv) {
+ (void)argv;
-void assign(char* to, char* from) {
- int i = 0;
- for (; from[i] != 0; i++) {
- to[i] = from[i];
- }
- to[i] = 0;
-}
+ debug = argc > 1;
+ if (check_for_backupignore()) return 1;
+ debugf(".backupignore found\n");
-int source_main(int argc, char** argv) {
- shift(&argc, &argv);
+ struct tmp_repo repo = tmp_repo_init();
+ assert(repo.repo != NULL);
+ debugf("%s initialized\n", repo.path);
- char* ignore_file = get_default_config();
- char* target = NULL_STR;
- int VERBOSE = 0;
+ copy_over_backupignore(repo);
+ debugf("ignore file loaded\n");
- 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;
- }
- }
+ char *cwd = getcwd(NULL, 0);
+ assert(cwd != NULL);
+ struct backup_streamer streamer = backup_streamer_init(cwd);
+ free(cwd);
- if (target == NULL_STR) {
- fprintf(stderr, "Missing [input] - The folder to backup\n");
- return 1;
- }
+ struct recursive_reader reader = recursive_reader_init();
+ while (recursive_reader_read(&reader) != -1) {
+ if (is_ignored(repo, reader.line)) continue;
+ if (is_dir(reader.line)) continue;
- if (VERBOSE) {
- printf("Target folder: %s\n", target);
- printf("Using ignore file: %s\n", ignore_file);
- printf("Using verbosity: %d\n", VERBOSE);
+ get_backup_destination(&streamer, reader.line);
+ debugf("cp %s %s\n", reader.line, streamer.cursor);
+ backup_file(reader.line, streamer.cursor);
}
- 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);
+ recursive_reader_deinit(reader);
+ tmp_repo_deinit(repo);
+ git_repository_free(repo.repo);
+ git_libgit2_shutdown();
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;
-}
+#include "tmp_repo.c"
+#include "recursive_reader.c"
+#include "backup_streamer.c"
diff --git a/src/recursive_reader.c b/src/recursive_reader.c
new file mode 100644
index 0000000..0a32131
--- /dev/null
+++ b/src/recursive_reader.c
@@ -0,0 +1,21 @@
+#include <assert.h>
+
+#include "lib.h"
+
+struct recursive_reader recursive_reader_init() {
+ FILE *pipe = popen("find \"$PWD\" -print", "r");
+ assert(pipe != NULL);
+ return (struct recursive_reader) { pipe, NULL, 0 };
+}
+
+int recursive_reader_read(struct recursive_reader *reader) {
+ int n = getline(&reader->line, &reader->capacity, reader->pfile);
+ reader->line[n - 1] = 0;
+ return n;
+}
+
+void recursive_reader_deinit(struct recursive_reader reader) {
+ free(reader.line);
+ int status = pclose(reader.pfile);
+ assert(status != -1);
+}
diff --git a/src/tmp_repo.c b/src/tmp_repo.c
new file mode 100644
index 0000000..ff4536c
--- /dev/null
+++ b/src/tmp_repo.c
@@ -0,0 +1,72 @@
+#include <unistd.h>
+#include <sys/random.h>
+#include <sys/wait.h>
+#include <assert.h>
+#include <string.h>
+
+#include "lib.h"
+
+struct tmp_repo tmp_repo_init() {
+ unsigned char rng_bytes[64];
+ assert(getrandom(rng_bytes, sizeof(rng_bytes), 0) == sizeof(rng_bytes));
+
+ char repo_path[70] = "/tmp/";
+ for (int i = 0; i < 64; i++) {
+ repo_path[5 + i] = (char)('a' + rng_bytes[i] % 26);
+ }
+ repo_path[69] = '\0';
+
+ git_libgit2_init();
+
+ git_repository *repo = NULL;
+ assert(git_repository_init(&repo, repo_path, 0) == 0);
+ struct tmp_repo output;
+ memcpy(output.path, repo_path, 70);
+ output.repo = repo;
+ return output;
+}
+
+void tmp_repo_deinit(struct tmp_repo repo) {
+ pid_t pid;
+ if ((pid = fork()) == 0) {
+ char *argv[] = {"/usr/bin/rm", "-rf", repo.path, NULL};
+ execv(argv[0], argv);
+ }
+ assert(waitpid(pid, NULL, 0) != -1);
+}
+
+int check_for_backupignore() {
+ FILE *file = fopen(".backupignore", "r");
+ if (file == NULL) {
+ fprintf(stderr, "Missing .backupignore file in the current directory\n");
+ return 1;
+ } else fclose(file);
+ return 0;
+}
+
+void copy_over_backupignore(struct tmp_repo repo) {
+ char cp_destination[81];
+ snprintf(cp_destination, 81, "%s/.gitignore", repo.path);
+
+ pid_t pid;
+ if ((pid = fork()) == 0) {
+ char *argv[] = {"/usr/bin/cp", ".backupignore", cp_destination, NULL};
+ execv(argv[0], argv);
+ }
+ assert(waitpid(pid, NULL, 0) != -1);
+}
+
+int is_ignored(struct tmp_repo repo, char *path) {
+ if (is_git_metadata_dir(path)) return 0;
+ int ignored;
+ assert(git_ignore_path_is_ignored(&ignored, repo.repo, path) >= 0);
+ return ignored;
+}
+
+int is_git_metadata_dir(char *path) {
+ int n = strnlen(path, PATH_MAX);
+ for (int i = 0; i < n - 6; i++) {
+ if (strncmp(path + i, "/.git/", 6) == 0) return 1;
+ }
+ return 0;
+}