From 4dc80a30d7e0cff86fbfa1c46072856dd3f67c08 Mon Sep 17 00:00:00 2001 From: askiiart Date: Tue, 1 Oct 2024 12:02:25 -0500 Subject: [PATCH 01/15] allow arguments for commands running in containers --- user | 1 + 1 file changed, 1 insertion(+) diff --git a/user b/user index b6624f0..ed8d6a4 100755 --- a/user +++ b/user @@ -208,6 +208,7 @@ def exec_c(container, cmds): exit(1) creation_env = os.environ.copy() creation_env['BLEND_NO_CHECK'] = 'true' + cmds = [ cmd.replace('\\-', '-') for cmd in cmds] subprocess.run(['blend', 'enter', '-cn', container, '--', *cmds], env=creation_env) From e47c5d7df79c3da42f19d2fe3776079fe1223e88 Mon Sep 17 00:00:00 2001 From: ALPERDURUKAN Date: Mon, 7 Oct 2024 21:15:48 +0300 Subject: [PATCH 02/15] Update blend Added Ubuntu 24.04 LTS to containers --- blend | 1 + 1 file changed, 1 insertion(+) diff --git a/blend b/blend index 570a068..0bc2eb5 100755 --- a/blend +++ b/blend @@ -92,6 +92,7 @@ distro_map = { 'fedora-39': 'registry.fedoraproject.org/fedora-toolbox:39', 'centos': 'quay.io/toolbx-images/centos-toolbox:latest', 'ubuntu-22.04': 'quay.io/toolbx/ubuntu-toolbox:22.04', + 'ubuntu-24.04-lts': 'quay.io/toolbx/ubuntu-toolbox:24.04', } default_distro = 'arch-linux' From a890b119d7ec01f8f6ad8f7ba83110a6d7fb7efd Mon Sep 17 00:00:00 2001 From: ALPERDURUKAN Date: Mon, 7 Oct 2024 21:18:50 +0300 Subject: [PATCH 03/15] Update containers.html Added Ubuntu 24.04 LTS container support --- blend-settings/src/pages/containers.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/blend-settings/src/pages/containers.html b/blend-settings/src/pages/containers.html index 54806c2..935a85a 100644 --- a/blend-settings/src/pages/containers.html +++ b/blend-settings/src/pages/containers.html @@ -15,6 +15,7 @@ +
@@ -66,4 +67,4 @@ - \ No newline at end of file + From 1c5c8ad313c1960f698ab0ceb5b504225d766724 Mon Sep 17 00:00:00 2001 From: askiiart Date: Thu, 7 Nov 2024 13:42:51 -0600 Subject: [PATCH 04/15] remove unused imports cuz why not --- user | 4 ---- 1 file changed, 4 deletions(-) diff --git a/user b/user index ed8d6a4..40c7422 100755 --- a/user +++ b/user @@ -1,13 +1,9 @@ #!/usr/bin/python3 import os -import yaml import click import subprocess -from urllib.request import urlopen - - class colors: reset = '\033[0m' bold = '\033[01m' From f7c5507d923403eb952be098e4529970c3ec312e Mon Sep 17 00:00:00 2001 From: askiiart Date: Tue, 8 Apr 2025 15:27:57 -0500 Subject: [PATCH 05/15] allow arguments for commands running in container (allow escaping hyphens) --- user | 1 + 1 file changed, 1 insertion(+) diff --git a/user b/user index b6624f0..ed8d6a4 100755 --- a/user +++ b/user @@ -208,6 +208,7 @@ def exec_c(container, cmds): exit(1) creation_env = os.environ.copy() creation_env['BLEND_NO_CHECK'] = 'true' + cmds = [ cmd.replace('\\-', '-') for cmd in cmds] subprocess.run(['blend', 'enter', '-cn', container, '--', *cmds], env=creation_env) From 4732a5735d61c3e9d9ddcfcc5731a4fa94d460e1 Mon Sep 17 00:00:00 2001 From: askiiart Date: Tue, 8 Apr 2025 15:31:39 -0500 Subject: [PATCH 06/15] misc fixes for distros/images not matching up right * fix arch/arch-linux mismatch * fix expecting fedora-rawhide (now fedora-*) * fix inconsistency in ubuntu naming * add aliases for arch and ubuntu to avoid breakage --- blend | 55 ++++++++++++++++++++++++++++++++++++++----------------- user | 11 ++++++----- 2 files changed, 44 insertions(+), 22 deletions(-) diff --git a/blend b/blend index 0bc2eb5..d34eda5 100755 --- a/blend +++ b/blend @@ -92,7 +92,7 @@ distro_map = { 'fedora-39': 'registry.fedoraproject.org/fedora-toolbox:39', 'centos': 'quay.io/toolbx-images/centos-toolbox:latest', 'ubuntu-22.04': 'quay.io/toolbx/ubuntu-toolbox:22.04', - 'ubuntu-24.04-lts': 'quay.io/toolbx/ubuntu-toolbox:24.04', + 'ubuntu-24.04': 'quay.io/toolbx/ubuntu-toolbox:24.04', } default_distro = 'arch-linux' @@ -168,8 +168,8 @@ def core_create_container(): info(f'creating container {name}, using {distro}') if check_container(name): - error(f'container {name} already exists') - exit(1) + error(f'container {name} already exists') + exit(1) podman_command = [] @@ -248,12 +248,15 @@ def core_run_container(cmd): def core_install_pkg(pkg): - if args.distro == 'fedora-rawhide': + if args.distro == 'arch': + args.distro = 'arch-linux' + + if args.distro.startswith('fedora-'): if args.noconfirm == True: core_run_container(f'sudo dnf -y install {pkg}') else: core_run_container(f'sudo dnf install {pkg}') - elif args.distro == 'arch': + elif args.distro == 'arch-linux': if core_get_retcode('[ -f /usr/bin/yay ]') != 0: core_run_container('sudo pacman -Sy') core_run_container( @@ -274,12 +277,15 @@ def core_install_pkg(pkg): def core_remove_pkg(pkg): - if args.distro == 'fedora-rawhide': + if args.distro == 'arch': + args.distro = 'arch-linux' + + if args.distro.startswith('fedora-'): if args.noconfirm == True: core_run_container(f'sudo dnf -y remove {pkg}') else: core_run_container(f'sudo dnf remove {pkg}') - elif args.distro == 'arch': + elif args.distro == 'arch-linux': if args.noconfirm == True: core_run_container(f'sudo pacman --noconfirm -Rcns {pkg}') else: @@ -293,9 +299,12 @@ def core_remove_pkg(pkg): def core_search_pkg(pkg): - if args.distro == 'fedora-rawhide': + if args.distro == 'arch': + args.distro = 'arch-linux' + + if args.distro.startswith('fedora-'): core_run_container(f'dnf search {pkg}') - elif args.distro == 'arch': + elif args.distro == 'arch-linux': core_run_container(f'yay -Sy') core_run_container(f'yay {pkg}') elif args.distro.startswith('ubuntu-'): @@ -304,9 +313,12 @@ def core_search_pkg(pkg): def core_show_pkg(pkg): - if args.distro == 'fedora-rawhide': + if args.distro == 'arch': + args.distro = 'arch-linux' + + if args.distro.startswith('fedora-'): core_run_container(f'dnf info {pkg}') - elif args.distro == 'arch': + elif args.distro == 'arch-linux': core_run_container(f'yay -Sy') core_run_container(f'yay -Si {pkg}') elif args.distro.startswith('ubuntu-'): @@ -358,21 +370,27 @@ def show_blend(): def sync_blends(): - if args.distro == 'fedora-rawhide': + if args.distro == 'arch': + args.distro = 'arch-linux' + + if args.distro.startswith('fedora-'): core_run_container(f'dnf makecache') - elif args.distro == 'arch': + elif args.distro == 'arch-linux': core_run_container(f'yay -Syy') elif args.distro.startswith('ubuntu-'): core_run_container(f'sudo apt-get update') def update_blends(): - if args.distro == 'fedora-rawhide': + if args.distro == 'arch': + args.distro = 'arch-linux' + + if args.distro.startswith('fedora-'): if args.noconfirm == True: core_run_container(f'sudo dnf -y upgrade') else: core_run_container(f'sudo dnf upgrade') - elif args.distro == 'arch': + elif args.distro == 'arch-linux': if args.noconfirm == True: core_run_container(f'yay --noconfirm') else: @@ -428,6 +446,7 @@ def enter_container(): def create_container(): for container in args.pkg: + container = 'ubuntu-24.04' if container == 'ubuntu-24.04-lts' else container args.container_name = container if container in distro_map.keys() and distro_input == None: args.distro = container @@ -443,10 +462,12 @@ def remove_container(): stdout=subprocess.DEVNULL) for bin in os.listdir(os.path.expanduser('~/.local/bin/blend_bin')): if bin.endswith(f'.{container}'): - os.remove(os.path.join(os.path.expanduser('~/.local/bin/blend_bin'), bin)) + os.remove(os.path.join(os.path.expanduser( + '~/.local/bin/blend_bin'), bin)) for app in os.listdir(os.path.expanduser('~/.local/share/applications')): if app.startswith(f'blend;{container};'): - os.remove(os.path.join(os.path.expanduser('~/.local/share/applications'), app)) + os.remove(os.path.join(os.path.expanduser( + '~/.local/share/applications'), app)) def start_containers(): diff --git a/user b/user index ed8d6a4..98b93cd 100755 --- a/user +++ b/user @@ -156,18 +156,19 @@ def associate_binary(association): @cli.command("create-container") @click.argument('container_name') -@click.argument('distro', default='arch') +@click.argument('distro', required=False) def create_container(container_name, distro): ''' Create a container ''' - if distro not in ('arch', 'almalinux-9', 'crystal-linux', 'debian', 'fedora-38', 'kali-linux', 'neurodebian-bookworm', 'rocky-linux', 'ubuntu-22.04', 'ubuntu-23.04'): - error( - f'distro {colors.bold}{distro}{colors.reset} not supported') if subprocess.run(['podman', 'container', 'exists', container_name], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL).returncode == 0: error(f'container {colors.bold}{container_name}{colors.reset} already exists') exit(1) - subprocess.run(['blend', 'create-container', '-cn', container_name, '-d', distro]) + args = ['blend', 'create-container', '-cn', container_name] + # blend handles no distro being specified already + if distro: + args.extend(['-d', distro]) + exit(subprocess.run(args).returncode) @cli.command("delete-container") From e4d2ca043ca2d76c801eb0e132b9d6125648fcc9 Mon Sep 17 00:00:00 2001 From: askiiart Date: Tue, 8 Apr 2025 21:18:14 -0500 Subject: [PATCH 07/15] bit of code cleanup, format --- blend | 10 +++++----- user | 23 +++++++++++++++-------- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/blend b/blend index d34eda5..c10175d 100755 --- a/blend +++ b/blend @@ -129,7 +129,7 @@ def check_container(name): def check_container_status(name): - if os.environ.get('SUDO_USER') == None: + if os.environ.get('SUDO_USER'): return host_get_output("podman inspect --type container " + name + " --format \"{{.State.Status}}\"") else: return host_get_output(f"sudo -u {os.environ.get('SUDO_USER')} podman inspect --type container " + name + " --format \"{{.State.Status}}\"") @@ -137,7 +137,7 @@ def check_container_status(name): def core_start_container(name, new_container=False): sudo = [] - if os.environ.get('SUDO_USER') != None: + if os.environ.get('SUDO_USER'): sudo = ['sudo', '-u', os.environ.get('SUDO_USER')] subprocess.call([*sudo, 'podman', 'start', name], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) @@ -150,7 +150,7 @@ def core_start_container(name, new_container=False): subprocess.call(['podman', 'logs', '--since', str(start_time), name]) exit(1) - if os.environ.get('SUDO_USER') == None: + if not os.environ.get('SUDO_USER'): logproc = pexpect.spawn( 'podman', args=['logs', '-f', '--since', str(start_time), name], timeout=3600) else: @@ -411,7 +411,7 @@ def enter_container(): podman_args = ['--env', 'LC_ALL=C.UTF-8'] sudo = [] - if os.environ.get('SUDO_USER') == None: + if not os.environ.get('SUDO_USER'): podman_args = ['--user', getpass.getuser()] else: sudo = ['sudo', '-u', os.environ.get( @@ -420,7 +420,7 @@ def enter_container(): if name not in ['LANG', 'LC_CTYPE', 'LC_ALL', 'PATH', 'HOST', 'HOSTNAME', 'SHELL'] and not name.startswith('_'): podman_args.append('--env') podman_args.append(name + '=' + val) - if os.environ.get('BLEND_COMMAND') == None or os.environ.get('BLEND_COMMAND') == '': + if not os.environ.get('BLEND_COMMAND'): if args.pkg == []: if os.getcwd() == os.path.expanduser('~') or os.getcwd().startswith(os.path.expanduser('~') + '/'): exit(subprocess.call([*sudo, 'podman', 'exec', *podman_args, diff --git a/user b/user index 98b93cd..a9b9b03 100755 --- a/user +++ b/user @@ -162,7 +162,8 @@ def create_container(container_name, distro): Create a container ''' if subprocess.run(['podman', 'container', 'exists', container_name], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL).returncode == 0: - error(f'container {colors.bold}{container_name}{colors.reset} already exists') + error( + f'container {colors.bold}{container_name}{colors.reset} already exists') exit(1) args = ['blend', 'create-container', '-cn', container_name] # blend handles no distro being specified already @@ -178,7 +179,8 @@ def delete_container(container): Delete a container ''' if subprocess.run(['podman', 'container', 'exists', container], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL).returncode != 0: - error(f'container {colors.bold}{container}{colors.reset} does not exist') + error( + f'container {colors.bold}{container}{colors.reset} does not exist') exit(1) subprocess.run(['blend', 'remove-container', container]) @@ -190,7 +192,8 @@ def shell(container): Enter a shell inside a container ''' if subprocess.run(['podman', 'container', 'exists', container], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL).returncode != 0: - error(f'container {colors.bold}{container}{colors.reset} does not exist') + error( + f'container {colors.bold}{container}{colors.reset} does not exist') exit(1) creation_env = os.environ.copy() creation_env['BLEND_NO_CHECK'] = 'true' @@ -205,12 +208,14 @@ def exec_c(container, cmds): Run a command inside a container ''' if subprocess.run(['podman', 'container', 'exists', container], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL).returncode != 0: - error(f'container {colors.bold}{container}{colors.reset} does not exist') + error( + f'container {colors.bold}{container}{colors.reset} does not exist') exit(1) creation_env = os.environ.copy() creation_env['BLEND_NO_CHECK'] = 'true' - cmds = [ cmd.replace('\\-', '-') for cmd in cmds] - subprocess.run(['blend', 'enter', '-cn', container, '--', *cmds], env=creation_env) + cmds = [cmd.replace('\\-', '-') for cmd in cmds] + subprocess.run(['blend', 'enter', '-cn', container, + '--', *cmds], env=creation_env) @cli.command("install") @@ -228,7 +233,8 @@ def install_c(container, pkgs): elif os.path.isfile(os.path.expanduser(f'~/.local/bin/blend_bin/pacman.{container}')): subprocess.run([f'sudo.{container}', 'pacman', '-Syu', *pkgs]) else: - error(f'container {colors.bold}{container}{colors.reset} does not exist') + error( + f'container {colors.bold}{container}{colors.reset} does not exist') exit(1) @@ -246,7 +252,8 @@ def remove_c(container, pkgs): elif os.path.isfile(os.path.expanduser(f'~/.local/bin/blend_bin/pacman.{container}')): subprocess.run([f'sudo.{container}', 'pacman', '-Rcns', *pkgs]) else: - error(f'container {colors.bold}{container}{colors.reset} does not exist') + error( + f'container {colors.bold}{container}{colors.reset} does not exist') exit(1) From fce7c69dc749d61a044d75fedbedc56d3c0e3cc9 Mon Sep 17 00:00:00 2001 From: Rudra Saraswat Date: Wed, 23 Apr 2025 15:44:57 +0100 Subject: [PATCH 08/15] chore: refactor code --- blend | 3 +- blend-settings/main.js | 21 +- blend-settings/src/index.html | 10 +- blend-system | 153 --------- blend.hook | 56 ---- blend.install | 19 -- overlayfs-tools/README.md | 42 --- overlayfs-tools/logic.c | 581 ---------------------------------- overlayfs-tools/logic.h | 37 --- overlayfs-tools/main.c | 266 ---------------- overlayfs-tools/makefile | 23 -- overlayfs-tools/sh.c | 98 ------ overlayfs-tools/sh.h | 20 -- 13 files changed, 23 insertions(+), 1306 deletions(-) delete mode 100755 blend-system delete mode 100755 blend.hook delete mode 100644 blend.install delete mode 100755 overlayfs-tools/README.md delete mode 100755 overlayfs-tools/logic.c delete mode 100755 overlayfs-tools/logic.h delete mode 100755 overlayfs-tools/main.c delete mode 100755 overlayfs-tools/makefile delete mode 100755 overlayfs-tools/sh.c delete mode 100755 overlayfs-tools/sh.h diff --git a/blend b/blend index c10175d..014989c 100755 --- a/blend +++ b/blend @@ -129,7 +129,7 @@ def check_container(name): def check_container_status(name): - if os.environ.get('SUDO_USER'): + if not os.environ.get('SUDO_USER'): return host_get_output("podman inspect --type container " + name + " --format \"{{.State.Status}}\"") else: return host_get_output(f"sudo -u {os.environ.get('SUDO_USER')} podman inspect --type container " + name + " --format \"{{.State.Status}}\"") @@ -143,6 +143,7 @@ def core_start_container(name, new_container=False): stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) start_time = time.time() - 1000 # workaround + time.sleep(1) if check_container_status(name) != 'running': print('') error('the entry point failed to run; try again later') diff --git a/blend-settings/main.js b/blend-settings/main.js index e4eac38..aeda78f 100644 --- a/blend-settings/main.js +++ b/blend-settings/main.js @@ -160,10 +160,15 @@ function loadTerminalWindow(title, cmd) { if (!terminalWindow.isDestroyed()) { terminalWindow.webContents.send("terminal.reset") terminalWindow.hide() - if (title.startsWith('Creating container: ')) { - mainWindow.webContents.send("container-created") - } else if (title.startsWith('Package installation')) { - packageWindow.webContents.send("installation-complete") + try { + if (title.startsWith('Creating container: ')) { + mainWindow.webContents.send("container-created") + } else if (title.startsWith('Package installation')) { + packageWindow.webContents.send("installation-complete") + } + } catch (err) { + console.log(err) + app.quit() } } }) @@ -178,10 +183,8 @@ function loadTerminalWindow(title, cmd) { app.whenReady().then(() => { app.allowRendererProcessReuse = false - if (process.argv.length > 2) { - if (process.argv[2] == 'package') { - createPackageWindow() - } + if (process.argv.includes('package')) { + createPackageWindow() } else { createWindow() } @@ -196,4 +199,4 @@ app.whenReady().then(() => { app.on('window-all-closed', function () { if (process.platform !== 'darwin') app.quit() -}) \ No newline at end of file +}) diff --git a/blend-settings/src/index.html b/blend-settings/src/index.html index d66f49c..1ef4cc7 100644 --- a/blend-settings/src/index.html +++ b/blend-settings/src/index.html @@ -18,7 +18,7 @@ Containers - +
@@ -43,6 +43,14 @@ if (fs.existsSync('/usr/bin/waydroid')) { document.getElementById('android-button').classList.remove('d-none') + } else { + document.getElementById('android-button').remove() + } + + if (fs.existsSync('/usr/bin/akshara')) { + document.getElementById('system-button').classList.remove('d-none') + } else { + document.getElementById('system-button').remove() } function page(page) { diff --git a/blend-system b/blend-system deleted file mode 100755 index 4fbf012..0000000 --- a/blend-system +++ /dev/null @@ -1,153 +0,0 @@ -#!/usr/bin/python3 -# Copyright (C) 2023 Rudra Saraswat -# -# This file is part of blend. -# -# blend is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# blend is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with blend. If not, see . - - -import os, re, sys, time -import argparse -import subprocess - -__version = '2.0.0' - -### Colors -class colors: - reset = '\033[0m' - bold = '\033[01m' - disable = '\033[02m' - underline = '\033[04m' - reverse = '\033[07m' - strikethrough = '\033[09m' - invisible = '\033[08m' - - class fg: - black = '\033[30m' - red = '\033[31m' - green = '\033[32m' - orange = '\033[33m' - blue = '\033[34m' - purple = '\033[35m' - cyan = '\033[36m' - lightgrey = '\033[37m' - darkgrey = '\033[90m' - lightred = '\033[91m' - lightgreen = '\033[92m' - yellow = '\033[93m' - lightblue = '\033[94m' - pink = '\033[95m' - lightcyan = '\033[96m' - - class bg: - black = '\033[40m' - red = '\033[41m' - green = '\033[42m' - orange = '\033[43m' - blue = '\033[44m' - purple = '\033[45m' - cyan = '\033[46m' - lightgrey = '\033[47m' - -### END - -### Helper functions - -def info(msg): - print (colors.bold + colors.fg.cyan + '>> i: ' + colors.reset + colors.bold + msg + colors.reset) - -def error(err): - print (colors.bold + colors.fg.red + '>> e: ' + colors.reset + colors.bold + err + colors.reset) - -### END - -def current_state(): - _state = -1 - for s in os.listdir('/.states'): - if re.match(r'^state([0-9]+)\.squashfs$', s): - if int(s[5:-7]) > _state: - _state = int(s[5:-7]) - return _state - -def save_state(): - subprocess.call(['mkdir', '-p', '/.states'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) - state = current_state() + 1 - - subprocess.call(['bash', '-c', 'rm -f /.states/*.tmp']) - - if subprocess.call(['mksquashfs', '/usr', f'/.states/state{state}.squashfs.tmp', '-no-compression'], stdout=sys.stdout, stderr=sys.stderr) == 0: - subprocess.call(['rm', '-rf', 'add-squashfs'], cwd='/tmp') - subprocess.call(['mkdir', '-p', 'add-squashfs'], cwd='/tmp') - subprocess.call(['cp', '-a', '/var/lib', 'add-squashfs/varlib'], cwd='/tmp') - if subprocess.call(['mksquashfs', 'add-squashfs', f'/.states/state{state}.squashfs.tmp', '-no-compression'], cwd='/tmp') == 0: - subprocess.call(['mv', f'/.states/state{state}.squashfs.tmp', f'/.states/state{state}.squashfs']) - else: - error('state creation failed') - exit(1) - else: - error('state creation failed') - exit(1) - - info(f'saved state {state}') - -def rollback(): - info("Rollback hasn't been implemented yet.") - -description = f''' -{colors.bold}{colors.fg.purple}Usage:{colors.reset} - blend-system [command] [options] [arguments] - -{colors.bold}{colors.fg.purple}Version:{colors.reset} {__version}{colors.bold} - -{colors.bold}{colors.fg.purple}available commands{colors.reset}: - {colors.bold}help{colors.reset} Show this help message and exit. - {colors.bold}version{colors.reset} Show version information and exit. - {colors.bold}save-state{colors.reset} Save the current state (backup). - {colors.bold}rollback{colors.reset} Rollback to previous state. - -{colors.bold}{colors.fg.purple}options for commands{colors.reset}: - {colors.bold}-v, --version{colors.reset} show version information and exit -''' - -epilog = f''' -{colors.bold}Made with {colors.fg.red}\u2764{colors.reset}{colors.bold} by Rudra Saraswat.{colors.reset} -''' - -parser = argparse.ArgumentParser(description=description, usage=argparse.SUPPRESS, - epilog=epilog, formatter_class=argparse.RawTextHelpFormatter) -command_map = { 'help': 'help', - 'version': 'version', - 'save-state': save_state, - 'rollback': rollback } -parser.add_argument('command', choices=command_map.keys(), help=argparse.SUPPRESS) -parser.add_argument('-v', '--version', action='version', version=f'%(prog)s {__version}', help=argparse.SUPPRESS) - -if len(sys.argv) == 1: - parser.print_help() - exit() - -if os.geteuid() != 0: - error('requires root') - exit(1) - -args = parser.parse_intermixed_args() - -command = command_map[args.command] - -if command == 'help': - parser.print_help() -elif command == 'version': - parser.parse_args(['--version']) -else: - command() diff --git a/blend.hook b/blend.hook deleted file mode 100755 index 0eb8f44..0000000 --- a/blend.hook +++ /dev/null @@ -1,56 +0,0 @@ -#!/bin/bash - -run_latehook() { - echo - - if [[ "$abort_staging" == true ]]; then - echo '[ BLEND ] Not applying system changes made in previous boot.' - rm -rf '/new_root/.upperdir' '/new_root/.workdir'; mkdir -p '/new_root/.upperdir' '/new_root/.workdir' - fi - - if [[ -d "/new_root/blend/overlay/current" ]]; then - echo '[ BLEND ] Detected old version of overlay implementation, switching.' - rm -rf /new_root/.upperdir /new_root/.workdir - mv /new_root/blend/overlay/current/usr /new_root/.upperdir - rm -rf /new_root/blend - fi - - # Broken attempt at getting rollback and snapshots working. - # - # if [[ -L "/new_root/.states/rollback.squashfs" ]] && [[ -e "/new_root/.states/rollback.squashfs" ]]; then - # echo -n '[ BLEND ] Rolling back to selected state. Do __not__ power off or reboot.' - # echo - # cd /new_root - # unsquashfs /new_root/.states/rollback.squashfs && ( - # for i in bin include lib lib32 share src; do - # rm -rf rm -rf /new_root/.workdir/"$i" rm -rf /new_root/.upperdir/"$i" /new_root/usr/"$i" - # mv squashfs-root/"$i" /new_root/usr - # done - # rm -rf /new_root/.workdir/varlib /new_root/.upperdir/varlib /new_root/var/lib - # mkdir -p /new_root/var/lib - # mv squashfs-root/varlib /new_root/var/varlib - # echo ' - SUCCESS ' - # echo - # ); cd .. - # fi - - for i in bin include lib lib32 share src; do - echo -n "[ BLEND ] Setting up /usr/${i} overlay (applying changes)." - rm -rf /new_root/.workdir/"$i" - mkdir -p /new_root/.upperdir/"$i" /new_root/.workdir/"$i" /new_root/usr/"$i" /new_root/tmp - cd /new_root/tmp; overlayfs-tools merge -l /new_root/usr/$i -u /new_root/.upperdir/$i &>/dev/null; chmod 755 ./overlay-tools-*; ./overlay-tools-* &>/dev/null; rm -f ./overlay-tools-*; cd / - mkdir -p /new_root/.upperdir/"$i" - mount -t overlay overlay -o 'lowerdir=/new_root/usr/'$i',upperdir=/new_root/.upperdir/'$i',workdir=/new_root/.workdir/'$i /new_root/usr/"$i" -o index=off - echo " - SUCCESS" - done - - echo - echo -n "[ BLEND ] Setting up /var/lib overlay (applying changes)." - rm -rf /new_root/.workdir/varlib - mkdir -p /new_root/.upperdir/varlib /new_root/.workdir/varlib /new_root/var/lib /new_root/tmp - cd /new_root/tmp; overlayfs-tools merge -l /new_root/var/lib -u /new_root/.upperdir/varlib &>/dev/null; chmod 755 ./overlay-tools-*; ./overlay-tools-* &>/dev/null; rm -f ./overlay-tools-*; cd / - mkdir -p /new_root/.upperdir/varlib - mount -t overlay overlay -o 'lowerdir=/new_root/var/lib,upperdir=/new_root/.upperdir/varlib,workdir=/new_root/.workdir/varlib' /new_root/var/lib -o index=off - echo ' - SUCCESS' - echo -} diff --git a/blend.install b/blend.install deleted file mode 100644 index 434a035..0000000 --- a/blend.install +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env bash -# SPDX-License-Identifier: GPL-3.0 - -build() { - add_module overlay - add_binary bash - add_binary tar - add_binary overlayfs-tools - add_runscript -} - -help() { - cat < A directory is made opaque by setting the xattr "trusted.overlay.opaque" to "y". - -However, only users with `CAP_SYS_ADMIN` can read `trusted.*` extended attributes. - -Warnings / limitations --------- - -- Only works for regular files and directories. Do not use it on OverlayFS with device files, socket files, etc.. -- Hard links may be broken (i.e. resulting in duplicated independent files). -- File owner, group and permission bits will be preserved. File timestamps, attributes and extended attributes might be lost. -- This program only works for OverlayFS with only one lower layer. -- It is recommended to have the OverlayFS unmounted before running this program. diff --git a/overlayfs-tools/logic.c b/overlayfs-tools/logic.c deleted file mode 100755 index 47ebfaa..0000000 --- a/overlayfs-tools/logic.c +++ /dev/null @@ -1,581 +0,0 @@ -#define _GNU_SOURCE -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "logic.h" -#include "sh.h" - -// exactly the same as in linux/fs.h -#define WHITEOUT_DEV 0 - -// exact the same as in fs/overlayfs/overlayfs.h -const char *ovl_opaque_xattr = "trusted.overlay.opaque"; -const char *ovl_redirect_xattr = "trusted.overlay.redirect"; -const char *ovl_metacopy_xattr = "trusted.overlay.metacopy"; - -#define MIN(X,Y) ((X) < (Y) ? (X) : (Y)) - -#define TRAILING_SLASH(ftype) (((ftype) == S_IFDIR) ? "/" : "") - -static inline mode_t file_type(const struct stat *status) { - return status->st_mode & S_IFMT; -} - -const char *ftype_name(mode_t type) { - switch (type) { - case S_IFDIR: - return "directory"; - case S_IFREG: - return "regular file"; - case S_IFLNK: - return "symbolic link"; - default: - return "special file"; - } -} - -const char *ftype_name_plural(mode_t type) { - switch (type) { - case S_IFDIR: - return "Directories"; - case S_IFREG: - return "Files"; - case S_IFLNK: - return "Symbolic links"; - default: - return "Special files"; - } -} - -static inline bool is_whiteout(const struct stat *status) { - return (file_type(status) == S_IFCHR) && (status->st_rdev == WHITEOUT_DEV); -} - -static inline mode_t permission_bits(const struct stat *status) { // not used yet. I haven't decided how to treat permission bit changes - return status->st_mode & (S_IRWXU | S_IRWXG | S_IRWXO | S_ISVTX); -} - -int is_opaque(const char *path, bool *output) { - char val; - ssize_t res = getxattr(path, ovl_opaque_xattr, &val, 1); - if ((res < 0) && (errno != ENODATA)) { - return -1; - } - *output = (res == 1 && val == 'y'); - return 0; -} - -int is_redirect(const char *path, bool *output) { - ssize_t res = getxattr(path, ovl_redirect_xattr, NULL, 0); - if ((res < 0) && (errno != ENODATA)) { - fprintf(stderr, "File %s redirect xattr can not be read.\n", path); - return -1; - } - *output = (res > 0); - return 0; -} - -int is_metacopy(const char *path, bool *output) { - ssize_t res = getxattr(path, ovl_metacopy_xattr, NULL, 0); - if ((res < 0) && (errno != ENODATA)) { - fprintf(stderr, "File %s metacopy xattr can not be read.\n", path); - return -1; - } - *output = (res >= 0); - return 0; -} - -// Treat redirect as opaque dir because it hides the tree in lower_path -// and we do not support following to redirected lower path -int is_opaquedir(const char *path, bool *output) { - bool opaque, redirect; - if (is_opaque(path, &opaque) < 0) { return -1; } - if (is_redirect(path, &redirect) < 0) { return -1; } - *output = opaque || redirect; - return 0; -} - -bool permission_identical(const struct stat *lower_status, const struct stat *upper_status) { - return (permission_bits(lower_status) == permission_bits(upper_status)) && (lower_status->st_uid == upper_status->st_uid) && (lower_status->st_gid == upper_status->st_gid); -} - -int read_chunk(int fd, char *buf, int len) { - ssize_t ret; - ssize_t remain = len; - while (remain > 0 && (ret = read(fd, buf, remain)) != 0) { - if (ret == -1) { - if (errno == EINTR) { - continue; - } - return -1; - } - remain -= ret; - buf += ret; - } - return len - remain; -} - -int regular_file_identical(const char *lower_path, const struct stat *lower_status, const char *upper_path, const struct stat *upper_status, bool *output) { - size_t blksize = (size_t) MIN(lower_status->st_blksize, upper_status->st_blksize); - if (lower_status->st_size != upper_status->st_size) { // different sizes - *output = false; - return 0; - } - bool metacopy, redirect; - if (is_metacopy(upper_path, &metacopy) < 0) { return -1; } - if (is_redirect(upper_path, &redirect) < 0) { return -1; } - if (metacopy) { - // metacopy means data is indentical, but redirect means it is not identical to lower_path - *output = !redirect; - return 0; - } - char lower_buffer[blksize]; - char upper_buffer[blksize]; - int lower_file = open(lower_path, O_RDONLY); - int upper_file = open(upper_path, O_RDONLY); - if (lower_file < 0) { - fprintf(stderr, "File %s can not be read for content.\n", lower_path); - return -1; - } - if (upper_file < 0) { - fprintf(stderr, "File %s can not be read for content.\n", upper_path); - return -1; - } - ssize_t read_lower; ssize_t read_upper; - do { // we can assume one will not reach EOF earlier than the other, as the file sizes are checked to be the same earlier - read_lower = read_chunk(lower_file, lower_buffer, blksize); - read_upper = read_chunk(upper_file, upper_buffer, blksize); - if (read_lower < 0) { - fprintf(stderr, "Error occured when reading file %s.\n", lower_path); - return -1; - } - if (read_upper < 0) { - fprintf(stderr, "Error occured when reading file %s.\n", upper_path); - return -1; - } - if (read_upper != read_lower) { // this should not happen as we've checked the sizes - fprintf(stderr, "Unexpected size difference: %s.\n", upper_path); - return -1; - } - if (memcmp(lower_buffer, upper_buffer, read_upper)) { *output = false; return 0; } // the output is by default false, but we still set it for ease of reading - } while (read_lower || read_upper); - *output = true; // now we can say they are identical - if (close(lower_file) || close(upper_file)) { return -1; } - return 0; -} - -int symbolic_link_identical(const char *lower_path, const char *upper_path, bool *output) { - char lower_buffer[PATH_MAX]; - char upper_buffer[PATH_MAX]; - ssize_t lower_len = readlink(lower_path, lower_buffer, PATH_MAX); - ssize_t upper_len = readlink(upper_path, upper_buffer, PATH_MAX); - if (lower_len < 0 || lower_len == PATH_MAX) { - fprintf(stderr, "Symbolic link %s cannot be resolved.\n", lower_path); - return -1; - } - if (upper_len < 0 || upper_len == PATH_MAX) { - fprintf(stderr, "Symbolic link %s cannot be resolved.\n", upper_path); - return -1; - } - lower_buffer[lower_len] = '\0'; - upper_buffer[upper_len] = '\0'; - *output = (strcmp(lower_buffer, upper_buffer) == 0); - return 0; -} - -static int vacuum_d(const char *lower_path, const char* upper_path, const size_t lower_root_len, const struct stat *lower_status, const struct stat *upper_status, FILE* script_stream, int *fts_instr) { - bool opaque; - if (is_opaquedir(upper_path, &opaque) < 0) { return -1; } - if (opaque) { // TODO: sometimes removing opaque directory (and combine with lower directory) might be better - *fts_instr = FTS_SKIP; - } - return 0; -} - -static int vacuum_dp(const char *lower_path, const char* upper_path, const size_t lower_root_len, const struct stat *lower_status, const struct stat *upper_status, FILE* script_stream, int *fts_instr) { - if (lower_status == NULL) { return 0; } // lower does not exist - if (file_type(lower_status) != S_IFDIR) { return 0; } - if (!permission_identical(lower_status, upper_status)) { return 0; } - bool opaque; - if (is_opaquedir(upper_path, &opaque) < 0) { - return -1; - } - if (opaque) { return 0; } - // this directory might be empty if all children are deleted in previous commands. but we simply don't test whether it's that case - return command(script_stream, "rmdir --ignore-fail-on-non-empty %U", upper_path); -} - -static int vacuum_f(const char *lower_path, const char* upper_path, const size_t lower_root_len, const struct stat *lower_status, const struct stat *upper_status, FILE* script_stream, int *fts_instr) { - if (lower_status == NULL) { return 0; } // lower does not exist - if (file_type(lower_status) != S_IFREG) { return 0; } - if (!permission_identical(lower_status, upper_status)) { return 0; } - bool identical; - if (regular_file_identical(lower_path, lower_status, upper_path, upper_status, &identical) < 0) { - return -1; - } - if (!identical) { return 0; } - return command(script_stream, "rm %U", upper_path); -} - -static int vacuum_sl(const char *lower_path, const char* upper_path, const size_t lower_root_len, const struct stat *lower_status, const struct stat *upper_status, FILE* script_stream, int *fts_instr) { - if (lower_status == NULL) { return 0; } // lower does not exist - if (file_type(lower_status) != S_IFLNK) { return 0; } - if (!permission_identical(lower_status, upper_status)) { return 0; } - bool identical; - if (symbolic_link_identical(lower_path, upper_path, &identical) < 0) { - return -1; - } - if (!identical) { return 0; } - return command(script_stream, "rm %U", upper_path); -} - -void print_only_in(const char *path) { - char *dirc = strdup(path); - char *basec = strdup(path); - char *dname = dirname(dirc); - char *bname = basename(basec); - printf("Only in %s: %s\n", dname, bname); - free(dirc); - free(basec); -} - -void print_removed(const char *lower_path, const size_t lower_root_len, mode_t lower_type) { - if (brief) { - print_only_in(lower_path); - } else { - printf("Removed: %s%s\n", &lower_path[lower_root_len], TRAILING_SLASH(lower_type)); - } -} - -void print_added(const char *lower_path, const size_t lower_root_len, const char *upper_path, mode_t upper_type) { - if (brief) { - print_only_in(upper_path); - } else { - printf("Added: %s%s\n", &lower_path[lower_root_len], TRAILING_SLASH(upper_type)); - } -} - -void print_replaced(const char *lower_path, const size_t lower_root_len, mode_t lower_type, const char *upper_path, mode_t upper_type) { - if (brief) { - printf("File %s is a %s while file %s is a %s\n", lower_path, ftype_name(lower_type), upper_path, ftype_name(upper_type)); - } else { - if (lower_type != S_IFDIR) { // dir removed already printed by list_deleted_files() - print_removed(lower_path, lower_root_len, lower_type); - } - print_added(lower_path, lower_root_len, upper_path, upper_type); - } -} - -void print_modified(const char *lower_path, const size_t lower_root_len, mode_t lower_type, const char *upper_path, bool identical) { - if (brief) { - if (!identical) { // brief format does not print permission difference - printf("%s %s and %s differ\n", ftype_name_plural(lower_type), lower_path, upper_path); - } - } else { - printf("Modified: %s%s\n", &lower_path[lower_root_len], TRAILING_SLASH(lower_type)); - } -} - -int list_deleted_files(const char *lower_path, size_t lower_root_len, mode_t upper_type) { // This WORKS with files and itself is listed. However, prefixs are WRONG! - // brief format needs to print only first level deleted children under opaque dir - bool children = (brief && (upper_type == S_IFDIR)); - if (!verbose && !children) { - if (!brief || upper_type == S_IFCHR) { // dir replaced already printed by print_replaced() - print_removed(lower_path, lower_root_len, S_IFDIR); - } - return 0; - } - FTSENT *cur; - char *paths[2] = {(char *) lower_path, NULL }; - FTS *ftsp = fts_open(paths, FTS_NOCHDIR | FTS_PHYSICAL, NULL); - if (ftsp == NULL) { return -1; } - int return_val = 0; - while (((cur = fts_read(ftsp)) != NULL) && (return_val == 0)) { - switch (cur->fts_info) { - case FTS_D: - // brief format does not need to print deleted grand children under opaque dir - if (children && cur->fts_level > 0) { - print_removed(cur->fts_path, lower_root_len, S_IFDIR); - fts_set(ftsp, cur, FTS_SKIP); - } - break; // do nothing - case FTS_DP: - // brief format does not need to print deleted dir under opaque dir itself - if (!children) { - print_removed(cur->fts_path, lower_root_len, S_IFDIR); - } - break; - case FTS_F: - print_removed(cur->fts_path, lower_root_len, S_IFREG); - break; - case FTS_SL: - print_removed(cur->fts_path, lower_root_len, S_IFLNK); - break; - case FTS_DEFAULT: - fprintf(stderr, "File %s is a special file (device or pipe). We cannot handle that.\n", cur->fts_path); - return_val = -1; - break; - default: - fprintf(stderr, "Error occured when opening %s.\n", cur->fts_path); - return_val = -1; - } - } - if (errno) { return_val = -1; } // if no error happened, fts_read will "sets the external variable errno to 0" according to the documentation - return fts_close(ftsp) || return_val; -} - -static int diff_d(const char *lower_path, const char* upper_path, const size_t lower_root_len, const struct stat *lower_status, const struct stat *upper_status, FILE* script_stream, int *fts_instr) { - bool opaque = false; - bool lower_exist = (lower_status != NULL); - if (lower_exist) { - if (file_type(lower_status) == S_IFDIR) { - if (is_opaquedir(upper_path, &opaque) < 0) { return -1; } - if (opaque) { - if (list_deleted_files(lower_path, lower_root_len, S_IFDIR) < 0) { return -1; } - } else { - if (!permission_identical(lower_status, upper_status)) { - print_modified(lower_path, lower_root_len, S_IFDIR, upper_path, true); - } - return 0; // children must be recursed, and directory itself does not need to be printed - } - } else { // other types of files - print_replaced(lower_path, lower_root_len, file_type(lower_status), upper_path, S_IFDIR); - } - } - if (!(verbose || (brief && opaque))) { // brief format needs to print children of opaque dir - *fts_instr = FTS_SKIP; - } - if (!lower_exist || (!brief && opaque)) { // brief format does not need to print opaque dir itself - print_added(lower_path, lower_root_len, upper_path, S_IFDIR); - } - return 0; -} - -static int diff_f(const char *lower_path, const char* upper_path, const size_t lower_root_len, const struct stat *lower_status, const struct stat *upper_status, FILE* script_stream, int *fts_instr) { - bool identical; - if (lower_status != NULL) { - switch (file_type(lower_status)) { - case S_IFREG: - if (regular_file_identical(lower_path, lower_status, upper_path, upper_status, &identical) < 0) { - return -1; - } - if (!(identical && permission_identical(lower_status, upper_status))) { - print_modified(lower_path, lower_root_len, S_IFREG, upper_path, identical); - } - return 0; - case S_IFDIR: - if (list_deleted_files(lower_path, lower_root_len, S_IFREG) < 0) { return -1; } - /* fallthrough */ - case S_IFLNK: - print_replaced(lower_path, lower_root_len, file_type(lower_status), upper_path, S_IFREG); - return 0; - default: - fprintf(stderr, "File %s is a special file (device or pipe). We cannot handle that.\n", lower_path); - return -1; - } - } - print_added(lower_path, lower_root_len, upper_path, S_IFREG); - return 0; -} - -static int diff_sl(const char *lower_path, const char* upper_path, const size_t lower_root_len, const struct stat *lower_status, const struct stat *upper_status, FILE* script_stream, int *fts_instr) { - bool identical; - if (lower_status != NULL) { - switch (file_type(lower_status)) { - case S_IFDIR: - if (list_deleted_files(lower_path, lower_root_len, S_IFLNK) < 0) { return -1; } - /* fallthrough */ - case S_IFREG: - print_replaced(lower_path, lower_root_len, file_type(lower_status), upper_path, S_IFLNK); - return 0; - case S_IFLNK: - if (symbolic_link_identical(lower_path, upper_path, &identical) < 0) { - return -1; - } - if (!(identical && permission_identical(lower_status, upper_status))) { - print_modified(lower_path, lower_root_len, S_IFLNK, upper_path, identical); - } - return 0; - default: - fprintf(stderr, "File %s is a special file (device or pipe). We cannot handle that.\n", lower_path); - return -1; - } - } - print_added(lower_path, lower_root_len, upper_path, S_IFLNK); - return 0; -} - -static int diff_whiteout(const char *lower_path, const char* upper_path, const size_t lower_root_len, const struct stat *lower_status, const struct stat *upper_status, FILE* script_stream, int *fts_instr) { - if (lower_status != NULL) { - if (file_type(lower_status) == S_IFDIR) { - if (list_deleted_files(lower_path, lower_root_len, S_IFCHR) < 0) { return -1; } - } else { - print_removed(lower_path, lower_root_len, file_type(lower_status)); - } - } // else: whiteouting a nonexistent file? must be an error. but we ignore that :) - return 0; -} - -static int merge_d(const char *lower_path, const char* upper_path, const size_t lower_root_len, const struct stat *lower_status, const struct stat *upper_status, FILE* script_stream, int *fts_instr) { - bool redirect; - if (is_redirect(upper_path, &redirect) < 0) { return -1; } - // merging redirects is not supported, we must abort merge so redirected lower (under whiteout) won't be deleted - // upper_path may be hiding the directory in lower_path, but there may be another redirect upper pointing at it - if (redirect) { - fprintf(stderr, "Found redirect on %s. Merging redirect is not supported - Abort.\n", upper_path); - return -1; - } - if (lower_status != NULL) { - if (file_type(lower_status) == S_IFDIR) { - bool opaque = false; - if (is_opaquedir(upper_path, &opaque) < 0) { return -1; } - if (opaque) { - if (command(script_stream, "rm -r %L", lower_path) < 0) { return -1; }; - } else { - if (!permission_identical(lower_status, upper_status)) { - command(script_stream, "chmod --reference %U %L", upper_path, lower_path); - } - return 0; // children must be recursed, and directory itself does not need to be printed - } - } else { - command(script_stream, "rm %L", lower_path); - } - } - *fts_instr = FTS_SKIP; - return command(script_stream, "mv -T %U %L", upper_path, lower_path); -} - -static int merge_dp(const char *lower_path, const char* upper_path, const size_t lower_root_len, const struct stat *lower_status, const struct stat *upper_status, FILE* script_stream, int *fts_instr) { - if (lower_status != NULL) { - if (file_type(lower_status) == S_IFDIR) { - bool opaque = false; - if (is_opaquedir(upper_path, &opaque) < 0) { return -1; } - if (!opaque) { // delete the directory: it should be empty already - return command(script_stream, "rmdir %U", upper_path); - } - } - } - return 0; -} - -static int merge_f(const char *lower_path, const char* upper_path, const size_t lower_root_len, const struct stat *lower_status, const struct stat *upper_status, FILE* script_stream, int *fts_instr) { - bool metacopy, redirect; - if (is_metacopy(upper_path, &metacopy) < 0) { return -1; } - if (is_redirect(upper_path, &redirect) < 0) { return -1; } - // merging metacopy is not supported, we must abort merge so lower data won't be deleted - if (metacopy || redirect) { - fprintf(stderr, "Found metacopy/redirect on %s. Merging metacopy/redirect is not supported - Abort.\n", upper_path); - return -1; - } - return command(script_stream, "rm -rf %L", lower_path) || command(script_stream, "mv -T %U %L", upper_path, lower_path); -} - -static int merge_sl(const char *lower_path, const char* upper_path, const size_t lower_root_len, const struct stat *lower_status, const struct stat *upper_status, FILE* script_stream, int *fts_instr) { - return command(script_stream, "rm -rf %L", lower_path) || command(script_stream, "mv -T %U %L", upper_path, lower_path); -} - -static int merge_whiteout(const char *lower_path, const char* upper_path, const size_t lower_root_len, const struct stat *lower_status, const struct stat *upper_status, FILE* script_stream, int *fts_instr) { - return command(script_stream, "rm -r %L", lower_path) || command(script_stream, "rm %U", upper_path); -} - -typedef int (*TRAVERSE_CALLBACK)(const char *lower_path, const char* upper_path, const size_t lower_root_len, const struct stat *lower_status, const struct stat *upper_status, FILE* script_stream, int *fts_instr); - -int traverse(const char *lower_root, const char *upper_root, FILE* script_stream, TRAVERSE_CALLBACK callback_d, TRAVERSE_CALLBACK callback_dp, TRAVERSE_CALLBACK callback_f, TRAVERSE_CALLBACK callback_sl, TRAVERSE_CALLBACK callback_whiteout) { // returns 0 on success - FTSENT *cur; - char *paths[2] = {(char *) upper_root, NULL }; - char lower_path[PATH_MAX]; - strcpy(lower_path, lower_root); - size_t upper_root_len = strlen(upper_root); - size_t lower_root_len = strlen(lower_root); - FTS *ftsp = fts_open(paths, FTS_NOCHDIR | FTS_PHYSICAL, NULL); - if (ftsp == NULL) { return -1; } - int return_val = 0; - while ((return_val == 0) && ((cur = fts_read(ftsp)) != NULL)) { - TRAVERSE_CALLBACK callback = NULL; - switch (cur->fts_info) { - case FTS_D: - callback = callback_d; - break; - case FTS_DP: - callback = callback_dp; - break; - case FTS_F: - callback = callback_f; - break; - case FTS_SL: - callback = callback_sl; - break; - case FTS_DEFAULT: - if (is_whiteout(cur->fts_statp)) { - callback = callback_whiteout; - } else { - return_val = -1; - fprintf(stderr, "File %s is a special file (device or pipe). We cannot handle that.\n", cur->fts_path); - } - break; - default: - return_val = -1; - fprintf(stderr, "Error occured when opening %s.\n", cur->fts_path); - } - if (callback != NULL) { - int fts_instr = 0; - struct stat lower_status; - bool lower_exist = true; - strcpy(&lower_path[lower_root_len], &(cur->fts_path[upper_root_len])); - if (lstat(lower_path, &lower_status) != 0) { - if (errno == ENOENT || errno == ENOTDIR) { // the corresponding lower file (or its ancestor) does not exist at all - lower_exist = false; - } else { // stat failed for some unknown reason - fprintf(stderr, "Failed to stat %s.\n", lower_path); - return_val = -1; - break; // do not call callback in this case - } - } - return_val = callback(lower_path, cur->fts_path, lower_root_len, lower_exist ? &lower_status : NULL, cur->fts_statp, script_stream, &fts_instr); // return_val must previously be 0 - if (fts_instr) { - fts_set(ftsp, cur, fts_instr); - } - } - } - if (errno) { return_val = -1; } // if no error happened, fts_read will "sets the external variable errno to 0" according to the documentation - return fts_close(ftsp) || return_val; -} - -static int deref_d(const char *mnt_path, const char* upper_path, const size_t mnt_root_len, const struct stat *mnt_status, const struct stat *upper_status, FILE* script_stream, int *fts_instr) { - bool redirect; - if (is_redirect(upper_path, &redirect) < 0) { return -1; } - if (!redirect) { return 0; } - *fts_instr = FTS_SKIP; - return command(script_stream, "rm -rf %U", upper_path) || command(script_stream, "cp -a %M %U", mnt_path, upper_path); -} - -static int deref_f(const char *mnt_path, const char* upper_path, const size_t mnt_root_len, const struct stat *mnt_status, const struct stat *upper_status, FILE* script_stream, int *fts_instr) { - bool metacopy; - if (is_metacopy(upper_path, &metacopy) < 0) { return -1; } - if (!metacopy) { return 0; } - return command(script_stream, "rm -r %U", upper_path) || command(script_stream, "cp -a %M %U", mnt_path, upper_path); -} - -int vacuum(const char* lowerdir, const char* upperdir, FILE* script_stream) { - return traverse(lowerdir, upperdir, script_stream, vacuum_d, vacuum_dp, vacuum_f, vacuum_sl, NULL); -} - -int diff(const char* lowerdir, const char* upperdir) { - return traverse(lowerdir, upperdir, NULL, diff_d, NULL, diff_f, diff_sl, diff_whiteout); -} - -int merge(const char* lowerdir, const char* upperdir, FILE* script_stream) { - return traverse(lowerdir, upperdir, script_stream, merge_d, merge_dp, merge_f, merge_sl, merge_whiteout); -} - -int deref(const char* mountdir, const char* upperdir, FILE* script_stream) { - return traverse(mountdir, upperdir, script_stream, deref_d, NULL, deref_f, NULL, NULL); -} diff --git a/overlayfs-tools/logic.h b/overlayfs-tools/logic.h deleted file mode 100755 index 374e517..0000000 --- a/overlayfs-tools/logic.h +++ /dev/null @@ -1,37 +0,0 @@ -/* - * logic.h / logic.c - * - * the logic for the three feature functions - */ - -#ifndef OVERLAYFS_TOOLS_LOGIC_H -#define OVERLAYFS_TOOLS_LOGIC_H - -#include - -extern bool verbose; -extern bool brief; - -/* - * feature function. will take very long time to complete. returns 0 on success - */ -int vacuum(const char* lowerdir, const char* upperdir, FILE* script_stream); - -/* - * feature function. will take very long time to complete. returns 0 on success - */ -int diff(const char* lowerdir, const char* upperdir); - -/* - * feature function. will take very long time to complete. returns 0 on success - */ -int merge(const char* lowerdir, const char* upperdir, FILE* script_stream); - -/* - * Unfold metacopy and redirect upper. - * - * mountdir is required and lowerdir is irrelevant. - */ -int deref(const char* mountdir, const char* upperdir, FILE* script_stream); - -#endif //OVERLAYFS_TOOLS_LOGIC_H diff --git a/overlayfs-tools/main.c b/overlayfs-tools/main.c deleted file mode 100755 index bea469f..0000000 --- a/overlayfs-tools/main.c +++ /dev/null @@ -1,266 +0,0 @@ -/* - * main.c - * - * the command line user interface - */ -#define _GNU_SOURCE -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#ifndef _SYS_STAT_H - #include -#endif -#include "logic.h" -#include "sh.h" - -#define STRING_BUFFER_SIZE PATH_MAX * 2 - -// currently, brief and verbose are mutually exclusive -bool verbose; -bool brief; -bool yes; - -void print_help(const char *program) { - printf("Usage: %s command options\n", program); - puts(""); - puts("Commands:"); - puts(" vacuum - remove duplicated files in upperdir where copy_up is done but the file is not actually modified"); - puts(" diff - show the list of actually changed files"); - puts(" merge - merge all changes from upperdir to lowerdir, and clear upperdir"); - puts(" deref - copy changes from upperdir to a new upperdir unfolding redirect and metacopy"); - puts(""); - puts("Options:"); - puts(" -l, --lowerdir=LOWERDIR the lowerdir of OverlayFS (required)"); - puts(" -u, --upperdir=UPPERDIR the upperdir of OverlayFS (required)"); - puts(" -m, --mountdir=MOUNTDIR the mountdir of OverlayFS (optional)"); - puts(" -L, --lowernew=LOWERNEW the lowerdir of new OverlayFS (optional)"); - puts(" -U, --uppernew=UPPERNEW the upperdir of new OverlayFS (optional)"); - puts(" -y --yes don't prompt if OverlayFS is still mounted (optional)"); - puts(" -v, --verbose with diff action only: when a directory only exists in one version, still list every file of the directory"); - puts(" -b, --brief with diff action only: conform to output of diff --brief --recursive --no-dereference"); - puts(" -h, --help show this help text"); - puts(""); - puts("See https://github.com/kmxz/overlayfs-tools/ for warnings and more information."); -} - -bool starts_with(const char *haystack, const char* needle) { - return strncmp(needle, haystack, strlen(needle)) == 0; -} - -bool is_mounted(const char *lower, const char *upper) { - FILE *f = fopen("/proc/mounts", "r"); - if (!f) { - fprintf(stderr, "Cannot read /proc/mounts to test whether OverlayFS is mounted.\n"); - return true; - } - char buf[STRING_BUFFER_SIZE]; - while (fgets(buf, STRING_BUFFER_SIZE, f)) { - if (!starts_with(buf, "overlay")) { - continue; - } - if (strlen(buf) == STRING_BUFFER_SIZE) { - fprintf(stderr, "OverlayFS line in /proc/mounts is too long.\n"); - return true; - } - char *m_lower = strstr(buf, "lowerdir="); - char *m_upper = strstr(buf, "upperdir="); - if (m_lower == NULL || m_upper == NULL) { - fprintf(stderr, "Cannot extract information from OverlayFS line in /proc/mounts.\n"); - return true; - } - m_lower = &(m_lower[strlen("lowerdir=")]); - m_upper = &(m_upper[strlen("upperdir=")]); - if (!(strncmp(lower, m_lower, strlen(lower)) && strncmp(upper, m_upper, strlen(upper)))) { - printf("The OverlayFS involved is still mounted.\n"); - return true; - } - } - return false; -} - -bool check_mounted(const char *lower, const char *upper) { - if (is_mounted(lower, upper) && !yes) { - printf("It is strongly recommended to unmount OverlayFS first. Still continue (not recommended)?: \n"); - int r = getchar(); - if (r != 'Y' && r != 'y') { - return true; - } - } - return false; -} - -bool directory_exists(const char *path) { - struct stat sb; - if (lstat(path, &sb) != 0) { return false; } - return (sb.st_mode & S_IFMT) == S_IFDIR; -} - -bool directory_create(const char *name, const char *path) { - if (mkdir(path, 0755) == 0 || errno == EEXIST) { return true; } - fprintf(stderr, "%s directory '%s' does not exist and cannot be created.\n", name, path); - exit(EXIT_FAILURE); -} - -bool real_check_xattr_trusted(const char *tmp_path, int tmp_file) { - int ret = fsetxattr(tmp_file, "trusted.overlay.test", "naive", 5, 0); - close(tmp_file); - if (ret) { return false; } - char verify_buffer[10]; - if (getxattr(tmp_path, "trusted.overlay.test", verify_buffer, 10) != 5) { return false; } - return !strncmp(verify_buffer, "naive", 5); -} - -bool check_xattr_trusted(const char *upper) { - char tmp_path[PATH_MAX]; - strcpy(tmp_path, upper); - strcat(tmp_path, "/.xattr_test_XXXXXX.tmp"); - int tmp_file = mkstemps(tmp_path, 4); - if (tmp_file < 0) { return false; } - bool ret = real_check_xattr_trusted(tmp_path, tmp_file); - unlink(tmp_path); - return ret; -} - -int main(int argc, char *argv[]) { - - char *lower = NULL; - char *upper = NULL; - char *dir, *mnt = NULL; - - static struct option long_options[] = { - { "lowerdir", required_argument, 0, 'l' }, - { "upperdir", required_argument, 0, 'u' }, - { "mountdir", required_argument, 0, 'm' }, - { "lowernew", required_argument, 0, 'L' }, - { "uppernew", required_argument, 0, 'U' }, - { "yes", no_argument , 0, 'y' }, - { "help", no_argument , 0, 'h' }, - { "verbose", no_argument , 0, 'v' }, - { "brief", no_argument , 0, 'b' }, - { 0, 0, 0, 0 } - }; - - int opt = 0; - int long_index = 0; - while ((opt = getopt_long_only(argc, argv, "l:u:m:L:U:yhvb", long_options, &long_index)) != -1) { - switch (opt) { - case 'l': - lower = realpath(optarg, NULL); - if (lower) { vars[LOWERDIR] = lower; } - break; - case 'u': - upper = realpath(optarg, NULL); - if (upper) { vars[UPPERDIR] = upper; } - break; - case 'm': - mnt = realpath(optarg, NULL); - if (mnt) { vars[MOUNTDIR] = mnt; } - break; - case 'L': - directory_create("New lowerdir", optarg); - dir = realpath(optarg, NULL); - if (dir) { vars[LOWERNEW] = dir; } - break; - case 'U': - directory_create("New upperdir", optarg); - dir = realpath(optarg, NULL); - if (dir) { vars[UPPERNEW] = dir; } - break; - case 'y': - yes = true; - break; - case 'h': - print_help(argv[0]); - return EXIT_SUCCESS; - case 'v': - verbose = true; - brief = false; - break; - case 'b': - verbose = false; - brief = true; - break; - default: - fprintf(stderr, "Option %c is not supported.\n", opt); - goto see_help; - } - } - - if (!lower) { - fprintf(stderr, "Lower directory not specified.\n"); - goto see_help; - } - if (!directory_exists(lower)) { - fprintf(stderr, "Lower directory cannot be opened.\n"); - goto see_help; - } - if (!upper) { - fprintf(stderr, "Upper directory not specified.\n"); - goto see_help; - } - if (!directory_exists(upper)) { - fprintf(stderr, "Upper directory cannot be opened.\n"); - goto see_help; - } - if (!check_xattr_trusted(upper)) { - fprintf(stderr, "The program cannot write trusted.* xattr. Try run again as root.\n"); - return EXIT_FAILURE; - } - // Relax check for mounted overlay if we are not going to modify lowerdir/upperdir - if ((!vars[LOWERNEW] || !vars[UPPERNEW]) && check_mounted(lower, upper)) { - return EXIT_FAILURE; - } - - if (optind == argc - 1) { - int out; - char filename_template[] = "overlay-tools-XXXXXX.sh"; - FILE *script = NULL; - if (strcmp(argv[optind], "diff") == 0) { - out = diff(lower, upper); - } else if (strcmp(argv[optind], "vacuum") == 0) { - script = create_shell_script(filename_template); - if (script == NULL) { fprintf(stderr, "Script file cannot be created.\n"); return EXIT_FAILURE; } - out = vacuum(lower, upper, script); - } else if (strcmp(argv[optind], "merge") == 0) { - script = create_shell_script(filename_template); - if (script == NULL) { fprintf(stderr, "Script file cannot be created.\n"); return EXIT_FAILURE; } - out = merge(lower, upper, script); - } else if (strcmp(argv[optind], "deref") == 0) { - if (!mnt || !vars[UPPERNEW]) { fprintf(stderr, "'deref' command requires --uppernew and --mountdir.\n"); return EXIT_FAILURE; } - if (!directory_exists(mnt)) { - fprintf(stderr, "OverlayFS mount directory cannot be opened.\n"); - goto see_help; - } - script = create_shell_script(filename_template); - if (script == NULL) { fprintf(stderr, "Script file cannot be created.\n"); return EXIT_FAILURE; } - out = deref(mnt, upper, script); - } else { - fprintf(stderr, "Action not supported.\n"); - goto see_help; - } - if (script != NULL) { - printf("The script %s is created. Run the script to do the actual work please. Remember to run it when the OverlayFS is not mounted.\n", filename_template); - fclose(script); - } - if (out) { - fprintf(stderr, "Action aborted due to fatal error.\n"); - return EXIT_FAILURE; - } - return EXIT_SUCCESS; - } - - fprintf(stderr, "Please specify one action.\n"); - -see_help: - fprintf(stderr, "Try '%s --help' for more information.\n", argv[0]); - return EXIT_FAILURE; - -} diff --git a/overlayfs-tools/makefile b/overlayfs-tools/makefile deleted file mode 100755 index 963cc99..0000000 --- a/overlayfs-tools/makefile +++ /dev/null @@ -1,23 +0,0 @@ -CC = gcc -CFLAGS = -Wall -std=c99 -LDFLAGS = -lm -ifneq (,$(wildcard /etc/alpine-release)) - LDFLAGS += -lfts -endif - -all: overlayfs-tools - -overlayfs-tools: main.o logic.o sh.o - $(CC) main.o logic.o sh.o -o overlayfs-tools $(LDFLAGS) - -main.o: main.c logic.h - $(CC) $(CFLAGS) -c main.c - -logic.o: logic.c logic.h sh.h - $(CC) $(CFLAGS) -c logic.c - -sh.o: sh.c sh.h - $(CC) $(CFLAGS) -c sh.c - -clean: - $(RM) main.o logic.o sh.o overlayfs-tools diff --git a/overlayfs-tools/sh.c b/overlayfs-tools/sh.c deleted file mode 100755 index 98565ac..0000000 --- a/overlayfs-tools/sh.c +++ /dev/null @@ -1,98 +0,0 @@ -#define _GNU_SOURCE -#include -#include -#include -#include -#include -#include -#include "sh.h" - -char * vars[NUM_VARS]; -const char * var_names[NUM_VARS] = { - "LOWERDIR", - "UPPERDIR", - "MOUNTDIR", - "LOWERNEW", - "UPPERNEW", -}; - -int quote(const char *filename, FILE *output); - -FILE* create_shell_script(char *tmp_path_buffer) { - int tmp_file = mkstemps(tmp_path_buffer, 3); // the 3 is for suffix length (".sh") - if (tmp_file < 0) { return NULL; } - fchmod(tmp_file, S_IRWXU); // chmod to 0700 - FILE* f = fdopen(tmp_file, "w"); - if (f == NULL) { return NULL; } - fprintf(f, "#!/usr/bin/env bash\n"); - fprintf(f, "set -x\n"); - time_t rawtime; - time (&rawtime); - fprintf(f, "# This shell script is generated by overlayfs-tools on %s\n", ctime (&rawtime)); - for (int i=0; i < NUM_VARS; i++) { - if (vars[i]) { - fprintf(f, "%s=", var_names[i]); - if (quote(vars[i], f) < 0) { return NULL; } - if (fputc('\n', f) == EOF) { return NULL; } - } - } - // Non-empty *NEW vars make a backup copy and override *DIR vars - if (vars[LOWERNEW]) { - fprintf(f, "rm -rf \"$LOWERNEW\"\n"); - fprintf(f, "cp -a \"$LOWERDIR\" \"$LOWERNEW\"\n"); - fprintf(f, "LOWERDIR="); - if (quote(vars[LOWERNEW], f) < 0) { return NULL; } - if (fputc('\n', f) == EOF) { return NULL; } - } - if (vars[UPPERNEW]) { - fprintf(f, "rm -rf \"$UPPERNEW\"\n"); - fprintf(f, "cp -a \"$UPPERDIR\" \"$UPPERNEW\"\n"); - fprintf(f, "UPPERDIR="); - if (quote(vars[UPPERNEW], f) < 0) { return NULL; } - if (fputc('\n', f) == EOF) { return NULL; } - } - return f; -} - -int quote(const char *filename, FILE *output) { - if (fputc('\'', output) == EOF) { return -1; } - for (const char *s = filename; *s != '\0'; s++) { - if (*s == '\'') { - if (fprintf(output, "'\"'\"'") < 0) { return -1; } - } else { - if (fputc(*s, output) == EOF) { return -1; } - } - } - if (fputc('\'', output) == EOF) { return -1; } - return 0; -} - -int substitue(char what, const char *filename, FILE *output) { - int i; - for (i=0; i < NUM_VARS; i++) - if (vars[i] && var_names[i][0] == what) - break; - if (i == NUM_VARS) { return -1; } - // filename prefix must match the var value - int prefix = strlen(vars[i]); - if (strncmp(filename, vars[i], prefix)) { return -1; } - filename += prefix; - fprintf(output, "\"$%s\"", var_names[i]); - return quote(filename, output); -} - -int command(FILE *output, const char *command_format, ...) { - va_list arg; - va_start(arg, command_format); - for (size_t i = 0; command_format[i] != '\0'; i++) { - if (command_format[i] == '%') { - const char *s = va_arg(arg, char *); - if (substitue(command_format[++i], s, output) < 0) { return -1; } - } else { - if (fputc(command_format[i], output) == EOF) { return -1; } - } - } - va_end(arg); - if (fputc('\n', output) == EOF) { return -1; } - return 0; -} diff --git a/overlayfs-tools/sh.h b/overlayfs-tools/sh.h deleted file mode 100755 index 19d9bdb..0000000 --- a/overlayfs-tools/sh.h +++ /dev/null @@ -1,20 +0,0 @@ -#ifndef OVERLAYFS_TOOLS_SH_H -#define OVERLAYFS_TOOLS_SH_H - -enum { - LOWERDIR, - UPPERDIR, - MOUNTDIR, - LOWERNEW, - UPPERNEW, - NUM_VARS -}; - -extern const char *var_names[NUM_VARS]; -extern char *vars[NUM_VARS]; - -FILE* create_shell_script(char *tmp_path_buffer); - -int command(FILE *output, const char *command_format, ...); - -#endif //OVERLAYFS_TOOLS_SH_H From 6bee27bb0d7df03cee93fee2da5d00e4e6ea59b8 Mon Sep 17 00:00:00 2001 From: Rudra Saraswat Date: Thu, 24 Apr 2025 00:05:46 +0100 Subject: [PATCH 09/15] feat: replace fedora-39 with fedora-42, fix package installer --- blend | 4 ++-- blend-settings/src/package-installer.html | 11 ++++++----- blend-settings/src/pages/containers.html | 2 +- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/blend b/blend index 014989c..98335d7 100755 --- a/blend +++ b/blend @@ -87,9 +87,9 @@ def error(err): distro_map = { - 'arch-linux': 'docker.io/library/archlinux', + 'arch-linux': 'quay.io/toolbx/arch-toolbox:latest', 'debian': 'quay.io/toolbx-images/debian-toolbox:testing', - 'fedora-39': 'registry.fedoraproject.org/fedora-toolbox:39', + 'fedora-42': 'quay.io/fedora/fedora-toolbox:42', 'centos': 'quay.io/toolbx-images/centos-toolbox:latest', 'ubuntu-22.04': 'quay.io/toolbx/ubuntu-toolbox:22.04', 'ubuntu-24.04': 'quay.io/toolbx/ubuntu-toolbox:24.04', diff --git a/blend-settings/src/package-installer.html b/blend-settings/src/package-installer.html index a20640b..a2cb791 100644 --- a/blend-settings/src/package-installer.html +++ b/blend-settings/src/package-installer.html @@ -88,7 +88,7 @@ - \ No newline at end of file + diff --git a/blend-settings/src/pages/containers.html b/blend-settings/src/pages/containers.html index 935a85a..c4f8ca1 100644 --- a/blend-settings/src/pages/containers.html +++ b/blend-settings/src/pages/containers.html @@ -13,7 +13,7 @@ - + From 7f59891a4e6e3657c3625126102c79fc9397e5c5 Mon Sep 17 00:00:00 2001 From: askiiart Date: Wed, 23 Apr 2025 13:42:17 -0500 Subject: [PATCH 10/15] only use `-it` for ttys --- blend | 63 +++++++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 46 insertions(+), 17 deletions(-) diff --git a/blend b/blend index 98335d7..716ddef 100755 --- a/blend +++ b/blend @@ -19,7 +19,7 @@ import os import sys -import glob +from sys import stdout import time import shutil import socket @@ -238,14 +238,22 @@ def host_get_output(cmd): return subprocess.run(['bash', '-c', cmd], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL).stdout.decode('UTF-8').strip() -def core_get_retcode(cmd): return subprocess.run(['podman', 'exec', '--user', getpass.getuser(), '-it', args.container_name, 'bash', '-c', cmd], - stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL).returncode +def core_get_retcode(cmd): + # only use `-it` if stdout is a tty + cmd = ['podman', 'exec', '--user', getpass.getuser(), '-it', + args.container_name, 'bash', '-c', cmd] + cmd = [x for x in cmd if x != '-it' or stdout.isatty()] + return subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL).returncode def core_run_container(cmd): if os.getcwd() == os.path.expanduser('~') or os.getcwd().startswith(os.path.expanduser('~') + '/'): - subprocess.call(['podman', 'exec', '--user', getpass.getuser(), - '-w', os.getcwd(), '-it', args.container_name, 'bash', '-c', cmd]) + # only use `-it` if stdout is a tty + cmd = ['podman', 'exec', '--user', getpass.getuser(), + '-w', os.getcwd(), '-it', args.container_name, 'bash', '-c', cmd] + cmd = [x for x in cmd if x != '-it' or stdout.isatty()] + + subprocess.call(cmd) def core_install_pkg(pkg): @@ -424,25 +432,46 @@ def enter_container(): if not os.environ.get('BLEND_COMMAND'): if args.pkg == []: if os.getcwd() == os.path.expanduser('~') or os.getcwd().startswith(os.path.expanduser('~') + '/'): - exit(subprocess.call([*sudo, 'podman', 'exec', *podman_args, - '-w', os.getcwd(), '-it', args.container_name, 'bash'])) + # only use `-it` if stdout is a tty + cmd = [*sudo, 'podman', 'exec', *podman_args, + '-w', os.getcwd(), '-it', args.container_name, 'bash'] + cmd = [x for x in cmd if x != '-it' or stdout.isatty()] + exit(subprocess.call(cmd)) + else: - exit(subprocess.call([*sudo, 'podman', 'exec', *podman_args, '-w', - '/run/host' + os.getcwd(), '-it', args.container_name, 'bash'])) + # only use `-it` if stdout is a tty + cmd = [*sudo, 'podman', 'exec', *podman_args, '-w', + '/run/host' + os.getcwd(), '-it', args.container_name, 'bash'] + cmd = [x for x in cmd if x != '-it' or stdout.isatty()] + exit(subprocess.call(cmd)) else: if os.getcwd() == os.path.expanduser('~') or os.getcwd().startswith(os.path.expanduser('~') + '/'): - exit(subprocess.call([*sudo, 'podman', 'exec', *podman_args, - '-w', os.getcwd(), '-it', args.container_name, *args.pkg])) + # only use `-it` if stdout is a tty + cmd = [*sudo, 'podman', 'exec', *podman_args, + '-w', os.getcwd(), '-it', args.container_name, *args.pkg] + cmd = [x for x in cmd if x != '-it' or stdout.isatty()] + exit(subprocess.call(cmd)) else: - exit(subprocess.call([*sudo, 'podman', 'exec', *podman_args, '-w', - '/run/host' + os.getcwd(), '-it', args.container_name, *args.pkg])) + # only use `-it` if stdout is a tty + cmd = [*sudo, 'podman', 'exec', *podman_args, '-w', + '/run/host' + os.getcwd(), '-it', args.container_name, *args.pkg] + cmd = [x for x in cmd if x != '-it' or stdout.isatty()] + exit(subprocess.call(cmd)) + else: if os.getcwd() == os.path.expanduser('~') or os.getcwd().startswith(os.path.expanduser('~') + '/'): - exit(subprocess.call([*sudo, 'podman', 'exec', *podman_args, '-w', os.getcwd( - ), '-it', args.container_name, 'bash', '-c', os.environ.get('BLEND_COMMAND')])) + # only use `-it` if stdout is a tty + cmd = [*sudo, 'podman', 'exec', *podman_args, '-w', os.getcwd(), '-it', + args.container_name, 'bash', '-c', os.environ.get('BLEND_COMMAND')] + cmd = [x for x in cmd if x != '-it' or stdout.isatty()] + exit(subprocess.call(cmd)) + else: - exit(subprocess.call([*sudo, 'podman', 'exec', *podman_args, '-w', - '/run/host' + os.getcwd(), '-it', args.container_name, 'bash'])) + # only use `-it` if stdout is a tty + cmd = [*sudo, 'podman', 'exec', *podman_args, '-w', + '/run/host' + os.getcwd(), '-it', args.container_name, 'bash'] + cmd = [x for x in cmd if x != '-it' or stdout.isatty()] + exit(subprocess.call(cmd)) def create_container(): From 6a6680403fb39782b87e13bd49996dc5a87a440b Mon Sep 17 00:00:00 2001 From: askiiart Date: Thu, 24 Apr 2025 11:15:12 -0500 Subject: [PATCH 11/15] DRY --- blend | 65 ++++++++++++++++++++++------------------------------------- 1 file changed, 24 insertions(+), 41 deletions(-) diff --git a/blend b/blend index 716ddef..3d20d54 100755 --- a/blend +++ b/blend @@ -83,6 +83,13 @@ def error(err): print(colors.bold + colors.fg.red + '>> e: ' + colors.reset + colors.bold + err + colors.reset) + +def podman_it_remover(cmd: list): + ''' + Removes `-it` from a podman command if stdout is not a tty + ''' + return [x for x in cmd if x != '-it' or stdout.isatty()] + # END @@ -239,21 +246,15 @@ def host_get_output(cmd): return subprocess.run(['bash', '-c', cmd], def core_get_retcode(cmd): - # only use `-it` if stdout is a tty - cmd = ['podman', 'exec', '--user', getpass.getuser(), '-it', - args.container_name, 'bash', '-c', cmd] - cmd = [x for x in cmd if x != '-it' or stdout.isatty()] - return subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL).returncode + podman_command = podman_it_remover(['podman', 'exec', '--user', getpass.getuser(), '-it', + args.container_name, 'bash', '-c', cmd]) + return subprocess.run(podman_command, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL).returncode def core_run_container(cmd): if os.getcwd() == os.path.expanduser('~') or os.getcwd().startswith(os.path.expanduser('~') + '/'): - # only use `-it` if stdout is a tty - cmd = ['podman', 'exec', '--user', getpass.getuser(), - '-w', os.getcwd(), '-it', args.container_name, 'bash', '-c', cmd] - cmd = [x for x in cmd if x != '-it' or stdout.isatty()] - - subprocess.call(cmd) + subprocess.call(podman_it_remover(['podman', 'exec', '--user', getpass.getuser(), + '-w', os.getcwd(), '-it', args.container_name, 'bash', '-c', cmd])) def core_install_pkg(pkg): @@ -432,46 +433,28 @@ def enter_container(): if not os.environ.get('BLEND_COMMAND'): if args.pkg == []: if os.getcwd() == os.path.expanduser('~') or os.getcwd().startswith(os.path.expanduser('~') + '/'): - # only use `-it` if stdout is a tty - cmd = [*sudo, 'podman', 'exec', *podman_args, - '-w', os.getcwd(), '-it', args.container_name, 'bash'] - cmd = [x for x in cmd if x != '-it' or stdout.isatty()] - exit(subprocess.call(cmd)) + exit(subprocess.call(podman_it_remover([*sudo, 'podman', 'exec', *podman_args, + '-w', os.getcwd(), '-it', args.container_name, 'bash']))) else: - # only use `-it` if stdout is a tty - cmd = [*sudo, 'podman', 'exec', *podman_args, '-w', - '/run/host' + os.getcwd(), '-it', args.container_name, 'bash'] - cmd = [x for x in cmd if x != '-it' or stdout.isatty()] - exit(subprocess.call(cmd)) + exit(subprocess.call(podman_it_remover([*sudo, 'podman', 'exec', *podman_args, '-w', + '/run/host' + os.getcwd(), '-it', args.container_name, 'bash']))) else: if os.getcwd() == os.path.expanduser('~') or os.getcwd().startswith(os.path.expanduser('~') + '/'): - # only use `-it` if stdout is a tty - cmd = [*sudo, 'podman', 'exec', *podman_args, - '-w', os.getcwd(), '-it', args.container_name, *args.pkg] - cmd = [x for x in cmd if x != '-it' or stdout.isatty()] - exit(subprocess.call(cmd)) + exit(subprocess.call(podman_it_remover([*sudo, 'podman', 'exec', *podman_args, + '-w', os.getcwd(), '-it', args.container_name, *args.pkg]))) else: - # only use `-it` if stdout is a tty - cmd = [*sudo, 'podman', 'exec', *podman_args, '-w', - '/run/host' + os.getcwd(), '-it', args.container_name, *args.pkg] - cmd = [x for x in cmd if x != '-it' or stdout.isatty()] - exit(subprocess.call(cmd)) + exit(subprocess.call(podman_it_remover([*sudo, 'podman', 'exec', *podman_args, '-w', + '/run/host' + os.getcwd(), '-it', args.container_name, *args.pkg]))) else: if os.getcwd() == os.path.expanduser('~') or os.getcwd().startswith(os.path.expanduser('~') + '/'): - # only use `-it` if stdout is a tty - cmd = [*sudo, 'podman', 'exec', *podman_args, '-w', os.getcwd(), '-it', - args.container_name, 'bash', '-c', os.environ.get('BLEND_COMMAND')] - cmd = [x for x in cmd if x != '-it' or stdout.isatty()] - exit(subprocess.call(cmd)) + exit(subprocess.call(podman_it_remover([*sudo, 'podman', 'exec', *podman_args, '-w', os.getcwd(), '-it', + args.container_name, 'bash', '-c', os.environ.get('BLEND_COMMAND')]))) else: - # only use `-it` if stdout is a tty - cmd = [*sudo, 'podman', 'exec', *podman_args, '-w', - '/run/host' + os.getcwd(), '-it', args.container_name, 'bash'] - cmd = [x for x in cmd if x != '-it' or stdout.isatty()] - exit(subprocess.call(cmd)) + exit(subprocess.call(podman_it_remover([*sudo, 'podman', 'exec', *podman_args, '-w', + '/run/host' + os.getcwd(), '-it', args.container_name, 'bash']))) def create_container(): From 021d2f0aa5e80b7df9f0e1486157b33b9965816e Mon Sep 17 00:00:00 2001 From: askiiart Date: Fri, 25 Apr 2025 08:52:44 -0500 Subject: [PATCH 12/15] add ability to load custom images from file (also not hardcoded anymore) --- blend | 17 +++++++++-------- images.example.yaml | 3 +++ images.yaml | 6 ++++++ 3 files changed, 18 insertions(+), 8 deletions(-) create mode 100644 images.example.yaml create mode 100644 images.yaml diff --git a/blend b/blend index 3d20d54..a5f8ecb 100755 --- a/blend +++ b/blend @@ -27,6 +27,7 @@ import getpass import pexpect import argparse import subprocess +import yaml __version = '2.0.0' @@ -93,14 +94,14 @@ def podman_it_remover(cmd: list): # END -distro_map = { - 'arch-linux': 'quay.io/toolbx/arch-toolbox:latest', - 'debian': 'quay.io/toolbx-images/debian-toolbox:testing', - 'fedora-42': 'quay.io/fedora/fedora-toolbox:42', - 'centos': 'quay.io/toolbx-images/centos-toolbox:latest', - 'ubuntu-22.04': 'quay.io/toolbx/ubuntu-toolbox:22.04', - 'ubuntu-24.04': 'quay.io/toolbx/ubuntu-toolbox:24.04', -} +distro_map = yaml.safe_load(open('/usr/share/blend/images.yaml')) +if os.path.exists(f'/etc/blend/images.yaml'): + distro_map += yaml.safe_load( + open(f'{os.getenv('XDG_CONFIG_HOME')}/blend/images.yaml')) + +if os.path.exists(f'{os.getenv('XDG_CONFIG_HOME')}/blend/images.yaml'): + distro_map += yaml.safe_load( + open(f'{os.getenv('XDG_CONFIG_HOME')}/blend/images.yaml')) default_distro = 'arch-linux' diff --git a/images.example.yaml b/images.example.yaml new file mode 100644 index 0000000..1927866 --- /dev/null +++ b/images.example.yaml @@ -0,0 +1,3 @@ +# Here you can put custom images to be used for blend +# for example: +# opensuse: quay.io/exampleauthor/opensuse:15 diff --git a/images.yaml b/images.yaml new file mode 100644 index 0000000..84a9f79 --- /dev/null +++ b/images.yaml @@ -0,0 +1,6 @@ +arch-linux: quay.io/toolbx/arch-toolbox:latest +debian: quay.io/toolbx-images/debian-toolbox:testing +fedora-42: quay.io/fedora/fedora-toolbox:42 +centos: quay.io/toolbx-images/centos-toolbox:latest +ubuntu-22.04: quay.io/toolbx/ubuntu-toolbox:22.04 +ubuntu-24.04: quay.io/toolbx/ubuntu-toolbox:24.04 From b771df150efd2fff1ff80c42fb0c5adff11db06b Mon Sep 17 00:00:00 2001 From: askiiart Date: Fri, 25 Apr 2025 10:39:14 -0500 Subject: [PATCH 13/15] add aliases and redo the format a bunch --- blend | 51 +++++++++++++++++++++++++++++++++++++-------- images.example.yaml | 3 --- images.yaml | 24 +++++++++++++++------ 3 files changed, 60 insertions(+), 18 deletions(-) delete mode 100644 images.example.yaml diff --git a/blend b/blend index a5f8ecb..204e5af 100755 --- a/blend +++ b/blend @@ -93,20 +93,53 @@ def podman_it_remover(cmd: list): # END +# TODO: fix temp paths before committing -distro_map = yaml.safe_load(open('/usr/share/blend/images.yaml')) -if os.path.exists(f'/etc/blend/images.yaml'): - distro_map += yaml.safe_load( - open(f'{os.getenv('XDG_CONFIG_HOME')}/blend/images.yaml')) -if os.path.exists(f'{os.getenv('XDG_CONFIG_HOME')}/blend/images.yaml'): - distro_map += yaml.safe_load( - open(f'{os.getenv('XDG_CONFIG_HOME')}/blend/images.yaml')) +def image_data_from_dict(dictionary): + ''' + Returns a distro map and aliases from a dict in this format: + + ``` + {'arch-linux': {'image': 'quay.io/toolbx/arch-toolbox:latest', 'aliases': ['arch']}} + ``` + + which is loaded in from yaml config files in this format: + + ```yaml + arch-linux: + image: "quay.io/toolbx/arch-toolbox:latest" + aliases: + - arch + ``` + ''' + distro_map = {} + aliases = {} + + for distro in dictionary.keys(): + distro_map[distro] = dictionary[distro]['image'] + try: + aliases[distro] = dictionary[distro]['aliases'] + except KeyError: + pass + + return (distro_map, aliases) + + +distro_map, aliases = image_data_from_dict( + yaml.safe_load(open('/usr/share/blend/images.yaml'))) +tmp = image_data_from_dict(yaml.safe_load(open('system.yaml'))['images']) +distro_map += tmp[0] +aliases += tmp[1] +tmp += image_data_from_dict(yaml.safe_load( + open(f'{os.getenv('XDG_CONFIG_HOME')}/blend/images.yaml'))) +distro_map += tmp[0] +aliases += tmp[1] default_distro = 'arch-linux' -def get_distro(): +def get_image(): try: return distro_map[args.distro] except: @@ -222,7 +255,7 @@ def core_create_container(): '--userns', 'keep-id', '--annotation', 'run.oci.keep_original_groups=1']) - podman_command.extend([get_distro()]) + podman_command.extend([get_image()]) # User (for init-blend) podman_command.extend(['--uid', str(os.geteuid())]) diff --git a/images.example.yaml b/images.example.yaml deleted file mode 100644 index 1927866..0000000 --- a/images.example.yaml +++ /dev/null @@ -1,3 +0,0 @@ -# Here you can put custom images to be used for blend -# for example: -# opensuse: quay.io/exampleauthor/opensuse:15 diff --git a/images.yaml b/images.yaml index 84a9f79..8753f84 100644 --- a/images.yaml +++ b/images.yaml @@ -1,6 +1,18 @@ -arch-linux: quay.io/toolbx/arch-toolbox:latest -debian: quay.io/toolbx-images/debian-toolbox:testing -fedora-42: quay.io/fedora/fedora-toolbox:42 -centos: quay.io/toolbx-images/centos-toolbox:latest -ubuntu-22.04: quay.io/toolbx/ubuntu-toolbox:22.04 -ubuntu-24.04: quay.io/toolbx/ubuntu-toolbox:24.04 +arch-linux: + image: quay.io/toolbx/arch-toolbox:latest + aliases: + - arch +debian: + image: quay.io/toolbx-images/debian-toolbox:testing +fedora-42: + image: quay.io/fedora/fedora-toolbox:42 + aliases: + - fedora +centos: + image: quay.io/toolbx-images/centos-toolbox:latest +ubuntu-22.04: + image: quay.io/toolbx/ubuntu-toolbox:22.04 +ubuntu-24.04: + image: quay.io/toolbx/ubuntu-toolbox:24.04 + aliases: + - ubuntu From da373914d68273cd77035a29b49f552de917cd6f Mon Sep 17 00:00:00 2001 From: askiiart Date: Fri, 25 Apr 2025 10:40:10 -0500 Subject: [PATCH 14/15] fix paths --- blend | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/blend b/blend index 204e5af..58a18b2 100755 --- a/blend +++ b/blend @@ -128,13 +128,14 @@ def image_data_from_dict(dictionary): distro_map, aliases = image_data_from_dict( yaml.safe_load(open('/usr/share/blend/images.yaml'))) -tmp = image_data_from_dict(yaml.safe_load(open('system.yaml'))['images']) -distro_map += tmp[0] -aliases += tmp[1] -tmp += image_data_from_dict(yaml.safe_load( - open(f'{os.getenv('XDG_CONFIG_HOME')}/blend/images.yaml'))) +tmp = image_data_from_dict(yaml.safe_load(open('/system.yaml'))['images']) distro_map += tmp[0] aliases += tmp[1] +if os.path.exists(f'{os.getenv('XDG_CONFIG_HOME')}/blend/images.yaml'): + tmp = image_data_from_dict(yaml.safe_load( + open(f'{os.getenv('XDG_CONFIG_HOME')}/blend/images.yaml'))) + distro_map += tmp[0] + aliases += tmp[1] default_distro = 'arch-linux' From f5bd881b5d547cb1d2e53be079ae24405014a04e Mon Sep 17 00:00:00 2001 From: askiiart Date: Fri, 25 Apr 2025 10:49:37 -0500 Subject: [PATCH 15/15] handle aliases --- blend | 23 ++++------------------- 1 file changed, 4 insertions(+), 19 deletions(-) diff --git a/blend b/blend index 58a18b2..32de591 100755 --- a/blend +++ b/blend @@ -293,9 +293,6 @@ def core_run_container(cmd): def core_install_pkg(pkg): - if args.distro == 'arch': - args.distro = 'arch-linux' - if args.distro.startswith('fedora-'): if args.noconfirm == True: core_run_container(f'sudo dnf -y install {pkg}') @@ -322,9 +319,6 @@ def core_install_pkg(pkg): def core_remove_pkg(pkg): - if args.distro == 'arch': - args.distro = 'arch-linux' - if args.distro.startswith('fedora-'): if args.noconfirm == True: core_run_container(f'sudo dnf -y remove {pkg}') @@ -344,9 +338,6 @@ def core_remove_pkg(pkg): def core_search_pkg(pkg): - if args.distro == 'arch': - args.distro = 'arch-linux' - if args.distro.startswith('fedora-'): core_run_container(f'dnf search {pkg}') elif args.distro == 'arch-linux': @@ -358,9 +349,6 @@ def core_search_pkg(pkg): def core_show_pkg(pkg): - if args.distro == 'arch': - args.distro = 'arch-linux' - if args.distro.startswith('fedora-'): core_run_container(f'dnf info {pkg}') elif args.distro == 'arch-linux': @@ -415,9 +403,6 @@ def show_blend(): def sync_blends(): - if args.distro == 'arch': - args.distro = 'arch-linux' - if args.distro.startswith('fedora-'): core_run_container(f'dnf makecache') elif args.distro == 'arch-linux': @@ -427,9 +412,6 @@ def sync_blends(): def update_blends(): - if args.distro == 'arch': - args.distro = 'arch-linux' - if args.distro.startswith('fedora-'): if args.noconfirm == True: core_run_container(f'sudo dnf -y upgrade') @@ -494,7 +476,6 @@ def enter_container(): def create_container(): for container in args.pkg: - container = 'ubuntu-24.04' if container == 'ubuntu-24.04-lts' else container args.container_name = container if container in distro_map.keys() and distro_input == None: args.distro = container @@ -578,6 +559,10 @@ if len(sys.argv) == 1: exit() args = parser.parse_intermixed_args() +if args.distro not in distro_map.keys(): + for (distro, al) in aliases: + if args.distro in al: + args.distro = distro command = command_map[args.command]