├── .clang-format ├── .clangd ├── .gitignore ├── .pre-commit-config.yaml ├── LICENSE ├── Makefile ├── README.md ├── doc └── bor.1 ├── scripts ├── brave.sh ├── chromium.sh ├── falkon.sh ├── firefox.sh ├── google-chrome-beta.sh ├── google-chrome-stable.sh ├── google-chrome-unstable.sh ├── opera.sh ├── vivaldi-snapshot.sh └── vivaldi.sh ├── src ├── config.c ├── include │ ├── config.h │ ├── ini.h │ ├── log.h │ ├── overlay.h │ ├── sync.h │ ├── types.h │ └── util.h ├── ini.c ├── log.c ├── main.c ├── overlay.c ├── sync.c ├── teeny-sha1.c ├── types.c └── util.c ├── systemd ├── system │ └── bor-sleep@.service └── user │ ├── bor-resync.service │ ├── bor-resync.timer │ ├── bor-sleep-resync.service │ ├── bor-sleep.target │ └── bor.service └── test └── start_test /.clang-format: -------------------------------------------------------------------------------- 1 | # https://github.com/torvalds/linux/blob/master/.clang-format 2 | # SPDX-License-Identifier: GPL-2.0 3 | # 4 | # clang-format configuration file. Intended for clang-format >= 11. 5 | # 6 | # For more information, see: 7 | # 8 | # Documentation/dev-tools/clang-format.rst 9 | # https://clang.llvm.org/docs/ClangFormat.html 10 | # https://clang.llvm.org/docs/ClangFormatStyleOptions.html 11 | # 12 | --- 13 | AccessModifierOffset: -4 14 | AlignAfterOpenBracket: Align 15 | AlignConsecutiveAssignments: false 16 | AlignConsecutiveDeclarations: false 17 | AlignEscapedNewlines: Left 18 | AlignOperands: true 19 | AlignTrailingComments: false 20 | AllowAllParametersOfDeclarationOnNextLine: false 21 | AllowShortBlocksOnASingleLine: false 22 | AllowShortCaseLabelsOnASingleLine: false 23 | AllowShortFunctionsOnASingleLine: None 24 | AllowShortIfStatementsOnASingleLine: false 25 | AllowShortLoopsOnASingleLine: false 26 | AlwaysBreakAfterDefinitionReturnType: None 27 | AlwaysBreakAfterReturnType: None 28 | AlwaysBreakBeforeMultilineStrings: false 29 | AlwaysBreakTemplateDeclarations: false 30 | BinPackArguments: true 31 | BinPackParameters: true 32 | BraceWrapping: 33 | AfterClass: false 34 | AfterControlStatement: false 35 | AfterEnum: false 36 | AfterFunction: true 37 | AfterNamespace: true 38 | AfterObjCDeclaration: false 39 | AfterStruct: false 40 | AfterUnion: false 41 | AfterExternBlock: false 42 | BeforeCatch: false 43 | BeforeElse: false 44 | IndentBraces: false 45 | SplitEmptyFunction: true 46 | SplitEmptyRecord: true 47 | SplitEmptyNamespace: true 48 | BreakBeforeBinaryOperators: None 49 | BreakBeforeBraces: Custom 50 | BreakBeforeInheritanceComma: false 51 | BreakBeforeTernaryOperators: false 52 | BreakConstructorInitializersBeforeComma: false 53 | BreakConstructorInitializers: BeforeComma 54 | BreakAfterJavaFieldAnnotations: false 55 | BreakStringLiterals: false 56 | ColumnLimit: 80 57 | CommentPragmas: '^ IWYU pragma:' 58 | CompactNamespaces: false 59 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 60 | ConstructorInitializerIndentWidth: 8 61 | ContinuationIndentWidth: 8 62 | Cpp11BracedListStyle: false 63 | DerivePointerAlignment: false 64 | DisableFormat: false 65 | ExperimentalAutoDetectBinPacking: false 66 | FixNamespaceComments: false 67 | IncludeBlocks: Preserve 68 | IncludeCategories: 69 | - Regex: '.*' 70 | Priority: 1 71 | IncludeIsMainRegex: '(Test)?$' 72 | IndentCaseLabels: false 73 | IndentGotoLabels: false 74 | IndentPPDirectives: None 75 | IndentWidth: 8 76 | IndentWrappedFunctionNames: false 77 | JavaScriptQuotes: Leave 78 | JavaScriptWrapImports: true 79 | KeepEmptyLinesAtTheStartOfBlocks: false 80 | MacroBlockBegin: '' 81 | MacroBlockEnd: '' 82 | MaxEmptyLinesToKeep: 1 83 | NamespaceIndentation: None 84 | ObjCBinPackProtocolList: Auto 85 | ObjCBlockIndentWidth: 8 86 | ObjCSpaceAfterProperty: true 87 | ObjCSpaceBeforeProtocolList: true 88 | 89 | # Taken from git's rules 90 | PenaltyBreakAssignment: 10 91 | PenaltyBreakBeforeFirstCallParameter: 30 92 | PenaltyBreakComment: 10 93 | PenaltyBreakFirstLessLess: 0 94 | PenaltyBreakString: 10 95 | PenaltyExcessCharacter: 100 96 | PenaltyReturnTypeOnItsOwnLine: 60 97 | 98 | PointerAlignment: Right 99 | ReflowComments: false 100 | SortIncludes: false 101 | SortUsingDeclarations: false 102 | SpaceAfterCStyleCast: false 103 | SpaceAfterTemplateKeyword: true 104 | SpaceBeforeAssignmentOperators: true 105 | SpaceBeforeCtorInitializerColon: true 106 | SpaceBeforeInheritanceColon: true 107 | SpaceBeforeParens: ControlStatementsExceptForEachMacros 108 | SpaceBeforeRangeBasedForLoopColon: true 109 | SpaceInEmptyParentheses: false 110 | SpacesBeforeTrailingComments: 1 111 | SpacesInAngles: false 112 | SpacesInContainerLiterals: false 113 | SpacesInCStyleCastParentheses: false 114 | SpacesInParentheses: false 115 | SpacesInSquareBrackets: false 116 | Standard: Cpp03 117 | TabWidth: 4 118 | UseTab: Never 119 | ... 120 | -------------------------------------------------------------------------------- /.clangd: -------------------------------------------------------------------------------- 1 | CompileFlags: 2 | Remove: -f* 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | test/* 3 | !test/start_test 4 | *.vim 5 | vgcore.* 6 | .* 7 | !.gitignore 8 | !.clang-format 9 | !.clang-tidy 10 | !.clangd 11 | !.pre-commit-config.yaml 12 | version.txt 13 | compile_commands.json 14 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # See https://pre-commit.com for more information 2 | # See https://pre-commit.com/hooks.html for more hooks 3 | repos: 4 | - repo: https://github.com/pre-commit/pre-commit-hooks 5 | rev: v5.0.0 6 | hooks: 7 | - id: trailing-whitespace 8 | args: [--markdown-linebreak-ext=md] 9 | - id: end-of-file-fixer 10 | - repo: https://github.com/pre-commit/mirrors-clang-format 11 | rev: v19.1.5 12 | hooks: 13 | - id: clang-format 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Foxe Chen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ifndef PREFIX 2 | PREFIX := /usr/local 3 | endif 4 | 5 | CC := gcc 6 | MACROS := -DINI_STOP_ON_FIRST_ERROR=1 -DINI_ALLOW_NO_VALUE=1 -DVERSION=\"$(shell git describe)\" 7 | CLIBS := $(shell pkg-config --cflags --libs libcap) 8 | CFLAGS := -Wextra -Wall -Wshadow -Wcast-align=strict -Wno-format-truncation -std=gnu11 $(MACROS) 9 | DEBUG_FLAGS := -ggdb -g3 -DDEBUG -fsanitize=address,undefined -fanalyzer 10 | REL_FLAGS := -O2 11 | 12 | REL_PREFIX := release 13 | DEBUG_PREFIX := debug 14 | 15 | ifeq ($(RELEASE), 1) 16 | BUILD_DIR := build/$(REL_PREFIX) 17 | CFLAGS += $(REL_FLAGS) 18 | else 19 | BUILD_DIR := build/$(DEBUG_PREFIX) 20 | CFLAGS += $(DEBUG_FLAGS) 21 | endif 22 | ifeq ($(NOSYSTEMD), 1) 23 | CFLAGS += -DNOSYSTEMD 24 | endif 25 | ifeq ($(NOOVERLAY), 1) 26 | CFLAGS += -DNOOVERLAY 27 | endif 28 | 29 | 30 | BIN_PATH := $(BUILD_DIR)/bin 31 | OBJ_PATH := $(BUILD_DIR)/bin 32 | DEP_PATH := $(BUILD_DIR)/dep 33 | 34 | SRC_PATH := src 35 | INCLUDE_PATH := ./src/include 36 | 37 | TARGET_NAME := bor 38 | TARGET := $(BIN_PATH)/$(TARGET_NAME) 39 | 40 | SRC := main.c log.c util.c config.c types.c sync.c overlay.c ini.c teeny-sha1.c 41 | OBJ := $(addprefix $(OBJ_PATH)/, $(SRC:.c=.o)) 42 | DEPS := $(addprefix $(DEP_PATH)/, $(notdir $(OBJ:.o=.d))) 43 | 44 | # set version (https://stackoverflow.com/a/69266365) 45 | version=$(shell git describe --always --dirty=-dirty) 46 | $(shell echo ${version} > version.tmp && \ 47 | { cmp -s version.tmp version.txt || cp version.tmp version.txt; } && \ 48 | rm -f version.tmp) 49 | 50 | all: prebuild $(TARGET) 51 | 52 | prebuild: 53 | @mkdir -p $(BIN_PATH) $(SRC_PATH) $(DEP_PATH) $(OBJ_PATH) $(INCLUDE_PATH) $(BUILD_PREFIX) 54 | 55 | rebuild: clean all 56 | 57 | clean: 58 | rm -fr build/release/*/* 59 | rm -fr build/debug/*/* 60 | 61 | $(TARGET): $(OBJ) 62 | $(CC) $(CFLAGS) -o $@ $^ $(CLIBS) 63 | 64 | # so that VERSION macro is changed when git describe has changed 65 | $(OBJ_PATH)/main.o: $(SRC_PATH)/main.c version.txt 66 | $(CC) $(CFLAGS) -I$(INCLUDE_PATH) -MD -MP -MF $(DEP_PATH)/main.d -o $@ -c $< $(CLIBS) 67 | 68 | $(OBJ_PATH)/%.o: $(SRC_PATH)/%.c 69 | $(CC) $(CFLAGS) -I$(INCLUDE_PATH) -MD -MP -MF $(DEP_PATH)/$(notdir $(basename $@).d) -o $@ -c $< $(CLIBS) 70 | 71 | test: all 72 | test/start_test 73 | 74 | setcap: 75 | sudo setcap 'cap_dac_override,cap_sys_admin=p' $(TARGET) 76 | 77 | install: install-files install-systemd install-man 78 | 79 | install-files: 80 | install -dm 755 $(PREFIX)/share/bor/scripts 81 | install -Dm 755 $(BUILD_DIR)/bin/bor $(PREFIX)/bin/bor 82 | install -Dm 644 scripts/*.sh $(PREFIX)/share/bor/scripts 83 | 84 | install-systemd: 85 | install -dm 755 $(PREFIX)/lib/systemd/user 86 | install -dm 755 $(PREFIX)/lib/systemd/system 87 | install -Dm 644 systemd/user/* $(PREFIX)/lib/systemd/user/ 88 | install -Dm 644 systemd/system/* $(PREFIX)/lib/systemd/system/ 89 | 90 | install-man: 91 | install -dm 755 $(PREFIX)/share/man/man1 92 | install -Dm 644 doc/bor.1 $(PREFIX)/share/man/man1/ 93 | 94 | install-cap: 95 | setcap 'cap_dac_override,cap_sys_admin=p' $(PREFIX)/bin/bor 96 | 97 | -include $(DEPS) 98 | 99 | .PHONY: all clean prebuild rebuild run setcap install install-files install-systemd install-man install-cap 100 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Browser-on-ram - Sync browser related directories to RAM for Linux 2 | 3 | * Speeds up browser 4 | * Reduces disk writes 5 | * Automatically backs up data 6 | * Supports mounting data with an overlay filesystem for reduced memory usage 7 | 8 | # Warning 9 | 10 | RAM is not persistent! You may lose up to an hour of browsing usage data with the automatic resync timer if your system suddenly exits/crashes. 11 | Moreover, before using this program for the first time, make sure to backup your data! I believe this software to 12 | be stable in terms of not randomly wiping your data, and have included safety/integrity checks & repairs, but it's 13 | always better to be safe. Additionally, make sure you have enough space in your 14 | runtime directory before running, depending on how big your browser directories are. 15 | 16 | # Supported Browsers 17 | 18 | In brackets are the names to be used in the config file 19 | 20 | * Firefox (firefox) 21 | * Chromium (chromium) 22 | * Google-chrome (google-chrome-stable, google-chrome-beta, google-chrome-unstable) 23 | * Vivaldi (vivaldi, vivaldi-snapshot) 24 | * Opera (opera) 25 | * Brave (brave) 26 | * Falkon (falkon) 27 | 28 | # Dependencies 29 | 30 | ``` 31 | gcc 32 | libcap # only if NOOVERLAY=1 is not specified 33 | rsync 34 | ``` 35 | 36 | # Install 37 | 38 | [AUR package](https://aur.archlinux.org/packages/browser-on-ram-git) 39 | 40 | ```sh 41 | # remove RELEASE=1 for debug builds (which require libasan and libubsan) 42 | # add NOSYSTEMD=1 to not compile systemd integration 43 | # add NOOVERLAY=1 to not compile the overlay feature (removes dependency on libcap) 44 | RELEASE=1 make 45 | 46 | sudo RELEASE=1 make install 47 | 48 | # enable overlay filesystem capabilities (if overlay support is compiled) 49 | sudo make install-cap 50 | ``` 51 | 52 | # Usage 53 | 54 | The recommended way is to use the systemd service, you can enable it it via 55 | `systemctl --user enable --now bor.service`. This will also start the hourly 56 | resync timer `bor-resync.timer`. If you want to resync on sleep, 57 | then enable run `systemctl enable bor-sleep@$(whoami).service` and 58 | `systemctl --user enable bor-sleep-resync.service`. 59 | 60 | The executable name is `bor`. To see the current status, run `bor --status`. Use 61 | `bor --help` for additional info. 62 | 63 | # Config 64 | Sample config file with defaults, in ini format (in $XDG_CONFIG_HOME/bor/bor.conf) 65 | ```ini 66 | # = true or false 67 | 68 | [config] 69 | # sync cache directories (if the browser has ones) 70 | enable_cache = false 71 | 72 | # resync them (will be resynced when unsynced however) 73 | resync_cache = true 74 | 75 | # enable overlay filesystem 76 | enable_overlay = false 77 | 78 | # when resyncing, remount the overlay in order to clear the upper directory 79 | # (where changes are stored on the tmpfs) 80 | reset_overlay = false 81 | 82 | # maximum number of log entries to store in log file 83 | # (0 to disable logging to a file and a negative number for infinite entries) 84 | max_log_entries = 10 85 | 86 | # default is no browser 87 | [browsers] 88 | mybrowser 89 | myotherbrowser 90 | # ... 91 | ``` 92 | 93 | # Overlay 94 | 95 | Browser-on-ram can mount your data on an overlay filesystem, which can significantly reduce memory usage, as only 96 | changed data needs to be stored on RAM. To do this, it uses Linux capabilities (specifically SYS_ADMIN_CAP and 97 | SYS_DAC_OVERRIDE), which are unfortunately very broad (But I supposed better than setuid). By default they are 98 | in permitted mode (doesn't actually affect the program), and are only raised to effective mode when mounting the 99 | overlay filesystem and deleting the root owned work directory needed by the overlay filesystem on unsync. 100 | If anything related to interacting with capabilties fails, the program immediately exits. 101 | 102 | # 103 | 104 | # Adding Browsers 105 | 106 | Browser-on-ram uses shell scripts that output the information needed to sync them. You can use `echo` for this. You 107 | can see existing scripts in this repository for reference in the `scripts` directory. In short they are in the format 108 | of an ini file, parsed line by line: 109 | ```sh 110 | # browser-on-ram will automatically set XDG_CONFIG_HOME, XDG_CACHE_HOME, and 111 | # XDG_DATA_HOME environment variables when calling the script 112 | 113 | # process name of the browser 114 | procname = mybrowser 115 | 116 | # cache directories (usually in $HOME/.cache) 117 | cache = /home/user/.cache/mycache 118 | 119 | # profile directory (such as an individual profile or a single monolithic one) 120 | profile = /home/user/.config/mybrowser 121 | 122 | # ... 123 | ``` 124 | These should be placed in `$XDG_CONFIG_HOME/bor/scripts`, `/usr/local/share/bor/scripts`, `/usr/share/bor/scripts` with `.sh` extension. 125 | The first one found in that order is used. Please also make a pull request too! 126 | 127 | # Design 128 | 129 | Browser-on-ram first parses the output from the shell script for each browser, 130 | and gets a list of directories to sync. It then copies each directory to the 131 | tmpfs, each prefixed with a SHA1 hash of the original path. Then, the directory 132 | is moved to the backup location and a symlink is created to the tmpfs. The 133 | reason for a singular location for backups is to allow for one single overlay 134 | filesystem to store all directories instead of per directory such as PSD. 135 | 136 | # Rationale and difference from profile-sync-daemon 137 | 138 | Browser-on-ram supports syncing cache directories. Another reason is that is that I was dismayed with the security issues of the overlay 139 | feature of PSD. I'm not saying browser-on-ram is completely secure, but it shouldn't have any blatant security holes. Addtionally, 140 | You can also add your own browsers in $XDG_CONFIG_HOME/bor/scripts too. 141 | 142 | # Projects Used 143 | * [inih](https://github.com/benhoyt/inih) - ini format parsing 144 | * [teeny-sha1](https://github.com/CTrabant/teeny-sha1) - creating hashes for directories 145 | * [profile-sync-daemon](https://github.com/graysky2/profile-sync-daemon) - inspiration 146 | 147 | # License 148 | [MIT License](LICENSE) 149 | -------------------------------------------------------------------------------- /doc/bor.1: -------------------------------------------------------------------------------- 1 | .TH bor 1 2 | .SH NAME 3 | Browser\-on\-ram (bor) \- Sync browser related directories to RAM for Linux 4 | .SH SYNOPSIS 5 | .B bor [\fIoption\fR] ... 6 | .SH DESCRIPTION 7 | Browser-on-ram is a program that syncs browser related directories to RAM. The benefits of this includes: 8 | .RS 9 | .IP \(bu 3 10 | Speeds up browser 11 | .IP \(bu 3 12 | Reduces disk writes 13 | .IP \(bu 3 14 | Automatically backs up data 15 | .IP \(bu 3 16 | Supports mounting data with an overlay filesystem for reduced memory usage 17 | .RE 18 | .SH WARNING 19 | RAM is not persistent! You may lose up to an hour of browsing usage data with the automatic resync timer if your system suddenly exits/crashes. 20 | Moreover, before using this program for the first time, make sure to backup your data! Additionally, make sure you have enough space in your 21 | runtime directory before running, depending on how 22 | big your browser directories are. 23 | .SH USAGE 24 | The recommended way is to use the systemd service, you can enable it it via \fBsystemctl --user enable --now bor.service\fR. This will also start the hourly 25 | resync timer \fIbor-resync.timer\fR. If you want to resync on sleep, then enable run \fBsystemctl enable bor-sleep@$(whoami).service\fR and \fBsystemctl 26 | --user enable bor-sleep-resync.service\fR. The executable name is \fIbor\fR. To see the current status, run \fBbor --status\fR. Use \fBbor --help\fR for additional info. 27 | .SH OPTIONS 28 | .TP 29 | .BR \-v ", " \-\-version 30 | show version 31 | .TP 32 | .BR \-V ", " \-\-verbose 33 | show debug logs 34 | .TP 35 | .BR \-h ", " \-\-help 36 | show help message 37 | .TP 38 | .BR \-s ", " \-\-sync 39 | synchronize all browsers to RAM 40 | .TP 41 | .BR \-u ", " \-\-unsync 42 | unsynchronize all browsers to RAM 43 | .TP 44 | .BR \-r ", " \-\-resync 45 | resynchronize all browsers to RAM 46 | .TP 47 | .BR \-c ", " \-\-clean 48 | remove detected recovery directories (from previous sessions) 49 | .TP 50 | .BR \-x ", " \-\-rm_cache 51 | clear cache directories 52 | .TP 53 | .BR \-p ", " \-\-status 54 | show current configuration and state 55 | 56 | .SH CONFIG 57 | Sample config file with defaults, in ini format (in $XDG_CACHE_HOME/bor/bor.conf): 58 | 59 | .RS 60 | .ft CR 61 | .nf 62 | .eo 63 | # = true or false 64 | 65 | [config] 66 | # sync cache directories (if the browser has ones) 67 | enable_cache = false 68 | 69 | # resync them (will be resynced when unsynced however) 70 | resync_cache = true 71 | 72 | # enable overlay filesystem 73 | enable_overlay = false 74 | 75 | # when resyncing, remount the overlay in order to clear the upper directory 76 | # (where changes are stored on the tmpfs) 77 | reset_overlay = false 78 | 79 | # maximum number of log entries to store in log file 80 | # (0 to disable logging to a file and a negative number for infinite entries) 81 | max_log_entires = 10 82 | 83 | # default is no browser 84 | [browsers] 85 | mybrowser 86 | myotherbrowser 87 | # ... 88 | .ec 89 | .fi 90 | .ft R 91 | .RE 92 | .SH SUPPORTED BROWSERS 93 | List of supported browsers, with the config names in brackets: 94 | .RS 95 | .IP \(bu 3 96 | Firefox (firefox) 97 | .IP \(bu 3 98 | Chromium (chromium) 99 | .IP \(bu 3 100 | Google-chrome (google-chrome-stable, google-chrome-beta, google-chrome-unstable) 101 | .IP \(bu 3 102 | Vivaldi (vivaldi, vivaldi-snapshot) 103 | .IP \(bu 3 104 | Opera (opera) 105 | .IP \(bu 3 106 | Brave (brave) 107 | .IP \(bu 3 108 | Falkon (falkon) 109 | .RE 110 | .SH OVERLAY 111 | Browser-on-ram can mount your data on an overlay filesystem, which can significantly reduce memory usage, as only changed data needs to be stored on RAM. To 112 | do this, it uses Linux capabilities (specifically \fISYS_ADMIN_CAP\fR and \fISYS_DAC_OVERRIDE\fR). These capabilities are only raised to effective mode when 113 | mounting the overlay filesystem and deleting the root owned work directory needed by the overlay filesystem on unsync. If anything related to interacting 114 | with capabilties fails, the program immediately exits. 115 | .SH ADDING BROWSERS 116 | Browser-on-ram uses shell scripts that output the information needed to sync them. You can use echo for this. 117 | .br 118 | In short they are in the format of an ini file, parsed line by line: 119 | 120 | .RS 121 | .ft CR 122 | .nf 123 | .eo 124 | # browser-on-ram will automatically set XDG_CONFIG_HOME, XDG_CACHE_HOME, and 125 | # XDG_DATA_HOME environment variables when calling the script 126 | 127 | # process name of the browser 128 | procname = mybrowser 129 | 130 | # cache directories (usually in $HOME/.cache) 131 | cache = /home/user/.cache/mycache 132 | 133 | # profile directory (such as an individual profile or a single monolithic one) 134 | profile = /home/user/.config/mybrowser 135 | 136 | # ... 137 | .ec 138 | .fi 139 | .ft R 140 | .RE 141 | 142 | These should be placed in $XDG_CONFIG_HOME/bor/scripts, /usr/local/share/bor/scripts, /usr/share/bor/scripts with .sh extension. 143 | .br 144 | The first one found in that order is used. 145 | .SH DESIGN 146 | Browser-on-ram first parses the output from the shell script for each browser, and gets a list of directories to sync. It then copies each directory to the 147 | tmpfs, each prefixed with a SHA1 hash of the original path. Then, the directory is moved to the backup location and a symlink is created to the tmpfs. 148 | .SH AUTHOR 149 | Written by Foxe Chen (64-bitman). 150 | .SH REPORTING BUGS 151 | Please file an issue report at \fIhttps://github.com/64-bitman/browser-on-ram/issues\fR. 152 | .SH REPOSITORY 153 | The source code repository is at \fIhttps://github.com/64-bitman/browser-on-ram\fR. 154 | -------------------------------------------------------------------------------- /scripts/brave.sh: -------------------------------------------------------------------------------- 1 | echo procname = brave 2 | echo profile = "$XDG_CONFIG_HOME/BraveSoftware" 3 | -------------------------------------------------------------------------------- /scripts/chromium.sh: -------------------------------------------------------------------------------- 1 | echo procname = chromium 2 | 3 | echo profile = "$XDG_CONFIG_HOME/chromium" 4 | echo cache = "XDG_CACHE_HOME/chromium" 5 | -------------------------------------------------------------------------------- /scripts/falkon.sh: -------------------------------------------------------------------------------- 1 | echo procname = falkon 2 | 3 | echo profile = "$XDG_CONFIG_HOME/falkon" 4 | echo cache = "$XDG_CACHE_HOME/falkon" 5 | -------------------------------------------------------------------------------- /scripts/firefox.sh: -------------------------------------------------------------------------------- 1 | echo procname = firefox 2 | 3 | # https://github.com/graysky2/profile-sync-daemon/blob/master/common/browsers/firefox 4 | while read -r profileItem; do 5 | if [[ $(echo "$profileItem" | cut -c1) = "/" ]]; then 6 | # path is not relative 7 | echo "profile = $profileItem" 8 | echo "cache = $XDG_CACHE_HOME/mozilla/firefox/$(basename "$profileItem")" 9 | else 10 | # we need to append the default path to give a 11 | # fully qualified path 12 | echo "profile = $HOME/.mozilla/firefox/$profileItem" 13 | echo "cache = $XDG_CACHE_HOME/mozilla/firefox/$profileItem" 14 | fi 15 | done < <(grep '[Pp]'ath= "$HOME"/.mozilla/firefox/profiles.ini | sed 's/[Pp]ath=//') 16 | -------------------------------------------------------------------------------- /scripts/google-chrome-beta.sh: -------------------------------------------------------------------------------- 1 | echo procname = chrome 2 | 3 | if [[ -n "$CHROME_CONFIG_HOME" ]]; then 4 | echo "profile = $CHROME_CONFIG_HOME" 5 | else 6 | echo "profile = $XDG_CONFIG_HOME/google-chrome-beta" 7 | fi 8 | echo "cache = $XDG_CACHE_HOME/google-chrome-beta" 9 | -------------------------------------------------------------------------------- /scripts/google-chrome-stable.sh: -------------------------------------------------------------------------------- 1 | echo procname = chrome 2 | 3 | if [[ -n "$CHROME_CONFIG_HOME" ]]; then 4 | echo "profile = $CHROME_CONFIG_HOME" 5 | else 6 | echo "profile = $XDG_CONFIG_HOME/google-chrome" 7 | fi 8 | echo "cache = $XDG_CACHE_HOME/google-chrome" 9 | -------------------------------------------------------------------------------- /scripts/google-chrome-unstable.sh: -------------------------------------------------------------------------------- 1 | echo procname = chrome 2 | 3 | if [[ -n "$CHROME_CONFIG_HOME" ]]; then 4 | echo "profile = $CHROME_CONFIG_HOME" 5 | else 6 | echo "profile = $XDG_CONFIG_HOME/google-chrome-unstable" 7 | fi 8 | echo "cache = $XDG_CACHE_HOME/google-chrome-unstable" 9 | -------------------------------------------------------------------------------- /scripts/opera.sh: -------------------------------------------------------------------------------- 1 | echo procname = opera 2 | 3 | echo profile = "$XDG_CONFIG_HOME/opera" 4 | echo cache = "$XDG_CACHE_HOME/opera" 5 | -------------------------------------------------------------------------------- /scripts/vivaldi-snapshot.sh: -------------------------------------------------------------------------------- 1 | echo procname = vivaldi-bin 2 | 3 | echo profile = "$XDG_CONFIG_HOME/vivaldi-snapshot" 4 | echo cache = "$XDG_CACHE_HOME/vivaldi-snapshot" 5 | -------------------------------------------------------------------------------- /scripts/vivaldi.sh: -------------------------------------------------------------------------------- 1 | echo procname = vivaldi-bin 2 | 3 | echo profile = "$XDG_CONFIG_HOME/vivaldi" 4 | echo cache = "$XDG_CACHE_HOME/vivaldi" 5 | -------------------------------------------------------------------------------- /src/config.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include "config.h" 3 | #include "log.h" 4 | #include "util.h" 5 | #include "ini.h" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | // OPT_END -> signify end of opt array 13 | enum OptType { OPT_END, OPT_BOOL, OPT_INT }; 14 | struct Opt { 15 | char *name; 16 | void *data; 17 | enum OptType type; 18 | }; 19 | 20 | static int set_environment(void); 21 | static int parse_config(const char *config_file); 22 | static int parse_config_handler(void *user, const char *section, 23 | const char *name, const char *value); 24 | static int section_config_handler(const char *name, const char *value); 25 | static int section_browsers_handler(const char *name); 26 | static struct Browser *run_browser_sh(const char *browsername); 27 | static int parse_browser_sh(const char *path, struct Browser *browser); 28 | static int parse_browser_sh_handler(void *user, const char *UNUSED(section), 29 | const char *name, const char *value); 30 | 31 | struct ConfigSkel CONFIG = { 0 }; 32 | struct PathsSkel PATHS = { 0 }; 33 | 34 | static struct Opt OPTS[] = { 35 | #ifndef NOOVERLAY 36 | { "enable_overlay", &CONFIG.enable_overlay, OPT_BOOL }, 37 | #endif 38 | { "enable_cache", &CONFIG.enable_cache, OPT_BOOL }, 39 | { "resync_cache", &CONFIG.resync_cache, OPT_BOOL }, 40 | { "reset_overlay", &CONFIG.reset_overlay, OPT_BOOL }, 41 | { "max_log_entries", &CONFIG.max_log_entries, OPT_INT }, 42 | { NULL, NULL, OPT_END } 43 | }; 44 | 45 | // initialize paths (does not create them) 46 | int init_paths(void) 47 | { 48 | plog(LOG_DEBUG, "initializing paths"); 49 | 50 | if (set_environment() == -1) { 51 | return -1; 52 | } 53 | 54 | snprintf(PATHS.runtime, PATH_MAX, "%s/bor", getenv("XDG_RUNTIME_DIR")); 55 | snprintf(PATHS.tmpfs, PATH_MAX, "%s/tmpfs", PATHS.runtime); 56 | snprintf(PATHS.config, PATH_MAX, "%s/bor", getenv("XDG_CONFIG_HOME")); 57 | snprintf(PATHS.backups, PATH_MAX, "%s/backups", PATHS.config); 58 | snprintf(PATHS.logs, PATH_MAX, "%s/logs", PATHS.config); 59 | snprintf(PATHS.share_dir, PATH_MAX, "/usr/share/bor/"); 60 | snprintf(PATHS.share_dir_local, PATH_MAX, "/usr/local/share/bor"); 61 | 62 | #ifndef NOOVERLAY 63 | snprintf(PATHS.overlay_upper, PATH_MAX, "%s/upper", PATHS.runtime); 64 | snprintf(PATHS.overlay_work, PATH_MAX, "%s/work", PATHS.runtime); 65 | #endif 66 | 67 | plog(LOG_DEBUG, "config dir: %s", PATHS.config); 68 | plog(LOG_DEBUG, "runtime dir: %s", PATHS.runtime); 69 | 70 | return 0; 71 | } 72 | 73 | // set the required environment variables 74 | static int set_environment(void) 75 | { 76 | char xdg_config[PATH_MAX], xdg_cache[PATH_MAX], xdg_run[PATH_MAX], 77 | xdg_data[PATH_MAX]; 78 | 79 | snprintf(xdg_config, PATH_MAX, "%s/.config", getenv("HOME")); 80 | snprintf(xdg_cache, PATH_MAX, "%s/.cache", getenv("HOME")); 81 | snprintf(xdg_run, PATH_MAX, "/run/user/%d", getuid()); 82 | snprintf(xdg_data, PATH_MAX, "%s/.local/share", getenv("HOME")); 83 | 84 | update_string(xdg_config, PATH_MAX, getenv("XDG_CONFIG_HOME")); 85 | update_string(xdg_cache, PATH_MAX, getenv("XDG_CACHE_HOME")); 86 | update_string(xdg_run, PATH_MAX, getenv("XDG_RUNTIME_DIR")); 87 | update_string(xdg_data, PATH_MAX, getenv("XDG_DATA_HoME")); 88 | 89 | trim(xdg_config); 90 | trim(xdg_cache); 91 | trim(xdg_run); 92 | trim(xdg_data); 93 | 94 | if (setenv("XDG_CONFIG_HOME", xdg_config, true) == -1 || 95 | setenv("XDG_CACHE_HOME", xdg_cache, true) == -1 || 96 | setenv("XDG_DATA_HOME", xdg_data, true) == -1 || 97 | setenv("XDG_RUNTIME_DIR", xdg_run, true) == -1) { 98 | plog(LOG_ERROR, "failed setting environment"); 99 | PERROR(); 100 | return -1; 101 | } 102 | 103 | return 0; 104 | } 105 | 106 | // initialize and parse config, requires paths to be initialized before 107 | int init_config(bool save_config) 108 | { 109 | struct stat sb; 110 | 111 | plog(LOG_DEBUG, "initializing config"); 112 | 113 | // defaults 114 | #ifndef NOOVERLAY 115 | CONFIG.enable_overlay = false; 116 | #endif 117 | CONFIG.enable_cache = false; 118 | CONFIG.resync_cache = true; 119 | CONFIG.reset_overlay = false; 120 | CONFIG.max_log_entries = 10; 121 | 122 | char borconf[PATH_MAX], dotborconf[PATH_MAX]; 123 | 124 | snprintf(borconf, PATH_MAX, "%s/bor.conf", PATHS.config); 125 | snprintf(dotborconf, PATH_MAX, "%s/.bor.conf", PATHS.config); 126 | 127 | if (!save_config) { 128 | if (parse_config(borconf) == -1) { 129 | return -1; 130 | } 131 | return 0; 132 | } 133 | 134 | // use .bor.conf if it exists, else copy bor.conf as .bor.conf 135 | // this is so changes don't affect current sync session 136 | if (FEXISTS(borconf)) { 137 | if (!FEXISTS(dotborconf)) { 138 | if (copy_rfile(borconf, dotborconf) == -1) { 139 | plog(LOG_ERROR, "failed copying config file"); 140 | PERROR(); 141 | return -1; 142 | } 143 | FILE *fp = fopen(dotborconf, "a"); 144 | 145 | if (fp != NULL) { 146 | fprintf(fp, "# DO NOT EDIT THIS FILE!\n"); 147 | fclose(fp); 148 | } 149 | 150 | if (chmod(dotborconf, 0444) == -1) { 151 | PERROR(); 152 | } 153 | } 154 | } else { 155 | plog(LOG_ERROR, "config file does not exist"); 156 | return -1; 157 | } 158 | 159 | if (parse_config(dotborconf) == -1) { 160 | return -1; 161 | } 162 | return 0; 163 | } 164 | 165 | static int parse_config(const char *config_file) 166 | { 167 | plog(LOG_DEBUG, "parsing config file"); 168 | 169 | if (ini_parse(config_file, parse_config_handler, NULL) != 0) { 170 | plog(LOG_ERROR, "failed parsing config file"); 171 | return -1; 172 | } 173 | 174 | return 0; 175 | } 176 | 177 | static int parse_config_handler(void *UNUSED(user), const char *section, 178 | const char *name, const char *value) 179 | { 180 | int err = 0; 181 | if (STR_EQUAL(section, "config")) { 182 | err = section_config_handler(name, value); 183 | } else if (STR_EQUAL(section, "browsers")) { 184 | err = section_browsers_handler(name); 185 | } else { 186 | plog(LOG_WARN, "unknown config section '%s'", section); 187 | return 0; 188 | } 189 | 190 | return (err == -1) ? 0 : 1; 191 | } 192 | 193 | // for [config] section of config file 194 | static int section_config_handler(const char *name, const char *value) 195 | { 196 | if (value == NULL) { 197 | plog(LOG_ERROR, "key '%s' does not have a value", name); 198 | return -1; 199 | } 200 | 201 | for (size_t i = 0; OPTS[i].type != OPT_END; i++) { 202 | if (!STR_EQUAL(OPTS[i].name, name)) { 203 | continue; 204 | } 205 | switch (OPTS[i].type) { 206 | case OPT_BOOL: 207 | if (STR_EQUAL(value, "true")) { 208 | *(bool *)(OPTS[i].data) = true; 209 | } else if (STR_EQUAL(value, "false")) { 210 | *(bool *)(OPTS[i].data) = false; 211 | } else { 212 | plog(LOG_WARN, 213 | "unknown bool value '%s' for '%s'," 214 | " defaulting to false", 215 | value, name); 216 | *(bool *)(OPTS[i].data) = false; 217 | } 218 | break; 219 | case OPT_INT: { 220 | long int num = strtol(value, NULL, 10); 221 | 222 | *(int *)(OPTS[i].data) = (int)num; 223 | break; 224 | } 225 | default: 226 | continue; 227 | } 228 | return 0; 229 | } 230 | plog(LOG_WARN, "unknown config option '%s'", name); 231 | return -1; 232 | } 233 | 234 | // for [browsers] section, which only have keys for browser names, no values for each 235 | static int section_browsers_handler(const char *name) 236 | { 237 | struct Browser *browser = run_browser_sh(name); 238 | 239 | if (browser == NULL) { 240 | plog(LOG_ERROR, "failed running browser script"); 241 | return -1; 242 | } 243 | CONFIG.browsers[CONFIG.browsers_num] = browser; 244 | (CONFIG.browsers_num)++; 245 | return 0; 246 | } 247 | 248 | // find browser shell script 249 | static struct Browser *run_browser_sh(const char *browsername) 250 | { 251 | plog(LOG_DEBUG, "running browser shell script for %s", browsername); 252 | 253 | size_t path_suffix_size = 254 | strlen(browsername) + strlen("scripts/.sh") + 1; 255 | char path_suffix[path_suffix_size]; 256 | 257 | snprintf(path_suffix, path_suffix_size, "scripts/%s.sh", browsername); 258 | 259 | int err = 0; 260 | size_t found_num = 0; 261 | // found in order of variadic arguments 262 | char **found = search_path(&found_num, path_suffix, 3, PATHS.config, 263 | PATHS.share_dir_local, PATHS.share_dir); 264 | 265 | struct Browser *browser = new_browser(browsername, NULL); 266 | 267 | if (browser == NULL) { 268 | err = -1; 269 | goto exit; 270 | } 271 | 272 | if (found == NULL) { 273 | plog(LOG_ERROR, "failed finding shell script"); 274 | PERROR(); 275 | err = -1; 276 | goto exit; 277 | } 278 | if (found_num == 0) { 279 | plog(LOG_ERROR, "no shell script found for browser %s", 280 | browsername); 281 | err = -1; 282 | goto exit; 283 | } 284 | 285 | plog(LOG_DEBUG, "found %s", found[0]); 286 | 287 | // only use first script found 288 | if (parse_browser_sh(found[0], browser) == -1) { 289 | err = -1; 290 | goto exit; 291 | } 292 | 293 | exit: 294 | free_str_array(found, found_num); 295 | free(found); 296 | if (err == -1) { 297 | free_browser(browser); 298 | return NULL; 299 | } 300 | 301 | return browser; 302 | } 303 | 304 | // parse output given by browser shell script 305 | // only initializes procname, dirs and dirs_num members 306 | static int parse_browser_sh(const char *path, struct Browser *browser) 307 | { 308 | char *command = NULL; 309 | 310 | if (asprintf(&command, "sh %s", path) == -1) { 311 | return -1; 312 | } 313 | 314 | FILE *pp = popen(command, "r"); 315 | 316 | free(command); 317 | if (pp == NULL) { 318 | plog(LOG_ERROR, "failed opening pipe for shell script"); 319 | PERROR(); 320 | return -1; 321 | } 322 | 323 | if (ini_parse_file(pp, parse_browser_sh_handler, browser) != 0) { 324 | plog(LOG_ERROR, "failed parsing shell script output"); 325 | pclose(pp); 326 | return -1; 327 | } 328 | 329 | pclose(pp); 330 | 331 | // check if procname was given 332 | if (STR_EQUAL(browser->procname, "")) { 333 | plog(LOG_ERROR, "browser process name not given"); 334 | return -1; 335 | } 336 | // warn if no directories were found 337 | if (browser->dirs_num == 0) { 338 | plog(LOG_WARN, "no directories given by shell script"); 339 | } 340 | 341 | return 0; 342 | } 343 | 344 | static int parse_browser_sh_handler(void *user, const char *UNUSED(section), 345 | const char *name, const char *value) 346 | { 347 | if (value == NULL) { 348 | plog(LOG_ERROR, "key '%s' does not have a value", name); 349 | return 0; 350 | } 351 | 352 | struct Browser *browser = user; 353 | 354 | if (STR_EQUAL(name, "procname")) { 355 | snprintf(browser->procname, PROCNAME_SIZE, "%s", value); 356 | return 1; 357 | } 358 | struct Dir *dir = NULL; 359 | 360 | if (STR_EQUAL(name, "profile")) { 361 | dir = new_dir(value, DIR_PROFILE, browser); 362 | } else if (STR_EQUAL(name, "cache")) { 363 | dir = new_dir(value, DIR_CACHE, browser); 364 | } else { 365 | plog(LOG_ERROR, "unknown key '%s'", name); 366 | return 0; 367 | } 368 | if (dir == NULL) { 369 | plog(LOG_ERROR, "unable to allocate directory structure %s", 370 | value); 371 | return 0; 372 | } 373 | 374 | add_dir_to_browser(browser, dir); 375 | 376 | return 1; 377 | } 378 | 379 | // vim: sw=8 ts=8 380 | -------------------------------------------------------------------------------- /src/include/config.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "types.h" 4 | 5 | #include 6 | #include 7 | 8 | #define MAX_BROWSERS 100 9 | 10 | struct ConfigSkel { 11 | #ifndef NOOVERLAY 12 | bool enable_overlay; 13 | #endif 14 | bool enable_cache; 15 | bool resync_cache; 16 | bool reset_overlay; 17 | int max_log_entries; 18 | struct Browser *browsers[MAX_BROWSERS]; 19 | size_t browsers_num; 20 | }; 21 | 22 | struct PathsSkel { 23 | char runtime[PATH_MAX]; 24 | char tmpfs[PATH_MAX]; 25 | char config[PATH_MAX]; 26 | char backups[PATH_MAX]; 27 | char logs[PATH_MAX]; 28 | char share_dir[PATH_MAX]; 29 | char share_dir_local[PATH_MAX]; 30 | 31 | #ifndef NOOVERLAY 32 | char overlay_upper[PATH_MAX]; 33 | char overlay_work[PATH_MAX]; 34 | #endif 35 | }; 36 | 37 | extern struct ConfigSkel CONFIG; 38 | extern struct PathsSkel PATHS; 39 | 40 | int init_paths(void); 41 | int init_config(bool save_config); 42 | 43 | // vim: sw=8 ts=8 44 | -------------------------------------------------------------------------------- /src/include/ini.h: -------------------------------------------------------------------------------- 1 | /* inih -- simple .INI file parser 2 | 3 | SPDX-License-Identifier: BSD-3-Clause 4 | 5 | Copyright (C) 2009-2020, Ben Hoyt 6 | 7 | inih is released under the New BSD license (see LICENSE.txt). Go to the project 8 | home page for more info: 9 | 10 | https://github.com/benhoyt/inih 11 | 12 | */ 13 | 14 | #ifndef INI_H 15 | #define INI_H 16 | 17 | /* Make this header file easier to include in C++ code */ 18 | #ifdef __cplusplus 19 | extern "C" { 20 | #endif 21 | 22 | #include 23 | 24 | /* Nonzero if ini_handler callback should accept lineno parameter. */ 25 | #ifndef INI_HANDLER_LINENO 26 | #define INI_HANDLER_LINENO 0 27 | #endif 28 | 29 | /* Visibility symbols, required for Windows DLLs */ 30 | #ifndef INI_API 31 | #if defined _WIN32 || defined __CYGWIN__ 32 | #ifdef INI_SHARED_LIB 33 | #ifdef INI_SHARED_LIB_BUILDING 34 | #define INI_API __declspec(dllexport) 35 | #else 36 | #define INI_API __declspec(dllimport) 37 | #endif 38 | #else 39 | #define INI_API 40 | #endif 41 | #else 42 | #if defined(__GNUC__) && __GNUC__ >= 4 43 | #define INI_API __attribute__((visibility("default"))) 44 | #else 45 | #define INI_API 46 | #endif 47 | #endif 48 | #endif 49 | 50 | /* Typedef for prototype of handler function. */ 51 | #if INI_HANDLER_LINENO 52 | typedef int (*ini_handler)(void *user, const char *section, const char *name, 53 | const char *value, int lineno); 54 | #else 55 | typedef int (*ini_handler)(void *user, const char *section, const char *name, 56 | const char *value); 57 | #endif 58 | 59 | /* Typedef for prototype of fgets-style reader function. */ 60 | typedef char *(*ini_reader)(char *str, int num, void *stream); 61 | 62 | /* Parse given INI-style file. May have [section]s, name=value pairs 63 | (whitespace stripped), and comments starting with ';' (semicolon). Section 64 | is "" if name=value pair parsed before any section heading. name:value 65 | pairs are also supported as a concession to Python's configparser. 66 | 67 | For each name=value pair parsed, call handler function with given user 68 | pointer as well as section, name, and value (data only valid for duration 69 | of handler call). Handler should return nonzero on success, zero on error. 70 | 71 | Returns 0 on success, line number of first error on parse error (doesn't 72 | stop on first error), -1 on file open error, or -2 on memory allocation 73 | error (only when INI_USE_STACK is zero). 74 | */ 75 | INI_API int ini_parse(const char *filename, ini_handler handler, void *user); 76 | 77 | /* Same as ini_parse(), but takes a FILE* instead of filename. This doesn't 78 | close the file when it's finished -- the caller must do that. */ 79 | INI_API int ini_parse_file(FILE *file, ini_handler handler, void *user); 80 | 81 | /* Same as ini_parse(), but takes an ini_reader function pointer instead of 82 | filename. Used for implementing custom or string-based I/O (see also 83 | ini_parse_string). */ 84 | INI_API int ini_parse_stream(ini_reader reader, void *stream, 85 | ini_handler handler, void *user); 86 | 87 | /* Same as ini_parse(), but takes a zero-terminated string with the INI data 88 | instead of a file. Useful for parsing INI data from a network socket or 89 | already in memory. */ 90 | INI_API int ini_parse_string(const char *string, ini_handler handler, 91 | void *user); 92 | 93 | /* Nonzero to allow multi-line value parsing, in the style of Python's 94 | configparser. If allowed, ini_parse() will call the handler with the same 95 | name for each subsequent line parsed. */ 96 | #ifndef INI_ALLOW_MULTILINE 97 | #define INI_ALLOW_MULTILINE 1 98 | #endif 99 | 100 | /* Nonzero to allow a UTF-8 BOM sequence (0xEF 0xBB 0xBF) at the start of 101 | the file. See https://github.com/benhoyt/inih/issues/21 */ 102 | #ifndef INI_ALLOW_BOM 103 | #define INI_ALLOW_BOM 1 104 | #endif 105 | 106 | /* Chars that begin a start-of-line comment. Per Python configparser, allow 107 | both ; and # comments at the start of a line by default. */ 108 | #ifndef INI_START_COMMENT_PREFIXES 109 | #define INI_START_COMMENT_PREFIXES ";#" 110 | #endif 111 | 112 | /* Nonzero to allow inline comments (with valid inline comment characters 113 | specified by INI_INLINE_COMMENT_PREFIXES). Set to 0 to turn off and match 114 | Python 3.2+ configparser behaviour. */ 115 | #ifndef INI_ALLOW_INLINE_COMMENTS 116 | #define INI_ALLOW_INLINE_COMMENTS 1 117 | #endif 118 | #ifndef INI_INLINE_COMMENT_PREFIXES 119 | #define INI_INLINE_COMMENT_PREFIXES ";" 120 | #endif 121 | 122 | /* Nonzero to use stack for line buffer, zero to use heap (malloc/free). */ 123 | #ifndef INI_USE_STACK 124 | #define INI_USE_STACK 1 125 | #endif 126 | 127 | /* Maximum line length for any line in INI file (stack or heap). Note that 128 | this must be 3 more than the longest line (due to '\r', '\n', and '\0'). */ 129 | #ifndef INI_MAX_LINE 130 | #define INI_MAX_LINE 200 131 | #endif 132 | 133 | /* Nonzero to allow heap line buffer to grow via realloc(), zero for a 134 | fixed-size buffer of INI_MAX_LINE bytes. Only applies if INI_USE_STACK is 135 | zero. */ 136 | #ifndef INI_ALLOW_REALLOC 137 | #define INI_ALLOW_REALLOC 0 138 | #endif 139 | 140 | /* Initial size in bytes for heap line buffer. Only applies if INI_USE_STACK 141 | is zero. */ 142 | #ifndef INI_INITIAL_ALLOC 143 | #define INI_INITIAL_ALLOC 200 144 | #endif 145 | 146 | /* Stop parsing on first error (default is to keep parsing). */ 147 | #ifndef INI_STOP_ON_FIRST_ERROR 148 | #define INI_STOP_ON_FIRST_ERROR 0 149 | #endif 150 | 151 | /* Nonzero to call the handler at the start of each new section (with 152 | name and value NULL). Default is to only call the handler on 153 | each name=value pair. */ 154 | #ifndef INI_CALL_HANDLER_ON_NEW_SECTION 155 | #define INI_CALL_HANDLER_ON_NEW_SECTION 0 156 | #endif 157 | 158 | /* Nonzero to allow a name without a value (no '=' or ':' on the line) and 159 | call the handler with value NULL in this case. Default is to treat 160 | no-value lines as an error. */ 161 | #ifndef INI_ALLOW_NO_VALUE 162 | #define INI_ALLOW_NO_VALUE 0 163 | #endif 164 | 165 | /* Nonzero to use custom ini_malloc, ini_free, and ini_realloc memory 166 | allocation functions (INI_USE_STACK must also be 0). These functions must 167 | have the same signatures as malloc/free/realloc and behave in a similar 168 | way. ini_realloc is only needed if INI_ALLOW_REALLOC is set. */ 169 | #ifndef INI_CUSTOM_ALLOCATOR 170 | #define INI_CUSTOM_ALLOCATOR 0 171 | #endif 172 | 173 | #ifdef __cplusplus 174 | } 175 | #endif 176 | 177 | #endif /* INI_H */ 178 | -------------------------------------------------------------------------------- /src/include/log.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | enum LogLevel { LOG_DEBUG, LOG_INFO, LOG_WARN, LOG_ERROR }; 4 | 5 | extern enum LogLevel LOG_LEVEL; 6 | 7 | int init_logger(void); 8 | void plog(enum LogLevel level, const char *format, ...); 9 | 10 | // vim: sw=8 ts=8 11 | -------------------------------------------------------------------------------- /src/include/overlay.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #ifndef NOOVERLAY 6 | 7 | int mount_overlay(void); 8 | int unmount_overlay(void); 9 | bool overlay_mounted(void); 10 | 11 | #endif 12 | 13 | #include 14 | -------------------------------------------------------------------------------- /src/include/sync.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "types.h" 4 | 5 | #include 6 | 7 | #define BOR_CRASH_PREFIX "bor-crash_" 8 | 9 | enum Action { 10 | ACTION_NONE, 11 | ACTION_SYNC, 12 | ACTION_UNSYNC, 13 | ACTION_RESYNC, 14 | ACTION_STATUS, 15 | ACTION_RMRECOVERY, 16 | ACTION_RMCACHE 17 | }; 18 | static char *action_str[] = { "none", "sync", "unsync", "resync", 19 | "status", "recovery", "clear cache" }; 20 | 21 | int do_action_on_browser(struct Browser *browser, enum Action action, 22 | bool overlay); 23 | #ifndef NOOVERLAY 24 | int reset_overlay(void); 25 | #endif 26 | int get_paths(struct Dir *dir, char *backup, char *tmpfs); 27 | int get_overlay_paths(struct Dir *dir, char *tmpfs); 28 | 29 | // vim: sw=8 ts=8 30 | -------------------------------------------------------------------------------- /src/include/types.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #define BROWSER_NAME_SIZE 100 7 | #define PROCNAME_SIZE 16 8 | #define MAX_DIRS 100 // max dirs per browser 9 | 10 | enum DirType { DIR_CACHE, DIR_PROFILE }; 11 | 12 | struct Dir { 13 | char path[PATH_MAX]; 14 | char parent_path[PATH_MAX]; 15 | char dirname[NAME_MAX]; 16 | enum DirType type; 17 | struct Browser *browser; 18 | }; 19 | 20 | struct Browser { 21 | char name[BROWSER_NAME_SIZE]; 22 | char procname[PROCNAME_SIZE]; 23 | struct Dir *dirs[MAX_DIRS]; 24 | size_t dirs_num; 25 | }; 26 | 27 | struct Dir *new_dir(const char *path, enum DirType type, 28 | struct Browser *browser); 29 | void free_dir(struct Dir *dir); 30 | struct Browser *new_browser(const char *name, const char *procname); 31 | void add_dir_to_browser(struct Browser *browser, struct Dir *dir); 32 | void free_browser(struct Browser *browser); 33 | 34 | // vim: sw=8 ts=8 35 | -------------------------------------------------------------------------------- /src/include/util.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | // https://stackoverflow.com/a/12891181/12809652 14 | #ifdef __GNUC__ 15 | #define UNUSED(x) UNUSED_##x __attribute__((__unused__)) 16 | #else 17 | #define UNUSED(x) UNUSED_##x 18 | #endif 19 | 20 | #define EXISTS(path) (stat(path, &sb) == 0) 21 | #define LEXISTS(path) (lstat(path, &sb) == 0) 22 | 23 | #define SYMEXISTS(path) (lstat(path, &sb) == 0 && S_ISLNK(sb.st_mode)) 24 | #define EXISTSNOTSYM(path) (lstat(path, &sb) == 0 && !S_ISLNK(sb.st_mode)) 25 | 26 | #define DIREXISTS(path) (lstat(path, &sb) == 0 && S_ISDIR(sb.st_mode)) 27 | #define EXISTSNOTDIR(path) (lstat(path, &sb) == 0 && !S_ISDIR(sb.st_mode)) 28 | 29 | #define FEXISTS(path) (lstat(path, &sb) == 0 && S_ISREG(sb.st_mode)) 30 | 31 | #define STR_EQUAL(str1, str2) (strcmp(str1, str2) == 0) 32 | #define TO__STRING(s) #s 33 | #define TO_STRING(s) TO__STRING(s) 34 | 35 | #define IS_DOT(fname) (STR_EQUAL(fname, ".") || STR_EQUAL(fname, "..")) 36 | 37 | #define MAX_FD 1024 38 | #define UNIQUE_PATH_MAX_ITER 1000 39 | 40 | #define MIB 1024 41 | 42 | #define LOGCWD() \ 43 | do { \ 44 | char logcwd_cwd[PATH_MAX]; \ 45 | getcwd(logcwd_cwd, PATH_MAX); \ 46 | plog(LOG_INFO, "CWD: %s", logcwd_cwd); \ 47 | } while (0) 48 | 49 | #define PERROR() \ 50 | do { \ 51 | if (errno != 0) { \ 52 | fprintf(stderr, "(%s:%d): %s\n", __FILE__, __LINE__, \ 53 | strerror(errno)); \ 54 | } \ 55 | } while (0) 56 | 57 | #define TRIM(buf, str) \ 58 | do { \ 59 | snprintf(buf, strlen(str) + 1, "%s", str); \ 60 | trim(buf); \ 61 | } while (0) 62 | 63 | int create_dir(const char *path, mode_t mode); 64 | char **search_path(size_t *len, const char *path, size_t count, ...); 65 | void free_str_array(char **arr, size_t arr_len); 66 | int trim(char *str); 67 | 68 | int copy_path(const char *src, const char *dest, bool include_root); 69 | int remove_dir(const char *path); 70 | int remove_path(const char *path); 71 | int clear_dir(const char *path); 72 | 73 | int move_path(const char *src, const char *dest, bool include_root); 74 | int replace_paths(const char *target, const char *src); 75 | 76 | void create_unique_path(char *buf, size_t buf_size, const char *path, 77 | size_t max_iter); 78 | bool file_has_bad_perms(const char *path); 79 | 80 | void set_caps(cap_flag_t set, cap_flag_value_t state, size_t count, ...); 81 | bool check_caps_state(cap_flag_t set, cap_flag_value_t state, size_t count, 82 | ...); 83 | 84 | bool sd_uunit_active(const char *name); 85 | pid_t get_pid(const char *name); 86 | off_t get_dir_size(const char *path); 87 | char *human_readable(off_t bytes); 88 | bool program_exists(const char *program); 89 | void update_string(char *str, size_t size, const char *input); 90 | bool name_is_dot(const char *name); 91 | int copy_rfile(const char *src, const char *dest); 92 | 93 | // from teeny-sha1.c 94 | int sha1digest(uint8_t *digest, char *hexdigest, const uint8_t *data, 95 | size_t databytes); 96 | 97 | // vim: sw=8 ts=8 98 | -------------------------------------------------------------------------------- /src/ini.c: -------------------------------------------------------------------------------- 1 | /* inih -- simple .INI file parser 2 | 3 | SPDX-License-Identifier: BSD-3-Clause 4 | 5 | Copyright (C) 2009-2020, Ben Hoyt 6 | 7 | inih is released under the New BSD license (see LICENSE.txt). Go to the project 8 | home page for more info: 9 | 10 | https://github.com/benhoyt/inih 11 | 12 | */ 13 | 14 | #if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) 15 | #define _CRT_SECURE_NO_WARNINGS 16 | #endif 17 | 18 | #include "ini.h" 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | #if !INI_USE_STACK 25 | #if INI_CUSTOM_ALLOCATOR 26 | #include 27 | void *ini_malloc(size_t size); 28 | void ini_free(void *ptr); 29 | void *ini_realloc(void *ptr, size_t size); 30 | #else 31 | #include 32 | #define ini_malloc malloc 33 | #define ini_free free 34 | #define ini_realloc realloc 35 | #endif 36 | #endif 37 | 38 | #define MAX_SECTION 50 39 | #define MAX_NAME 50 40 | 41 | /* Used by ini_parse_string() to keep track of string parsing state. */ 42 | typedef struct { 43 | const char *ptr; 44 | size_t num_left; 45 | } ini_parse_string_ctx; 46 | 47 | /* Strip whitespace chars off end of given string, in place. Return s. */ 48 | static char *ini_rstrip(char *s) 49 | { 50 | char *p = s + strlen(s); 51 | while (p > s && isspace((unsigned char)(*--p))) 52 | *p = '\0'; 53 | return s; 54 | } 55 | 56 | /* Return pointer to first non-whitespace char in given string. */ 57 | static char *ini_lskip(const char *s) 58 | { 59 | while (*s && isspace((unsigned char)(*s))) 60 | s++; 61 | return (char *)s; 62 | } 63 | 64 | /* Return pointer to first char (of chars) or inline comment in given string, 65 | or pointer to NUL at end of string if neither found. Inline comment must 66 | be prefixed by a whitespace character to register as a comment. */ 67 | static char *ini_find_chars_or_comment(const char *s, const char *chars) 68 | { 69 | #if INI_ALLOW_INLINE_COMMENTS 70 | int was_space = 0; 71 | while (*s && (!chars || !strchr(chars, *s)) && 72 | !(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) { 73 | was_space = isspace((unsigned char)(*s)); 74 | s++; 75 | } 76 | #else 77 | while (*s && (!chars || !strchr(chars, *s))) { 78 | s++; 79 | } 80 | #endif 81 | return (char *)s; 82 | } 83 | 84 | /* Similar to strncpy, but ensures dest (size bytes) is 85 | NUL-terminated, and doesn't pad with NULs. */ 86 | static char *ini_strncpy0(char *dest, const char *src, size_t size) 87 | { 88 | /* Could use strncpy internally, but it causes gcc warnings (see issue #91) 89 | */ 90 | size_t i; 91 | for (i = 0; i < size - 1 && src[i]; i++) 92 | dest[i] = src[i]; 93 | dest[i] = '\0'; 94 | return dest; 95 | } 96 | 97 | /* See documentation in header file. */ 98 | int ini_parse_stream(ini_reader reader, void *stream, ini_handler handler, 99 | void *user) 100 | { 101 | /* Uses a fair bit of stack (use heap instead if you need to) */ 102 | #if INI_USE_STACK 103 | char line[INI_MAX_LINE]; 104 | size_t max_line = INI_MAX_LINE; 105 | #else 106 | char *line; 107 | size_t max_line = INI_INITIAL_ALLOC; 108 | #endif 109 | #if INI_ALLOW_REALLOC && !INI_USE_STACK 110 | char *new_line; 111 | size_t offset; 112 | #endif 113 | char section[MAX_SECTION] = ""; 114 | #if INI_ALLOW_MULTILINE 115 | char prev_name[MAX_NAME] = ""; 116 | #endif 117 | 118 | char *start; 119 | char *end; 120 | char *name; 121 | char *value; 122 | int lineno = 0; 123 | int error = 0; 124 | 125 | #if !INI_USE_STACK 126 | line = (char *)ini_malloc(INI_INITIAL_ALLOC); 127 | if (!line) { 128 | return -2; 129 | } 130 | #endif 131 | 132 | #if INI_HANDLER_LINENO 133 | #define HANDLER(u, s, n, v) handler(u, s, n, v, lineno) 134 | #else 135 | #define HANDLER(u, s, n, v) handler(u, s, n, v) 136 | #endif 137 | 138 | /* Scan through stream line by line */ 139 | while (reader(line, (int)max_line, stream) != NULL) { 140 | #if INI_ALLOW_REALLOC && !INI_USE_STACK 141 | offset = strlen(line); 142 | while (offset == max_line - 1 && line[offset - 1] != '\n') { 143 | max_line *= 2; 144 | if (max_line > INI_MAX_LINE) 145 | max_line = INI_MAX_LINE; 146 | new_line = ini_realloc(line, max_line); 147 | if (!new_line) { 148 | ini_free(line); 149 | return -2; 150 | } 151 | line = new_line; 152 | if (reader(line + offset, (int)(max_line - offset), 153 | stream) == NULL) 154 | break; 155 | if (max_line >= INI_MAX_LINE) 156 | break; 157 | offset += strlen(line + offset); 158 | } 159 | #endif 160 | 161 | lineno++; 162 | 163 | start = line; 164 | #if INI_ALLOW_BOM 165 | if (lineno == 1 && (unsigned char)start[0] == 0xEF && 166 | (unsigned char)start[1] == 0xBB && 167 | (unsigned char)start[2] == 0xBF) { 168 | start += 3; 169 | } 170 | #endif 171 | start = ini_rstrip(ini_lskip(start)); 172 | 173 | if (strchr(INI_START_COMMENT_PREFIXES, *start)) { 174 | /* Start-of-line comment */ 175 | } 176 | #if INI_ALLOW_MULTILINE 177 | else if (*prev_name && *start && start > line) { 178 | #if INI_ALLOW_INLINE_COMMENTS 179 | end = ini_find_chars_or_comment(start, NULL); 180 | if (*end) 181 | *end = '\0'; 182 | ini_rstrip(start); 183 | #endif 184 | /* Non-blank line with leading whitespace, treat as continuation 185 | of previous name's value (as per Python configparser). */ 186 | if (!HANDLER(user, section, prev_name, start) && !error) 187 | error = lineno; 188 | } 189 | #endif 190 | else if (*start == '[') { 191 | /* A "[section]" line */ 192 | end = ini_find_chars_or_comment(start + 1, "]"); 193 | if (*end == ']') { 194 | *end = '\0'; 195 | ini_strncpy0(section, start + 1, 196 | sizeof(section)); 197 | #if INI_ALLOW_MULTILINE 198 | *prev_name = '\0'; 199 | #endif 200 | #if INI_CALL_HANDLER_ON_NEW_SECTION 201 | if (!HANDLER(user, section, NULL, NULL) && 202 | !error) 203 | error = lineno; 204 | #endif 205 | } else if (!error) { 206 | /* No ']' found on section line */ 207 | error = lineno; 208 | } 209 | } else if (*start) { 210 | /* Not a comment, must be a name[=:]value pair */ 211 | end = ini_find_chars_or_comment(start, "=:"); 212 | if (*end == '=' || *end == ':') { 213 | *end = '\0'; 214 | name = ini_rstrip(start); 215 | value = end + 1; 216 | #if INI_ALLOW_INLINE_COMMENTS 217 | end = ini_find_chars_or_comment(value, NULL); 218 | if (*end) 219 | *end = '\0'; 220 | #endif 221 | value = ini_lskip(value); 222 | ini_rstrip(value); 223 | 224 | #if INI_ALLOW_MULTILINE 225 | ini_strncpy0(prev_name, name, 226 | sizeof(prev_name)); 227 | #endif 228 | /* Valid name[=:]value pair found, call handler */ 229 | if (!HANDLER(user, section, name, value) && 230 | !error) 231 | error = lineno; 232 | } else if (!error) { 233 | /* No '=' or ':' found on name[=:]value line */ 234 | #if INI_ALLOW_NO_VALUE 235 | *end = '\0'; 236 | name = ini_rstrip(start); 237 | if (!HANDLER(user, section, name, NULL) && 238 | !error) 239 | error = lineno; 240 | #else 241 | error = lineno; 242 | #endif 243 | } 244 | } 245 | 246 | #if INI_STOP_ON_FIRST_ERROR 247 | if (error) 248 | break; 249 | #endif 250 | } 251 | 252 | #if !INI_USE_STACK 253 | ini_free(line); 254 | #endif 255 | 256 | return error; 257 | } 258 | 259 | /* See documentation in header file. */ 260 | int ini_parse_file(FILE *file, ini_handler handler, void *user) 261 | { 262 | return ini_parse_stream((ini_reader)fgets, file, handler, user); 263 | } 264 | 265 | /* See documentation in header file. */ 266 | int ini_parse(const char *filename, ini_handler handler, void *user) 267 | { 268 | FILE *file; 269 | int error; 270 | 271 | file = fopen(filename, "r"); 272 | if (!file) 273 | return -1; 274 | error = ini_parse_file(file, handler, user); 275 | fclose(file); 276 | return error; 277 | } 278 | 279 | /* An ini_reader function to read the next line from a string buffer. This 280 | is the fgets() equivalent used by ini_parse_string(). */ 281 | static char *ini_reader_string(char *str, int num, void *stream) 282 | { 283 | ini_parse_string_ctx *ctx = (ini_parse_string_ctx *)stream; 284 | const char *ctx_ptr = ctx->ptr; 285 | size_t ctx_num_left = ctx->num_left; 286 | char *strp = str; 287 | char c; 288 | 289 | if (ctx_num_left == 0 || num < 2) 290 | return NULL; 291 | 292 | while (num > 1 && ctx_num_left != 0) { 293 | c = *ctx_ptr++; 294 | ctx_num_left--; 295 | *strp++ = c; 296 | if (c == '\n') 297 | break; 298 | num--; 299 | } 300 | 301 | *strp = '\0'; 302 | ctx->ptr = ctx_ptr; 303 | ctx->num_left = ctx_num_left; 304 | return str; 305 | } 306 | 307 | /* See documentation in header file. */ 308 | int ini_parse_string(const char *string, ini_handler handler, void *user) 309 | { 310 | ini_parse_string_ctx ctx; 311 | 312 | ctx.ptr = string; 313 | ctx.num_left = strlen(string); 314 | return ini_parse_stream((ini_reader)ini_reader_string, &ctx, handler, 315 | user); 316 | } 317 | -------------------------------------------------------------------------------- /src/log.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include "log.h" 3 | #include "config.h" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | enum LogLevel LOG_LEVEL = LOG_INFO; 14 | 15 | static const char *log_str[] = { "DEBUG", "INFO", "WARN", "ERROR" }; 16 | 17 | static FILE *LOG_FILE = NULL; 18 | 19 | // allow for at max MAX_LOG_ENTRIES entries in log file 20 | static int truncate_log_file(void) 21 | { 22 | char tmp_path[PATH_MAX]; 23 | char log_path[PATH_MAX]; 24 | 25 | snprintf(tmp_path, PATH_MAX, "%s/.tmp.txt", PATHS.logs); 26 | snprintf(log_path, PATH_MAX, "%s/log.txt", PATHS.logs); 27 | 28 | // count number of entries 29 | char *line = NULL; 30 | size_t size = 0; 31 | ssize_t nread = 0; 32 | long int count = 0; 33 | 34 | rewind(LOG_FILE); 35 | 36 | while ((nread = getline(&line, &size, LOG_FILE)) != -1) { 37 | if (line[0] == '<') { 38 | count++; 39 | } 40 | } 41 | 42 | // tmp file where log lines will be written to 43 | FILE *tmp_fp = fopen(tmp_path, "w"); 44 | 45 | if (tmp_fp == NULL) { 46 | return -1; 47 | } 48 | 49 | long int entries_to_be_rm = 50 | count - (long int)CONFIG.max_log_entries + 1; 51 | 52 | // if negative then we have less than max entries 53 | if (entries_to_be_rm < 0) { 54 | goto exit; 55 | } 56 | 57 | rewind(LOG_FILE); 58 | count = 0; 59 | // skip entries that should be removed 60 | while ((nread = getline(&line, &size, LOG_FILE)) != -1) { 61 | if (line[0] == '<') { 62 | count++; 63 | } 64 | if (count > entries_to_be_rm) { 65 | fprintf(tmp_fp, "%s", line); 66 | } 67 | } 68 | 69 | // swap tmp and log file 70 | if (renameat2(AT_FDCWD, log_path, AT_FDCWD, tmp_path, 71 | RENAME_EXCHANGE) == -1) { 72 | goto exit; 73 | } 74 | 75 | // point log file fp to new file 76 | fclose(LOG_FILE); 77 | LOG_FILE = fopen(log_path, "a+"); 78 | 79 | if (LOG_FILE == NULL) { 80 | goto exit; 81 | } 82 | 83 | exit: 84 | fclose(tmp_fp); 85 | unlink(tmp_path); 86 | free(line); 87 | fseek(LOG_FILE, 0L, SEEK_END); 88 | 89 | return 0; 90 | } 91 | 92 | // should be run after paths and config have been initialized 93 | // and log directory created 94 | int init_logger(void) 95 | { 96 | char log_path[PATH_MAX]; 97 | 98 | snprintf(log_path, PATH_MAX, "%s/log.txt", PATHS.logs); 99 | 100 | if (CONFIG.max_log_entries == 0) { 101 | unlink(log_path); 102 | return 0; 103 | } 104 | 105 | LOG_FILE = fopen(log_path, "a+"); 106 | 107 | if (LOG_FILE == NULL) { 108 | return -1; 109 | } 110 | 111 | // print current time into file 112 | time_t unixtime = time(NULL); 113 | struct tm *time_info = NULL; 114 | 115 | if (unixtime == (time_t)-1 || 116 | (time_info = localtime(&unixtime)) == NULL) { 117 | return -1; 118 | } 119 | char time_buf[100]; 120 | 121 | strftime(time_buf, 100, "%d-%m-%y %H:%M:%S", time_info); 122 | 123 | if (CONFIG.max_log_entries > 0) { 124 | if (truncate_log_file() == -1) { 125 | return -1; 126 | } 127 | } 128 | // print header with current time 129 | fprintf(LOG_FILE, "\n<%s>\n", time_buf); 130 | 131 | return 0; 132 | } 133 | 134 | void plog(enum LogLevel level, const char *format, ...) 135 | { 136 | va_list args; 137 | 138 | if (LOG_FILE != NULL) { 139 | va_start(args, format); 140 | 141 | fprintf(LOG_FILE, "%5s: ", log_str[level]); 142 | vfprintf(LOG_FILE, format, args); 143 | fprintf(LOG_FILE, "\n"); 144 | 145 | fflush(LOG_FILE); 146 | 147 | va_end(args); 148 | } 149 | if (LOG_LEVEL > level) { 150 | return; 151 | } 152 | 153 | va_start(args, format); 154 | 155 | fprintf(stderr, "%5s: ", log_str[level]); 156 | vfprintf(stderr, format, args); 157 | fprintf(stderr, "\n"); 158 | 159 | va_end(args); 160 | } 161 | 162 | // vim: sw=8 ts=8 163 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | #include "config.h" 2 | #include "log.h" 3 | #include "sync.h" 4 | #include "overlay.h" 5 | #include "util.h" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #ifndef VERSION 20 | #define VERSION "UNKNOWN" 21 | #endif 22 | 23 | int do_action(enum Action action); 24 | 25 | int init(bool save_config); 26 | int uninit(void); 27 | 28 | int check_runtime_space(void); 29 | 30 | int clear_recovery_dirs(void); 31 | int remove_glob(glob_t *gb); 32 | int get_recovery_dirs(struct Dir *target_dir, glob_t *glob_struct); 33 | 34 | int log_paths(void); 35 | 36 | void print_help(void); 37 | void print_status(void); 38 | 39 | int main(int argc, char **argv) 40 | { 41 | // clang-format off 42 | struct option long_options[] = { { "version", no_argument, NULL, 'v' }, 43 | { "verbose", no_argument, NULL, 'V' }, 44 | { "help", no_argument, NULL, 'h' }, 45 | { "sync", no_argument, NULL, 's' }, 46 | { "unsync", no_argument, NULL, 'u' }, 47 | { "resync", no_argument, NULL, 'r' }, 48 | { "clean", no_argument, NULL, 'c' }, 49 | { "rm_cache", no_argument, NULL, 'x' }, 50 | { "status", no_argument, NULL, 'p' }, 51 | { NULL, 0, NULL, 0 } }; 52 | // clang-format on 53 | 54 | int opt, opt_index; 55 | enum Action action = ACTION_NONE; 56 | 57 | while ((opt = getopt_long(argc, argv, "Vvhsurcxp", long_options, 58 | &opt_index)) != -1) { 59 | switch (opt) { 60 | case 'V': 61 | LOG_LEVEL = LOG_DEBUG; 62 | break; 63 | case 'v': 64 | printf("browser-on-ram version " VERSION "\n"); 65 | return 0; 66 | case 'h': 67 | print_help(); 68 | return 0; 69 | case 's': 70 | action = ACTION_SYNC; 71 | break; 72 | case 'u': 73 | action = ACTION_UNSYNC; 74 | break; 75 | case 'r': 76 | action = ACTION_RESYNC; 77 | break; 78 | case 'c': 79 | action = ACTION_RMRECOVERY; 80 | break; 81 | case 'x': 82 | action = ACTION_RMCACHE; 83 | break; 84 | case 'p': 85 | action = ACTION_STATUS; 86 | break; 87 | default: 88 | return 0; 89 | } 90 | } 91 | if (action == ACTION_STATUS) { 92 | print_status(); 93 | return 0; 94 | } else if (action == ACTION_RMRECOVERY) { 95 | return (clear_recovery_dirs() == -1) ? 1 : 0; 96 | } 97 | if (action == ACTION_NONE) { 98 | return 0; 99 | } 100 | 101 | // init everything before doing the given action 102 | if (init(true) == -1) { 103 | plog(LOG_ERROR, "failed initializing"); 104 | return 1; 105 | } 106 | if (create_dir(PATHS.backups, 0755) == -1 || 107 | create_dir(PATHS.tmpfs, 0755) == -1 || 108 | create_dir(PATHS.logs, 0755) == -1) { 109 | plog(LOG_ERROR, "failed creating required directories"); 110 | PERROR(); 111 | return 1; 112 | } 113 | 114 | if (init_logger() == -1) { 115 | plog(LOG_ERROR, "failed starting logger"); 116 | PERROR(); 117 | return 1; 118 | } 119 | 120 | plog(LOG_INFO, "starting browser-on-ram " VERSION); 121 | 122 | // need rsync 123 | if (!program_exists("rsync")) { 124 | plog(LOG_ERROR, "rsync is required, please install"); 125 | return 1; 126 | } 127 | 128 | if (do_action(action) == -1) { 129 | plog(LOG_ERROR, "failed attempting to do %s", 130 | action_str[action]); 131 | return 1; 132 | } 133 | 134 | return 0; 135 | } 136 | 137 | // loop through configured browsers and do sync/unsync/resync on them 138 | int do_action(enum Action action) 139 | { 140 | size_t did_action = 0; 141 | bool overlay = false; 142 | 143 | #ifndef NOOVERLAY 144 | // check if we have required capabilities 145 | // do it before any action so that unsync/resync 146 | // works properly in case we don't 147 | if (CONFIG.enable_overlay && 148 | !check_caps_state(CAP_PERMITTED, CAP_SET, 2, CAP_SYS_ADMIN, 149 | CAP_DAC_OVERRIDE)) { 150 | plog(LOG_WARN, "CAP_SYS_ADMIN and CAP_DAC_OVERRIDE " 151 | "is needed for overlay feature"); 152 | } else if (CONFIG.enable_overlay) { 153 | if (action == ACTION_SYNC && overlay_mounted()) { 154 | plog(LOG_WARN, "tmpfs is already mounted, aborting"); 155 | return -1; 156 | } 157 | overlay = true; 158 | } 159 | 160 | // check if there is enough free space 161 | if (action == ACTION_SYNC && !overlay && check_runtime_space() == -1) { 162 | plog(LOG_ERROR, "not enough runtime free space, aborting"); 163 | return -1; 164 | } 165 | #endif 166 | 167 | for (size_t i = 0; i < CONFIG.browsers_num; i++) { 168 | struct Browser *browser = CONFIG.browsers[i]; 169 | 170 | // if a directory or entire browser was not u/r/synced (error) 171 | // then skip it and still continue 172 | if (do_action_on_browser(browser, action, overlay) == -1) { 173 | plog(LOG_WARN, "failed '%s' for browser %s", 174 | action_str[action], browser->name); 175 | continue; 176 | } 177 | did_action++; 178 | } 179 | 180 | #ifndef NOOVERLAY 181 | // reset overlay if configured 182 | if (action == ACTION_RESYNC && overlay && did_action > 0 && 183 | CONFIG.reset_overlay) { 184 | if (reset_overlay() == -1) { 185 | plog(LOG_ERROR, "failed resetting overlay"); 186 | return -1; 187 | } 188 | } 189 | 190 | // we mount after because modifying lowerdir before mount 191 | // doesn't reflect changes 192 | if (did_action > 0 && overlay && action == ACTION_SYNC) { 193 | if (overlay_mounted()) { 194 | plog(LOG_WARN, 195 | "tmpfs is mounted, cannot mount overlay; please check"); 196 | } else if (mount_overlay() == -1) { 197 | plog(LOG_ERROR, "failed creating overlay"); 198 | return -1; 199 | } 200 | } 201 | 202 | if (!overlay && overlay_mounted() && action == ACTION_UNSYNC) { 203 | plog(LOG_WARN, 204 | "tmpfs is mounted, but required capabilities do not exist"); 205 | } else if (action == ACTION_UNSYNC && overlay_mounted() && 206 | unmount_overlay() == -1) { 207 | plog(LOG_ERROR, "failed removing overlay"); 208 | return -1; 209 | } 210 | #endif 211 | 212 | if (action == ACTION_UNSYNC) { 213 | plog(LOG_INFO, "finding leftover or unknown directories/files"); 214 | if (log_paths()) { 215 | plog(LOG_ERROR, "failed finding unknown paths"); 216 | return -1; 217 | } 218 | if (uninit() == -1) { 219 | plog(LOG_WARN, "failed uninitializing"); 220 | } 221 | } 222 | 223 | return 0; 224 | } 225 | 226 | // initialize paths and config 227 | // if save_config is true then make a .bor.conf file to save state 228 | int init(bool save_config) 229 | { 230 | plog(LOG_DEBUG, "initializing"); 231 | if (init_paths() == -1) { 232 | plog(LOG_ERROR, "failed initializing paths"); 233 | return -1; 234 | } 235 | if (init_config(save_config) == -1) { 236 | plog(LOG_ERROR, "failed initializing config"); 237 | return -1; 238 | } 239 | 240 | return 0; 241 | } 242 | 243 | // remove certain directories and files (should be done after unsync) 244 | int uninit(void) 245 | { 246 | struct stat sb; 247 | 248 | // delete temporary config file 249 | char config_file[PATH_MAX]; 250 | 251 | snprintf(config_file, PATH_MAX, "%s/.bor.conf", PATHS.config); 252 | 253 | if (FEXISTS(config_file) && unlink(config_file) == -1) { 254 | plog(LOG_WARN, "failed removing .bor.conf"); 255 | PERROR(); 256 | } 257 | 258 | return 0; 259 | } 260 | 261 | // return -1 if there is not enough space in the runtime/tmpfs directory 262 | // only should be run if overlay is not enabled 263 | int check_runtime_space(void) 264 | { 265 | // get total size of dirs 266 | struct stat sb; 267 | off_t size = 0; 268 | 269 | for (size_t i = 0; i < CONFIG.browsers_num; i++) { 270 | struct Browser *browser = CONFIG.browsers[i]; 271 | 272 | for (size_t k = 0; k < browser->dirs_num; k++) { 273 | struct Dir *dir = browser->dirs[k]; 274 | 275 | if (stat(dir->path, &sb) == -1 || 276 | !S_ISDIR(sb.st_mode)) { 277 | plog(LOG_WARN, 278 | "failed getting info about directory %s", 279 | dir->path); 280 | continue; 281 | } 282 | 283 | size += get_dir_size(dir->path); 284 | } 285 | } 286 | struct statvfs svfsb; 287 | 288 | if (statvfs(PATHS.runtime, &svfsb) == -1) { 289 | plog(LOG_ERROR, "failed getting info about runtime directory"); 290 | return -1; 291 | } 292 | 293 | // check if at least 100 MIB of free space will remain after 294 | if ((off_t)(svfsb.f_bsize * svfsb.f_bavail - 100 * MIB) <= size) { 295 | plog(LOG_ERROR, 296 | "not enough space in runtime directory to be safe"); 297 | return -1; 298 | } 299 | 300 | return 0; 301 | } 302 | 303 | // initializes paths and config itself 304 | int clear_recovery_dirs(void) 305 | { 306 | if (init(false) == -1) { 307 | return -1; 308 | } 309 | 310 | for (size_t i = 0; i < CONFIG.browsers_num; i++) { 311 | struct Browser *browser = CONFIG.browsers[i]; 312 | 313 | plog(LOG_DEBUG, "clearing browser %s", browser->name); 314 | 315 | for (size_t k = 0; k < browser->dirs_num; k++) { 316 | struct Dir *dir = browser->dirs[k]; 317 | 318 | glob_t gb; 319 | 320 | if (get_recovery_dirs(dir, &gb) == -1) { 321 | plog(LOG_WARN, "failed getting directories"); 322 | continue; 323 | } 324 | remove_glob(&gb); 325 | 326 | globfree(&gb); 327 | } 328 | } 329 | return 0; 330 | } 331 | 332 | // remove files/dirs specified in glob struct 333 | int remove_glob(glob_t *gb) 334 | { 335 | for (size_t i = 0; i < gb->gl_pathc; i++) { 336 | plog(LOG_INFO, "removing %s", gb->gl_pathv[i]); 337 | 338 | if (remove_path(gb->gl_pathv[i]) == -1) { 339 | plog(LOG_ERROR, "failed removing file/dir"); 340 | PERROR(); 341 | continue; 342 | } 343 | } 344 | return 0; 345 | } 346 | 347 | // return a list of recovery dirs that belong to target_dir in a glob 348 | int get_recovery_dirs(struct Dir *target_dir, glob_t *glob_struct) 349 | { 350 | plog(LOG_DEBUG, "getting recovery dirs for directory %s", 351 | target_dir->path); 352 | 353 | // use a glob to get recovery dirs 354 | char pattern[PATH_MAX]; 355 | 356 | snprintf(pattern, PATH_MAX, "%s/" BOR_CRASH_PREFIX "*", 357 | target_dir->parent_path); 358 | 359 | int err = glob(pattern, GLOB_NOSORT | GLOB_ONLYDIR, NULL, glob_struct); 360 | 361 | if (err != 0 && err != GLOB_NOMATCH) { 362 | plog(LOG_ERROR, "failed globbing directories"); 363 | PERROR(); 364 | return -1; 365 | } 366 | 367 | return 0; 368 | } 369 | 370 | // log dirs/files in backups and tmpfs 371 | // should be done after unsync to find unknown directories 372 | int log_paths(void) 373 | { 374 | DIR *backups_dp = opendir(PATHS.backups); 375 | struct dirent *de = NULL; 376 | 377 | if (backups_dp == NULL) { 378 | PERROR(); 379 | return -1; 380 | } 381 | 382 | while ((de = readdir(backups_dp)) != NULL) { 383 | if (name_is_dot(de->d_name)) { 384 | continue; 385 | } 386 | plog(LOG_INFO, "found %s/%s", PATHS.backups, de->d_name); 387 | } 388 | closedir(backups_dp); 389 | 390 | DIR *tmpfs_dp = opendir(PATHS.tmpfs); 391 | 392 | if (tmpfs_dp == NULL) { 393 | PERROR(); 394 | return -1; 395 | } 396 | 397 | while ((de = readdir(tmpfs_dp)) != NULL) { 398 | if (name_is_dot(de->d_name)) { 399 | continue; 400 | } 401 | plog(LOG_INFO, "found %s/%s", PATHS.tmpfs, de->d_name); 402 | } 403 | closedir(tmpfs_dp); 404 | 405 | return 0; 406 | } 407 | 408 | void print_help(void) 409 | { 410 | printf("Browser-on-ram " VERSION "\n"); 411 | printf("Usage: bor [option]\n\n"); 412 | 413 | printf("Options:\n"); 414 | printf(" -v, --version show version\n"); 415 | printf(" -V, --verbose show debug logs\n"); 416 | printf(" -h, --help show this message\n"); 417 | printf(" -s, --sync do sync\n"); 418 | printf(" -u, --unsync do unsync\n"); 419 | printf(" -r, --resync do resync\n"); 420 | printf(" -c, --clean remove recovery directories\n"); 421 | printf(" -x, --rm_cache clear cache directories\n"); 422 | printf(" -p, --status show current configuration & state\n"); 423 | 424 | #ifndef NOSYSTEMD 425 | printf("\nNot recommended to use sync functions directly.\n"); 426 | printf("Please use the systemd service and timer\n"); 427 | #endif 428 | } 429 | 430 | void print_status(void) 431 | { 432 | if (init(false) == -1) { 433 | return; 434 | } 435 | 436 | printf("Browser-on-ram " VERSION "\n"); 437 | 438 | printf("\nStatus:\n"); 439 | #ifndef NOSYSTEMD 440 | bool service_active = sd_uunit_active("bor.service"), 441 | timer_active = sd_uunit_active("bor-resync.timer"); 442 | 443 | printf("Systemd service: %s\n", 444 | service_active ? "Active" : "Inactive"); 445 | printf("Systemd resync timer: %s\n", 446 | timer_active ? "Active" : "Inactive"); 447 | #endif 448 | 449 | #ifndef NOOVERLAY 450 | printf("Overlay: %s\n", 451 | CONFIG.enable_overlay ? "Enabled" : "Disabled"); 452 | 453 | if (overlay_mounted()) { 454 | // totol overlay upper size 455 | char *otosize = 456 | human_readable(get_dir_size(PATHS.overlay_upper)); 457 | 458 | printf("Total overlay size: %s\n", otosize); 459 | 460 | free(otosize); 461 | } 462 | #endif 463 | char *tosize = human_readable(get_dir_size(PATHS.tmpfs)); 464 | 465 | printf("Total size %s\n", tosize); 466 | 467 | free(tosize); 468 | 469 | printf("\nDirectories:\n\n"); 470 | 471 | struct stat sb; 472 | char backup[PATH_MAX], tmpfs[PATH_MAX], otmpfs[PATH_MAX]; 473 | 474 | for (size_t i = 0; i < CONFIG.browsers_num; i++) { 475 | struct Browser *browser = CONFIG.browsers[i]; 476 | 477 | printf("Browser: %s\n", browser->name); 478 | 479 | for (size_t k = 0; k < browser->dirs_num; k++) { 480 | struct Dir *dir = browser->dirs[k]; 481 | 482 | if (get_paths(dir, backup, tmpfs) == -1) { 483 | printf("Error\n"); 484 | continue; 485 | } 486 | bool dir_exists = false; 487 | char *type = (dir->type == DIR_PROFILE) ? "profile" : 488 | (dir->type == DIR_CACHE) ? "cache" : 489 | "unknown"; 490 | 491 | printf("Type: %s\n", type); 492 | if (DIREXISTS(dir->path) || SYMEXISTS(dir->path)) { 493 | printf("Directory: %s\n", dir->path); 494 | dir_exists = true; 495 | } else { 496 | printf("Directory: %s (DOES NOT EXIST)\n", 497 | dir->path); 498 | } 499 | if (DIREXISTS(backup)) { 500 | printf("Backup: %s\n", backup); 501 | } 502 | if (DIREXISTS(tmpfs)) { 503 | printf("Tmpfs: %s\n", tmpfs); 504 | } 505 | if (dir_exists) { 506 | char *size = 507 | human_readable(get_dir_size(dir->path)); 508 | printf("Size: %s\n", size); 509 | free(size); 510 | } 511 | #ifndef NOOVERLAY 512 | if (overlay_mounted()) { 513 | if (get_overlay_paths(dir, otmpfs) == -1) { 514 | printf("Error\n"); 515 | continue; 516 | } 517 | char *osize = 518 | human_readable(get_dir_size(otmpfs)); 519 | 520 | printf("Overlay size: %s\n", osize); 521 | 522 | free(osize); 523 | } 524 | #endif 525 | // print recovery dirs 526 | glob_t gb; 527 | 528 | if (get_recovery_dirs(dir, &gb) == 0) { 529 | for (size_t j = 0; j < gb.gl_pathc; j++) { 530 | printf("Recovery: %s\n", 531 | gb.gl_pathv[j]); 532 | } 533 | 534 | globfree(&gb); 535 | } 536 | 537 | printf("\n"); 538 | } 539 | } 540 | } 541 | 542 | // vim: sw=8 ts=8 543 | -------------------------------------------------------------------------------- /src/overlay.c: -------------------------------------------------------------------------------- 1 | #include "overlay.h" 2 | #include "config.h" 3 | #include "log.h" 4 | #include "util.h" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #ifndef NOOVERLAY 13 | 14 | // creates overlay mounted on tmpfs 15 | // does not check enable_overlay config option or if we have required caps 16 | int mount_overlay(void) 17 | { 18 | plog(LOG_INFO, "mounting overlay"); 19 | 20 | if (create_dir(PATHS.overlay_upper, 0755) == -1 || 21 | create_dir(PATHS.overlay_work, 0755) == -1) { 22 | plog(LOG_ERROR, "cannt create require directories"); 23 | PERROR(); 24 | return -1; 25 | } 26 | struct stat sb; 27 | 28 | // I believe symlinks aren't resolved in the data string, but 29 | // its always better to be safe 30 | if (SYMEXISTS(PATHS.backups) || SYMEXISTS(PATHS.overlay_upper) || 31 | SYMEXISTS(PATHS.overlay_work)) { 32 | plog(LOG_ERROR, 33 | "either the backup directory or upper/work directory is a" 34 | "symlink, cannot proceed"); 35 | return -1; 36 | } 37 | 38 | unsigned long mountflags = MS_NOSUID | MS_NODEV | MS_NOATIME; 39 | char data[PATH_MAX * 2 + 100]; 40 | 41 | snprintf(data, sizeof(data), 42 | "index=off,lowerdir=%s,upperdir=%s,workdir=%s", PATHS.backups, 43 | PATHS.overlay_upper, PATHS.overlay_work); 44 | 45 | // elevate permissions 46 | set_caps(CAP_EFFECTIVE, CAP_SET, 2, CAP_SYS_ADMIN, CAP_DAC_OVERRIDE); 47 | 48 | int err = mount("overlay", PATHS.tmpfs, "overlay", mountflags, data); 49 | 50 | // drop permissions 51 | set_caps(CAP_EFFECTIVE, CAP_CLEAR, 2, CAP_SYS_ADMIN, CAP_DAC_OVERRIDE); 52 | 53 | if (err == -1) { 54 | plog(LOG_ERROR, "failed mounting overlay"); 55 | PERROR(); 56 | return -1; 57 | } 58 | 59 | return 0; 60 | } 61 | 62 | int unmount_overlay(void) 63 | { 64 | plog(LOG_INFO, "unmounting overlay"); 65 | 66 | set_caps(CAP_EFFECTIVE, CAP_SET, 1, CAP_SYS_ADMIN); 67 | 68 | int err = umount2(PATHS.tmpfs, MNT_DETACH | UMOUNT_NOFOLLOW); 69 | 70 | set_caps(CAP_EFFECTIVE, CAP_CLEAR, 1, CAP_SYS_ADMIN); 71 | 72 | if (err == -1) { 73 | plog(LOG_ERROR, "failed unmounting overlay"); 74 | PERROR(); 75 | return -1; 76 | } 77 | // in case there are multiple mounts on tmpfs 78 | if (overlay_mounted()) { 79 | plog(LOG_ERROR, 80 | "tmpfs is still mounted; multiple mounts on it?"); 81 | return -1; 82 | } 83 | 84 | // delete required dirs 85 | err = remove_path(PATHS.overlay_upper); 86 | 87 | set_caps(CAP_EFFECTIVE, CAP_SET, 1, CAP_DAC_OVERRIDE); 88 | err = remove_path(PATHS.overlay_work); // work dir is owned by root 89 | set_caps(CAP_EFFECTIVE, CAP_CLEAR, 1, CAP_DAC_OVERRIDE); 90 | 91 | if (err == -1) { 92 | plog(LOG_WARN, "could not delete leftover directories"); 93 | PERROR(); 94 | return -1; 95 | } 96 | 97 | return 0; 98 | } 99 | 100 | // check if overlay is mounted 101 | bool overlay_mounted(void) 102 | { 103 | struct stat sb, sb2; 104 | 105 | if (stat(PATHS.runtime, &sb) == -1 || stat(PATHS.tmpfs, &sb2) == -1) { 106 | return false; 107 | } 108 | 109 | // check if runtime and tmpfs are on different devices 110 | if (sb.st_dev == sb2.st_dev) { 111 | // same filesystem 112 | return false; 113 | } 114 | struct statfs sfsb; 115 | 116 | if (statfs(PATHS.tmpfs, &sfsb) == -1) { 117 | plog(LOG_WARN, "failed getting info about tmpfs"); 118 | return true; 119 | } 120 | // warn if overlay is not actually an overlay mount 121 | if (sfsb.f_type != OVERLAYFS_SUPER_MAGIC) { 122 | plog(LOG_WARN, "tmpfs is mounted on a different filesytem " 123 | "which is not an overlay filesystem"); 124 | } 125 | 126 | return true; 127 | } 128 | 129 | #endif 130 | 131 | // vim: sw=8 ts=8 132 | -------------------------------------------------------------------------------- /src/sync.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include "sync.h" 3 | #include "log.h" 4 | #include "overlay.h" 5 | #include "types.h" 6 | #include "util.h" 7 | #include "config.h" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | static int sync_dir(struct Dir *dir, char *backup, char *tmpfs, bool overlay); 21 | static int unsync_dir(struct Dir *dir, char *backup, char *tmpfs, char *otmpfs, 22 | bool overlay); 23 | static int resync_dir(struct Dir *dir, char *backup, char *tmpfs, char *otmpfs, 24 | bool overlay); 25 | 26 | #ifndef NOOVERLAY 27 | static int repoint_dirs(const char *target); 28 | #endif 29 | 30 | static int repair_state(struct Dir *dir, char *backup, char *tmpfs, 31 | bool overlay); 32 | static int fix_session(struct Dir *dir, char *backup, char *tmpfs, 33 | bool overlay); 34 | static int fix_backup(char *backup, char *tmpfs); 35 | static int fix_tmpfs(char *backup, char *tmpfs, bool overlay); 36 | 37 | static int recover_path(struct Dir *syncdir, const char *path); 38 | 39 | static int clear_cache(struct Dir *dir, const char *backup, const char *tmpfs); 40 | 41 | static bool directory_is_safe(struct Dir *dir); 42 | 43 | // perform action on directories of browser 44 | int do_action_on_browser(struct Browser *browser, enum Action action, 45 | bool overlay) 46 | { 47 | plog(LOG_INFO, "doing '%s' on browser %s", action_str[action], 48 | browser->name); 49 | 50 | char backup[PATH_MAX], tmpfs[PATH_MAX], otmpfs[PATH_MAX]; 51 | int did_something = 0; 52 | 53 | for (size_t i = 0; i < browser->dirs_num; i++) { 54 | struct Dir *dir = browser->dirs[i]; 55 | int err = 0; 56 | 57 | if (!directory_is_safe(dir)) { 58 | plog(LOG_WARN, "directory %s is unsafe, skipping", 59 | dir->path); 60 | continue; 61 | } 62 | 63 | // get required paths 64 | if (get_paths(dir, backup, tmpfs) == -1) { 65 | plog(LOG_WARN, "failed getting required paths for %s", 66 | dir->path); 67 | continue; 68 | } 69 | #ifndef NOOVERLAY 70 | if ((action == ACTION_UNSYNC || action == ACTION_RESYNC) && 71 | overlay && get_overlay_paths(dir, otmpfs) == -1) { 72 | plog(LOG_WARN, "failed getting overlay path for %s", 73 | dir->path); 74 | continue; 75 | } 76 | #endif 77 | 78 | // clear cache in tmpfs and backup 79 | if (action == ACTION_RMCACHE && dir->type == DIR_CACHE) { 80 | if (clear_cache(dir, backup, tmpfs) == -1) { 81 | plog(LOG_ERROR, "failed clearing cache for %s", 82 | dir->path); 83 | } 84 | continue; 85 | } 86 | 87 | // attempt to repair state if previous/current 88 | // sync session is corrupted 89 | if (repair_state(dir, backup, tmpfs, overlay) == -1) { 90 | plog(LOG_WARN, 91 | "failed checking state of previous sync session for %s", 92 | dir->path); 93 | continue; 94 | } 95 | 96 | // perform action 97 | if (action == ACTION_SYNC) { 98 | err = sync_dir(dir, backup, tmpfs, overlay); 99 | } else if (action == ACTION_UNSYNC) { 100 | err = unsync_dir(dir, backup, tmpfs, otmpfs, overlay); 101 | } else if (action == ACTION_RESYNC) { 102 | err = resync_dir(dir, backup, tmpfs, otmpfs, overlay); 103 | } 104 | if (err == -1) { 105 | plog(LOG_WARN, "failed %sing directory %s", 106 | action_str[action], dir->path); 107 | continue; 108 | } 109 | did_something++; 110 | } 111 | 112 | return (did_something > 0) ? 0 : -1; 113 | } 114 | 115 | // if overlay is true then don't copy to tmpfs 116 | static int sync_dir(struct Dir *dir, char *backup, char *tmpfs, bool overlay) 117 | { 118 | struct stat sb; 119 | 120 | if (SYMEXISTS(dir->path) && DIREXISTS(tmpfs) && DIREXISTS(backup)) { 121 | plog(LOG_INFO, "directory %s is already synced", dir->path); 122 | return 0; 123 | } 124 | if (!DIREXISTS(dir->path)) { 125 | plog(LOG_ERROR, "directory %s does not exist or is invalid", 126 | dir->path); 127 | if (SYMEXISTS(dir->path)) { 128 | plog(LOG_WARN, 129 | "dangling symlink exists in its place, directory may have possibly been lost"); 130 | } 131 | return -1; 132 | } 133 | 134 | // don't sync if cache dirs not enabled 135 | if (!CONFIG.enable_cache && dir->type == DIR_CACHE) { 136 | return 0; 137 | } 138 | bool did_something = false; 139 | 140 | plog(LOG_INFO, "syncing directory %s", dir->path); 141 | 142 | // if backup exists but dir doesnt, then move backup to dir location 143 | if (DIREXISTS(backup) && !DIREXISTS(dir->path)) { 144 | if (move_path(backup, dir->path, false) == -1) { 145 | plog(LOG_ERROR, "failed moving backup to dir location"); 146 | PERROR(); 147 | return -1; 148 | } 149 | } 150 | 151 | // copy dir to tmpfs if we are not mounted (overlay) 152 | if (!overlay && !DIREXISTS(tmpfs)) { 153 | if (copy_path(dir->path, tmpfs, false) == -1) { 154 | plog(LOG_ERROR, "failed syncing dir to tmpfs"); 155 | PERROR(); 156 | return -1; 157 | } 158 | did_something = true; 159 | } 160 | 161 | if (DIREXISTS(dir->path) && !LEXISTS(backup)) { 162 | // temporary path to swap with dir 163 | char tmp_path[PATH_MAX]; 164 | 165 | create_unique_path(tmp_path, PATH_MAX, dir->path, 0); 166 | 167 | // create symlink 168 | if (symlink(tmpfs, tmp_path) == -1) { 169 | plog(LOG_ERROR, "failed creating symlink"); 170 | PERROR(); 171 | return -1; 172 | } 173 | 174 | // swap atomically symlink and dir 175 | if (renameat2(AT_FDCWD, tmp_path, AT_FDCWD, dir->path, 176 | RENAME_EXCHANGE) == -1) { 177 | plog(LOG_ERROR, "failed swapping dir and symlink"); 178 | unlink(tmp_path); 179 | PERROR(); 180 | return -1; 181 | } 182 | // move dir (tmp_path) to backup location 183 | if (move_path(tmp_path, backup, false) == -1) { 184 | plog(LOG_ERROR, "failed moving dir to backups"); 185 | PERROR(); 186 | return -1; 187 | } 188 | // update tmpfs in case backup was modified after copy, 189 | // only if browser is running 190 | if (!overlay && get_pid(dir->browser->procname) >= 0) { 191 | if (copy_path(backup, tmpfs, false) == -1) { 192 | plog(LOG_ERROR, 193 | "failed syncing tmpfs with backup"); 194 | PERROR(); 195 | return -1; 196 | } 197 | } 198 | did_something = true; 199 | } 200 | 201 | if (!did_something) { 202 | plog(LOG_INFO, "no sync action was performed"); 203 | } 204 | 205 | return 0; 206 | } 207 | 208 | // automatically resyncs directory 209 | static int unsync_dir(struct Dir *dir, char *backup, char *tmpfs, char *otmpfs, 210 | bool overlay) 211 | { 212 | struct stat sb; 213 | plog(LOG_INFO, "unsyncing directory %s", dir->path); 214 | 215 | if (EXISTSNOTSYM(dir->path)) { 216 | // not a symlink 217 | plog(LOG_INFO, "already unsynced"); 218 | return 0; 219 | } 220 | if (DIREXISTS(tmpfs)) { 221 | // sync backup if tmpfs exists 222 | if (resync_dir(dir, backup, tmpfs, otmpfs, overlay) == -1) { 223 | plog(LOG_ERROR, "failed resyncing"); 224 | return -1; 225 | } 226 | } else if (!DIREXISTS(backup)) { 227 | plog(LOG_ERROR, "backup nor tmpfs exists, cannot unsync"); 228 | return -1; 229 | } 230 | if (replace_paths(dir->path, backup) == -1) { 231 | plog(LOG_ERROR, "failed to replace dir with backup"); 232 | PERROR(); 233 | return -1; 234 | } 235 | // update dir in case tmpfs was modified after copy, 236 | // only if browser is running 237 | if (DIREXISTS(tmpfs) && get_pid(dir->browser->procname) >= 0) { 238 | if (copy_path(tmpfs, dir->path, false) == -1) { 239 | plog(LOG_ERROR, "failed syncing dir with tmpfs"); 240 | PERROR(); 241 | return -1; 242 | } 243 | } 244 | 245 | // we don't need to remove tmpfs if overlay is mounted 246 | // because it will disappear after unmount anyways 247 | if (!overlay && DIREXISTS(tmpfs) && remove_dir(tmpfs) == -1) { 248 | plog(LOG_ERROR, "failed removing tmpfs"); 249 | PERROR(); 250 | return -1; 251 | } 252 | 253 | return 0; 254 | } 255 | 256 | static int resync_dir(struct Dir *dir, char *backup, char *tmpfs, char *otmpfs, 257 | bool overlay) 258 | { 259 | if (!CONFIG.resync_cache && dir->type == DIR_CACHE) { 260 | return 0; 261 | } 262 | 263 | struct stat sb; 264 | 265 | plog(LOG_INFO, "resyncing directory %s", dir->path); 266 | 267 | if (!DIREXISTS(tmpfs)) { 268 | plog(LOG_ERROR, "%s does not exist", tmpfs); 269 | return -1; 270 | } 271 | // check if backup exists but not a directory 272 | if (EXISTSNOTDIR(backup)) { 273 | plog(LOG_ERROR, "%s is not a directory", backup); 274 | return -1; 275 | } 276 | char *tmp = (overlay) ? otmpfs : tmpfs; 277 | bool do_sync = true; 278 | 279 | // dont resync if otmpfs doesn't exist (means there arent any changes) 280 | if (overlay && !DIREXISTS(otmpfs)) { 281 | do_sync = false; 282 | } else { 283 | plog(LOG_DEBUG, "syncing tmpfs %s to backup", tmp); 284 | } 285 | 286 | if (do_sync && copy_path(tmp, backup, false) == -1) { 287 | plog(LOG_ERROR, "failed syncing %s with %s", tmpfs, backup); 288 | PERROR(); 289 | return -1; 290 | } 291 | 292 | return 0; 293 | } 294 | 295 | #ifndef NOOVERLAY 296 | // remount overlay in order to clear upper dir in an atomic way 297 | // a normal resync should be done before this this 298 | int reset_overlay(void) 299 | { 300 | plog(LOG_INFO, "resetting overlay"); 301 | 302 | if (!overlay_mounted()) { 303 | plog(LOG_WARN, "overlay not mounted"); 304 | return -1; 305 | } 306 | 307 | if (repoint_dirs("backup") == -1) { 308 | plog(LOG_ERROR, 309 | "failed repointing symlinks to respective backups"); 310 | return -1; 311 | } 312 | 313 | if (unmount_overlay() == -1) { 314 | plog(LOG_ERROR, "failed unmounting directory"); 315 | return -1; 316 | } 317 | 318 | if (mount_overlay() == -1) { 319 | plog(LOG_ERROR, "failed mounting directory"); 320 | return -1; 321 | } 322 | 323 | if (repoint_dirs("tmpfs") == -1) { 324 | plog(LOG_ERROR, 325 | "failed repointing symlinks to respective tmpfs'"); 326 | return -1; 327 | } 328 | 329 | return 0; 330 | } 331 | 332 | // make all directory symlinks point to backup or tmpfs in an atomic way 333 | static int repoint_dirs(const char *target) 334 | { 335 | struct stat sb; 336 | 337 | char backup[PATH_MAX], tmpfs[PATH_MAX]; 338 | const char *path = (strcmp(target, "tmpfs") == 0) ? tmpfs : 339 | (strcmp(target, "backup") == 0) ? backup : 340 | NULL; 341 | 342 | if (path == NULL) { 343 | return -1; 344 | } 345 | 346 | for (size_t i = 0; i < CONFIG.browsers_num; i++) { 347 | struct Browser *browser = CONFIG.browsers[i]; 348 | 349 | for (size_t k = 0; k < browser->dirs_num; k++) { 350 | struct Dir *dir = browser->dirs[k]; 351 | 352 | // skip if path doesn't exist or is not a symlink 353 | if (!LEXISTS(dir->path) || !S_ISLNK(sb.st_mode)) { 354 | plog(LOG_WARN, "not resyncing directory %s", 355 | dir->path); 356 | continue; 357 | } 358 | if (get_paths(dir, backup, tmpfs) == -1) { 359 | plog(LOG_WARN, 360 | "failed getting required paths for %s", 361 | dir->path); 362 | continue; 363 | } 364 | 365 | char tmp_path[PATH_MAX]; 366 | 367 | create_unique_path(tmp_path, PATH_MAX, dir->path, 0); 368 | 369 | // create symlink 370 | if (symlink(path, tmp_path) == -1) { 371 | plog(LOG_WARN, "failed creating symlink %s", 372 | tmp_path); 373 | PERROR(); 374 | continue; 375 | } 376 | 377 | // swap atomically symlink and new symlink 378 | if (renameat2(AT_FDCWD, tmp_path, AT_FDCWD, dir->path, 379 | RENAME_EXCHANGE) == -1) { 380 | plog(LOG_WARN, 381 | "failed swapping dir and symlink for %s", 382 | dir->path); 383 | unlink(tmp_path); 384 | PERROR(); 385 | continue; 386 | } 387 | 388 | // remove old symlink 389 | if (unlink(tmp_path) == -1) { 390 | plog(LOG_WARN, "failed removing %s", tmp_path); 391 | PERROR(); 392 | continue; 393 | } 394 | } 395 | } 396 | 397 | return 0; 398 | } 399 | #endif 400 | 401 | // should be run before any action. 402 | // repairs current session for directory or sends 403 | // directories that are alone as recovery directories. 404 | static int repair_state(struct Dir *dir, char *backup, char *tmpfs, 405 | bool overlay) 406 | { 407 | struct stat sb; 408 | 409 | if (DIREXISTS(dir->path)) { 410 | // if dir exists, then assume we aren't synced 411 | // any tmpfs or backup dirs are then converted into 412 | // recovery dirs 413 | if (recover_path(dir, backup) == -1 || 414 | recover_path(dir, tmpfs) == -1) { 415 | plog(LOG_ERROR, "failed recovering directories"); 416 | return -1; 417 | } 418 | } 419 | if (fix_session(dir, backup, tmpfs, overlay) == -1) { 420 | plog(LOG_ERROR, "failed checking state"); 421 | return -1; 422 | } 423 | 424 | return 0; 425 | } 426 | 427 | // attempt to fix session if the at least one directory exists. 428 | static int fix_session(struct Dir *dir, char *backup, char *tmpfs, bool overlay) 429 | { 430 | struct stat sb; 431 | 432 | if (fix_backup(backup, tmpfs) == -1 || 433 | fix_tmpfs(backup, tmpfs, overlay) == -1) { 434 | plog(LOG_ERROR, "failed fixing directories"); 435 | return -1; 436 | } 437 | 438 | check: 439 | // create symlink if it doesn't exist 440 | if (DIREXISTS(tmpfs) && !LEXISTS(dir->path)) { 441 | plog(LOG_INFO, "symlink does not exist, creating it"); 442 | 443 | if (symlink(tmpfs, dir->path) == -1) { 444 | plog(LOG_ERROR, "failed creating symlink"); 445 | PERROR(); 446 | return -1; 447 | } 448 | } else if (lstat(dir->path, &sb) == 0 && !S_ISDIR(sb.st_mode) && 449 | !S_ISLNK(sb.st_mode)) { 450 | // dir is not a directory or symlink 451 | plog(LOG_ERROR, "dir is not a directory nor a symlink"); 452 | return -1; 453 | } else if (SYMEXISTS(dir->path)) { 454 | // check if symlink points to correct path (tmpfs) 455 | char linkpath[PATH_MAX] = { 0 }; 456 | 457 | if (readlink(dir->path, linkpath, PATH_MAX - 1) == -1) { 458 | plog(LOG_ERROR, "failed reading link"); 459 | PERROR(); 460 | return -1; 461 | } 462 | if (!STR_EQUAL(linkpath, tmpfs)) { 463 | plog(LOG_ERROR, 464 | "symlink %s does not point to tmpfs, removing it", 465 | dir->path); 466 | if (unlink(dir->path) == -1) { 467 | plog(LOG_ERROR, "failed removing symlink"); 468 | PERROR(); 469 | return -1; 470 | } 471 | // go back to create symlink 472 | goto check; 473 | } 474 | } 475 | 476 | return 0; 477 | } 478 | 479 | static int fix_backup(char *backup, char *tmpfs) 480 | { 481 | struct stat sb; 482 | // create backup by copying tmpfs 483 | if (DIREXISTS(tmpfs) && !DIREXISTS(backup)) { 484 | plog(LOG_INFO, 485 | "backup not found, syncing tmpfs to backup location"); 486 | 487 | if (copy_path(tmpfs, backup, false) == -1) { 488 | plog(LOG_ERROR, "failed syncing tmpfs to backup"); 489 | PERROR(); 490 | return -1; 491 | } 492 | } else if (EXISTSNOTDIR(backup)) { 493 | // check if backup is not actually a directory 494 | plog(LOG_ERROR, "backup is not a directory"); 495 | return -1; 496 | } 497 | return 0; 498 | } 499 | 500 | static int fix_tmpfs(char *backup, char *tmpfs, bool overlay) 501 | { 502 | struct stat sb; 503 | 504 | // copy backup to tmpfs if it doesn't exist (only if no overlay) 505 | if (!overlay_mounted() && DIREXISTS(backup) && !DIREXISTS(tmpfs)) { 506 | plog(LOG_INFO, 507 | "tmpfs not found, syncing backup to tmpfs location"); 508 | 509 | if (copy_path(backup, tmpfs, false) == -1) { 510 | plog(LOG_ERROR, "failed syncing backup to tmpfs"); 511 | PERROR(); 512 | return -1; 513 | } 514 | } else if (EXISTSNOTDIR(tmpfs)) { 515 | // check if tmpfs is not a directory 516 | plog(LOG_ERROR, "tmpfs is not a directory"); 517 | return -1; 518 | } 519 | return 0; 520 | } 521 | 522 | // if path exists, then move path to parent dir 523 | // of syncdir and rename it as a recovery directory 524 | static int recover_path(struct Dir *sync_dir, const char *path) 525 | { 526 | struct stat sb; 527 | if (!LEXISTS(path)) { 528 | return 0; 529 | } 530 | time_t unixtime = time(NULL); 531 | struct tm *time_info = NULL; 532 | 533 | if (unixtime == (time_t)-1 || 534 | (time_info = localtime(&unixtime)) == NULL) { 535 | plog(LOG_ERROR, "failed getting current time"); 536 | PERROR(); 537 | return -1; 538 | } 539 | plog(LOG_INFO, "recovering %s", path); 540 | 541 | // get parent directory path 542 | char *tmp = strdup(sync_dir->path); 543 | 544 | if (tmp == NULL) { 545 | PERROR(); 546 | return -1; 547 | } 548 | char *parent_dir = dirname(tmp); 549 | 550 | if (STR_EQUAL(parent_dir, ".")) { 551 | plog(LOG_ERROR, "dir path returned '.'"); 552 | free(tmp); 553 | return -1; 554 | } 555 | 556 | char recovery_path[PATH_MAX]; 557 | char time_buf[100]; 558 | 559 | if (strftime(time_buf, 100, "%d-%m-%y_%H:%M:%S", time_info) != 0) { 560 | snprintf(recovery_path, PATH_MAX, 561 | "%s/" BOR_CRASH_PREFIX "%s_%s", parent_dir, 562 | sync_dir->dirname, time_buf); 563 | } else { 564 | plog(LOG_ERROR, "time is empty"); 565 | free(tmp); 566 | return -1; 567 | } 568 | free(tmp); 569 | 570 | char unique_path[PATH_MAX]; 571 | 572 | create_unique_path(unique_path, PATH_MAX, recovery_path, 0); 573 | 574 | if (move_path(path, unique_path, false) == -1) { 575 | plog(LOG_ERROR, "failed moving dir to %s", unique_path); 576 | PERROR(); 577 | return -1; 578 | } 579 | plog(LOG_INFO, "saved path as %s", unique_path); 580 | 581 | return 0; 582 | } 583 | 584 | static int clear_cache(struct Dir *dir, const char *backup, const char *tmpfs) 585 | { 586 | struct stat sb; 587 | 588 | // remove tmpfs first before backup 589 | // reverse order seems to break overlay filesystem 590 | // (possibly to do with whiteout files?) 591 | if (DIREXISTS(tmpfs)) { 592 | plog(LOG_INFO, "clearing cache %s", tmpfs); 593 | if (clear_dir(tmpfs) == -1) { 594 | PERROR(); 595 | return -1; 596 | } 597 | } 598 | 599 | if (DIREXISTS(backup)) { 600 | plog(LOG_INFO, "clearing cache %s", backup); 601 | if (clear_dir(backup) == -1) { 602 | PERROR(); 603 | return -1; 604 | } 605 | } 606 | 607 | if (DIREXISTS(dir->path)) { 608 | plog(LOG_INFO, "clearing cache %s", dir->path); 609 | if (clear_dir(dir->path) == -1) { 610 | PERROR(); 611 | return -1; 612 | } 613 | } 614 | 615 | return 0; 616 | } 617 | 618 | // write backup and tmpfs path for dir in given buffers 619 | // buffers should be PATH_MAX in size 620 | int get_paths(struct Dir *dir, char *backup, char *tmpfs) 621 | { 622 | // generate hash from path of dir to prevent filename conflicts 623 | char hash[41] = { 0 }; 624 | 625 | if (sha1digest(NULL, hash, (uint8_t *)dir->path, strlen(dir->path)) != 626 | 0) { 627 | PERROR(); 628 | return -1; 629 | } 630 | 631 | plog(LOG_DEBUG, "using dirname %s_%s for %s", hash, dir->dirname, 632 | dir->path); 633 | 634 | snprintf(backup, PATH_MAX, "%s/%s_%s", PATHS.backups, hash, 635 | dir->dirname); 636 | snprintf(tmpfs, PATH_MAX, "%s/%s_%s", PATHS.tmpfs, hash, dir->dirname); 637 | 638 | return 0; 639 | } 640 | 641 | #ifndef NOOVERLAY 642 | // same as get_paths but only return tmpfs located in upper 643 | int get_overlay_paths(struct Dir *dir, char *tmpfs) 644 | { 645 | char hash[41] = { 0 }; 646 | 647 | if (sha1digest(NULL, hash, (uint8_t *)dir->path, strlen(dir->path)) != 648 | 0) { 649 | PERROR(); 650 | return -1; 651 | } 652 | 653 | plog(LOG_DEBUG, "using overlay dirname %s_%s for %s", hash, 654 | dir->dirname, dir->path); 655 | 656 | snprintf(tmpfs, PATH_MAX, "%s/%s_%s", PATHS.overlay_upper, hash, 657 | dir->dirname); 658 | 659 | return 0; 660 | } 661 | #endif 662 | 663 | // return true if directory and its parent directory is safe to handle 664 | // safe means if file/dir is owned by user and if owner has read + write bits 665 | static bool directory_is_safe(struct Dir *dir) 666 | { 667 | struct stat sb; 668 | 669 | if (lstat(dir->path, &sb) == 0) { 670 | if (file_has_bad_perms(dir->path)) { 671 | return false; 672 | } 673 | } 674 | 675 | if (file_has_bad_perms(dir->parent_path)) { 676 | return false; 677 | } 678 | 679 | return true; 680 | } 681 | 682 | // vim: sw=8 ts=8 683 | -------------------------------------------------------------------------------- /src/teeny-sha1.c: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Teeny SHA-1 3 | * 4 | * The below sha1digest() calculates a SHA-1 hash value for a 5 | * specified data buffer and generates a hex representation of the 6 | * result. This implementation is a re-forming of the SHA-1 code at 7 | * https://github.com/jinqiangshou/EncryptionLibrary. 8 | * 9 | * Copyright (c) 2017 CTrabant 10 | * 11 | * License: MIT, see included LICENSE file under https://github.com/CTrabant/teeny-sha1 for details. 12 | * 13 | * To use the sha1digest() function either copy it into an existing 14 | * project source code file or include this file in a project and put 15 | * the declaration (example below) in the sources files where needed. 16 | ******************************************************************************/ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | /* Declaration: 24 | extern int sha1digest(uint8_t *digest, char *hexdigest, const uint8_t *data, size_t databytes); 25 | */ 26 | 27 | /******************************************************************************* 28 | * sha1digest: https://github.com/CTrabant/teeny-sha1 29 | * 30 | * Calculate the SHA-1 value for supplied data buffer and generate a 31 | * text representation in hexadecimal. 32 | * 33 | * Based on https://github.com/jinqiangshou/EncryptionLibrary, credit 34 | * goes to @jinqiangshou, all new bugs are mine. 35 | * 36 | * @input: 37 | * data -- data to be hashed 38 | * databytes -- bytes in data buffer to be hashed 39 | * 40 | * @output: 41 | * digest -- the result, MUST be at least 20 bytes 42 | * hexdigest -- the result in hex, MUST be at least 41 bytes 43 | * 44 | * At least one of the output buffers must be supplied. The other, if not 45 | * desired, may be set to NULL. 46 | * 47 | * @return: 0 on success and non-zero on error. 48 | ******************************************************************************/ 49 | int sha1digest(uint8_t *digest, char *hexdigest, const uint8_t *data, 50 | size_t databytes) 51 | { 52 | #define SHA1ROTATELEFT(value, bits) \ 53 | (((value) << (bits)) | ((value) >> (32 - (bits)))) 54 | 55 | uint32_t W[80]; 56 | uint32_t H[] = { 0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 57 | 0xC3D2E1F0 }; 58 | uint32_t a; 59 | uint32_t b; 60 | uint32_t c; 61 | uint32_t d; 62 | uint32_t e; 63 | uint32_t f = 0; 64 | uint32_t k = 0; 65 | 66 | uint32_t idx; 67 | uint32_t lidx; 68 | uint32_t widx; 69 | uint32_t didx = 0; 70 | 71 | int32_t wcount; 72 | uint32_t temp; 73 | uint64_t databits = ((uint64_t)databytes) * 8; 74 | uint32_t loopcount = (databytes + 8) / 64 + 1; 75 | uint32_t tailbytes = 64 * loopcount - databytes; 76 | uint8_t datatail[128] = { 0 }; 77 | 78 | if (!digest && !hexdigest) 79 | return -1; 80 | 81 | if (!data) 82 | return -1; 83 | 84 | /* Pre-processing of data tail (includes padding to fill out 512-bit chunk): 85 | Add bit '1' to end of message (big-endian) 86 | Add 64-bit message length in bits at very end (big-endian) */ 87 | datatail[0] = 0x80; 88 | datatail[tailbytes - 8] = (uint8_t)(databits >> 56 & 0xFF); 89 | datatail[tailbytes - 7] = (uint8_t)(databits >> 48 & 0xFF); 90 | datatail[tailbytes - 6] = (uint8_t)(databits >> 40 & 0xFF); 91 | datatail[tailbytes - 5] = (uint8_t)(databits >> 32 & 0xFF); 92 | datatail[tailbytes - 4] = (uint8_t)(databits >> 24 & 0xFF); 93 | datatail[tailbytes - 3] = (uint8_t)(databits >> 16 & 0xFF); 94 | datatail[tailbytes - 2] = (uint8_t)(databits >> 8 & 0xFF); 95 | datatail[tailbytes - 1] = (uint8_t)(databits >> 0 & 0xFF); 96 | 97 | /* Process each 512-bit chunk */ 98 | for (lidx = 0; lidx < loopcount; lidx++) { 99 | /* Compute all elements in W */ 100 | memset(W, 0, 80 * sizeof(uint32_t)); 101 | 102 | /* Break 512-bit chunk into sixteen 32-bit, big endian words */ 103 | for (widx = 0; widx <= 15; widx++) { 104 | wcount = 24; 105 | 106 | /* Copy byte-per byte from specified buffer */ 107 | while (didx < databytes && wcount >= 0) { 108 | W[widx] += (((uint32_t)data[didx]) << wcount); 109 | didx++; 110 | wcount -= 8; 111 | } 112 | /* Fill out W with padding as needed */ 113 | while (wcount >= 0) { 114 | W[widx] += 115 | (((uint32_t)datatail[didx - databytes]) 116 | << wcount); 117 | didx++; 118 | wcount -= 8; 119 | } 120 | } 121 | 122 | /* Extend the sixteen 32-bit words into eighty 32-bit words, with potential optimization from: 123 | "Improving the Performance of the Secure Hash Algorithm (SHA-1)" by Max Locktyukhin */ 124 | for (widx = 16; widx <= 31; widx++) { 125 | W[widx] = SHA1ROTATELEFT((W[widx - 3] ^ W[widx - 8] ^ 126 | W[widx - 14] ^ W[widx - 16]), 127 | 1); 128 | } 129 | for (widx = 32; widx <= 79; widx++) { 130 | W[widx] = SHA1ROTATELEFT((W[widx - 6] ^ W[widx - 16] ^ 131 | W[widx - 28] ^ W[widx - 32]), 132 | 2); 133 | } 134 | 135 | /* Main loop */ 136 | a = H[0]; 137 | b = H[1]; 138 | c = H[2]; 139 | d = H[3]; 140 | e = H[4]; 141 | 142 | for (idx = 0; idx <= 79; idx++) { 143 | if (idx <= 19) { 144 | f = (b & c) | ((~b) & d); 145 | k = 0x5A827999; 146 | } else if (idx >= 20 && idx <= 39) { 147 | f = b ^ c ^ d; 148 | k = 0x6ED9EBA1; 149 | } else if (idx >= 40 && idx <= 59) { 150 | f = (b & c) | (b & d) | (c & d); 151 | k = 0x8F1BBCDC; 152 | } else if (idx >= 60 && idx <= 79) { 153 | f = b ^ c ^ d; 154 | k = 0xCA62C1D6; 155 | } 156 | temp = SHA1ROTATELEFT(a, 5) + f + e + k + W[idx]; 157 | e = d; 158 | d = c; 159 | c = SHA1ROTATELEFT(b, 30); 160 | b = a; 161 | a = temp; 162 | } 163 | 164 | H[0] += a; 165 | H[1] += b; 166 | H[2] += c; 167 | H[3] += d; 168 | H[4] += e; 169 | } 170 | 171 | /* Store binary digest in supplied buffer */ 172 | if (digest) { 173 | for (idx = 0; idx < 5; idx++) { 174 | digest[idx * 4 + 0] = (uint8_t)(H[idx] >> 24); 175 | digest[idx * 4 + 1] = (uint8_t)(H[idx] >> 16); 176 | digest[idx * 4 + 2] = (uint8_t)(H[idx] >> 8); 177 | digest[idx * 4 + 3] = (uint8_t)(H[idx]); 178 | } 179 | } 180 | 181 | /* Store hex version of digest in supplied buffer */ 182 | if (hexdigest) { 183 | snprintf(hexdigest, 41, "%08x%08x%08x%08x%08x", H[0], H[1], 184 | H[2], H[3], H[4]); 185 | } 186 | 187 | return 0; 188 | } /* End of sha1digest() */ 189 | -------------------------------------------------------------------------------- /src/types.c: -------------------------------------------------------------------------------- 1 | #include "types.h" 2 | #include "util.h" 3 | #include "log.h" 4 | 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | // returns NULL if path doesn't exist 11 | // will still work if basename of path does not exist 12 | // uses realpath (3) to expand path 13 | struct Dir *new_dir(const char *path, enum DirType type, 14 | struct Browser *browser) 15 | { 16 | struct stat sb; 17 | 18 | char buf[PATH_MAX], buf2[PATH_MAX], rlpath[PATH_MAX]; 19 | char *bn = NULL, *parent_dir = NULL; 20 | 21 | snprintf(buf, PATH_MAX, "%s", path); 22 | trim(buf); 23 | bn = basename(buf); 24 | 25 | snprintf(buf2, PATH_MAX, "%s", path); 26 | trim(buf); 27 | parent_dir = dirname(buf2); 28 | 29 | // only expand path excluding basename 30 | if (realpath(parent_dir, rlpath) == NULL || !DIREXISTS(rlpath)) { 31 | plog(LOG_ERROR, "'%s' does not exist or is not a directory", 32 | rlpath); 33 | return NULL; 34 | } 35 | 36 | if (STR_EQUAL(bn, ".")) { 37 | plog(LOG_ERROR, 38 | "basename returned '.' when creating dir struct"); 39 | return NULL; 40 | } 41 | struct Dir *new = malloc(sizeof(*new)); 42 | 43 | if (new == NULL) { 44 | PERROR(); 45 | return NULL; 46 | } 47 | new->type = type; 48 | new->browser = browser; 49 | 50 | snprintf(new->path, PATH_MAX, "%s/%s", rlpath, bn); 51 | snprintf(new->dirname, NAME_MAX, "%s", bn); 52 | snprintf(new->parent_path, PATH_MAX, "%s", rlpath); 53 | 54 | return new; 55 | } 56 | 57 | void free_dir(struct Dir *dir) 58 | { 59 | free(dir); 60 | } 61 | 62 | struct Browser *new_browser(const char *name, const char *procname) 63 | { 64 | struct Browser *new = calloc(1, sizeof(*new)); 65 | 66 | if (new == NULL) { 67 | PERROR(); 68 | return NULL; 69 | } 70 | if (name != NULL) { 71 | snprintf(new->name, BROWSER_NAME_SIZE, "%s", name); 72 | } 73 | if (procname != NULL) { 74 | snprintf(new->procname, BROWSER_NAME_SIZE, "%s", procname); 75 | } 76 | 77 | return new; 78 | } 79 | 80 | void add_dir_to_browser(struct Browser *browser, struct Dir *dir) 81 | { 82 | if (dir != NULL) { 83 | browser->dirs[browser->dirs_num] = dir; 84 | (browser->dirs_num)++; 85 | } 86 | } 87 | 88 | void free_browser(struct Browser *browser) 89 | { 90 | if (browser != NULL) { 91 | for (size_t i = 0; i < browser->dirs_num; i++) { 92 | free_dir(browser->dirs[i]); 93 | } 94 | free(browser); 95 | } 96 | } 97 | 98 | // vim: sw=8 ts=8 99 | -------------------------------------------------------------------------------- /src/util.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include "util.h" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | // essentially mkdir -p 26 | int create_dir(const char *path, mode_t mode) 27 | { 28 | struct stat sb; 29 | // chdir through each directory and 30 | // create the next, then chdir onto that one 31 | int err = 0; 32 | char prev_cwd[PATH_MAX], path_str[PATH_MAX]; 33 | 34 | snprintf(path_str, PATH_MAX, "%s", path); 35 | if (getcwd(prev_cwd, PATH_MAX) == NULL) { 36 | err = -1; 37 | goto exit; 38 | } 39 | 40 | // chdir to root dir if absolute path 41 | if (path_str[0] == '/') { 42 | if (chdir("/") == -1) { 43 | err = -1; 44 | goto exit; 45 | } 46 | } 47 | 48 | char *dir = strtok(path_str, "/"); 49 | 50 | while (dir != NULL) { 51 | if (EXISTS(dir)) 52 | goto finish_cycle; 53 | 54 | if (mkdir(dir, mode) == -1) { 55 | err = -1; 56 | goto exit; 57 | } 58 | 59 | finish_cycle: 60 | if (chdir(dir) == -1) { 61 | err = -1; 62 | goto exit; 63 | } 64 | dir = strtok(NULL, "/"); 65 | } 66 | 67 | exit: 68 | if (chdir(prev_cwd) == -1) { 69 | return -1; 70 | } 71 | return err; 72 | } 73 | 74 | // find specified file/dir specified by path in given directories, 75 | // return malloc'd array of malloc'd strings of size count and length len 76 | // each string is an absolute path 77 | char **search_path(size_t *len, const char *path, size_t count, ...) 78 | { 79 | struct stat sb; 80 | 81 | int err = 0; 82 | char prev_cwd[PATH_MAX]; 83 | char **array = malloc(count * sizeof(*array)); 84 | va_list args; 85 | 86 | if (getcwd(prev_cwd, PATH_MAX) == NULL || array == NULL) { 87 | free(array); 88 | return NULL; 89 | } 90 | 91 | va_start(args, count); 92 | 93 | char *current_dir = NULL; 94 | (*len) = 0; 95 | 96 | for (size_t i = 0; i < count; i++) { 97 | current_dir = va_arg(args, char *); 98 | 99 | if (!DIREXISTS(current_dir) || chdir(current_dir) == -1) { 100 | continue; 101 | } 102 | 103 | if (EXISTS(path)) { 104 | char *unexpanded = NULL; 105 | 106 | asprintf(&unexpanded, "%s/%s", current_dir, path); 107 | 108 | array[*len] = realpath(unexpanded, NULL); 109 | free(unexpanded); 110 | 111 | if (array[*len] == NULL) { 112 | err = -1; 113 | break; 114 | } 115 | (*len)++; 116 | } 117 | 118 | if (chdir(prev_cwd) == -1) { 119 | err = -1; 120 | break; 121 | } 122 | } 123 | va_end(args); 124 | 125 | if (chdir(prev_cwd) == -1) { 126 | err = -1; 127 | } 128 | if (err == -1) { 129 | free_str_array(array, *len); 130 | free(array); 131 | return NULL; 132 | } 133 | return array; 134 | } 135 | 136 | // free array of strings up to arr_len not including the array 137 | void free_str_array(char **arr, size_t arr_len) 138 | { 139 | for (size_t i = 0; i < arr_len; i++) { 140 | free(arr[i]); 141 | } 142 | } 143 | 144 | // trim characters before and after the first or last non-whitespace chars 145 | // modifies string in place 146 | int trim(char *str) 147 | { 148 | size_t start = 0, end = strlen(str) - 1; 149 | 150 | while (start < strlen(str) && isspace(str[start])) { 151 | start++; 152 | } 153 | while (end > start && isspace(str[end])) { 154 | end--; 155 | } 156 | char *copy = strdup(str); 157 | if (copy == NULL) { 158 | return -1; 159 | } 160 | 161 | snprintf(str, end - start + 2, "%s", copy + start); 162 | 163 | free(copy); 164 | 165 | return 0; 166 | } 167 | 168 | // copy directory using by forking off rsync 169 | // if include_root param is true then make dest the 170 | // parent directory of src when copying 171 | int copy_path(const char *src, const char *dest, bool include_root) 172 | { 173 | if (file_has_bad_perms(src)) { 174 | return -1; 175 | } 176 | 177 | char *cmdline = NULL; 178 | char *template = NULL; 179 | char *src_dup = strdup(src); 180 | 181 | if (src_dup == NULL) { 182 | return -1; 183 | } 184 | 185 | // remove trailing slash in src if there is one 186 | if (src_dup[strlen(src_dup) - 1] == '/') { 187 | src_dup[strlen(src_dup) - 1] = 0; 188 | } 189 | struct stat sb; 190 | if (stat(src_dup, &sb) == -1) { 191 | return -1; 192 | } 193 | 194 | // trailing clash indicates to only copy contents (only if directory) 195 | if (!include_root && S_ISDIR(sb.st_mode)) { 196 | template = "rsync -aAX --no-whole-file --inplace '%s/' '%s'"; 197 | } else { 198 | template = "rsync -aAX --no-whole-file --inplace '%s' '%s'"; 199 | } 200 | 201 | if (asprintf(&cmdline, template, src_dup, dest) == -1) { 202 | free(src_dup); 203 | return -1; 204 | } 205 | free(src_dup); 206 | 207 | FILE *cmdp = popen(cmdline, "r"); 208 | 209 | free(cmdline); 210 | 211 | if (cmdp == NULL || pclose(cmdp) != 0) { 212 | return -1; 213 | } 214 | 215 | return 0; 216 | } 217 | 218 | // handles fies/directories passed from nftw (3) 219 | static int remove_dir_handler(const char *fpath, const struct stat *sb, 220 | int UNUSED(typeflag), struct FTW *UNUSED(ftwbuf)) 221 | { 222 | // cannot chmod a symlink 223 | if (!S_ISLNK(sb->st_mode) && chmod(fpath, S_IWUSR) == -1) { 224 | return -1; 225 | } else if (remove(fpath) == -1) { 226 | return -1; 227 | } 228 | 229 | return 0; 230 | } 231 | 232 | int remove_dir(const char *path) 233 | { 234 | if (file_has_bad_perms(path)) { 235 | return -1; 236 | } 237 | 238 | struct stat sb; 239 | 240 | if (!DIREXISTS(path)) { 241 | return -1; 242 | } 243 | 244 | if (nftw(path, remove_dir_handler, MAX_FD, FTW_DEPTH | FTW_PHYS) == 245 | -1) { 246 | return -1; 247 | } 248 | 249 | return 0; 250 | } 251 | 252 | int remove_path(const char *path) 253 | { 254 | // try using remove() and then remove_dir if path is not empty 255 | errno = 0; 256 | if (remove(path) == -1) { 257 | int err = 0; 258 | if (errno == ENOTEMPTY) { 259 | err = remove_dir(path); 260 | } 261 | if (errno != EISDIR && (errno != 0 || err == -1)) { 262 | return -1; 263 | } 264 | } 265 | return 0; 266 | } 267 | 268 | // remove dir contents 269 | int clear_dir(const char *path) 270 | { 271 | glob_t gb; 272 | 273 | char pattern[PATH_MAX]; 274 | 275 | snprintf(pattern, PATH_MAX, "%s/*", path); 276 | 277 | int err = glob(pattern, GLOB_NOSORT, NULL, &gb); 278 | 279 | if (err != 0 && err != GLOB_NOMATCH) { 280 | globfree(&gb); 281 | return -1; 282 | } 283 | 284 | for (size_t i = 0; i < gb.gl_pathc; i++) { 285 | if (remove_path(gb.gl_pathv[i]) == -1) { 286 | err = -1; 287 | } 288 | } 289 | 290 | globfree(&gb); 291 | 292 | return err; 293 | } 294 | 295 | // move src to dest inplace via rename (2) if on same filesystem 296 | // else copy it to dest and remove src 297 | // include_root -> see copy_dir() 298 | int move_path(const char *src, const char *dest, bool include_root) 299 | { 300 | if (file_has_bad_perms(src)) { 301 | return -1; 302 | } 303 | 304 | char *dest_dup = NULL; 305 | 306 | if (include_root) { 307 | if (mkdir(dest, 0755) == -1 && errno != EEXIST) { 308 | return -1; 309 | } 310 | 311 | char *tmp = strdup(src); 312 | asprintf(&dest_dup, "%s/%s", dest, basename(tmp)); 313 | free(tmp); 314 | 315 | if (dest_dup == NULL) { 316 | return -1; 317 | } 318 | } else { 319 | dest_dup = (char *)dest; 320 | } 321 | 322 | // attempt to use rename(), if returns EXDEV errno then do copy method 323 | errno = 0; 324 | if (rename(src, dest_dup) == -1) { 325 | if (errno == EXDEV) { 326 | if (copy_path(src, dest_dup, false) == -1) { 327 | return -1; 328 | } 329 | if (remove_dir(src) == -1) { 330 | return -1; 331 | } 332 | return 0; 333 | } 334 | return -1; 335 | } 336 | 337 | return 0; 338 | } 339 | 340 | // replace target with src atomically, works over different filesystems 341 | // deletes target after its been swapped 342 | int replace_paths(const char *target, const char *src) 343 | { 344 | char beside_path[PATH_MAX]; 345 | 346 | create_unique_path(beside_path, PATH_MAX, target, 0); 347 | 348 | if (move_path(src, beside_path, false) == -1) { 349 | return -1; 350 | } 351 | if (renameat2(AT_FDCWD, beside_path, AT_FDCWD, target, 352 | RENAME_EXCHANGE) == -1) { 353 | return -1; 354 | } 355 | if (remove_path(beside_path) == -1) { 356 | return -1; 357 | } 358 | 359 | return 0; 360 | } 361 | 362 | // iterate over a number until there is a unused filename 363 | // in format of -; will null terminate buf and preserve errno 364 | // if max_iter is 0 then use UNIQUE_PATH_MAX_ITER macro value 365 | void create_unique_path(char *buf, size_t buf_size, const char *path, 366 | size_t max_iter) 367 | { 368 | int prev_errno = errno; 369 | size_t max = (max_iter == 0) ? UNIQUE_PATH_MAX_ITER : max_iter; 370 | struct stat sb; 371 | 372 | snprintf(buf, buf_size, "%s", path); 373 | 374 | if (EXISTS(buf)) { 375 | size_t i = 1; 376 | 377 | while (EXISTS(buf) && i <= max) { 378 | snprintf(buf, PATH_MAX, "%s-%ld", path, i); 379 | i++; 380 | } 381 | } 382 | errno = prev_errno; 383 | } 384 | 385 | // return true if file/dir is not owned by user or 386 | // if owner does not have read + write bits 387 | bool file_has_bad_perms(const char *path) 388 | { 389 | struct stat sb; 390 | 391 | if (lstat(path, &sb) == -1) { 392 | return true; 393 | } else { 394 | if (sb.st_uid != getuid() || (sb.st_mode & 0777) < 0600) { 395 | return true; 396 | } 397 | } 398 | return false; 399 | } 400 | 401 | #ifndef NOOVERLAY 402 | 403 | // set the state of given capabiltiies in set and exit program on failure 404 | void set_caps(cap_flag_t set, cap_flag_value_t state, size_t count, ...) 405 | { 406 | cap_value_t caps[cap_max_bits()]; 407 | cap_value_t current_cap; 408 | va_list args; 409 | 410 | va_start(args, count); 411 | 412 | for (size_t i = 0; i < count; i++) { 413 | current_cap = va_arg(args, cap_value_t); 414 | 415 | if (!CAP_IS_SUPPORTED(current_cap)) { 416 | goto error; 417 | } 418 | 419 | caps[i] = current_cap; 420 | } 421 | 422 | va_end(args); 423 | 424 | cap_t caps_state = cap_get_proc(); 425 | 426 | if (caps_state == NULL) { 427 | goto error; 428 | } 429 | 430 | if (cap_set_flag(caps_state, set, (int)count, caps, state) == -1) { 431 | goto error; 432 | } 433 | if (cap_set_proc(caps_state) == -1) { 434 | goto error; 435 | } 436 | 437 | cap_free(caps_state); 438 | return; 439 | error: 440 | perror("failed setting capability"); 441 | exit(1); 442 | } 443 | 444 | // return true if specified capabilities are in state in given set 445 | // exit program on failure 446 | bool check_caps_state(cap_flag_t set, cap_flag_value_t state, size_t count, ...) 447 | { 448 | cap_t caps_state = cap_get_proc(); 449 | 450 | if (caps_state == NULL) { 451 | goto error; 452 | } 453 | 454 | va_list args; 455 | cap_flag_value_t cstate; 456 | cap_value_t current_cap; 457 | bool bad = false; 458 | 459 | va_start(args, count); 460 | 461 | for (size_t i = 0; i < count; i++) { 462 | current_cap = va_arg(args, cap_value_t); 463 | 464 | if (!CAP_IS_SUPPORTED(current_cap)) { 465 | bad = true; 466 | break; 467 | } 468 | 469 | if (cap_get_flag(caps_state, current_cap, set, &cstate) == -1) { 470 | goto error; 471 | } 472 | if (cstate != state) { 473 | bad = true; 474 | break; 475 | } 476 | } 477 | 478 | va_end(args); 479 | cap_free(caps_state); 480 | 481 | return (bad) ? false : true; 482 | error: 483 | perror("failed checking capabilities"); 484 | exit(1); 485 | } 486 | 487 | #endif 488 | 489 | #ifndef NOSYSTEMD 490 | // check if systemd user unit is active 491 | bool sd_uunit_active(const char *name) 492 | { 493 | char *cmd = NULL; 494 | 495 | asprintf(&cmd, "systemctl --user --quiet is-active '%s'", name); 496 | 497 | if (cmd == NULL) { 498 | return false; 499 | } 500 | 501 | int status = system(cmd); 502 | free(cmd); 503 | 504 | if (status == 0) { 505 | return true; 506 | } 507 | 508 | return false; 509 | } 510 | #endif 511 | 512 | // returns pid if name is found, else return -1 513 | pid_t get_pid(const char *name) 514 | { 515 | DIR *dp = opendir("/proc"); 516 | struct dirent *ent; 517 | 518 | if (dp == NULL) { 519 | return -1; 520 | } 521 | char exepath[PATH_MAX]; 522 | char rlpath[PATH_MAX]; 523 | 524 | while (errno = 0, (ent = readdir(dp)) != NULL) { 525 | long lpid = atol(ent->d_name); 526 | 527 | snprintf(exepath, PATH_MAX, "/proc/%ld/exe", lpid); 528 | 529 | if (realpath(exepath, rlpath) != NULL) { 530 | if (strcmp(basename(rlpath), name) == 0) { 531 | closedir(dp); 532 | return (pid_t)lpid; 533 | } 534 | } 535 | } 536 | closedir(dp); 537 | return -1; 538 | } 539 | 540 | static off_t dir_size = 0; 541 | 542 | static int get_dir_size_handler(const char *UNUSED(fpath), 543 | const struct stat *sb, int typeflag, 544 | struct FTW *UNUSED(ftwbuf)) 545 | { 546 | if (typeflag == FTW_F) { 547 | dir_size += sb->st_size; 548 | } 549 | return 0; 550 | } 551 | 552 | // get dir size in bytes 553 | off_t get_dir_size(const char *path) 554 | { 555 | dir_size = 0; 556 | 557 | if (nftw(path, get_dir_size_handler, MAX_FD, 0) == -1) { 558 | return -1; 559 | } 560 | 561 | return dir_size; 562 | } 563 | 564 | // convert size in bytes to malloc'd string in human readable format 565 | char *human_readable(off_t bytes) 566 | { 567 | if (bytes < 0) { 568 | char *str = NULL; 569 | asprintf(&str, "UNKNOWN"); 570 | return str; 571 | } 572 | 573 | char *suffix[] = { "B", "KB", "MB", "GB", "TB" }; 574 | char length = sizeof(suffix) / sizeof(suffix[0]); 575 | 576 | int i = 0; 577 | double dblBytes = (double)bytes; 578 | 579 | if (bytes > 1024) { 580 | for (i = 0; (bytes / 1024) > 0 && i < length - 1; 581 | i++, bytes /= 1024) 582 | dblBytes = (double)bytes / 1024.0; 583 | } 584 | 585 | char *str = NULL; 586 | asprintf(&str, "%.4g %s", dblBytes, suffix[i]); 587 | 588 | return str; 589 | } 590 | 591 | // check if program exists in $PATH 592 | bool program_exists(const char *program) 593 | { 594 | char cmdline[strlen(program) + 100]; 595 | 596 | snprintf(cmdline, strlen(program) + 100, 597 | "command -v %s > /dev/null 2>&1", program); 598 | 599 | int exit = system(cmdline); 600 | 601 | if (exit == 0) { 602 | return true; 603 | } 604 | 605 | return false; 606 | } 607 | 608 | // only update str if input is not NULL or empty 609 | void update_string(char *str, size_t size, const char *input) 610 | { 611 | if (input != NULL && strlen(input) > 0) { 612 | snprintf(str, size, "%s", input); 613 | } 614 | } 615 | 616 | // check if name is . or .. 617 | bool name_is_dot(const char *name) 618 | { 619 | if (STR_EQUAL(name, ".") || STR_EQUAL(name, "..")) { 620 | return true; 621 | } 622 | return false; 623 | } 624 | 625 | // copy a single regular file 626 | int copy_rfile(const char *src, const char *dest) 627 | { 628 | int err = 0; 629 | int src_fd = open(src, O_RDONLY), dest_fd = creat(dest, 0644); 630 | 631 | if (src_fd == -1 || dest_fd == -1) { 632 | err = -1; 633 | goto exit; 634 | } 635 | struct stat sb; 636 | 637 | if (fstat(src_fd, &sb) == -1) { 638 | err = -1; 639 | goto exit; 640 | } 641 | 642 | off_t offset = 0; 643 | ssize_t copied = 0; 644 | 645 | while (copied < sb.st_size) { 646 | ssize_t w = 647 | sendfile(dest_fd, src_fd, &offset, sb.st_size - copied); 648 | 649 | if (w == -1) { 650 | err = -1; 651 | goto exit; 652 | } 653 | copied += w; 654 | } 655 | 656 | exit: 657 | if (src_fd != -1) { 658 | close(src_fd); 659 | } 660 | if (dest_fd != -1) { 661 | close(dest_fd); 662 | } 663 | 664 | return err; 665 | } 666 | 667 | // vim: sw=8 ts=8 668 | -------------------------------------------------------------------------------- /systemd/system/bor-sleep@.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Resync on system sleep 3 | Before=sleep.target 4 | 5 | [Service] 6 | Type=oneshot 7 | ExecStart=/usr/bin/systemctl --user --machine=%i@ start --wait bor-sleep.target 8 | 9 | [Install] 10 | WantedBy=sleep.target 11 | 12 | # vim: ft=systemd 13 | -------------------------------------------------------------------------------- /systemd/user/bor-resync.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Timed resync 3 | After=bor.service 4 | Wants=bor-resync.timer 5 | BindsTo=bor.service 6 | 7 | [Service] 8 | Type=oneshot 9 | ExecStart=bor --resync --verbose 10 | Slice=background.slice 11 | 12 | # vim: ft=systemd 13 | -------------------------------------------------------------------------------- /systemd/user/bor-resync.timer: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Hourly timer for browser-on-ram 3 | BindsTo=bor.service 4 | 5 | [Timer] 6 | OnActiveSec=1h 7 | OnUnitActiveSec=1h 8 | 9 | # vim: ft=systemd 10 | -------------------------------------------------------------------------------- /systemd/user/bor-sleep-resync.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Resync on system sleep 3 | BindsTo=bor.service 4 | After=bor.service 5 | Before=bor-sleep.target 6 | 7 | [Service] 8 | Type=oneshot 9 | ExecStart=bor --resync --verbose 10 | Slice=background.slice 11 | 12 | [Install] 13 | WantedBy=bor-sleep.target 14 | 15 | # vim: ft=systemd 16 | -------------------------------------------------------------------------------- /systemd/user/bor-sleep.target: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=System sleep target for browser-on-ram 3 | StopWhenUnneeded=yes 4 | -------------------------------------------------------------------------------- /systemd/user/bor.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Browser-on-ram 3 | Wants=bor-resync.timer 4 | RequiresMountsFor=/home/ 5 | 6 | [Service] 7 | Type=oneshot 8 | RemainAfterExit=yes 9 | ExecStart=bor --sync --verbose 10 | ExecStop=bor --unsync --verbose 11 | Slice=background.slice 12 | 13 | [Install] 14 | WantedBy=default.target 15 | 16 | # vim: ft=systemd 17 | -------------------------------------------------------------------------------- /test/start_test: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cd "$(pwd)/test" 4 | 5 | rm -rf config runtime cache data 6 | 7 | mkdir -p config/bor/scripts runtime cache/test-browser/subdir data/test-browser/subdir 8 | 9 | export XDG_CONFIG_HOME=$(realpath config) 10 | export XDG_RUNTIME_DIR=$(realpath runtime) 11 | 12 | cat << EOT >> config/bor/bor.conf 13 | [config] 14 | enable_cache = true 15 | enable_overlay = true 16 | 17 | [browsers] 18 | test-browser 19 | EOT 20 | 21 | cat << EOT >> config/bor/scripts/test-browser.sh 22 | echo "procname = test-browser" 23 | echo "profile = $(realpath 'data/test-browser')" 24 | echo "cache = $(realpath 'cache/test-browser')" 25 | EOT 26 | 27 | touch data/test-browser/{data1,data2,subdir/data3} 28 | touch cache/test-browser/{cache1,cache2,subdir/cache3} 29 | 30 | echo -e "\nSYNC\n" 31 | ../build/debug/bin/bor --verbose --sync 32 | 33 | echo -e "\nRESYNC\n" 34 | 35 | ../build/debug/bin/bor --verbose --resync 36 | 37 | echo -e "\nUNSYNC\n" 38 | 39 | ../build/debug/bin/bor --verbose --unsync 40 | --------------------------------------------------------------------------------