textselect

Interactively select lines and pipe it to a command
Log | Files | Refs | README | LICENSE

commit 2ce1130421b517eac05514a7834c842b8d5f9182
Author: Friedel Schön <[email protected]>
Date:   Tue,  6 Aug 2024 13:46:00 +0200

first commit

Diffstat:
A.clang-format | 46++++++++++++++++++++++++++++++++++++++++++++++
ALICENSE | 18++++++++++++++++++
AMakefile | 20++++++++++++++++++++
Aarg.h | 19+++++++++++++++++++
Areadme.md | 83+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atextselect.1 | 58++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atextselect.c | 272+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
7 files changed, 516 insertions(+), 0 deletions(-)

diff --git a/.clang-format b/.clang-format @@ -0,0 +1,46 @@ +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: true +AlignConsecutiveDeclarations: true +AlignConsecutiveMacros: true +AlignEscapedNewlines: true +AlignOperands: AlignAfterOperator +AllowShortBlocksOnASingleLine: true +AllowShortIfStatementsOnASingleLine: true +AllowShortLoopsOnASingleLine: true +BreakBeforeBraces: Attach +BreakBeforeTernaryOperators: true +BreakConstructorInitializers: BeforeComma +BreakInheritanceList: BeforeComma +BreakStringLiterals: false +ColumnLimit: 0 +Cpp11BracedListStyle: false +FixNamespaceComments: true +IncludeBlocks: Regroup +IndentCaseLabels: true +IndentPPDirectives: AfterHash +IndentWidth: 4 +MaxEmptyLinesToKeep: 2 +NamespaceIndentation: All +PointerAlignment: Left +SortIncludes: true +SortUsingDeclarations: true +SpaceAfterCStyleCast: true +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: false +SpaceBeforeAssignmentOperators: true +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeRangeBasedForLoopColon: true +SpaceBeforeSquareBrackets: false +SpaceInEmptyBlock: false +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 4 +SpacesInAngles: false +SpacesInCStyleCastParentheses: false +SpacesInConditionalStatement: false +SpacesInContainerLiterals: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +TabWidth: 4 +UseTab: ForIndentation diff --git a/LICENSE b/LICENSE @@ -0,0 +1,17 @@ +Copyright (c) 2023 Friedel Schön <[email protected]> + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +\ No newline at end of file diff --git a/Makefile b/Makefile @@ -0,0 +1,20 @@ +CFLAGS += -O2 -Wall -Wextra -Wpedantic +LDFLAGS += -lncurses +PREFIX = /usr + +.PHONY: all install clean + +all: textselect + +textselect.o: textselect.c arg.h + $(CC) -c $< -o $@ $(CFLAGS) $(CPPFLAGS) + +textselect: textselect.o + $(CC) $< -o $@ $(LDFLAGS) + +install: textselect textselect.1 + cp textselect $(PREFIX)/bin/ + cp textselect.1 $(PREFIX)/share/man/man1/ + +clean: + rm -f textselect textselect.o diff --git a/arg.h b/arg.h @@ -0,0 +1,19 @@ +#pragma once + +#define SHIFT (argc--, argv++) + +#define ARGBEGIN \ + for (SHIFT; *argv && *argv[0] == '-'; SHIFT) { \ + if ((*argv)[1] == '-' && (*argv)[2] == '\0') { \ + SHIFT; \ + break; \ + } \ + for (char* opt = *argv + 1; *opt; opt++) { + +#define ARGEND \ + } \ + } + +#define OPT (*opt) +#define ARGF (argv[1] ? (SHIFT, *argv) : NULL) +#define EARGF(usage) (argv[1] ? ARGF : (printf("'-%c' requires an argument\n", *opt), usage, NULL)) diff --git a/readme.md b/readme.md @@ -0,0 +1,83 @@ +# weakbox + +**weakbox** is a tool for Linux designed to create a weak (not secured) container for running programs from another Linux distribution. It is particularly useful for executing glibc-based programs (mostly closed-source software) under systems that are musl-based. + +## Features + +- Create a container environment for running programs from different Linux distributions. +- Bind mount directories from the host system into the container. +- Map user and group IDs inside the container. +- Customizable root path for the container. +- Option to run commands within the container as `root`. + +## Installation + +To install **weakbox**, simply clone the repository and compile the source code: + +```bash +git clone https://github.com/friedelschoen/weakbox.git +cd weakbox +make +sudo make install # which installs /usr/bin/weakbox and /usr/share/man/man1/weakbox.1 +sudo make PREFIX=... install # which installs $PREFIX/bin/weakbox and $PREFIX/share/man/man1/weakbox.1 +``` + +## Usage + +Run **weakbox** with the desired options and command to execute within the container: + +```bash +weakbox [options] command ... +``` + +By default `command` is executed, if command is omitted current shell or `/bin/bash` is executed. + +### Options + +- `-h`: Display usage information. +- `-s`: Run the specified command within the container as root. +- `-v`: Enable verbose mode for debugging purposes. +- `-r path`: Set the root path of the container to `path`. By default the container lays at `$WEAKBOX`. +- `-b source[:target]`: Bind mount the specified source directory to the target directory within the container. Target is relative to `root`. +- `-B source`: Remove a default bind mount from the container. +- `-u uid[:uid]`: Map user IDs inside the container. +- `-g gid[:gid]`: Map group IDs inside the container. + +### Default Mounts +- `/dev`: directory containing all devices +- `/home`: home directories of users +- `/proc`: directories containing information about processes +- `/sys`: system directories for various devices +- `/tmp`: temporary directory +- `/run`: temporary directory for daemons and long-running programs +- `/etc/resolv.conf`: nameserver-resolution configuration +- `/etc/passwd`: file containing information about users +- `/etc/group`: file containing information about groups + +### Examples + +1. Run a program within the container: + +```bash +weakbox -s /path/to/program +``` + +2. Create a container with custom root path and bind mount directories: + +```bash +weakbox -r /custom/root -b /host/dir:/dir /path/to/program +``` + +3. Map user and group IDs inside the container: + +```bash +weakbox -u 1000:1000 -g 1000:1000 /path/to/program +``` + +## Contributing + +Contributions are welcome! Feel free to submit bug reports, feature requests, or pull requests through GitHub issues and pull requests. + +## License + +This project is licensed under the zlib-license. See the [LICENSE](LICENSE) file for details. diff --git a/textselect.1 b/textselect.1 @@ -0,0 +1,58 @@ +.TH TEXTSELECT 1 "August 2024" "1.0" "Text Selection Tool" +.SH NAME +textselect \- A command-line tool to mark and select lines from a text file using ncurses. + +.SH SYNOPSIS +.B textselect +[\-v] +.IR file + +.SH DESCRIPTION +.B textselect +is a command-line tool that allows users to interactively mark and select lines from a text file using ncurses. It displays the contents of a file, enables navigation through the lines, and allows lines to be marked. On exit, it prints the selected lines to the standard output. + +.SH OPTIONS +.TP +.B \-v +Invert the selection mode. When this option is used, lines that are not marked will be printed upon exit. + +.SH USAGE +Navigate through the file using the arrow keys. Mark or unmark a line by pressing the spacebar. Exit the program by pressing 'q'. + +The program reads the specified file and displays its contents in an ncurses window. The following keys are used for navigation and selection: +.TP +.UP +Move the cursor up by one line. +.TP +.DOWN +Move the cursor down by one line. +.TP +.SPACE +Toggle the mark on the current line. +.TP +.q +Exit the program and print the marked lines. + +.SH EXAMPLES +.TP +Select lines from a file: +.B +textselect myfile.txt +.TP +Invert the selection: +.B +textselect \-v myfile.txt + +.SH AUTHOR +Written by [Your Name]. + +.SH REPORTING BUGS +Report bugs to <[email protected]>. + +.SH COPYRIGHT +This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. + +.SH SEE ALSO +.BR ncurses (3X), +.BR vi (1), +.BR less (1) diff --git a/textselect.c b/textselect.c @@ -0,0 +1,272 @@ +#include "arg.h" + +#include <fcntl.h> +#include <ncurses.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/wait.h> + +#define READBUFFER 512 +#define BUFFERGROW 512 + +#define NORETURN __attribute__((noreturn)) + +char *buffer = NULL; +size_t buffer_ptr = 0; +size_t buffer_alloc = 0; +int line_count = 0; +bool *chosen_lines = NULL; +bool invert_chosen = false; +char *argv0; + +/** + * @brief Handles error messages and exits the program. + * + * @param message Error message to print. + */ +void die(const char *message) { + perror(message); + exit(EXIT_FAILURE); +} + +/** + * @brief Allocates memory for the buffer, growing it by BUFFERGROW bytes. + */ +void grow_buffer(void) { + char *new_buf; + if ((new_buf = realloc(buffer, buffer_alloc += BUFFERGROW)) == NULL) { + die("allocating buffer"); + } + buffer = new_buf; +} + +/** + * @brief Loads a file into memory, storing its lines in the buffer. + * + * @param filename Name of the file to load, or NULL to read from stdin. + */ +void load_file(const char *filename) { + static char readbuf[READBUFFER]; + ssize_t nread; + int fd; + + fd = (filename == NULL) ? STDIN_FILENO : open(filename, O_RDONLY); + if (fd == -1) die("Failed to open file"); + + while ((nread = read(fd, readbuf, sizeof(readbuf))) > 0) { + for (ssize_t i = 0; i < nread; i++) { + if (buffer_ptr == buffer_alloc) grow_buffer(); + + if (readbuf[i] == '\n') { + if (buffer[buffer_ptr - 1] != '\0') { + buffer[buffer_ptr++] = '\0'; + line_count++; + } + } else { + buffer[buffer_ptr++] = readbuf[i]; + } + } + } + + buffer[buffer_ptr++] = '\0'; + if (fd > 2) close(fd); + + chosen_lines = calloc(line_count, sizeof(bool)); + if (chosen_lines == NULL) die("allocating chosen_lines"); +} + +/** + * @brief Retrieves a line from the buffer. + * + * @param line Line number to retrieve. + * @return Pointer to the start of the line. + */ +char *get_line(size_t line) { + size_t current = 0; + if (line == 0) return buffer; + for (size_t i = 0; i < buffer_ptr; i++) { + if (buffer[i] == '\0') { + current++; + if (current == line) return &buffer[i + 1]; + } + } + return NULL; +} + +/** + * @brief Prints lines to the ncurses window. + * + * @param win ncurses window to print to. + * @param current_line Line currently selected. + * @param start_line Line to start printing from. + * @param max_y Maximum number of lines to print. + */ +void draw_lines(WINDOW *win, int current_line, int start_line, int max_y) { + werase(win); + for (int i = 0; i < max_y && (start_line + i) < line_count; i++) { + if ((start_line + i) == current_line) wattron(win, A_REVERSE); + if (chosen_lines[start_line + i] != invert_chosen) wattron(win, A_BOLD); + mvwprintw(win, i, 0, "%s", get_line(start_line + i)); + wattroff(win, A_REVERSE | A_BOLD); + } + wrefresh(win); +} + +/** + * @brief Prints the chosen lines to the specified file descriptor. + * + * @param fd File descriptor to print to. + */ +void output_chosen_lines_fd(int fd) { + size_t current = 0; + if (chosen_lines[0] != invert_chosen) + dprintf(fd, "%s\n", buffer); + + for (size_t i = 0; i < buffer_ptr; i++) { + if (buffer[i] == '\0') { + current++; + if (chosen_lines[current] != invert_chosen) + dprintf(fd, "%s\n", &buffer[i + 1]); + } + } +} + +/** + * @brief Prints the chosen lines to the specified output file. + * + * @param filename Name of the output file. + */ +void output_chosen_lines(char *filename) { + int fd; + + fd = (filename == NULL) ? STDOUT_FILENO : open(filename, O_WRONLY | O_TRUNC | O_CREAT, 0664); + if (fd == -1) die("Failed to open file"); + + output_chosen_lines_fd(fd); +} + +/** + * @brief Displays usage information and exits the program. + * + * @param exitcode Exit code. + */ +NORETURN void usage(int exitcode) { + fprintf(stderr, "Usage: %s [-hv] [-o output] <input>\n", argv0); + exit(exitcode); +} + +/** + * @brief Initializes and handles the ncurses window for line selection. + */ +void display_window(void) { + initscr(); + cbreak(); + noecho(); + keypad(stdscr, TRUE); + + int current_line = 0; + int start_line = 0; + int ch; + int max_y = getmaxy(stdscr); + + draw_lines(stdscr, current_line, start_line, max_y); + + while ((ch = getch()) != 'q') { + switch (ch) { + case KEY_UP: + if (current_line > 0) { + (current_line)--; + if (current_line < start_line) + start_line--; + } + break; + case KEY_DOWN: + if (current_line < line_count - 1) { + (current_line)++; + if (current_line >= start_line + max_y) + start_line++; + } + break; + case 'v': + invert_chosen = !invert_chosen; + break; + case KEY_ENTER: + case ' ': + chosen_lines[current_line] = !chosen_lines[current_line]; + break; + } + draw_lines(stdscr, current_line, start_line, max_y); + } + + endwin(); +} + +/** + * @brief Executes a command and passes the chosen lines as stdin. + * + * @param argv Command and its arguments. + */ +void execute_command(char **argv) { + int pipefd[2]; + pid_t pid; + + if (pipe(pipefd) == -1) die("pipe"); + + if ((pid = fork()) == -1) die("fork"); + + if (pid == 0) { // Child process + close(pipefd[1]); // Close write end of the pipe + dup2(pipefd[0], STDIN_FILENO); // Redirect stdin to read end of the pipe + execvp(argv[0], argv); + die("execvp"); // If execvp fails + } else { // Parent process + close(pipefd[0]); // Close read end of the pipe + output_chosen_lines_fd(pipefd[1]); + close(pipefd[1]); // Close write end after writing + wait(NULL); // Wait for the child process to finish + } +} + +int main(int argc, char *argv[]) { + char *output = NULL; + + argv0 = argv[0]; + ARGBEGIN + switch (OPT) { + case 'h': + usage(0); + case 'v': + invert_chosen = true; + break; + case 'o': + output = EARGF(usage(1)); + break; + default: + fprintf(stderr, "error: unknown option '-%c'\n", OPT); + usage(1); + } + ARGEND; + + if (argc == 0) { + fprintf(stderr, "error: missing input\n"); + usage(1); + } + + load_file(argv[0]); + SHIFT; + + display_window(); + + if (argc == 0) { + output_chosen_lines(output); + } else { + execute_command(argv); + } + + free(buffer); + free(chosen_lines); + + return 0; +}