├── packaging ├── debian │ ├── compat │ ├── source │ │ └── format │ ├── changelog │ ├── rules │ ├── control │ └── copyright ├── rmw_icon_32x32.png ├── rmw_icon_64x64.png ├── rmw_icon_128x128.png ├── Slackbuild │ └── rmw │ │ ├── doinst.sh │ │ ├── rmw.info │ │ ├── README │ │ ├── slack-desc │ │ └── rmw.SlackBuild ├── appimage │ ├── AppRun │ ├── docker-compose.yml │ └── pre-appimage.sh ├── rmw.desktop ├── file_id.diz └── release-checklist.txt ├── .dockerignore ├── man ├── meson.build └── rmw.1 ├── po ├── LINGUAS ├── POTFILES └── meson.build ├── test ├── rmw-btrfs-test.img.sha256sum ├── conf │ ├── btrfs_img.testrc │ ├── rmw.alt.testrc │ ├── rmw.testrc │ └── rmw.purge_disabled.testrc ├── test.h ├── README ├── COMMON ├── meson.build ├── test_btrfs_clone.sh ├── test_media_root.sh ├── test_basic.sh ├── test_restore.sh └── test_purging.sh ├── ReleaseNotes ├── .github ├── dependabot.yml ├── spellcheck-config.yml ├── workflows │ ├── shellcheck.yml │ ├── spellcheck.yml │ ├── docker-build-env.yml │ ├── cirrus-email.yml │ ├── docker.yml │ ├── codeql.yml │ ├── coverity.yml │ ├── release.yml │ └── c-cpp.yml └── wordlist.txt ├── subprojects └── canfigger.wrap ├── src ├── bsdutils │ ├── README.md │ ├── meson.build │ ├── LICENSE │ ├── LICENSE.bsdutils │ ├── strmode.c │ ├── compat.h │ └── rm.c ├── globals.c ├── btrfs.h ├── strings_rmw.h ├── purging.h ├── parse_cli_options.h ├── main.h ├── config_rmw.h ├── globals.h ├── time_rmw.h ├── messages.h ├── restore.h ├── utils.h ├── meson.build ├── time_rmw.c ├── btrfs.c ├── trashinfo.h ├── strings_rmw.c ├── messages.c ├── trashinfo.c ├── gettext.h ├── parse_cli_options.c └── purging.c ├── docker ├── Dockerfile-build-env_alpine ├── Dockerfile-build-env_bookworm ├── README.md ├── Dockerfile-build-env_tumbleweed └── Dockerfile ├── .gitignore ├── completions ├── README.md └── fish │ └── rmw.fish ├── .editorconfig ├── meson_options.txt ├── rmwrc.example ├── AUTHORS.md ├── .cirrus.yml ├── meson.build ├── README.md └── CONTRIBUTING.md /packaging/debian/compat: -------------------------------------------------------------------------------- 1 | 10 2 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | * 2 | .* 3 | !/docker/ 4 | -------------------------------------------------------------------------------- /man/meson.build: -------------------------------------------------------------------------------- 1 | install_man('rmw.1') 2 | -------------------------------------------------------------------------------- /packaging/debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (native) 2 | -------------------------------------------------------------------------------- /po/LINGUAS: -------------------------------------------------------------------------------- 1 | de fr nl hi uk ru pt_BR es_MX pl 2 | -------------------------------------------------------------------------------- /packaging/rmw_icon_32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theimpossibleastronaut/rmw/HEAD/packaging/rmw_icon_32x32.png -------------------------------------------------------------------------------- /packaging/rmw_icon_64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theimpossibleastronaut/rmw/HEAD/packaging/rmw_icon_64x64.png -------------------------------------------------------------------------------- /packaging/rmw_icon_128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theimpossibleastronaut/rmw/HEAD/packaging/rmw_icon_128x128.png -------------------------------------------------------------------------------- /test/rmw-btrfs-test.img.sha256sum: -------------------------------------------------------------------------------- 1 | cae56d95af17163700dd3402235248fcf52a81ea86b8cbf34733056096dda445 rmw-btrfs-test.img 2 | -------------------------------------------------------------------------------- /packaging/Slackbuild/rmw/doinst.sh: -------------------------------------------------------------------------------- 1 | DEPRECATED="etc/rmwrc" 2 | if [ -f "$DEPRECATED" ]; then 3 | rm "$DEPRECATED" 4 | fi 5 | -------------------------------------------------------------------------------- /ReleaseNotes: -------------------------------------------------------------------------------- 1 | === RMW Release Notes === 2 | 3 | v0.9.4 4 | 5 | * (bugfix) Allow rmw to move directories to waste folder on btrfs filesystems 6 | (#497) 7 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | -------------------------------------------------------------------------------- /packaging/debian/changelog: -------------------------------------------------------------------------------- 1 | rmw (0.9.4) stable; urgency=medium 2 | 3 | [ Andy Alt ] 4 | * New upstream release 5 | 6 | -- Andy Alt Sun, 03 Nov 2024 04:39:09 +0000 7 | -------------------------------------------------------------------------------- /test/conf/btrfs_img.testrc: -------------------------------------------------------------------------------- 1 | #waste = /tmp/rmw-loop/.Trash-$UID 2 | waste = /tmp/rmw-loop/@two/Waste 3 | waste = /tmp/rmw-loop/trashlink 4 | WASTE = $HOME/.local/share/Waste 5 | 6 | expire_age = 45 7 | -------------------------------------------------------------------------------- /subprojects/canfigger.wrap: -------------------------------------------------------------------------------- 1 | [wrap-git] 2 | url = https://github.com/andy5995/canfigger.git 3 | 4 | revision = trunk 5 | #revision = v0.3.0 6 | depth = 1 7 | 8 | [provide] 9 | canfigger = canfigger_dep 10 | -------------------------------------------------------------------------------- /src/bsdutils/README.md: -------------------------------------------------------------------------------- 1 | The files in this directory are from 2 | [BSDCoreUtils](https://github.com/DiegoMagdaleno) and 3 | [bsdutils](https://github.com/dcantrell/bsdutils). They have been modified to 4 | work with rmw. 5 | -------------------------------------------------------------------------------- /test/test.h: -------------------------------------------------------------------------------- 1 | 2 | // assert() does nothing if NDEBUG is defined, so we'll make sure it isn't. 3 | #ifdef NDEBUG 4 | #undef NDEBUG 5 | #endif 6 | #include 7 | 8 | #define ARRAY_SIZE(a) (sizeof(a) / sizeof(a)[0]) 9 | -------------------------------------------------------------------------------- /po/POTFILES: -------------------------------------------------------------------------------- 1 | # Package source files 2 | 3 | src/parse_cli_options.c 4 | src/main.c 5 | src/config_rmw.c 6 | src/messages.c 7 | src/restore.c 8 | src/purging.c 9 | src/time_rmw.c 10 | src/strings_rmw.c 11 | src/trashinfo.c 12 | src/utils.c 13 | -------------------------------------------------------------------------------- /packaging/appimage/AppRun: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env bash 2 | 3 | set -e 4 | 5 | HERE="$(readlink -f "$(dirname "$0")")" 6 | BINARY_NAME=$(basename "$ARGV0") 7 | 8 | export RMW_LOCALEDIR=$HERE/usr/share/locale 9 | 10 | exec "$HERE/usr/bin/rmw" "$@" 11 | 12 | -------------------------------------------------------------------------------- /test/README: -------------------------------------------------------------------------------- 1 | # Testing 2 | 3 | From the build directory: 4 | 5 | ninja test 6 | 7 | For more information about testing, see the Code Testing 8 | page on the rmw 9 | website. 10 | -------------------------------------------------------------------------------- /packaging/rmw.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Version=1.0 3 | Categories=Utility; 4 | Icon=rmw 5 | Name=rmw 6 | GenericName=Safe-remove utility for the command line 7 | Type=Application 8 | MimeType=application/octet-stream 9 | Exec=rmw 10 | Terminal=true 11 | Keywords=trashcan;system;utilities;file-management;cli; 12 | -------------------------------------------------------------------------------- /docker/Dockerfile-build-env_alpine: -------------------------------------------------------------------------------- 1 | FROM alpine 2 | 3 | RUN apk update && apk upgrade && \ 4 | apk add \ 5 | gcc \ 6 | gettext \ 7 | git \ 8 | linux-headers \ 9 | meson \ 10 | musl-dev \ 11 | musl-fts-dev \ 12 | musl-libintl \ 13 | ncurses-dev 14 | 15 | CMD ["/bin/sh","-l"] 16 | -------------------------------------------------------------------------------- /packaging/debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | 3 | export DEB_BUILD_MAINT_OPTIONS=hardening=+all 4 | 5 | %: 6 | dh $@ 7 | 8 | override_dh_auto_configure: 9 | dh_auto_configure -- \ 10 | --buildtype=release \ 11 | --wrap-mode forcefallback \ 12 | --prefix=/usr \ 13 | -Dstrip=true\ 14 | -Db_sanitize=none \ 15 | -Db_pie=true \ 16 | -Db_lto=true 17 | -------------------------------------------------------------------------------- /docker/Dockerfile-build-env_bookworm: -------------------------------------------------------------------------------- 1 | FROM debian:bookworm 2 | RUN apt-get update \ 3 | && apt-get upgrade -y \ 4 | && apt-get install -y \ 5 | gettext \ 6 | git \ 7 | libncurses-dev \ 8 | meson \ 9 | ninja-build \ 10 | pip \ 11 | && rm -rf /var/lib/apt/lists 12 | 13 | RUN useradd -U rmwbuilder 14 | RUN passwd -d rmwbuilder 15 | 16 | CMD ["/bin/bash","-l"] 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /builddir/ 2 | *~ 3 | /packaging/rpm 4 | /packaging/PKGBUILD 5 | *.tar.* 6 | *.deb 7 | packaging/appimage/AppDir 8 | packaging/appimage/squashfs* 9 | *AppImage* 10 | /.vscode 11 | 12 | subprojects/** 13 | !subprojects/packagecache/ 14 | !subprojects/packagecache/** 15 | 16 | # wrap files required 17 | !subprojects/**/*.wrap 18 | /packaging/appimage/.env 19 | /test/rmw-btrfs-test.img 20 | -------------------------------------------------------------------------------- /docker/README.md: -------------------------------------------------------------------------------- 1 | # Docker 2 | 3 | You may find the Docker images useful (or you may not). 4 | 5 | ## [andy5995/rmw](https://hub.docker.com/repository/docker/andy5995/rmw) 6 | 7 | Image contains a build of rmw in '/usr/bin'. You can try it out inside the 8 | container if you like. 9 | 10 | docker pull andy5995/rmw:latest (latest git revision) 11 | docker pull andy5995/rmw:0.9.4 (latest release) 12 | -------------------------------------------------------------------------------- /packaging/Slackbuild/rmw/rmw.info: -------------------------------------------------------------------------------- 1 | PRGNAM="rmw" 2 | VERSION="0.9.4" 3 | HOMEPAGE="https://theimpossibleastronaut.github.io/rmw-website/" 4 | DOWNLOAD="https://github.com/theimpossibleastronaut/rmw/releases/download/v0.9.4/rmw-0.9.4.tar.xz" 5 | MD5SUM="0df2da3c1b6985cdd4022ff4b3d6fd13" 6 | DOWNLOAD_x86_64="" 7 | MD5SUM_x86_64="" 8 | REQUIRES="" 9 | MAINTAINER="Andy Alt" 10 | EMAIL="arch_stanton5995@proton.me" 11 | -------------------------------------------------------------------------------- /docker/Dockerfile-build-env_tumbleweed: -------------------------------------------------------------------------------- 1 | FROM opensuse/tumbleweed 2 | 3 | RUN zypper --non-interactive refresh && \ 4 | zypper --non-interactive update && \ 5 | zypper --non-interactive install \ 6 | gettext \ 7 | gcc \ 8 | git \ 9 | ncurses-devel \ 10 | meson \ 11 | ninja && \ 12 | zypper clean 13 | 14 | RUN useradd -U rmwbuilder && \ 15 | passwd -d rmwbuilder 16 | 17 | CMD ["/bin/bash", "-l"] 18 | -------------------------------------------------------------------------------- /packaging/appimage/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | build-appimage: 5 | environment: 6 | - HOSTUID=$HOSTUID 7 | - HOST_SOURCE_ROOT=$HOST_SOURCE_ROOT 8 | container_name: linuxdeploy 9 | image: andy5995/linuxdeploy 10 | working_dir: /workspace 11 | command: packaging/appimage/pre-appimage.sh 12 | user: root 13 | volumes: 14 | - $HOST_SOURCE_ROOT:/workspace 15 | restart: "no" 16 | -------------------------------------------------------------------------------- /packaging/Slackbuild/rmw/README: -------------------------------------------------------------------------------- 1 | rmw (ReMove to Waste) is a trashcan/recycle bin utility for the command 2 | line. It can move and restore files to and from directories specified in 3 | a configuration file, and can also be integrated with your regular 4 | desktop trash folder (if your desktop environment uses the 5 | FreeDesktop.org Trash specification). One of the unique features of rmw 6 | is the ability to purge items from your waste (or trash) directories 7 | after x number of days. 8 | -------------------------------------------------------------------------------- /completions/README.md: -------------------------------------------------------------------------------- 1 | # Shell completion scripts 2 | 3 | By default, rmw completions are copied to the rmw documentation dir when rmw 4 | is installed. If they aren't present on your system, you can install a shell 5 | completion manually by getting the completion from the source package or git 6 | repository. See the links below for specific installation instructions for 7 | each completion. 8 | 9 | ## Fish 10 | 11 | [Where to put completions](https://fishshell.com/docs/current/completions.html#where-to-put-completions) 12 | -------------------------------------------------------------------------------- /.github/spellcheck-config.yml: -------------------------------------------------------------------------------- 1 | # see https://facelessuser.github.io/pyspelling/filters/html/ 2 | 3 | matrix: 4 | - name: Markdown 5 | aspell: 6 | lang: en 7 | ignore-case: true 8 | dictionary: 9 | wordlists: 10 | - .github/wordlist.txt 11 | encoding: utf-8 12 | pipeline: 13 | - pyspelling.filters.markdown: 14 | markdown_extensions: 15 | - markdown.extensions.extra: 16 | - pyspelling.filters.html: 17 | comments: true 18 | sources: 19 | - '**/*.md|!AUTHORS.md' 20 | default_encoding: utf-8 21 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM andy5995/rmw:build-env-alpine 2 | 3 | RUN git clone --depth 1 https://github.com/theimpossibleastronaut/rmw \ 4 | && cd /rmw \ 5 | && meson setup builddir --buildtype=release -Db_sanitize=none --prefix=/usr \ 6 | && cd /rmw/builddir \ 7 | && ninja -v \ 8 | && meson test -v \ 9 | && DESTDIR=/tmp/rmw ninja install 10 | 11 | FROM alpine 12 | COPY --from=0 /tmp/rmw/ / 13 | RUN apk add \ 14 | libmenuw \ 15 | mandoc \ 16 | musl-fts \ 17 | ncurses 18 | 19 | CMD ["/bin/sh","-l"] 20 | -------------------------------------------------------------------------------- /packaging/file_id.diz: -------------------------------------------------------------------------------- 1 | rmw v0.9.1 2 | ReMove to Waste is a safe-remove utility for 3 | the command line. It can move and restore 4 | files to and from directories specified in a 5 | configuration file, and can also be 6 | integrated with your regular desktop trash 7 | folder (if your desktop environment uses the 8 | FreeDesktop.org Trash specification). One of 9 | the unique features of rmw is the ability to 10 | purge items from your waste (or trash) 11 | directories after x number of days. 12 | 13 | https://theimpossibleastronaut.github.io/rmw-website 14 | -------------------------------------------------------------------------------- /.github/workflows/shellcheck.yml: -------------------------------------------------------------------------------- 1 | name: shellcheck 2 | 3 | on: 4 | push: 5 | branches: master 6 | paths: 7 | - '**.sh' 8 | - '**/shellcheck.yml' 9 | pull_request: 10 | branches: master 11 | paths: 12 | - '**.sh' 13 | - '**/shellcheck.yml' 14 | 15 | jobs: 16 | shellcheck: 17 | name: Shellcheck 18 | runs-on: ubuntu-latest 19 | steps: 20 | - uses: actions/checkout@v5 21 | - name: Run ShellCheck 22 | uses: ludeeus/action-shellcheck@master 23 | env: 24 | SHELLCHECK_OPTS: -x -P $GITHUB_WORKSPACE 25 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Unix-style newlines with a newline ending every file 7 | [*] 8 | end_of_line = lf 9 | insert_final_newline = true 10 | 11 | # Matches multiple files with brace expansion notation 12 | # Set default charset 13 | #[*.{js,py}] 14 | #charset = utf-8 15 | 16 | # 2 space indentation 17 | [*.{c,h}] 18 | indent_style = space 19 | indent_size = 2 20 | 21 | # Tab indentation (no size specified) 22 | [Makefile] 23 | indent_style = tab 24 | 25 | [meson.build] 26 | indent_size = 2 27 | -------------------------------------------------------------------------------- /.github/wordlist.txt: -------------------------------------------------------------------------------- 1 | AppImages 2 | bli 3 | BSDCoreUtils 4 | bsdutils 5 | btrfs 6 | builddir 7 | CentOS 8 | cli 9 | config 10 | dev 11 | devel 12 | Dnls 13 | Dprefix 14 | Dwant 15 | entrypoint 16 | env 17 | env 18 | filesystem 19 | FreeDesktop 20 | gettext 21 | gpl 22 | GPLv 23 | homebrew 24 | https 25 | libncursesw 26 | linuxbrew 27 | MacOS 28 | mnt 29 | mrl 30 | ncurses 31 | ncurses 32 | npcs 33 | pkgconfig 34 | plaintext 35 | pwd 36 | README 37 | repo 38 | Repology 39 | rmdir 40 | rmw 41 | rmw'ed 42 | rmw'ing 43 | rmwrc 44 | sexualized 45 | src 46 | subvolumes 47 | theimpossibleastronaut 48 | trashinfo 49 | UID 50 | uncomment 51 | uncommented 52 | usr 53 | vvg 54 | XDG 55 | -------------------------------------------------------------------------------- /.github/workflows/spellcheck.yml: -------------------------------------------------------------------------------- 1 | name: Spellcheck Action 2 | 3 | on: 4 | push: 5 | branches: master 6 | paths: 7 | - '**/**.md' 8 | - '**/wordlist.txt' 9 | - '**/spellcheck.yml' 10 | - '**/spellcheck-config.yml' 11 | pull_request: 12 | branches: master 13 | paths: 14 | - '**/**.md' 15 | - '**/wordlist.txt' 16 | - '**/spellcheck.yml' 17 | - '**/spellcheck-config.yml' 18 | 19 | jobs: 20 | spellcheck: 21 | name: Spellcheck 22 | runs-on: ubuntu-latest 23 | steps: 24 | - uses: actions/checkout@v5 25 | - uses: rojopolis/spellcheck-github-actions@v0 26 | name: Spellcheck 27 | continue-on-error: false 28 | with: 29 | config_path: .github/spellcheck-config.yml 30 | -------------------------------------------------------------------------------- /meson_options.txt: -------------------------------------------------------------------------------- 1 | option('build_tests', type : 'boolean', value : true, 2 | description : 'build tests') 3 | option( 4 | 'want_btrfs_clone', 5 | type : 'boolean', 6 | value: 'true', 7 | description: 'Include support for cloning files between btrfs root volumes and subvolumes') 8 | option('nls', type : 'boolean', value : true, 9 | description : 'include native language support (install translations)') 10 | 11 | option('docdir', type : 'string', value : 'share/doc/rmw', 12 | description : 'directory where the documentation should be installed to') 13 | 14 | option('without-curses', type : 'boolean', value : 'false', 15 | description : 'build without curses/menu support') 16 | 17 | option('install_shell_completions', type: 'boolean', value: true, 18 | description: 'Install shell completions') 19 | 20 | -------------------------------------------------------------------------------- /rmwrc.example: -------------------------------------------------------------------------------- 1 | # rmw default waste directory, separate from the desktop trash 2 | WASTE = $HOME/.local/share/Waste 3 | 4 | # The directory used by the FreeDesktop.org Trash spec 5 | # Note to macOS and Windows users: moving files to 'Desktop' trash 6 | # doesn't work yet 7 | # WASTE=$HOME/.local/share/Trash 8 | 9 | # A folder can use the $UID variable. 10 | # See the README or man page for details about using the 'removable' attribute 11 | # WASTE=/mnt/flash/.Trash-$UID, removable 12 | 13 | # How many days should items be allowed to stay in the waste 14 | # directories before they are permanently deleted 15 | # 16 | # use '0' to disable purging (can be overridden by using --purge=N_DAYS) 17 | # 18 | expire_age = 0 19 | 20 | # purge is allowed to run without the '-f' option. If you'd rather 21 | # require the use of '-f', you may uncomment the line below. 22 | # 23 | # force_required 24 | -------------------------------------------------------------------------------- /src/bsdutils/meson.build: -------------------------------------------------------------------------------- 1 | dep_fts = dependency('', required: false) 2 | if not cc.has_function( 3 | 'fts_open', 4 | prefix: '#include \n#include \n#include ', 5 | ) 6 | dep_fts = cc.find_library('fts') 7 | endif 8 | 9 | src_lib_bsdutilsrm = ['rm.c'] 10 | 11 | args_lib_bsdutilsrm = [] 12 | if cc.has_function('strmode') 13 | args_lib_bsdutilsrm = ['-DHAVE_STRMODE'] 14 | else 15 | src_lib_bsdutilsrm += 'strmode.c' 16 | endif 17 | 18 | lib_bsdutilsrm = static_library( 19 | 'bsdutilsrm', 20 | src_lib_bsdutilsrm, 21 | dependencies: [dep_fts], 22 | c_args: args_lib_bsdutilsrm 23 | ) 24 | 25 | if get_option('build_tests') 26 | incdir = include_directories('../../test') 27 | 28 | exe = executable( 29 | 'rm', 30 | 'rm.c', 31 | link_with: [lib_bsdutilsrm], 32 | include_directories: incdir, 33 | c_args: ['-DTEST_LIB', args_lib_bsdutilsrm], 34 | ) 35 | 36 | test('rm', exe) 37 | endif 38 | -------------------------------------------------------------------------------- /packaging/debian/control: -------------------------------------------------------------------------------- 1 | Source: rmw 2 | Section: utils 3 | Priority: optional 4 | Maintainer: Andy Alt 5 | Build-Depends: debhelper (>= 12~), 6 | libncursesw5-dev, 7 | libtinfo-dev, 8 | meson (>= 0.59.0) 9 | Standards-Version: 4.6.2 10 | Homepage: https://theimpossibleastronaut.github.io/rmw-website/ 11 | 12 | Package: rmw 13 | Architecture: any 14 | Depends: ${shlibs:Depends}, ${misc:Depends} 15 | Description: Trashcan/recycle bin utility for the command line 16 | rmw (ReMove to Waste) is a trashcan/recycle bin utility for the command line. 17 | It can move and restore files to and from directories specified in a 18 | configuration file, and can also be integrated with your regular desktop 19 | trash folder (if your desktop environment uses the FreeDesktop.org Trash 20 | specification). One of the unique features of rmw is the ability to purge 21 | items from your waste (or trash) directories after x number of days. 22 | -------------------------------------------------------------------------------- /AUTHORS.md: -------------------------------------------------------------------------------- 1 | # AUTHORS 2 | 3 | GitHub's automated list: [graphs/contributors](https://github.com/andy5995/rmw/graphs/contributors) 4 | 5 | ## Contributors and Roles 6 | | Name | Roles 7 | | :-----------: | ----------------------------------| 8 | | Andy Alt | Project Manager | 9 | | [Parth Suresh](https://github.com/parthsuresh) | C | 10 | | [Bruce Hernandez](https://github.com/blah1898) | C | 11 | | [suve](https://svgames.pl/en) | C | 12 | | [Martijn de Boer](https://github.com/martijndeb) | NL translation, testing | 13 | | [Ankur Jyoti Phukan](https://github.com/ajphukan) | C | 14 | | [James Sherratt](https://github.com/Jammyjamjamman) | C | 15 | | [Guillermo Franco](https://github.com/gfranco93) | man page | 16 | | [João Rodrigues](https://github.com/jmrodriguesgoncalves) | PT translation | 17 | | [Svitlana Galianova](https://github.com/svitlana-galianova) | RU & UK translation | 18 | | [Mario Carrillo](https://github.com/marioecg) | Spanish (MX) translation | 19 | | [Subhashree Mishra](https://github.com/Mishrasubha) | Hindi translation | 20 | -------------------------------------------------------------------------------- /src/globals.c: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of rmw 3 | 4 | Copyright (C) 2012-2021 Andy Alt (arch_stanton5995@proton.me) 5 | Other authors: https://github.com/theimpossibleastronaut/rmw/blob/master/AUTHORS.md 6 | 7 | This program is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | This program is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with this program. If not, see . 19 | */ 20 | 21 | #include "globals.h" 22 | 23 | int verbose = 0; 24 | const char *lit_info = "info"; 25 | const char trashinfo_ext[] = ".trashinfo"; 26 | const int len_trashinfo_ext = sizeof trashinfo_ext - 1; /* Subtract 1 for the terminating NULL */ 27 | -------------------------------------------------------------------------------- /packaging/Slackbuild/rmw/slack-desc: -------------------------------------------------------------------------------- 1 | # HOW TO EDIT THIS FILE: 2 | # The "handy ruler" below makes it easier to edit a package description. 3 | # Line up the first '|' above the ':' following the base package name, and 4 | # the '|' on the right side marks the last column you can put a character in. 5 | # You must make exactly 11 lines for the formatting to be correct. It's also 6 | # customary to leave one space after the ':' except on otherwise blank lines. 7 | 8 | |-----handy-ruler------------------------------------------------------| 9 | rmw: rmw (command line trash can/recycle bin) 10 | rmw: 11 | rmw: rmw (ReMove to Waste) is a trashcan/recycle bin utility for the 12 | rmw: command line. It can move and restore files to and from directories 13 | rmw: specified in a configuration file, and can also be integrated with 14 | rmw: your regular desktop trash folder (if your desktop environment uses 15 | rmw: the FreeDesktop.org Trash specification). One of the unique features 16 | rmw: of rmw is the ability to purge items from your waste (or trash) 17 | rmw: directories after x number of days. 18 | rmw: 19 | rmw: Homepage: https://theimpossibleastronaut.github.io/rmw-website/ 20 | -------------------------------------------------------------------------------- /src/btrfs.h: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of rmw 3 | 4 | Copyright (C) 2012-2024 Andy Alt (arch_stanton5995@proton.me) 5 | Other authors: https://github.com/theimpossibleastronaut/rmw/blob/master/AUTHORS.md 6 | 7 | This program is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | This program is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with this program. If not, see . 19 | */ 20 | 21 | #ifndef _INC_BTRFS_H 22 | #define _INC_BTRFS_H 23 | 24 | #include 25 | 26 | #define BTRFS_SUPER_MAGIC 0x9123683E 27 | 28 | bool is_btrfs(const char *path); 29 | 30 | int do_btrfs_clone(const char *source, const char *dest, int *save_errno); 31 | 32 | #endif 33 | -------------------------------------------------------------------------------- /packaging/debian/copyright: -------------------------------------------------------------------------------- 1 | Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Upstream-Name: rmw 3 | Source: 4 | 5 | Files: * 6 | Copyright: 2012-2024 Andy Alt 7 | License: GPL-3.0+ 8 | 9 | Files: debian/* 10 | Copyright: 2024 Andy Alt 11 | License: GPL-3.0+ 12 | 13 | License: GPL-3.0+ 14 | This program is free software: you can redistribute it and/or modify 15 | it under the terms of the GNU General Public License as published by 16 | the Free Software Foundation, either version 3 of the License, or 17 | (at your option) any later version. 18 | . 19 | This package is distributed in the hope that it will be useful, 20 | but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 | GNU General Public License for more details. 23 | . 24 | You should have received a copy of the GNU General Public License 25 | along with this program. If not, see . 26 | . 27 | On Debian systems, the complete text of the GNU General 28 | Public License version 3 can be found in "/usr/share/common-licenses/GPL-3". 29 | -------------------------------------------------------------------------------- /test/COMMON: -------------------------------------------------------------------------------- 1 | # included by tests scripts 2 | 3 | create_some_files() { 4 | mkdir -p somefiles/topdir/dir1/dir2/dir3 5 | 6 | c=1 7 | while [ $c -lt 25 ] 8 | do 9 | touch somefiles/topdir/dir1/dir2/dir3/$c 10 | c=`expr $c + 1` 11 | done 12 | 13 | touch somefiles/read_only_file 14 | chmod ugo-w somefiles/read_only_file 15 | } 16 | 17 | cmp_substr () { 18 | if [ -z "$1" -o -z "$2" ]; then 19 | return 1 20 | fi 21 | [ -z "${1##*$2*}" ] 22 | return $? 23 | } 24 | 25 | if test -z "${MESON_BUILD_ROOT}" || test -z "${RMW_FAKE_HOME}"; then 26 | echo "This script is used by the build system. Use 'meson test' 27 | echo 'from the builddir." 28 | exit 1 29 | fi 30 | 31 | RMW_FAKE_HOME="${RMW_FAKE_HOME}"/$(basename $0)_dir 32 | TESTS_DIR="${MESON_SOURCE_ROOT}/test" 33 | CONFIG="${MESON_SOURCE_ROOT}/test/conf/rmw.testrc" 34 | ALT_CONFIG="${MESON_SOURCE_ROOT}/test/conf/rmw.alt.testrc" 35 | PURGE_DISABLED_CONFIG="${MESON_SOURCE_ROOT}/test/conf/rmw.purge_disabled.testrc" 36 | export BIN_DIR="${MESON_BUILD_ROOT}" 37 | 38 | export RMW_FAKE_HOME 39 | 40 | if [ -e ${RMW_FAKE_HOME} ]; then 41 | rm -rf ${RMW_FAKE_HOME} 42 | fi 43 | 44 | RMW_TEST_CMD_STRING="${BIN_DIR}/rmw -c $CONFIG" 45 | 46 | PRIMARY_WASTE_DIR="${RMW_FAKE_HOME}"/.Waste 47 | 48 | RMW_ALT_TEST_CMD_STRING="${BIN_DIR}/rmw -c ${ALT_CONFIG}" 49 | RMW_PURGE_DISABLED_CMD="${BIN_DIR}/rmw -c ${PURGE_DISABLED_CONFIG}" 50 | SEPARATOR="\n\n--- " 51 | -------------------------------------------------------------------------------- /src/bsdutils/LICENSE: -------------------------------------------------------------------------------- 1 | This code originates from OpenBSD but has been modified for building on 2 | systems other than OpenBSD. Please see this site: 3 | 4 | https://www.openbsd.org/policy.html 5 | 6 | For details on the OpenBSD license and copyright policies. Unless 7 | otherwise noted in the source file, the following license and copyright 8 | statement applies to the code found in this project: 9 | 10 | /* 11 | * Copyright (c) 2017 David Cantrell 12 | * Jim Bair 13 | * Diego Magdaleno 14 | * 15 | * Permission to use, copy, modify, and distribute this software for any 16 | * purpose with or without fee is hereby granted, provided that the above 17 | * copyright notice and this permission notice appear in all copies. 18 | * 19 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 20 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 21 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 22 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 23 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 24 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 25 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 26 | */ 27 | -------------------------------------------------------------------------------- /.github/workflows/docker-build-env.yml: -------------------------------------------------------------------------------- 1 | name: Docker Build Environment Image CI 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: [ "master" ] 7 | paths: 8 | - '.github/workflows/docker-build-env.yml' 9 | - 'docker/Dockerfile-build-env_**' 10 | 11 | jobs: 12 | docker: 13 | name: build-env_${{ matrix.tag }} 14 | strategy: 15 | fail-fast: true 16 | matrix: 17 | os: [ubuntu-latest] 18 | tag: [bookworm, alpine, tumbleweed] 19 | runs-on: ${{ matrix.os }} 20 | env: 21 | REGISTRY_IMAGE: andy5995/rmw 22 | steps: 23 | - 24 | name: Checkout 25 | uses: actions/checkout@v5 26 | with: 27 | submodules: false 28 | - 29 | name: Set up QEMU 30 | uses: docker/setup-qemu-action@v3 31 | - 32 | name: Login to Docker Hub 33 | uses: docker/login-action@v3 34 | with: 35 | username: andy5995 36 | password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} 37 | - 38 | name: Set up Docker Buildx 39 | uses: docker/setup-buildx-action@v3 40 | - 41 | name: Build and push 42 | uses: docker/build-push-action@v6 43 | with: 44 | context: . 45 | file: ./docker/Dockerfile-build-env_${{ matrix.tag }} 46 | platforms: linux/amd64,linux/arm64 47 | push: true 48 | tags: ${{ env.REGISTRY_IMAGE }}:build-env-${{ matrix.tag }} 49 | 50 | -------------------------------------------------------------------------------- /test/meson.build: -------------------------------------------------------------------------------- 1 | # The tests must be configured with 'b_sanitize=none' for this to work 2 | # unless faketime has been built with sanitizer 3 | faketime = find_program('faketime', required: false) 4 | if faketime.found() 5 | add_test_setup( 6 | 'epochalypse', 7 | exe_wrapper: [faketime, '-f', '+14y'], 8 | env: ['RMW_TEST_EPOCHALYPSE=true'], 9 | ) 10 | endif 11 | 12 | test_cases = ['strings_rmw', 'utils', 'restore'] 13 | 14 | scripts = [ 15 | 'test_basic.sh', 16 | 'test_media_root.sh', 17 | 'test_purging.sh', 18 | 'test_restore.sh', 19 | ] 20 | 21 | if has_statfs and has_btrfs_header 22 | scripts += ['test_btrfs_clone.sh'] 23 | endif 24 | 25 | RMW_FAKE_HOME = join_paths(meson.current_build_dir(), 'rmw-tests-home') 26 | 27 | foreach case : test_cases 28 | exe = executable( 29 | 'test_' + case, 30 | '../src/' + case + '.c', 31 | c_args: ['-DTEST_LIB', '-DRMW_FAKE_HOME="@0@"'.format(RMW_FAKE_HOME)], 32 | dependencies: rmw_dep, 33 | override_options: ['b_lto=false'], 34 | ) 35 | test('test_' + case, exe) 36 | endforeach 37 | 38 | foreach s : scripts 39 | test( 40 | s, 41 | files(s), 42 | env: [ 43 | 'MESON_SOURCE_ROOT=' + meson.project_source_root(), 44 | 'RMW_FAKE_HOME=' + RMW_FAKE_HOME, 45 | 'MESON_BUILD_ROOT=' + meson.project_build_root(), 46 | ], 47 | depends: main_bin, 48 | ) 49 | endforeach 50 | -------------------------------------------------------------------------------- /.github/workflows/cirrus-email.yml: -------------------------------------------------------------------------------- 1 | on: 2 | check_suite: 3 | type: ['completed'] 4 | paths: 5 | - '**' 6 | - '!**.yml' 7 | - '!**/.github' 8 | - '!**.md' 9 | - '!docker/**' 10 | - '!packaging/**' 11 | - '**/cirrus-email.yml' 12 | 13 | name: Email about Cirrus CI failures 14 | jobs: 15 | continue: 16 | name: After Cirrus CI Failure 17 | if: >- 18 | github.event.check_suite.app.name == 'Cirrus CI' 19 | && github.event.check_suite.conclusion != 'success' 20 | && github.event.check_suite.conclusion != 'cancelled' 21 | && github.event.check_suite.conclusion != 'skipped' 22 | && github.event.check_suite.conclusion != 'neutral' 23 | runs-on: ubuntu-latest 24 | steps: 25 | - uses: octokit/request-action@v2.x 26 | id: get_failed_check_run 27 | with: 28 | route: GET /repos/${{ github.repository }}/check-suites/${{ github.event.check_suite.id }}/check-runs?status=completed 29 | mediaType: '{"previews": ["antiope"]}' 30 | env: 31 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 32 | - run: | 33 | echo "Cirrus CI ${{ github.event.check_suite.conclusion }} on ${{ github.event.check_suite.head_branch }} branch!" 34 | echo "SHA ${{ github.event.check_suite.head_sha }}" 35 | echo $MESSAGE 36 | echo "##[error]See $CHECK_RUN_URL for details" && false 37 | env: 38 | CHECK_RUN_URL: ${{ fromJson(steps.get_failed_check_run.outputs.data).check_runs[0].html_url }} 39 | -------------------------------------------------------------------------------- /po/meson.build: -------------------------------------------------------------------------------- 1 | dep_intl = [] 2 | inc_intl_arg = [] 3 | if get_option('nls') 4 | inc_args = [] 5 | dep_intl = dependency('intl', required: false) 6 | if not dep_intl.found() 7 | if host_sys == 'freebsd' or host_sys == 'openbsd' or host_sys == 'dragonfly' 8 | inc_intl_arg = ['/usr/local/include'] 9 | dep_intl = cc.find_library( 10 | 'intl', 11 | has_headers: ['libintl.h'], 12 | dirs: ['/usr/local/lib'], 13 | header_include_directories: include_directories(inc_intl_arg), 14 | ) 15 | elif host_sys == 'netbsd' 16 | inc_intl_arg = ['/usr/pkg/include'] 17 | dep_intl = cc.find_library( 18 | 'intl', 19 | has_headers: ['libintl.h'], 20 | dirs: ['/usr/pkg/lib'], 21 | header_include_directories: include_directories(inc_intl_arg), 22 | ) 23 | else # darwin, cygwin 24 | inc_intl_arg = ['/opt/homebrew/opt/gettext/include'] 25 | dep_intl = cc.find_library( 26 | 'intl', 27 | dirs: ['/opt/homebrew/opt/gettext/lib'], 28 | has_headers: ['libintl.h'], 29 | header_include_directories: include_directories(inc_intl_arg), 30 | ) 31 | endif 32 | endif 33 | if dep_intl.found() 34 | add_project_arguments(inc_args, language: 'c') 35 | conf.set('ENABLE_NLS', 1) 36 | conf.set_quoted('LOCALEDIR', localedir) 37 | i18n = import('i18n') 38 | i18n.gettext( 39 | meson.project_name(), 40 | args: ['--directory=' + meson.project_source_root()], 41 | ) 42 | endif 43 | endif 44 | -------------------------------------------------------------------------------- /src/strings_rmw.h: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of rmw 3 | 4 | Copyright (C) 2012-2021 Andy Alt (arch_stanton5995@proton.me) 5 | Other authors: https://github.com/theimpossibleastronaut/rmw/blob/master/AUTHORS.md 6 | 7 | This program is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | This program is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with this program. If not, see . 19 | */ 20 | 21 | #pragma once 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #define sn_check(a, b) real_sn_check(a, b, __func__, __LINE__) 32 | 33 | 34 | void 35 | bufchk_len(const size_t len, const size_t boundary, const char *func, 36 | const int line); 37 | 38 | void 39 | real_sn_check(const ssize_t len, const ssize_t dest_boundary, 40 | const char *func, const int line); 41 | 42 | void trim_whitespace(char *str); 43 | 44 | void truncate_str(char *str, const size_t pos); 45 | -------------------------------------------------------------------------------- /src/purging.h: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of rmw 3 | 4 | Copyright (C) 2012-2023 Andy Alt (arch_stanton5995@proton.me) 5 | Other authors: https://github.com/theimpossibleastronaut/rmw/blob/master/AUTHORS.md 6 | 7 | This program is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | This program is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with this program. If not, see . 19 | */ 20 | 21 | #ifndef _INC_PURGING_H 22 | #define _INC_PURGING_H 23 | 24 | #include 25 | #include /* for rmdir() */ 26 | 27 | #include "time_rmw.h" 28 | #include "config_rmw.h" 29 | 30 | 31 | bool is_time_to_purge(st_time * st_time_var, const char *file); 32 | 33 | int 34 | purge(st_config * st_config_data, 35 | const rmw_options * cli_user_options, 36 | st_time * st_time_var, int *orphan_ctr); 37 | 38 | short orphan_maint(st_waste * waste_head, st_time * st_time_var, 39 | int *orphan_ctr); 40 | 41 | 42 | int bsdutils_rm(const char *const argv, const bool want_verbose); 43 | #endif 44 | -------------------------------------------------------------------------------- /.cirrus.yml: -------------------------------------------------------------------------------- 1 | filter_template: &filter_template 2 | only_if: "changesInclude( 3 | 'src/*', 4 | 'test/*', 5 | '**meson.build', 6 | '.cirrus.yml', 7 | '**.wrap' 8 | )" 9 | 10 | common_meson_steps: &common_meson_steps 11 | build_script: 12 | - cd builddir && ninja -v 13 | test_script: 14 | - cd builddir && meson test -v --suite rmw 15 | 16 | common_freebsd_steps: &common_freebsd_steps 17 | pkginstall_script: 18 | - pkg update -f 19 | - pkg install -y git meson ninja gettext pkgconf 20 | 21 | freebsd_task: 22 | <<: *filter_template 23 | name: freebsd-14-3 24 | freebsd_instance: 25 | image_family: freebsd-14-3 26 | <<: *common_freebsd_steps 27 | setup_script: 28 | - meson setup builddir 29 | <<: *common_meson_steps 30 | 31 | freebsd_task: 32 | <<: *filter_template 33 | name: freebsd-15-0-snap 34 | freebsd_instance: 35 | image_family: freebsd-15-0-snap 36 | <<: *common_freebsd_steps 37 | setup_script: 38 | - meson setup builddir 39 | <<: *common_meson_steps 40 | 41 | arm_task: 42 | <<: *filter_template 43 | arm_container: 44 | image: andy5995/rmw:build-env-bookworm 45 | env: 46 | CIRRUS_CLONE_SUBMODULES: true 47 | setup_script: 48 | - meson setup builddir -Db_sanitize=none 49 | <<: *common_meson_steps 50 | 51 | macos_task: 52 | <<: *filter_template 53 | name: macOS arm64 54 | macos_instance: 55 | image: ghcr.io/cirruslabs/macos-runner:sonoma 56 | env: 57 | CIRRUS_CLONE_SUBMODULES: true 58 | pkginstall_script: 59 | - brew install meson ninja ncurses 60 | setup_script: 61 | - meson setup builddir 62 | <<: *common_meson_steps 63 | -------------------------------------------------------------------------------- /src/parse_cli_options.h: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of rmw 3 | 4 | Copyright (C) 2012-2023 Andy Alt (arch_stanton5995@proton.me) 5 | Other authors: https://github.com/theimpossibleastronaut/rmw/blob/master/AUTHORS.md 6 | 7 | This program is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | This program is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with this program. If not, see . 19 | */ 20 | 21 | #pragma once 22 | 23 | #include 24 | 25 | /** CLI option switches for rmw. */ 26 | typedef struct 27 | { 28 | int want_purge; 29 | int force; 30 | 31 | /*! Alternate configuration file given at the command line with -c */ 32 | const char *alt_config_file; 33 | 34 | bool want_restore; 35 | bool want_empty_trash; 36 | bool want_top_level_bypass; 37 | bool want_orphan_chk; 38 | bool want_selection_menu; 39 | bool want_undo; 40 | bool most_recent_list; 41 | bool want_dry_run; 42 | 43 | /*! list waste folder option */ 44 | bool list; 45 | } rmw_options; 46 | 47 | 48 | 49 | void init_rmw_options(rmw_options * options); 50 | 51 | void 52 | parse_cli_options(const int argc, char *const argv[], rmw_options * options); 53 | -------------------------------------------------------------------------------- /test/conf/rmw.alt.testrc: -------------------------------------------------------------------------------- 1 | # rmw test configuration file 2 | # https://github.com/andy5995/rmw/wiki/ 3 | # 4 | # NOTE: If two WASTE folders are on the same file system, rmw will move files 5 | # to the first WASTE folder listed, ignoring the second one. 6 | # 7 | WASTE = $HOME/.Waste 8 | # The additional folders are just to show multiples in the listing 9 | WASTE = $HOME/.local/share/Waste-2 10 | WASTE = $HOME/.local/share/Waste-3 11 | # 12 | 13 | # If you would like this to be your primary trash folder (which usually means 14 | # that it will be the same as your Desktop Trash folder) be sure it precedes 15 | # any other WASTE folders listed in the config file 16 | # 17 | # If you want it checked for files that need purging, simply uncomment 18 | # The line below. Files you move with rmw will go to the folder above by 19 | # default. 20 | # 21 | #WASTE=$HOME/.local/share/Trash 22 | # 23 | 24 | # Removable media: If a folder has ',removable' appended to it, rmw 25 | # will not try to create it; it must be initially created manually. If 26 | # the folder exists when rmw is run, it will be used; if not, it will be 27 | # skipped. Once you create "example_waste", rmw will automatically create 28 | # example_waste/info and example_waste/files 29 | # 30 | WAsTE = /mnt/sda10000/example_waste, removable 31 | # 32 | 33 | # How many days should files be allowed to stay in the waste folders before 34 | # they are permanently deleted 35 | # 36 | # use '0' to disable purging 37 | # 38 | expire_age = 90 39 | # 40 | 41 | # purge is allowed to run without the '-f' option. If you'd rather 42 | # require the use of '-f', you may uncomment the line below. 43 | # 44 | force_required 45 | # 46 | 47 | invalid_or_unknown_option 48 | -------------------------------------------------------------------------------- /test/conf/rmw.testrc: -------------------------------------------------------------------------------- 1 | # rmw test configuration file 2 | # 3 | # NOTE: If two WASTE folders are on the same file system, rmw will move files 4 | # to the first WASTE folder listed, ignoring the second one. 5 | # 6 | WASTE = $HOME/.Waste/ 7 | # The additional folders are just to show multiples in the listing 8 | WASTE = $HOME/.local/share/Waste-2 9 | waste = ~/.local/share/Waste-3 10 | # 11 | WASTE = /mnt/fs/Trash-$UID, removable 12 | # 13 | 14 | # If you would like this to be your primary trash folder (which usually means 15 | # that it will be the same as your Desktop Trash folder) be sure it precedes 16 | # any other WASTE folders listed in the config file 17 | # 18 | # If you want it checked for files that need purging, simply uncomment 19 | # The line below. Files you move with rmw will go to the folder above by 20 | # default. 21 | # 22 | #WASTE=$HOME/.local/share/Trash 23 | # 24 | 25 | # Removable media: If a folder has ',removable' appended to it, rmw 26 | # will not try to create it; it must be initially created manually. If 27 | # the folder exists when rmw is run, it will be used; if not, it will be 28 | # skipped. Once you create "example_waste", rmw will automatically create 29 | # example_waste/info and example_waste/files 30 | # 31 | WASTE = /mnt/sda10000/example_waste, removable 32 | # 33 | 34 | # How many days should files be allowed to stay in the waste folders before 35 | # they are permanently deleted 36 | # 37 | # use '0' to disable purging 38 | # 39 | expire_age = 90 40 | # 41 | 42 | # purge is allowed to run without the '-f' option. If you'd rather 43 | # require the use of '-f', you may uncomment the line below. 44 | # 45 | # force_required 46 | # 47 | 48 | # test_for_invalid_or_unknown_option 49 | -------------------------------------------------------------------------------- /src/main.h: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of rmw 3 | 4 | Copyright (C) 2012-2021 Andy Alt (arch_stanton5995@proton.me) 5 | Other authors: https://github.com/theimpossibleastronaut/rmw/blob/master/AUTHORS.md 6 | 7 | This program is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | This program is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with this program. If not, see . 19 | */ 20 | 21 | #pragma once 22 | 23 | #include "canfigger.h" 24 | 25 | /*! 26 | * Holds a list of files that rmw will be ReMoving. 27 | */ 28 | typedef struct st_removed st_removed; 29 | struct st_removed 30 | { 31 | char file[PATH_MAX]; 32 | st_removed *next_node; 33 | }; 34 | 35 | typedef struct st_dir st_dir; 36 | struct st_dir 37 | { 38 | const char *home; 39 | char configroot[PATH_MAX]; 40 | char dataroot[PATH_MAX]; 41 | }; 42 | 43 | typedef struct st_loc st_loc; 44 | struct st_loc 45 | { 46 | const char *home_dir; 47 | const char *config_dir; 48 | const char *config_file; 49 | 50 | const char *data_dir; 51 | const char *purge_time_file; 52 | const char *mrl_file; 53 | 54 | const st_dir *st_directory; // Pointer to another struct, placed at the end for clarity 55 | }; 56 | -------------------------------------------------------------------------------- /test/conf/rmw.purge_disabled.testrc: -------------------------------------------------------------------------------- 1 | # rmw test configuration file 2 | # https://github.com/andy5995/rmw/wiki/ 3 | # 4 | # NOTE: If two WASTE folders are on the same file system, rmw will move files 5 | # to the first WASTE folder listed, ignoring the second one. 6 | # 7 | WASTE = $HOME/.Waste 8 | # The additional folders are just to show multiples in the listing 9 | WASTE = $HOME/.local/share/Waste-2 10 | WASTE = $HOME/.local/share/Waste-3 11 | # 12 | 13 | # If you would like this to be your primary trash folder (which usually means 14 | # that it will be the same as your Desktop Trash folder) be sure it precedes 15 | # any other WASTE folders listed in the config file 16 | # 17 | # If you want it checked for files that need purging, simply uncomment 18 | # The line below. Files you move with rmw will go to the folder above by 19 | # default. 20 | # 21 | #WASTE=$HOME/.local/share/Trash 22 | # 23 | 24 | # Removable media: If a folder has ',removable' appended to it, rmw 25 | # will not try to create it; it must be initially created manually. If 26 | # the folder exists when rmw is run, it will be used; if not, it will be 27 | # skipped. Once you create "example_waste", rmw will automatically create 28 | # example_waste/info and example_waste/files 29 | # 30 | WASTE = /mnt/sda10000/example_waste, removable 31 | # 32 | 33 | # How many days should files be allowed to stay in the waste folders before 34 | # they are permanently deleted 35 | # 36 | # use '0' to disable purging 37 | # 38 | expire_age = 0 39 | # 40 | 41 | # purge is allowed to run without the '-f' option. If you'd rather 42 | # require the use of '-f', you may uncomment the line below. 43 | # 44 | # force_required 45 | # 46 | 47 | # test_for_invalid_or_unknown_option 48 | -------------------------------------------------------------------------------- /.github/workflows/docker.yml: -------------------------------------------------------------------------------- 1 | name: Docker rmw Image CI 2 | concurrency: 3 | group: ${{ github.workflow }} 4 | cancel-in-progress: true 5 | 6 | on: 7 | workflow_dispatch: 8 | inputs: 9 | version: 10 | description: 'version number' 11 | required: true 12 | type: string 13 | default: 'latest' 14 | push: 15 | branches: [ "master" ] 16 | paths: 17 | - '.github/workflows/docker.yml' 18 | - 'docker/Dockerfile' 19 | - 'src/**' 20 | 21 | jobs: 22 | docker: 23 | runs-on: ubuntu-latest 24 | env: 25 | VERSION: ${{ inputs.version }} 26 | steps: 27 | - name: Set version 28 | run: | 29 | # If this workflow isn't run manually through a dispatch event 30 | # the version will be blank 31 | if [ -z "$VERSION" ]; then 32 | echo "VERSION=latest" >> $GITHUB_ENV 33 | fi 34 | 35 | - name: Checkout 36 | uses: actions/checkout@v5 37 | with: 38 | submodules: false 39 | 40 | - name: Set up QEMU 41 | uses: docker/setup-qemu-action@v3 42 | 43 | - name: Login to Docker Hub 44 | uses: docker/login-action@v3 45 | with: 46 | username: ${{ secrets.DOCKER_HUB_USERNAME }} 47 | password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} 48 | - name: Set up Docker Buildx 49 | uses: docker/setup-buildx-action@v3 50 | - name: Build and push 51 | uses: docker/build-push-action@v6 52 | with: 53 | context: . 54 | file: ./docker/Dockerfile 55 | platforms: linux/amd64,linux/arm64 56 | push: true 57 | tags: ${{ secrets.DOCKER_HUB_USERNAME }}/rmw:${{ env.VERSION }} 58 | -------------------------------------------------------------------------------- /completions/fish/rmw.fish: -------------------------------------------------------------------------------- 1 | # This file contains the fish completion for rmw 2 | # 3 | # rmw is a trashcan/recycle bin utility for the command line 4 | # 5 | # More information about this tool on https://github.com/theimpossibleastronaut/rmw 6 | # 7 | # This file is intended to be released with rmw with various packaging managers 8 | 9 | complete rmw -s h -l help -d 'show help for command line options' -f 10 | complete rmw -s c -l config -d 'use an alternate configuration' -r 11 | complete rmw -s l -l list -d 'list waste directories' -f 12 | complete rmw -s g -l purge -d 'purge expired files' 13 | complete rmw -s o -l orphaned -d 'check for orphaned files (maintenance)' -f 14 | complete rmw -s f -l force -d 'allow purging of expired files' -f 15 | complete rmw -l empty -d 'completely empty (purge) all waste directories' -f 16 | complete rmw -l top-level-bypass -d 'bypass protection of top-level files' 17 | 18 | complete rmw -s v -l verbose -d 'increase output messages' -f 19 | complete rmw -l warranty -d 'display warranty' -f 20 | complete rmw -l version -d 'display version and license information' -f 21 | 22 | complete rmw -s z -l restore -d 'restore FILE(s) (see man page example)' 23 | complete rmw -s s -l select -d 'select files from list to restore' -f 24 | complete rmw -s u -l undo-last -d 'undo last move' -f 25 | complete rmw -s m -l most-recent-list -d "list most recently rmw'ed files" -f 26 | 27 | # Options kept for compatibility purpose that do not need to be completed 28 | # they are listed here to avoid people wondering why they are missing 29 | # complete rmw -s R -s r -l recursive -d 'kept for compatibility purpose with rm (recursive operation is enabled by default)' 30 | # complete rmw -s i -l interactive -d 'Prompt for removal (not implemented)' 31 | 32 | -------------------------------------------------------------------------------- /src/config_rmw.h: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of rmw 3 | 4 | Copyright (C) 2012-2023 Andy Alt (arch_stanton5995@proton.me) 5 | Other authors: https://github.com/theimpossibleastronaut/rmw/blob/master/AUTHORS.md 6 | 7 | This program is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | This program is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with this program. If not, see . 19 | */ 20 | 21 | #pragma once 22 | 23 | #include "trashinfo.h" 24 | #include "parse_cli_options.h" 25 | #include "messages.h" 26 | #include "canfigger.h" 27 | #include "main.h" 28 | 29 | #define ENV_RMW_FAKE_HOME "RMW_FAKE_HOME" 30 | 31 | extern const char *expire_age_str; 32 | 33 | typedef struct 34 | { 35 | st_waste *st_waste_folder_props_head; // Pointer with high alignment requirements 36 | 37 | int expire_age; // 4-byte alignment, placed next 38 | 39 | bool force_required; // Minimal alignment, placed after the int 40 | char uid[10]; // Character array, lower alignment, placed last 41 | } st_config; 42 | 43 | 44 | void print_config(FILE * restrict stream); 45 | 46 | void 47 | parse_config_file(const rmw_options * cli_user_options, 48 | st_config * st_config_data, const st_loc * st_location); 49 | 50 | void init_config_data(st_config * x); 51 | 52 | void 53 | show_folder_line(const char *folder, const bool is_r, const bool is_attached); 54 | -------------------------------------------------------------------------------- /src/globals.h: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of rmw 3 | 4 | Copyright (C) 2012-2021 Andy Alt (arch_stanton5995@proton.me) 5 | Other authors: https://github.com/theimpossibleastronaut/rmw/blob/master/AUTHORS.md 6 | 7 | This program is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | This program is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with this program. If not, see . 19 | */ 20 | 21 | #pragma once 22 | 23 | #include "config.h" 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #include "strings_rmw.h" 32 | 33 | #ifdef ENABLE_NLS 34 | #include 35 | #endif 36 | #include "gettext.h" 37 | #define _(String) gettext (String) 38 | #define gettext_noop(String) String 39 | #define N_(String) gettext_noop (String) 40 | 41 | #ifdef DEBUG 42 | #define DEBUG_PREFIX printf ("[DEBUG] "); 43 | #endif 44 | 45 | /* This is always defined when 'configure' is used */ 46 | #ifndef VERSION 47 | #define VERSION "unversioned" 48 | #endif 49 | 50 | #define LEN_MAX_ESCAPED_PATH (PATH_MAX * 3) 51 | 52 | #define EBUF 11 53 | 54 | typedef enum 55 | { 56 | TI_HEADER, 57 | PATH_KEY, 58 | DELETIONDATE_KEY, 59 | TI_LINE_MAX 60 | } ti_key; 61 | 62 | extern int verbose; 63 | extern const char *lit_info; 64 | extern const char trashinfo_ext[]; 65 | extern const int len_trashinfo_ext; 66 | -------------------------------------------------------------------------------- /test/test_btrfs_clone.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -ve 3 | 4 | if [ -e COMMON ]; then 5 | . ./COMMON 6 | else 7 | . "${MESON_SOURCE_ROOT}/test/COMMON" 8 | fi 9 | 10 | BTRFS_IMAGE_MOUNTPOINT="/tmp/rmw-loop" 11 | IMAGE_PATH="${MESON_SOURCE_ROOT}/test/rmw-btrfs-test.img" 12 | 13 | if [ ! -f "$IMAGE_PATH" ]; then 14 | echo "Test image not found, but exiting with zero anyway." 15 | exit 0 16 | fi 17 | 18 | if [ ! -d "$BTRFS_IMAGE_MOUNTPOINT" ]; then 19 | sudo mkdir "$BTRFS_IMAGE_MOUNTPOINT" 20 | fi 21 | IS_BTRFS_MOUNTED="$(mount | grep rmw-btrfs)" || true 22 | if [ -z "$IS_BTRFS_MOUNTED" ]; then 23 | sudo mount -o loop "$IMAGE_PATH" \ 24 | "$BTRFS_IMAGE_MOUNTPOINT" 25 | sudo chown $(id -u) -R "$BTRFS_IMAGE_MOUNTPOINT" 26 | fi 27 | 28 | cd "$BTRFS_IMAGE_MOUNTPOINT" 29 | RMW_TEST_CMD_STRING="$BIN_DIR/rmw -c ${MESON_SOURCE_ROOT}/test/conf/btrfs_img.testrc" 30 | 31 | SUBVOLUME_USED="/tmp/rmw-loop/@two" 32 | 33 | WASTE_USED="$SUBVOLUME_USED/Waste" 34 | if [ -d "$WASTE_USED" ]; then 35 | rm -rf "$WASTE_USED" 36 | fi 37 | 38 | TEST_DIR="$BTRFS_IMAGE_MOUNTPOINT/test_dir" 39 | if [ -d "$TEST_DIR" ]; then 40 | rm -rf "$TEST_DIR" 41 | fi 42 | 43 | mkdir "$TEST_DIR" 44 | touch "$TEST_DIR/bar" 45 | $RMW_TEST_CMD_STRING "$TEST_DIR" 46 | test ! -d "$TEST_DIR" 47 | 48 | $RMW_TEST_CMD_STRING -u 49 | test -d "$TEST_DIR" 50 | 51 | touch foo 52 | $RMW_TEST_CMD_STRING foo 53 | test -f "$WASTE_USED/files/foo" 54 | test -f "$WASTE_USED/info/foo.trashinfo" 55 | test ! -f foo 56 | $RMW_TEST_CMD_STRING -u 57 | test -f foo 58 | 59 | RMW_FAKE_YEAR=true $RMW_TEST_CMD_STRING foo 60 | test -f "$WASTE_USED/files/foo" 61 | $RMW_TEST_CMD_STRING -g 62 | test ! -f "$WASTE_USED/files/foo" 63 | 64 | cd "$RMW_FAKE_HOME" 65 | touch foo 66 | $RMW_TEST_CMD_STRING foo -v 67 | test ! -f foo 68 | $RMW_TEST_CMD_STRING -u 69 | test -f foo 70 | 71 | cd 72 | 73 | if [ -n "$(mount | grep rmw-btrfs)" ]; then 74 | sudo umount "$BTRFS_IMAGE_MOUNTPOINT" 75 | fi 76 | 77 | exit 0 78 | -------------------------------------------------------------------------------- /src/time_rmw.h: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of rmw 3 | 4 | Copyright (C) 2012-2023 Andy Alt (arch_stanton5995@proton.me) 5 | Other authors: https://github.com/theimpossibleastronaut/rmw/blob/master/AUTHORS.md 6 | 7 | This program is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | This program is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with this program. If not, see . 19 | */ 20 | 21 | #ifndef _INC_TIME_RMW_H 22 | #define _INC_TIME_RMW_H 23 | 24 | /* for strptime() 25 | * this macro doesn't get defined on some systems, such as Centos or Slackware, 26 | * so gives the the warning: 27 | * "implicit declaration of function 'strptime'" 28 | * 29 | * -andy5995 2019-07-30 */ 30 | #ifndef __USE_XOPEN 31 | #define __USE_XOPEN 32 | #endif 33 | #include 34 | #include 35 | 36 | #define LEN_MAX_DELETION_DATE (sizeof "2016-01-01T12:00:00") 37 | #define LEN_MAX_TIME_STR_SUFFIX (sizeof "_000000-000000") 38 | #define LEN_TIME_STR_SUFFIX (LEN_MAX_TIME_STR_SUFFIX - 1) 39 | #define SECONDS_IN_A_DAY (60 * 60 * 24) 40 | 41 | /*! Holds variables related to time 42 | */ 43 | typedef struct 44 | { 45 | time_t now; // Largest member, placed first to avoid alignment padding issues. 46 | char deletion_date[LEN_MAX_DELETION_DATE]; // Grouped char arrays together. 47 | char t_fmt[LEN_MAX_DELETION_DATE]; 48 | char suffix_added_dup_exists[LEN_MAX_TIME_STR_SUFFIX]; 49 | } st_time; 50 | 51 | 52 | void init_time_vars(st_time * st_time_var); 53 | 54 | #endif 55 | -------------------------------------------------------------------------------- /src/messages.h: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of rmw 3 | 4 | Copyright (C) 2012-2021 Andy Alt (arch_stanton5995@proton.me) 5 | Other authors: https://github.com/theimpossibleastronaut/rmw/blob/master/AUTHORS.md 6 | 7 | This program is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | This program is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with this program. If not, see . 19 | */ 20 | 21 | #pragma once 22 | 23 | #include "globals.h" 24 | 25 | #ifndef BUFSIZ 26 | #define BUFSIZ 8192 27 | #endif 28 | 29 | #define fatal_malloc() real_fatal_malloc(__func__, __LINE__) 30 | 31 | void print_msg_error(void); 32 | 33 | void print_msg_warn(void); 34 | 35 | void open_err(const char *filename, const char *function_name); 36 | 37 | int close_file(FILE ** fp, const char *filename, const char *function_name); 38 | 39 | void display_dot_trashinfo_error(const char *dti); 40 | 41 | void real_fatal_malloc(const char *func, const int line); 42 | 43 | void msg_err_close_dir(const char *dir, const char *func, const int line); 44 | 45 | void msg_err_open_dir(const char *dir, const char *func, const int line); 46 | 47 | void msg_err_rename(const char *src_file, const char *dest_file); 48 | 49 | void msg_err_fatal_fprintf(const char *func); 50 | 51 | void msg_err_buffer_overrun(const char *func, const int line); 52 | 53 | void msg_err_lstat(const char *file, const char *func, const int LINE); 54 | 55 | void msg_err_remove(const char *file, const char *func); 56 | 57 | void msg_err_mkdir(const char *dir, const char *func); 58 | 59 | void msg_success_mkdir(const char *dir); 60 | 61 | void msg_warn_file_not_found(const char *file); 62 | -------------------------------------------------------------------------------- /src/restore.h: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of rmw 3 | 4 | Copyright (C) 2012-2023 Andy Alt (arch_stanton5995@proton.me) 5 | Other authors: https://github.com/theimpossibleastronaut/rmw/blob/master/AUTHORS.md 6 | 7 | This program is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | This program is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with this program. If not, see . 19 | */ 20 | 21 | #pragma once 22 | 23 | #if defined HAVE_NCURSESW_MENU_H 24 | #include 25 | #elif defined HAVE_NCURSES_MENU_H 26 | #include 27 | #elif defined HAVE_MENU_H 28 | #include 29 | #else 30 | #error "SysV-compatible Curses Menu header file required" 31 | #endif 32 | 33 | #if defined HAVE_NCURSESW_CURSES_H 34 | #include 35 | #elif defined HAVE_NCURSESW_H 36 | #include 37 | #elif defined HAVE_NCURSES_CURSES_H 38 | #include 39 | #elif defined HAVE_NCURSES_H 40 | #include 41 | #elif defined HAVE_CURSES_H 42 | #include 43 | #else 44 | #error "SysV or X/Open-compatible Curses header file required" 45 | #endif 46 | 47 | #include "trashinfo.h" 48 | #include "parse_cli_options.h" 49 | 50 | #define CTRLD 4 51 | 52 | int 53 | restore(const char *src, st_time * st_time_var, 54 | const rmw_options * cli_user_options, st_waste * waste_head); 55 | 56 | int 57 | restore_select(st_waste * waste_head, st_time * st_time_var, 58 | const rmw_options * cli_user_options); 59 | 60 | int 61 | undo_last_rmw(st_time * st_time_var, const 62 | rmw_options * cli_user_options, char *mrl_contents, st_waste 63 | * waste_head); 64 | -------------------------------------------------------------------------------- /src/utils.h: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of rmw 3 | 4 | Copyright (C) 2012-2025 Andy Alt (arch_stanton5995@proton.me) 5 | Other authors: https://github.com/theimpossibleastronaut/rmw/blob/master/AUTHORS.md 6 | 7 | This program is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | This program is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with this program. If not, see . 19 | */ 20 | 21 | #ifndef _INC_UTILS_H 22 | #define _INC_UTILS_H 23 | 24 | #include 25 | #include 26 | #include 27 | 28 | #include "trashinfo.h" 29 | 30 | #define join_paths(...) real_join_paths(__VA_ARGS__, NULL) 31 | 32 | #define LEN_MAX_HUMAN_READABLE_SIZE (sizeof "xxxx.y GiB") 33 | #define LEN_MAX_FILE_DETAILS (LEN_MAX_HUMAN_READABLE_SIZE + sizeof "[] (D)" - 1) 34 | 35 | bool is_symlink(const char *path); 36 | 37 | char *rmw_dirname(char *path); 38 | 39 | int rmw_mkdir(const char *dir); 40 | 41 | int make_dir(const char *dir); 42 | 43 | int check_pathname_state(const char *pathname); 44 | 45 | void dispose_waste(st_waste * node); 46 | 47 | void make_size_human_readable(off_t size, char *dest_hr_size); 48 | 49 | bool user_verify(void); 50 | 51 | char *escape_url(const char *str); 52 | 53 | char *unescape_url(const char *str); 54 | 55 | bool isdotdir(const char *dir); 56 | 57 | char *resolve_path(const char *file, const char *b); 58 | 59 | void trim_char(const int c, char *str); 60 | 61 | char *real_join_paths(const char *argv, ...); 62 | 63 | bool is_dir_f(const char *pathname); 64 | 65 | int count_chars(const char c, const char *str); 66 | 67 | int safe_mv_via_exec(const char *src, const char *dst, int *out_errno); 68 | 69 | #endif 70 | -------------------------------------------------------------------------------- /src/meson.build: -------------------------------------------------------------------------------- 1 | subdir('bsdutils') 2 | disable_curses = '' 3 | dep_menu = [] 4 | dep_curses = [] 5 | if not get_option('without-curses') 6 | dep_curses = dependency('curses') 7 | dep_menu = dependency('menuw', required: false) 8 | if not dep_menu.found() 9 | dep_menu = dependency('menu', required: false) 10 | if not dep_menu.found() 11 | dep_menu = cc.find_library('menuw') 12 | if not dep_menu.found() 13 | dep_menu = cc.find_library('menu') 14 | if not dep_menu.found() 15 | conf.set('DISABLE_CURSES', 1) 16 | endif 17 | endif 18 | endif 19 | endif 20 | else 21 | conf.set('DISABLE_CURSES', 1) 22 | endif 23 | 24 | src = [ 25 | 'globals.c', 26 | 'btrfs.c', 27 | 'restore.c', 28 | 'config_rmw.c', 29 | 'parse_cli_options.c', 30 | 'strings_rmw.c', 31 | 'purging.c', 32 | 'messages.c', 33 | 'time_rmw.c', 34 | 'trashinfo.c', 35 | 'utils.c', 36 | ] 37 | 38 | # Used for btrfs functions 39 | has_statfs = false 40 | has_btrfs_header = false 41 | if host_sys == 'linux' 42 | if get_option('want_btrfs_clone') 43 | has_statfs = cc.has_function( 44 | 'statfs', 45 | prefix: '#include ', 46 | ) 47 | has_btrfs_header = cc.has_header('linux/btrfs.h') 48 | if has_statfs and has_btrfs_header 49 | conf.set('HAVE_LINUX_BTRFS', 1) 50 | else 51 | error(''' 52 | 53 | : Requirements not met for btrfs clone support. 54 | : If missing linux/btrfs.h, you probably need to install the linux-headers package. 55 | : To build without btrfs clone support and skip this check, add 56 | : "-Dwant_btrfs_clone=false" to the meson setup options.''') 57 | endif 58 | endif 59 | endif 60 | 61 | config_h = configure_file(output: 'config.h', configuration: conf) 62 | 63 | deps_librmw = [dep_intl, canfigger_dep, dep_menu, dep_curses] 64 | 65 | lib_rmw = static_library( 66 | 'rmw', 67 | src, 68 | include_directories: include_directories(['.', inc_intl_arg]), 69 | dependencies: deps_librmw, 70 | ) 71 | 72 | rmw_dep = declare_dependency( 73 | link_with: [lib_rmw, lib_bsdutilsrm], 74 | include_directories: include_directories(['.', inc_intl_arg]), 75 | dependencies: deps_librmw, 76 | ) 77 | -------------------------------------------------------------------------------- /src/bsdutils/LICENSE.bsdutils: -------------------------------------------------------------------------------- 1 | This code originates from FreeBSD but has been modified for building 2 | on Linux. Please see the COPYRIGHT file for the original license and 3 | copyright terms of the FreeBSD code. 4 | 5 | Unless otherwise noted in the source file, the following license and 6 | copyright statement applies to the code created as part of this 7 | porting effort. All existing licenses and copyrights apply. 8 | 9 | This is the BSD-3-Clause license as defined on spdx.org. Individual 10 | authors will replace NAME with their name and EMAIL with their email 11 | address. The year may change as well depending on when their 12 | contribution originated. 13 | 14 | For the purposes of code originating in this port, it is under a 15 | BSD-3-Clause license from a number of different authors. 16 | 17 | /* 18 | * Copyright 2021 NAME 19 | * 20 | * Redistribution and use in source and binary forms, with or without 21 | * modification, are permitted provided that the following conditions 22 | * are met: 23 | * 24 | * 1. Redistributions of source code must retain the above copyright 25 | * notice, this list of conditions and the following disclaimer. 26 | * 27 | * 2. Redistributions in binary form must reproduce the above 28 | * copyright notice, this list of conditions and the following 29 | * disclaimer in the documentation and/or other materials provided 30 | * with the distribution. 31 | * 32 | * 3. Neither the name of the copyright holder nor the names of its 33 | * contributors may be used to endorse or promote products derived 34 | * from this software without specific prior written permission. 35 | * 36 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 37 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 38 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 39 | * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 40 | * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 41 | * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 42 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 43 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 44 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 45 | * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 46 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 47 | * OF THE POSSIBILITY OF SUCH DAMAGE. 48 | */ 49 | -------------------------------------------------------------------------------- /packaging/appimage/pre-appimage.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Set bash options: 4 | # -e: Exit immediately if a command exits with a non-zero status. 5 | # -v: Print each command to stderr before executing it. 6 | set -ev 7 | 8 | # Set default workspace if not provided 9 | WORKSPACE=${WORKSPACE:-$(pwd)} 10 | echo $WORKSPACE 11 | # Check if the workspace path is absolute 12 | if [[ "$WORKSPACE" != /* ]]; then 13 | echo "The workspace path must be absolute" 14 | exit 1 15 | fi 16 | test -d "$WORKSPACE" 17 | 18 | # Set default source root if not provided 19 | SOURCE_ROOT=${SOURCE_ROOT:-$WORKSPACE} 20 | # Check if the source root path is absolute 21 | if [[ "$SOURCE_ROOT" != /* ]]; then 22 | echo "The source root path must be absolute" 23 | exit 1 24 | fi 25 | # Verify that you're in the source root 26 | echo $SOURCE_ROOT 27 | test -f "$SOURCE_ROOT/src/main.c" 28 | 29 | # Define and create application directory if it doesn't exist 30 | # This is the directory where your project will be installed to 31 | # and an AppImage created 32 | APPDIR=${APPDIR:-"/tmp/$USER-AppDir"} 33 | if [ -d "$APPDIR" ]; then 34 | rm -rf "$APPDIR" 35 | else 36 | mkdir -v -p "$APPDIR" 37 | fi 38 | 39 | # Install necessary dependencies 40 | sudo apt update && sudo apt upgrade -y 41 | sudo apt install -y libncursesw5-dev 42 | 43 | # Set up build directory 44 | BUILD_DIR="$SOURCE_ROOT/appimage_build" 45 | cd "$SOURCE_ROOT" 46 | BUILD_DIR="$SOURCE_ROOT/_build_prep_appdir" 47 | # Clean build directory if specified and it exists 48 | if [ "$CLEAN_BUILD" = "true" ] && [ -d "$BUILD_DIR" ]; then 49 | rm -rf "$BUILD_DIR" 50 | fi 51 | 52 | # Setup project for building 53 | if [ ! -d "$BUILD_DIR" ]; then 54 | meson setup "$BUILD_DIR" \ 55 | -Dbuildtype=release \ 56 | -Dstrip=true \ 57 | -Db_sanitize=none \ 58 | -Dprefix=/usr 59 | fi 60 | 61 | # Build project 62 | cd "$BUILD_DIR" 63 | ninja 64 | meson test -v 65 | meson install --destdir=$APPDIR --skip-subprojects 66 | 67 | # Set up output directory 68 | OUT_DIR="$WORKSPACE/out" 69 | if [ ! -d "$OUT_DIR" ]; then 70 | mkdir "$OUT_DIR" 71 | fi 72 | cd "$OUT_DIR" 73 | 74 | # Set LinuxDeploy output version 75 | export LINUXDEPLOY_OUTPUT_VERSION="$VERSION" 76 | # Generate AppImage using linuxdeploy 77 | linuxdeploy \ 78 | --appdir="$APPDIR" \ 79 | --custom-apprun=$SOURCE_ROOT/packaging/appimage/AppRun \ 80 | -d $SOURCE_ROOT/packaging/rmw.desktop \ 81 | --icon-file=$SOURCE_ROOT/packaging/rmw_icon_32x32.png \ 82 | --icon-filename=rmw \ 83 | --executable=$APPDIR/usr/bin/rmw \ 84 | -o appimage 85 | -------------------------------------------------------------------------------- /src/time_rmw.c: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of rmw 3 | 4 | Copyright (C) 2012-2023 Andy Alt (arch_stanton5995@proton.me) 5 | Other authors: https://github.com/theimpossibleastronaut/rmw/blob/master/AUTHORS.md 6 | 7 | This program is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | This program is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with this program. If not, see . 19 | */ 20 | 21 | #include "globals.h" 22 | #include "time_rmw.h" 23 | #include "trashinfo.h" 24 | #include "messages.h" 25 | 26 | /*! 27 | * Assigns a time string to *tm_str based on the format requested 28 | */ 29 | static void 30 | set_time_string(char *tm_str, const size_t len, const char *format, 31 | time_t time_t_now) 32 | { 33 | struct tm result; 34 | if (localtime_r(&time_t_now, &result) == NULL) 35 | { 36 | fputs 37 | ("Error: localtime_r() failed for time_t value beyond 32-bit limit.\n", 38 | stderr); 39 | exit(EXIT_FAILURE); 40 | } 41 | strftime(tm_str, len, format, &result); 42 | trim_whitespace(tm_str); 43 | 44 | return; 45 | } 46 | 47 | /*! 48 | * Returns a formatted string based on whether or not 49 | * a fake year is requested at runtime. If a fake-year is not requested, 50 | * the returned string will be based on the local-time of the user's system. 51 | */ 52 | static void 53 | set_which_deletion_date(char *format) 54 | { 55 | char *fake_year = getenv("RMW_FAKE_YEAR"); 56 | bool valid_value = false; 57 | if (fake_year != NULL) 58 | { 59 | valid_value = strcasecmp(fake_year, "true") == 0; 60 | puts("RMW_FAKE_YEAR:true"); 61 | } 62 | sn_check(snprintf 63 | (format, LEN_MAX_DELETION_DATE, "%s", 64 | valid_value ? "1999-%m-%dT%T" : "%FT%T"), LEN_MAX_DELETION_DATE); 65 | 66 | return; 67 | } 68 | 69 | 70 | void 71 | init_time_vars(st_time *x) 72 | { 73 | x->now = time(NULL); 74 | // x->now = 0x80000000; 75 | 76 | set_which_deletion_date(x->t_fmt); 77 | 78 | set_time_string(x->deletion_date, LEN_MAX_DELETION_DATE, x->t_fmt, x->now); 79 | 80 | set_time_string(x->suffix_added_dup_exists, 81 | LEN_MAX_TIME_STR_SUFFIX, "_%H%M%S-%y%m%d", x->now); 82 | 83 | return; 84 | } 85 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | name: CodeQL 2 | concurrency: 3 | group: ${{ github.workflow }} 4 | cancel-in-progress: true 5 | 6 | on: 7 | push: 8 | branches: master 9 | paths: 10 | - '**.c' 11 | - '**.h' 12 | - '**/codeql.yml' 13 | pull_request: 14 | branches: master 15 | paths: 16 | - '**.c' 17 | - '**.h' 18 | - '**/codeql.yml' 19 | 20 | jobs: 21 | analyze: 22 | name: Analyze 23 | runs-on: ubuntu-latest 24 | 25 | strategy: 26 | fail-fast: false 27 | matrix: 28 | # Override automatic language detection by changing the below list 29 | # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python'] 30 | language: ['cpp'] 31 | # Learn more... 32 | # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection 33 | 34 | steps: 35 | - name: Checkout repository 36 | uses: actions/checkout@v5 37 | with: 38 | # We must fetch at least the immediate parents so that if this is a 39 | # pull request then we can checkout the head. 40 | fetch-depth: 2 41 | submodules: false 42 | - name: Install dependencies 43 | run: | 44 | sudo apt update 45 | sudo apt remove -y firefox 46 | sudo apt upgrade -y 47 | sudo apt-get install -y gettext meson 48 | 49 | # Initializes the CodeQL tools for scanning. 50 | - name: Initialize CodeQL 51 | uses: github/codeql-action/init@v4 52 | with: 53 | languages: ${{ matrix.language }} 54 | # If you wish to specify custom queries, you can do so here or in a config file. 55 | # By default, queries listed here will override any specified in a config file. 56 | # Prefix the list here with "+" to use these queries and those in the config file. 57 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 58 | 59 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 60 | # If this step fails, then you should remove it and run the build manually (see below) 61 | #- name: Autobuild 62 | # uses: github/codeql-action/autobuild@v1 63 | 64 | # ℹ️ Command-line programs to run using the OS shell. 65 | # 📚 https://git.io/JvXDl 66 | 67 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 68 | # and modify them (or add more) to build your code if your project 69 | # uses a compiled language 70 | 71 | - run: | 72 | meson setup -Db_sanitize=none builddir 73 | cd builddir 74 | meson compile 75 | meson test 76 | 77 | - name: Perform CodeQL Analysis 78 | uses: github/codeql-action/analyze@v4 79 | 80 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project( 2 | 'rmw', 3 | 'c', 4 | version: '0.9.4', 5 | meson_version: '>= 0.59.0', 6 | default_options: [ 7 | 'warning_level=3', 8 | #'b_sanitize=address,undefined', 9 | #'b_lundef=false', 10 | 'pkg_config_path=/opt/homebrew/opt/gettext/lib/pkgconfig,/opt/homebrew/opt/ncurses/lib/pkgconfig,/usr/local/opt/ncurses/lib/pkgconfig', 11 | ], 12 | ) 13 | 14 | cc = meson.get_compiler('c') 15 | host_sys = host_machine.system() 16 | 17 | extra_flags = [ 18 | '-fno-common', 19 | '-fstack-protector-strong', 20 | '-Wformat-security', 21 | '-Wformat-overflow=2', 22 | '-Wshadow', 23 | '-Wstrict-overflow=5', 24 | '-Werror=odr', 25 | '-Werror=lto-type-mismatch', 26 | '-Werror=strict-aliasing', 27 | # '-Wconversion' 28 | ] 29 | 30 | add_project_arguments(cc.get_supported_arguments(extra_flags), language: 'c') 31 | 32 | meson.add_devenv({'RMW_LOCALEDIR': join_paths(meson.project_build_root(), 'po')}) 33 | 34 | conf = configuration_data() 35 | conf.set_quoted('VERSION', meson.project_version()) 36 | conf.set_quoted('PACKAGE_STRING', meson.project_name()) 37 | conf.set_quoted( 38 | 'PACKAGE_URL', 39 | 'https://theimpossibleastronaut.github.io/rmw-website/', 40 | ) 41 | conf.set_quoted( 42 | 'PACKAGE_BUGREPORT', 43 | 'https://github.com/theimpossibleastronaut/rmw/issues', 44 | ) 45 | 46 | check_headers = [ 47 | ['ncursesw/menu.h', 'HAVE_NCURSESW_MENU_H'], 48 | ['ncurses/menu.h', 'HAVE_NCURSES_MENU_H'], 49 | ['menu.h', 'HAVE_MENU_H'], 50 | ['ncursesw/curses.h', 'HAVE_NCURSESW_CURSES_H'], 51 | ['ncursesw.h', 'HAVE_NCURSESW_H'], 52 | ['ncurses/curses.h', 'HAVE_NCURSES_CURSES_H'], 53 | ['ncurses.h', 'HAVE_NCURSES_H'], 54 | ['curses.h', 'HAVE_CURSES_H'], 55 | ] 56 | 57 | foreach h : check_headers 58 | if cc.has_header(h[0]) 59 | conf.set(h[1], 1) 60 | endif 61 | endforeach 62 | 63 | localedir = join_paths(get_option('prefix'), get_option('localedir')) 64 | subdir('po') 65 | 66 | # https://mesonbuild.com/Subprojects.html 67 | canfigger_dep = dependency( 68 | 'canfigger', 69 | version: '>=0.3.0', 70 | fallback: ['canfigger', 'canfigger_dep'], 71 | default_options: 'default_library=static', 72 | ) 73 | 74 | subdir('src') 75 | 76 | main_bin = executable('rmw', 'src/main.c', dependencies: rmw_dep, install: true) 77 | 78 | subdir('man') 79 | 80 | if get_option('build_tests') 81 | subdir('test') 82 | endif 83 | 84 | install_data( 85 | files( 86 | 'AUTHORS.md', 87 | 'COPYING', 88 | 'ChangeLog', 89 | 'README.md', 90 | 'ReleaseNotes', 91 | 'rmwrc.example', 92 | ), 93 | install_dir: get_option('docdir'), 94 | ) 95 | 96 | # Copy completions to docdir 97 | install_subdir( 98 | 'completions', 99 | install_dir: join_paths(get_option('docdir')), 100 | strip_directory: false, 101 | ) 102 | -------------------------------------------------------------------------------- /test/test_media_root.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # If a waste folder is in $topdir of a partition or device, the 3 | # Path should be relative 4 | # 5 | #https://specifications.freedesktop.org/trash-spec/1.0/#id-1.6.10.1 The key 6 | #“Path” contains the original location of the file/directory, as either an 7 | #absolute pathname (starting with the slash character “/”) or a relative 8 | #pathname (starting with any other character). A relative pathname is to be 9 | #from the directory in which the trash directory resides (for example, from 10 | #$XDG_DATA_HOME for the “home trash” directory); it MUST not include a “..” 11 | #directory, and for files not “under” that directory, absolute pathnames must 12 | #be used. The system SHOULD support absolute pathnames only in the “home trash” 13 | #directory, not in the directories under $topdir. 14 | 15 | set -ve 16 | 17 | if [ -e COMMON ]; then 18 | . ./COMMON 19 | else 20 | . "${MESON_SOURCE_ROOT}/test/COMMON" 21 | fi 22 | 23 | # This test will only work on Andy's workstation. 24 | # The media root, /home/andy/src is on a different partition than /home/andy 25 | # It's mounted with 'bind' and therefore has a different device id 26 | test ! -d /mnt/918375c2 && exit 0; 27 | 28 | # This test use Andy's regular config, outside of the test sandbox, so don't 29 | # run it if the epoch test setup is being used. Otherwise all the waste dirs 30 | # will be purged. 31 | test -n "$RMW_TEST_EPOCHALYPSE" && exit 0; 32 | echo "hello" 33 | 34 | test_file="media_root_test" 35 | PREV_RMW_FAKE_HOME=${RMW_FAKE_HOME} 36 | # needs to be unset so rmw will use $HOME instead 37 | unset RMW_FAKE_HOME 38 | mkdir -p "$PREV_RMW_FAKE_HOME" 39 | test_file_path=${PREV_RMW_FAKE_HOME}/$test_file 40 | if test -f $test_file_path; then 41 | rm $test_file_path 42 | fi 43 | if test -f /home/andy/src/rmw-project/.Trash-1000/files/$test_file; then 44 | rm /home/andy/src/rmw-project/.Trash-1000/files/$test_file 45 | fi 46 | if test -f /home/andy/src/rmw-project/.Trash-1000/info/$test_file.trashinfo; then 47 | rm /home/andy/src/rmw-project/.Trash-1000/info/$test_file.trashinfo 48 | fi 49 | touch $test_file_path 50 | $BIN_DIR/rmw -c /home/andy/.config/rmwrc $test_file_path 51 | 52 | output=$(grep Path /home/andy/src/rmw-project/.Trash-1000/info/$test_file.trashinfo) 53 | 54 | # There should be no leading '/' in the filename. 55 | path_expected=$(echo ${MESON_BUILD_ROOT} | sed -e "s/\/home\/andy\/src\/rmw-project\///g") 56 | echo $path_expected 57 | test "$output" = "Path=${path_expected}/test/rmw-tests-home/test_media_root.sh_dir/media_root_test" 58 | 59 | output=$($BIN_DIR/rmw -uvv -c /home/andy/.config/rmwrc | grep media_root_test) 60 | 61 | test "$output" = "+'/home/andy/src/rmw-project/.Trash-1000/files/media_root_test' -> '${MESON_BUILD_ROOT}/test/rmw-tests-home/test_media_root.sh_dir/media_root_test' 62 | -/home/andy/src/rmw-project/.Trash-1000/info/media_root_test.trashinfo" 63 | 64 | exit 0 65 | -------------------------------------------------------------------------------- /packaging/release-checklist.txt: -------------------------------------------------------------------------------- 1 | 2 | Change version number 3 | ./meson.build 4 | README.md 5 | man/rmw.1 6 | packaging/Slackbuild/rmw/rmw.info (also change version numbers in download link) 7 | packaging/Slackbuild/rmw/rmw.Slackbuild 8 | packaging/file_id.diz (filegate.net) 9 | cd packaging and run 'dch -r' (updates the changelog timestamp in debian/ 10 | 11 | In meson,build, comment out 12 | 'b_sanitize=address,undefined', 13 | 'b_lundef=false' 14 | 15 | ## canfigger.wrap 16 | 17 | Make sure the revision points to the last release, or to a revision where 18 | sanitize is disabled 19 | 20 | For later Debian package creation, check standards version 21 | https://www.debian.org/doc/debian-policy/upgrading-checklist.html 22 | 23 | 24 | update po and pot files 25 | meson compile rmw-pot 26 | ninja rmw-update-po 27 | 28 | Copy ChangeLog entries that apply to current release to ReleaseNotes 29 | (remove dates and reorder entries in in order of important or interest) 30 | 31 | 32 | Add release date to ChangeLog 33 | Ex: 34 | 2021-04-25 35 | 36 | * rmw v0.7.07 released 37 | 38 | 39 | Change Month and Year in man/rmw.1 40 | 41 | Generate html version of the manual: 42 | `groff -Thtml -mandoc < rmw.1 > rmw_man.html` (mv the output to the website repo) 43 | 44 | commit changes (NOTE: meson dist only packages *committed* changes) 45 | meson dist --include-subprojects 46 | 47 | Remove packaging directory from the resulting xz file 48 | (The process is now in the GitHub workflow) 49 | 50 | Make the AppImage 51 | 52 | To pass the test on https://github.com/AppImage/appimage.github.io, the 53 | image must be built on an older system, such as Ubuntu focal (see 54 | https://github.com/AppImage/AppImageKit/discussions/1254). I do that in a 55 | docker image. (The process is now in the GitHub workflow) 56 | 57 | Check copyright date/year 58 | packaging/debian/copyright 59 | packaging/Slackbuild/rmw/rmw.Slackbuild 60 | 61 | Make Debian package 62 | cd packaging 63 | tar xf rmw-.tar.gz 64 | cd rmw- 65 | cp -a /packaging/debian . 66 | dpkg-buildpackage -us -uc -tc (The resulting package will be one level up) 67 | 68 | Check the Debian package 69 | lintian -i -I --show-overrides rmw_0.7.07_amd64.changes 70 | 71 | push all changes 72 | 73 | Get the sha256sum of the source tarball, AppImage, and Debian package 74 | 75 | Publish the release 76 | paste in the ReleaseNotes file and the 'sums of the two files listed above. 77 | 78 | Close milestone (https://github.com/theimpossibleastronaut/rmw/milestones) 79 | 80 | Create Slackbuild tarball and submit to Slackbuilds.org (https://slackbuilds.org/guidelines/) 81 | Use sbo-maintainer-tools to check for problems. 82 | https://slackbuilds.org/repository/15.0/system/sbo-maintainer-tools/ 83 | 84 | Update latest release info on /docker/README.md 85 | 86 | Tag latest release with version number and push to docker hub 87 | -------------------------------------------------------------------------------- /src/btrfs.c: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of rmw 3 | 4 | Copyright (C) 2012-2024 Andy Alt (arch_stanton5995@proton.me) 5 | Other authors: https://github.com/theimpossibleastronaut/rmw/blob/master/AUTHORS.md 6 | 7 | This program is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | This program is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with this program. If not, see . 19 | */ 20 | 21 | #ifndef INC_GLOBALS_H 22 | #define INC_GLOBALS_H 23 | #include "globals.h" 24 | #endif 25 | 26 | #ifdef HAVE_LINUX_BTRFS 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #endif 34 | 35 | #include "btrfs.h" 36 | #include "messages.h" 37 | 38 | bool 39 | is_btrfs(const char *path) 40 | { 41 | #ifdef HAVE_LINUX_BTRFS 42 | struct statfs buf; 43 | 44 | if (statfs(path, &buf) == -1) 45 | { 46 | print_msg_error(); 47 | perror("statfs"); 48 | exit(EXIT_FAILURE); 49 | } 50 | 51 | return buf.f_type == BTRFS_SUPER_MAGIC; 52 | #else 53 | (void) path; 54 | return false; 55 | #endif 56 | } 57 | 58 | 59 | int 60 | do_btrfs_clone(const char *source, const char *dest, int *save_errno) 61 | { 62 | #ifdef HAVE_LINUX_BTRFS 63 | int src_fd, dest_fd; 64 | struct stat src_stat; 65 | 66 | // Open the source file in read-only mode 67 | src_fd = open(source, O_RDONLY); 68 | if (src_fd == -1) 69 | { 70 | perror("open source"); 71 | return src_fd; 72 | } 73 | 74 | // Retrieve source file permissions 75 | if (fstat(src_fd, &src_stat) == -1) 76 | { 77 | perror("fstat source"); 78 | close(src_fd); 79 | return -1; 80 | } 81 | 82 | // Ensure no umask setting interferes with the permissions 83 | mode_t old_umask = umask(0); 84 | // Open or create the destination file with the same permissions as the source 85 | dest_fd = open(dest, O_WRONLY | O_CREAT, src_stat.st_mode & 0777); 86 | umask(old_umask); 87 | if (dest_fd == -1) 88 | { 89 | perror("open destination"); 90 | close(src_fd); 91 | return dest_fd; 92 | } 93 | 94 | int res = ioctl(dest_fd, BTRFS_IOC_CLONE, src_fd); 95 | *save_errno = errno; 96 | 97 | close(src_fd); 98 | close(dest_fd); 99 | 100 | if (res == -1) 101 | { 102 | if (*save_errno != EXDEV) 103 | fprintf(stderr, "ioctl: %s in %s\n", strerror(*save_errno), __func__); 104 | // Clone failed, remove the destination file 105 | if (unlink(dest) != 0) 106 | fprintf(stderr, "unlink: %s in %s\n", strerror(errno), __func__); 107 | return res; 108 | } 109 | 110 | res = unlink(source); 111 | if (res == -1) 112 | { 113 | perror("unlink source"); 114 | return res; 115 | } 116 | 117 | return 0; 118 | #else 119 | (void) source; 120 | (void) dest; 121 | (void) save_errno; 122 | return 0; 123 | #endif 124 | } 125 | -------------------------------------------------------------------------------- /.github/workflows/coverity.yml: -------------------------------------------------------------------------------- 1 | name: "Coverity Scan" 2 | 3 | on: 4 | push: 5 | branches: coverity_scan 6 | # pull_request: 7 | # branches: master 8 | 9 | jobs: 10 | ubuntu: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v5 14 | with: 15 | submodules: false 16 | - name: Install Dependencies 17 | run: sudo -H python3 -m pip install meson ninja 18 | - run: meson setup builddir 19 | - uses: vapier/coverity-scan-action@v1 20 | with: 21 | # Project name in Coverity Scan. 22 | # 23 | # This should be as it appears on the Coverity Scan website. 24 | # Find it in your dashboard: 25 | # https://scan.coverity.com/dashboard 26 | # 27 | # For example, a GitHub project will look like "gentoo/pax-utils". 28 | # 29 | # Default: ${{ github.repository }} 30 | project: 'theimpossibleastronaut/rmw' 31 | 32 | # Secret project token for accessing this project in Coverity Scan. 33 | # 34 | # Find this in the project's "Project Settings" tab under "Project token" on 35 | # the Coverity Scan website. 36 | # 37 | # This value should not be specified in the yaml file directly. Instead it 38 | # should be set in your repositories secrets. "COVERITY_SCAN_TOKEN" is a 39 | # common name here. 40 | # https://docs.github.com/en/actions/security-guides/encrypted-secrets 41 | # 42 | # You still have to write ${{ secrets.COVERITY_SCAN_TOKEN }} explicitly as 43 | # GitHub Actions are not allowed to access secrets directly. 44 | # 45 | # REQUIRED. 46 | token: ${{ secrets.COVERITY_SCAN_TOKEN }} 47 | 48 | # Where Coverity Scan should send notifications. 49 | # 50 | # The Coverity Scan tool requires this be set. 51 | # 52 | # If you don't want to write this in your config files, you can also use a 53 | # repository secret. "COVERITY_SCAN_EMAIL" is a common name. See the 54 | # previous "token" section for more information. 55 | # 56 | # REQUIRED. 57 | email: 'andy400-dev@yahoo.com' 58 | 59 | # Which Coverity Scan language pack to download. 60 | # 61 | # May be "cxx", "java", "csharp", "javascript", or "other". 62 | # 63 | # See the Coverity Scan download page for possible values: 64 | # https://scan.coverity.com/download 65 | # The tab strip along the top lists the languages. 66 | # 67 | # NB: 'cxx' is used for both C & C++ code. 68 | # 69 | # Default: 'cxx' 70 | build_language: 'cxx' 71 | 72 | # Which Coverity Scan platform pack to download. 73 | # 74 | # See the Coverity Scan download page for possible values: 75 | # https://scan.coverity.com/download 76 | # The tab strip along the right side lists the platforms. 77 | # 78 | # Default: 'linux64' 79 | build_platform: 'linux64' 80 | 81 | # Command to pass to cov-build. 82 | # 83 | # Default: 'make' 84 | command: 'ninja -C builddir' 85 | 86 | # (Informational) The source version being built. 87 | # 88 | # Default: ${{ github.sha }} 89 | # version: '' 90 | 91 | # (Informational) A description for this particular build. 92 | # 93 | # Default: coverity-scan-action ${{ github.repository }} / ${{ github.ref }} 94 | # description: '' 95 | -------------------------------------------------------------------------------- /packaging/Slackbuild/rmw/rmw.SlackBuild: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Slackware build script for rmw 4 | # 5 | # Copyright 2019-2025 / Andy Alt / United States 6 | # All rights reserved. 7 | # 8 | # Redistribution and use of this script, with or without modification, is 9 | # permitted provided that the following conditions are met: 10 | # 11 | # 1. Redistributions of this script must retain the above copyright 12 | # notice, this list of conditions and the following disclaimer. 13 | # 14 | # THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED 15 | # WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 16 | # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO 17 | # EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 18 | # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | # OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 23 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | cd $(dirname $0) ; CWD=$(pwd) 26 | 27 | PRGNAM=rmw 28 | VERSION=${VERSION:-0.9.4} 29 | BUILD=${BUILD:-1} 30 | TAG=${TAG:-_SBo} 31 | PKGTYPE=${PKGTYPE:-tgz} 32 | 33 | if [ -z "$ARCH" ]; then 34 | case "$( uname -m )" in 35 | i?86) ARCH=i586 ;; 36 | arm*) ARCH=arm ;; 37 | *) ARCH=$( uname -m ) ;; 38 | esac 39 | fi 40 | 41 | # If the variable PRINT_PACKAGE_NAME is set, then this script will report what 42 | # the name of the created package would be, and then exit. This information 43 | # could be useful to other scripts. 44 | if [ ! -z "${PRINT_PACKAGE_NAME}" ]; then 45 | echo "$PRGNAM-$VERSION-$ARCH-$BUILD$TAG.$PKGTYPE" 46 | exit 0 47 | fi 48 | 49 | TMP=${TMP:-/tmp/SBo} 50 | PKG=$TMP/package-$PRGNAM 51 | OUTPUT=${OUTPUT:-/tmp} 52 | 53 | if [ "$ARCH" = "i586" ]; then 54 | SLKCFLAGS="-O2 -march=i586 -mtune=i686" 55 | elif [ "$ARCH" = "i686" ]; then 56 | SLKCFLAGS="-O2 -march=i686 -mtune=i686" 57 | elif [ "$ARCH" = "x86_64" ]; then 58 | SLKCFLAGS="-O2 -fPIC" 59 | else 60 | SLKCFLAGS="-O2" 61 | fi 62 | 63 | set -e 64 | 65 | rm -rf $PKG 66 | mkdir -p $TMP $PKG $OUTPUT 67 | cd $TMP 68 | rm -rf $PRGNAM-$VERSION 69 | tar xvf $CWD/$PRGNAM-$VERSION.tar.xz 70 | cd $PRGNAM-$VERSION 71 | chown -R root:root . 72 | find -L . \ 73 | \( -perm 777 -o -perm 775 -o -perm 750 -o -perm 711 -o -perm 555 \ 74 | -o -perm 511 \) -exec chmod 755 {} \; -o \ 75 | \( -perm 666 -o -perm 664 -o -perm 640 -o -perm 600 -o -perm 444 \ 76 | -o -perm 440 -o -perm 400 \) -exec chmod 644 {} \; 77 | 78 | mkdir build 79 | cd build 80 | CFLAGS="$SLKCFLAGS" \ 81 | CXXFLAGS="$SLKCFLAGS" \ 82 | meson .. \ 83 | --buildtype=release \ 84 | -Dstrip=true \ 85 | -Db_sanitize=none \ 86 | -Db_lto=true \ 87 | --mandir=/usr/man \ 88 | --prefix=/usr \ 89 | --localedir=/usr/share/locale \ 90 | -Ddocdir=/usr/doc/$PRGNAM-$VERSION 91 | ninja 92 | DESTDIR=$PKG ninja install 93 | cd .. 94 | 95 | find $PKG/usr/man -type f -exec gzip -9 {} \; 96 | for i in $( find $PKG/usr/man -type l ) ; do ln -s $( readlink $i ).gz $i.gz ; rm $i ; done 97 | 98 | cat $CWD/$PRGNAM.SlackBuild > $PKG/usr/doc/$PRGNAM-$VERSION/$PRGNAM.SlackBuild 99 | 100 | mkdir -p $PKG/install 101 | cat $CWD/slack-desc > $PKG/install/slack-desc 102 | cat $CWD/doinst.sh > $PKG/install/doinst.sh 103 | 104 | cd $PKG 105 | /sbin/makepkg -l y -c n $OUTPUT/$PRGNAM-$VERSION-$ARCH-$BUILD$TAG.$PKGTYPE 106 | -------------------------------------------------------------------------------- /src/trashinfo.h: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of rmw 3 | 4 | Copyright (C) 2012-2024 Andy Alt (arch_stanton5995@proton.me) 5 | Other authors: https://github.com/theimpossibleastronaut/rmw/blob/master/AUTHORS.md 6 | 7 | This program is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | This program is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with this program. If not, see . 19 | */ 20 | 21 | #pragma once 22 | 23 | #include "globals.h" 24 | #include 25 | 26 | #include "time_rmw.h" 27 | 28 | /** Each waste directory is added to a linked list and has the data 29 | * from this structure associated with it. 30 | */ 31 | typedef struct st_waste st_waste; 32 | struct st_waste 33 | { 34 | /** The parent directory, e.g. $HOME/.local/share/Trash */ 35 | char *parent; 36 | 37 | /** The info directory (where .trashinfo files are written) will be appended to the parent directory */ 38 | char *info; 39 | 40 | /** Appended to the parent directory, where files are moved to when they are rmw'ed */ 41 | char *files; 42 | 43 | /** If a waste folder is at the top level, a relative path will be 44 | * used (per the Freedesktop trash spec). See 45 | * https://github.com/theimpossibleastronaut/rmw/issues/299 for more 46 | * info */ 47 | char *media_root; 48 | 49 | /** Points to the previous WASTE directory in the linked list */ 50 | st_waste *prev_node; 51 | 52 | /** Points to the next WASTE directory in the linked list */ 53 | st_waste *next_node; 54 | 55 | /** The device number of the filesystem on which the file resides. rmw does 56 | * not copy files from one filesystem to another, but rather only moves them. 57 | * They must reside on the same filesystem as a WASTE folder specified in 58 | * the configuration file. 59 | */ 60 | dev_t dev_num; 61 | 62 | int len_info; 63 | int len_files; 64 | 65 | /** Set to true if the parent directory is on a removable device, 66 | * false otherwise. 67 | */ 68 | bool removable; 69 | 70 | bool is_btrfs; 71 | }; 72 | 73 | 74 | /** Holds information about a file that was specified for rmw'ing 75 | */ 76 | typedef struct 77 | { 78 | /** The absolute path to the file, stored later in a .trashinfo file */ 79 | char *real_path; 80 | 81 | /** The basename of the target file, and used for the basename of its corresponding 82 | * .trashinfo file */ 83 | const char *base_name; 84 | 85 | /** The device number of the filesystem on which the file resides */ 86 | dev_t dev_num; 87 | 88 | /** The destination file name. This may be different if a file of the same name already 89 | * exists in the WASTE folder */ 90 | char waste_dest_name[PATH_MAX]; 91 | 92 | /** Is true if the file exists in the destination WASTE/files folder, 93 | * false otherwise. If it's a duplicate, a string based on the current time 94 | * will be appended to \ref dest_name 95 | */ 96 | bool is_duplicate; 97 | } rmw_target; 98 | 99 | extern const struct trashinfo_template 100 | { 101 | const char *header; 102 | const char *path_key; 103 | const char *deletion_date_key; 104 | } trashinfo_template; 105 | 106 | 107 | int 108 | create_trashinfo(rmw_target * st_f_props, st_waste * waste_curr, 109 | st_time * st_time_var); 110 | 111 | char *validate_and_get_value(const char *file, ti_key key); 112 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | concurrency: 3 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} 4 | cancel-in-progress: true 5 | 6 | on: 7 | workflow_dispatch: 8 | pull_request: 9 | branches: master 10 | paths: 11 | - '**release.yml' 12 | 13 | jobs: 14 | dist: 15 | runs-on: ubuntu-22.04 16 | outputs: 17 | rmw_version: ${{ steps.get_version.outputs.version }} 18 | steps: 19 | - uses: actions/checkout@v5 20 | with: 21 | submodules: false 22 | - id: get_version 23 | run: | 24 | VERSION=$(grep "version:" meson.build | awk -F"'" '{print $2}') 25 | echo "version=$VERSION" >> $GITHUB_OUTPUT 26 | echo "VERSION=$VERSION" >> $GITHUB_ENV 27 | - name: Install dependencies 28 | run: | 29 | sudo apt install -y \ 30 | meson \ 31 | ninja-build \ 32 | xz-utils 33 | - name: 34 | run: | 35 | meson setup builddir 36 | cd builddir 37 | meson dist --include-subprojects 38 | cd meson-dist 39 | REL_TAR="rmw-$VERSION.tar" 40 | xz -d "$REL_TAR.xz" 41 | tar --delete -f "$REL_TAR" "rmw-$VERSION/packaging" 42 | xz "$REL_TAR" 43 | sha256sum "$REL_TAR.xz" > "$REL_TAR.xz.sha256sum" 44 | - name: Upload Artifacts 45 | uses: actions/upload-artifact@v5 46 | with: 47 | name: rmw_dist_archive 48 | path: ${{ github.workspace }}/builddir/meson-dist/* 49 | 50 | build-appimage: 51 | runs-on: ubuntu-latest 52 | needs: dist 53 | strategy: 54 | matrix: 55 | platform: 56 | - linux/amd64 57 | - linux/arm64 58 | 59 | steps: 60 | - name: Checkout 61 | uses: actions/checkout@v5 62 | with: 63 | submodules: false 64 | 65 | - if: ${{ ! contains(matrix.platform, 'amd64') }} 66 | uses: docker/setup-qemu-action@v3 67 | 68 | - name: Set variables 69 | run: | 70 | VERSION=${{ needs.dist.outputs.rmw_version }} 71 | echo "VERSION=$VERSION" >> $GITHUB_ENV 72 | 73 | - name: Build AppImage 74 | run: | 75 | docker run -t \ 76 | --rm \ 77 | --platform=${{ matrix.platform }} \ 78 | -e HOSTUID=$(id -u) \ 79 | -e VERSION \ 80 | -v $GITHUB_WORKSPACE:/workspace \ 81 | -w /workspace \ 82 | andy5995/linuxdeploy:v3-jammy packaging/appimage/pre-appimage.sh 83 | 84 | - name: Create sha256sum 85 | run: | 86 | IMAGE_FILENAME=$(basename `find out/*AppImage`) 87 | echo "IMAGE_FILENAME=$IMAGE_FILENAME" >> $GITHUB_ENV 88 | cd out 89 | sha256sum "$IMAGE_FILENAME" > "$IMAGE_FILENAME.sha256sum" 90 | 91 | - name: Upload AppImage 92 | uses: actions/upload-artifact@v5 93 | with: 94 | name: ${{ env.IMAGE_FILENAME }} 95 | path: ./out/* 96 | if-no-files-found: error 97 | 98 | build-deb: 99 | strategy: 100 | matrix: 101 | platform: 102 | - amd64 103 | - arm64 104 | - 386 105 | 106 | runs-on: ubuntu-22.04 107 | steps: 108 | - uses: actions/checkout@v5 109 | 110 | - name: Copy debian directory 111 | run: cp -a packaging/debian . 112 | 113 | - uses: andy5995/gh-action-build-deb@v1 114 | with: 115 | args: | 116 | --no-sign 117 | --compression=xz 118 | platform: ${{ matrix.platform }} 119 | 120 | - name: Create sha256sum 121 | run: | 122 | DEB_FILENAME=$(basename `find output/*deb`) 123 | echo "DEB_FILENAME=$DEB_FILENAME" >> $GITHUB_ENV 124 | cd output 125 | sha256sum "$DEB_FILENAME" > "../$DEB_FILENAME.sha256sum" 126 | 127 | - name: Upload Artifacts 128 | uses: actions/upload-artifact@v5 129 | with: 130 | name: ${{ env.DEB_FILENAME }} 131 | path: | 132 | output/*.deb 133 | *deb.sha256sum 134 | if-no-files-found: error 135 | -------------------------------------------------------------------------------- /src/bsdutils/strmode.c: -------------------------------------------------------------------------------- 1 | /*- 2 | * SPDX-License-Identifier: BSD-3-Clause 3 | * 4 | * Copyright (c) 1990, 1993 5 | * The Regents of the University of California. All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions 9 | * are met: 10 | * 1. Redistributions of source code must retain the above copyright 11 | * notice, this list of conditions and the following disclaimer. 12 | * 2. Redistributions in binary form must reproduce the above copyright 13 | * notice, this list of conditions and the following disclaimer in the 14 | * documentation and/or other materials provided with the distribution. 15 | * 3. Neither the name of the University nor the names of its contributors 16 | * may be used to endorse or promote products derived from this software 17 | * without specific prior written permission. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 20 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 | * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 23 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 25 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 26 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 27 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 28 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 29 | * SUCH DAMAGE. 30 | */ 31 | 32 | #if defined(LIBC_SCCS) && !defined(lint) 33 | static char sccsid[] = "@(#)strmode.c 8.3 (Berkeley) 8/15/94"; 34 | #endif /* LIBC_SCCS and not lint */ 35 | 36 | #include 37 | #include 38 | #include 39 | 40 | void 41 | strmode(/* mode_t */ int mode, char *p) 42 | { 43 | /* print type */ 44 | switch (mode & S_IFMT) { 45 | case S_IFDIR: /* directory */ 46 | *p++ = 'd'; 47 | break; 48 | case S_IFCHR: /* character special */ 49 | *p++ = 'c'; 50 | break; 51 | case S_IFBLK: /* block special */ 52 | *p++ = 'b'; 53 | break; 54 | case S_IFREG: /* regular */ 55 | *p++ = '-'; 56 | break; 57 | case S_IFLNK: /* symbolic link */ 58 | *p++ = 'l'; 59 | break; 60 | case S_IFSOCK: /* socket */ 61 | *p++ = 's'; 62 | break; 63 | #ifdef S_IFIFO 64 | case S_IFIFO: /* fifo */ 65 | *p++ = 'p'; 66 | break; 67 | #endif 68 | #ifdef S_IFWHT 69 | case S_IFWHT: /* whiteout */ 70 | *p++ = 'w'; 71 | break; 72 | #endif 73 | default: /* unknown */ 74 | *p++ = '?'; 75 | break; 76 | } 77 | /* usr */ 78 | if (mode & S_IRUSR) 79 | *p++ = 'r'; 80 | else 81 | *p++ = '-'; 82 | if (mode & S_IWUSR) 83 | *p++ = 'w'; 84 | else 85 | *p++ = '-'; 86 | switch (mode & (S_IXUSR | S_ISUID)) { 87 | case 0: 88 | *p++ = '-'; 89 | break; 90 | case S_IXUSR: 91 | *p++ = 'x'; 92 | break; 93 | case S_ISUID: 94 | *p++ = 'S'; 95 | break; 96 | case S_IXUSR | S_ISUID: 97 | *p++ = 's'; 98 | break; 99 | } 100 | /* group */ 101 | if (mode & S_IRGRP) 102 | *p++ = 'r'; 103 | else 104 | *p++ = '-'; 105 | if (mode & S_IWGRP) 106 | *p++ = 'w'; 107 | else 108 | *p++ = '-'; 109 | switch (mode & (S_IXGRP | S_ISGID)) { 110 | case 0: 111 | *p++ = '-'; 112 | break; 113 | case S_IXGRP: 114 | *p++ = 'x'; 115 | break; 116 | case S_ISGID: 117 | *p++ = 'S'; 118 | break; 119 | case S_IXGRP | S_ISGID: 120 | *p++ = 's'; 121 | break; 122 | } 123 | /* other */ 124 | if (mode & S_IROTH) 125 | *p++ = 'r'; 126 | else 127 | *p++ = '-'; 128 | if (mode & S_IWOTH) 129 | *p++ = 'w'; 130 | else 131 | *p++ = '-'; 132 | switch (mode & (S_IXOTH | S_ISVTX)) { 133 | case 0: 134 | *p++ = '-'; 135 | break; 136 | case S_IXOTH: 137 | *p++ = 'x'; 138 | break; 139 | case S_ISVTX: 140 | *p++ = 'T'; 141 | break; 142 | case S_IXOTH | S_ISVTX: 143 | *p++ = 't'; 144 | break; 145 | } 146 | *p++ = ' '; 147 | *p = '\0'; 148 | } 149 | -------------------------------------------------------------------------------- /test/test_basic.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -ve 3 | 4 | if [ -e COMMON ]; then 5 | . ./COMMON 6 | else 7 | . "${MESON_SOURCE_ROOT}/test/COMMON" 8 | fi 9 | 10 | echo "== On first run, directories should get created" 11 | $RMW_TEST_CMD_STRING 12 | 13 | echo "$SEPARATOR" 14 | echo "List the waste folders..." 15 | echo "rmw should display folders on removable devices that are not mounted" 16 | echo "$SEPARATOR" 17 | output="$($RMW_TEST_CMD_STRING -l)" 18 | echo "${output}" 19 | test "${output}" = "/mnt/fs/Trash-$(id -u) 20 | /mnt/sda10000/example_waste 21 | "${MESON_BUILD_ROOT}"/test/rmw-tests-home/test_basic.sh_dir/.Waste 22 | "${MESON_BUILD_ROOT}"/test/rmw-tests-home/test_basic.sh_dir/.local/share/Waste-2 23 | "${MESON_BUILD_ROOT}"/test/rmw-tests-home/test_basic.sh_dir/.local/share/Waste-3" 24 | 25 | echo "$SEPARATOR" 26 | 27 | cd "${RMW_FAKE_HOME}" 28 | # Make some temporary files 29 | mkdir tmp-files 30 | cd "${RMW_FAKE_HOME}"/tmp-files 31 | 32 | echo "\n\n == creating temporary files to be deleted" 33 | for file in 1 2 3; do 34 | touch $file 35 | done 36 | cd "${RMW_FAKE_HOME}"/.. 37 | 38 | echo "\n\n == rmw should be able to operate on multiple files\n" 39 | $RMW_TEST_CMD_STRING --verbose "${RMW_FAKE_HOME}"/tmp-files/* 40 | 41 | test -f "${PRIMARY_WASTE_DIR}/files/1" 42 | test -f "${PRIMARY_WASTE_DIR}/files/2" 43 | test -f "${PRIMARY_WASTE_DIR}/files/3" 44 | test -f "${PRIMARY_WASTE_DIR}/info/1.trashinfo" 45 | test -f "${PRIMARY_WASTE_DIR}/info/2.trashinfo" 46 | test -f "${PRIMARY_WASTE_DIR}/info/3.trashinfo" 47 | 48 | echo "$SEPARATOR" 49 | echo "rmw should append a time_string to duplicate files..." 50 | cd "${RMW_FAKE_HOME}"/tmp-files 51 | for file in 1 2 3; do 52 | touch $file 53 | done 54 | $RMW_TEST_CMD_STRING 1 2 3 55 | 56 | echo "\n\n == Show contents of the files and info directories" 57 | 58 | test -n "$(ls -A "$PRIMARY_WASTE_DIR"/files)" 59 | test -n "$(ls -A "$PRIMARY_WASTE_DIR"/info)" 60 | 61 | output="$(ls -A "$PRIMARY_WASTE_DIR"/files | wc -l | sed 's/ //g')" 62 | test "$output" = "6" 63 | 64 | echo "$SEPARATOR" 65 | echo " == rmw should refuse to move a waste folder or a file that resides within a waste folder" 66 | output="$($RMW_TEST_CMD_STRING "$PRIMARY_WASTE_DIR"/info || true)" 67 | test "${output}" = " :warning: "$PRIMARY_WASTE_DIR"/info resides within a waste folder and has been ignored 68 | 0 items were removed to the waste folder" 69 | 70 | # If the file gets removed (which it shouldn't), then the test that follows it will fail 71 | $RMW_TEST_CMD_STRING "$PRIMARY_WASTE_DIR"/info/1.trashinfo || true 72 | 73 | echo "\n\n == Display contents of 1.trashinfo " 74 | cat "$PRIMARY_WASTE_DIR"/info/1.trashinfo 75 | 76 | echo "$SEPARATOR" 77 | echo " == Test -m option" 78 | output=$($RMW_TEST_CMD_STRING --verbose -m) 79 | cmp_substr "$output" ".Waste/files/1_" 80 | cmp_substr "$output" ".Waste/files/2_" 81 | cmp_substr "$output" ".Waste/files/3_" 82 | 83 | echo "$SEPARATOR" 84 | echo " == test undo/restore feature" 85 | 86 | $RMW_TEST_CMD_STRING --verbose -u 87 | $RMW_TEST_CMD_STRING --verbose -z "$PRIMARY_WASTE_DIR"/files/1 88 | $RMW_TEST_CMD_STRING --verbose -z "$PRIMARY_WASTE_DIR"/files/2 89 | $RMW_TEST_CMD_STRING --verbose -z "$PRIMARY_WASTE_DIR"/files/3 90 | 91 | echo "\n\n == test that the files are restored to their previous locations" 92 | 93 | test -f "${RMW_FAKE_HOME}"/tmp-files/1 94 | test -f "${RMW_FAKE_HOME}"/tmp-files/2 95 | test -f "${RMW_FAKE_HOME}"/tmp-files/3 96 | 97 | echo "\n\n == test that the .trashinfo files have been removed" 98 | 99 | test ! -f "$PRIMARY_WASTE_DIR"/info/1.trashinfo 100 | test ! -f "$PRIMARY_WASTE_DIR"/info/2.trashinfo 101 | test ! -f "$PRIMARY_WASTE_DIR"/info/3.trashinfo 102 | 103 | # Ignore dot directories 104 | mkdir tmpdot 105 | touch "tmpdot/.hello world" 106 | touch "tmpdot/.hello earth" 107 | 108 | output="$($RMW_TEST_CMD_STRING tmpdot/.*)" 109 | cmp_substr "$output" "2 items were removed to the waste folder" 110 | 111 | expected="0 orphans found" 112 | output="$($RMW_TEST_CMD_STRING -o)" 113 | test "${output}" = "${expected}" 114 | 115 | cmp_substr "$($RMW_ALT_TEST_CMD_STRING -l)" \ 116 | "invalid option" 117 | 118 | cmp_substr "$($RMW_TEST_CMD_STRING '')" \ 119 | "skipping" 120 | 121 | output=$($RMW_TEST_CMD_STRING ${RMW_FAKE_HOME}) 122 | cmp_substr "$output" "Skipping" 123 | 124 | echo "Basic tests passed" 125 | exit 0 126 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rmw-0.9.4 2 | ## Description 3 | 4 | rmw (ReMove to Waste) is a trashcan/recycle bin utility for the command line. 5 | It can move and restore files to and from directories specified in a 6 | configuration file, and can also be integrated with your regular desktop trash 7 | folder (if your desktop environment uses the FreeDesktop.org Trash 8 | specification). One of the unique features of rmw is the ability to purge 9 | items from your waste (or trash) directories after x number of days. 10 | 11 | Web site: 12 | 13 | [![codeql-badge]][codeql-url] 14 | [![c-cpp-badge]][c-cpp-url] 15 | 16 | [c-cpp-badge]: https://github.com/theimpossibleastronaut/rmw/actions/workflows/c-cpp.yml/badge.svg 17 | [c-cpp-url]: https://github.com/theimpossibleastronaut/rmw/actions/workflows/c-cpp.yml 18 | [codeql-badge]: https://github.com/theimpossibleastronaut/rmw/workflows/CodeQL/badge.svg 19 | [codeql-url]: https://github.com/theimpossibleastronaut/rmw/actions?query=workflow%3ACodeQL 20 | 21 | rmw is for people who sometimes use rm or rmdir at the command line and 22 | would occasionally like an alternative choice. It's not intended or 23 | designed to act as a replacement for rm, as it's more closely related 24 | to how the [FreeDesktop.org trash 25 | system](https://specifications.freedesktop.org/trash-spec/trashspec-latest.html) 26 | functions. 27 | 28 | ## Features and Usage 29 | 30 | See the [manual on the 31 | website](https://theimpossibleastronaut.github.io/rmw-website/rmw_man.html) 32 | 33 | ## Screenshots 34 | 35 | See the [Screenshots](https://theimpossibleastronaut.github.io/rmw-website/screenshots.html) 36 | page on the website. 37 | 38 | ## Contact / Support 39 | 40 | * [Bug Reports](https://github.com/theimpossibleastronaut/rmw/blob/master/CONTRIBUTING.md#bug-reports) 41 | * [General Help, Support, Discussion](https://theimpossibleastronaut.github.io/rmw-website/#support) 42 | 43 | ## Installation 44 | 45 | rmw is available in the [homebrew and 46 | linuxbrew](https://github.com/Homebrew/) repositories; or there may may 47 | be a binary package available for your OS. You can view a list at 48 | [Repology](https://repology.org/project/rmw/versions) to see in which 49 | repositories rmw is included. Since v0.7.09, x86_64 AppImages are 50 | available. 51 | 52 | AppImages and maintainer-created amd64 Debian packages are available in 53 | the [releases section][releases-url]. 54 | 55 | [releases-url]: https://github.com/theimpossibleastronaut/rmw/releases 56 | 57 | ## Installing from source 58 | 59 | ### Dependencies 60 | 61 | * libncursesw (ncurses-devel on some systems, such as CentOS) 62 | * gettext (or use '-Dnls=false' when configuring with meson if you only need English language support) 63 | 64 | If you're building from source, you will need the libncursesw(5 or 65 | 6)-dev package from your operating system distribution. On some systems 66 | just the ncurses packages is needed, and it's often already installed. 67 | 68 | #### Linux only 69 | 70 | * linux-headers 71 | 72 | `linux/btrfs.h` from the headers package is required for btrfs clone support. 73 | `meson setup` will fail if the header is missing. To bypass the check and 74 | exclude support for for btrfs (which is not needed for btrfs normally, but 75 | needed if you want to use `rmw` for rmw'ing files across btrfs root and 76 | subvolumes), add 77 | 78 | -Dwant_btrfs_clone=false 79 | 80 | to the meson setup options. 81 | 82 | ### Compiling 83 | 84 | #### As a normal user: 85 | 86 | (This examples places the generated files to a separate folder, but you can 87 | run 'configure' from any directory you like.) 88 | 89 | meson setup builddir 90 | cd builddir 91 | ninja 92 | 93 | Use `meson configure` in the build dir to view or change available 94 | options. 95 | 96 | #### Installing without superuser privileges 97 | 98 | If you would like to install rmw without superuser privileges, use a prefix 99 | that you have write access to. Example: 100 | 101 | meson setup -Dprefix=$HOME/.local builddir 102 | 103 | or while in the build dir 104 | 105 | meson configure -Dprefix=$HOME/.local 106 | 107 | To install: 108 | 109 | meson install 110 | 111 | In the example above, the rmw binary will be installed to 112 | `$HOME/.local/bin` and documentation to `$HOME/.local/doc`. 113 | 114 | ### Uninstall 115 | 116 | ninja uninstall (uninstalls the program if installed with 'ninja install`) 117 | 118 | ### Docker 119 | 120 | see the [Docker README](https://github.com/theimpossibleastronaut/rmw/tree/master/docker) 121 | -------------------------------------------------------------------------------- /test/test_restore.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # restore.sh: tests rmw's restore features 3 | # 4 | # This script can be used to demonstrate the restore features of rmw to 5 | # new users, and to test for bugs when making code changes 6 | # 7 | set -ve 8 | 9 | if [ -e COMMON ]; then 10 | . ./COMMON 11 | else 12 | . "${MESON_SOURCE_ROOT}/test/COMMON" 13 | fi 14 | 15 | echo "$SEPARATOR" 16 | echo "Initialize" 17 | $RMW_TEST_CMD_STRING 18 | 19 | echo "$SEPARATOR" 20 | echo "If the mrl file doesn't exist yet..." 21 | cmp_substr "$($RMW_TEST_CMD_STRING -u)" \ 22 | "no items in the list" 23 | 24 | echo "$SEPARATOR" 25 | echo " Creating some files for testing..." 26 | cd "${RMW_FAKE_HOME}" 27 | create_some_files 28 | 29 | echo "$SEPARATOR" 30 | echo "Try to restore files that aren't in a Waste/files folder" 31 | cmp_substr "$($RMW_TEST_CMD_STRING -z "${RMW_FAKE_HOME}"/somefiles/* 2>&1 && exit 1)" \ 32 | "not in a Waste directory" 33 | 34 | echo "$SEPARATOR" 35 | echo "ReMove files and then restore them by using -u" 36 | $RMW_TEST_CMD_STRING "${RMW_FAKE_HOME}"/somefiles/* 37 | echo "$SEPARATOR" 38 | output=$($RMW_TEST_CMD_STRING -uvv | grep Waste) 39 | echo "$SEPARATOR" 40 | echo "OUTPUT:" 41 | echo "---" 42 | echo "$output" 43 | echo "---" 44 | test "$output" = "+'"${RMW_FAKE_HOME}"/.Waste/files/read_only_file' -> '"${RMW_FAKE_HOME}"/somefiles/read_only_file' 45 | -"${RMW_FAKE_HOME}"/.Waste/info/read_only_file.trashinfo 46 | +'"${RMW_FAKE_HOME}"/.Waste/files/topdir' -> '"${RMW_FAKE_HOME}"/somefiles/topdir' 47 | -"${RMW_FAKE_HOME}"/.Waste/info/topdir.trashinfo" 48 | 49 | echo "$SEPARATOR" 50 | echo "Show result when no undo file exists..." 51 | output=$(${RMW_TEST_CMD_STRING} -u || true) 52 | test "${output}" = "There are no items in the list - please check back later." 53 | 54 | echo "$SEPARATOR" 55 | echo "restore using wildcard pattern, but be in the trash directory" 56 | $RMW_TEST_CMD_STRING "${RMW_FAKE_HOME}"/somefiles/topdir -v 57 | cd "$PRIMARY_WASTE_DIR"/files 58 | $RMW_TEST_CMD_STRING -z topd* 59 | test -e "${RMW_FAKE_HOME}"/somefiles/topdir 60 | 61 | echo "$SEPARATOR" 62 | echo "Try restoring a file that doesn't exist" 63 | $RMW_TEST_CMD_STRING -z nonexistent_fil* && exit 1 64 | 65 | # This test is inaccurate when run with superuser privileges. 66 | 67 | #echo "$SEPARATOR" 68 | #echo "What happens when trying to ReMove file inside dir with no write permissions..." 69 | #mkdir "${RMW_FAKE_HOME}"/no_write_perms 70 | #touch "${RMW_FAKE_HOME}"/no_write_perms/test.tmp 71 | #chmod -R -w "${RMW_FAKE_HOME}"/no_write_perms 72 | #$RMW_TEST_CMD_STRING "${RMW_FAKE_HOME}"/no_write_perms 73 | #test_result_want_fail $? 74 | 75 | echo "$SEPARATOR" 76 | echo "Symlinks" 77 | ln -s ${RMW_FAKE_HOME} "${RMW_FAKE_HOME}"/link_test 78 | # broken link 79 | ln -s broken_symlink_test "${RMW_FAKE_HOME}"/link_test2 80 | $RMW_TEST_CMD_STRING "${RMW_FAKE_HOME}"/link_test "${RMW_FAKE_HOME}"/link_test2 81 | test -h "${PRIMARY_WASTE_DIR}/files/link_test" 82 | 83 | ${RMW_TEST_CMD_STRING} -u 84 | test -h "${RMW_FAKE_HOME}"/link_test2 85 | 86 | # test using relative path and dotfiles 87 | cd "${RMW_FAKE_HOME}" 88 | mkdir tmpfoo 89 | for t in "foo" "bar" ".boo" ".far"; do 90 | touch tmpfoo/$t 91 | ${RMW_TEST_CMD_STRING} tmpfoo/$t 92 | cat ${PRIMARY_WASTE_DIR}/info/$t.trashinfo 93 | cd ${PRIMARY_WASTE_DIR} 94 | ${RMW_TEST_CMD_STRING} -z files/$t 95 | cd "${RMW_FAKE_HOME}" 96 | test -f tmpfoo/$t 97 | done 98 | 99 | # Now try dotfiles with wildcards 100 | for t in ".boo" ".far"; do 101 | touch tmpfoo/$t 102 | ${RMW_TEST_CMD_STRING} tmpfoo/.* 103 | ${RMW_TEST_CMD_STRING} -z ${PRIMARY_WASTE_DIR}/files/$t 104 | test -f tmpfoo/$t 105 | done 106 | 107 | # a dot dir 108 | cmp_substr "$(${RMW_TEST_CMD_STRING} -z ${PRIMARY_WASTE_DIR}/files/. && exit 1)" \ 109 | "refusing to process" 110 | 111 | # I don't want to force anyone to install Xvfb for this single test 112 | # so I'll only run it if it's already installed 113 | if [ -n "$(command -v Xvfb)" ] && [ ! $(grep "DISABLE_CURSES" "$MESON_BUILD_ROOT/src/config.h") ]; then 114 | # Start Xvfb on display :99 115 | Xvfb :99 & 116 | XVFB_PID=$! 117 | 118 | # Save the current DISPLAY value and set it to use the virtual display 119 | OLD_DISPLAY="$DISPLAY" 120 | export DISPLAY=:99 121 | 122 | # This may be needed to prevent a failure on OpenBSD: 123 | # Error opening terminal: unknown. 124 | export TERM=xterm 125 | 126 | cd "$RMW_FAKE_HOME" 127 | for file in "foo(1)far" "far side" "clippity-clap"; do 128 | touch "$file" 129 | ${RMW_TEST_CMD_STRING} "$file" 130 | done 131 | # No visual test here, but when used with llvm sanitize or valgrind, 132 | # the chances of spotting any memory leaks are pretty good. 133 | # https://github.com/theimpossibleastronaut/rmw/issues/464 134 | # rmw -s in some cases, when built using _FORTIFY=3, results in an immediate crash 135 | ${RMW_TEST_CMD_STRING} -s & 136 | RMW_PID=$! 137 | sleep 1 && kill $RMW_PID # OpenBSD sleep doesn't accept '1s' 138 | kill $XVFB_PID 139 | 140 | # Restore the original DISPLAY value if it was set 141 | if [ -n "$OLD_DISPLAY" ]; then 142 | export DISPLAY="$OLD_DISPLAY" 143 | else 144 | unset DISPLAY 145 | fi 146 | fi 147 | 148 | exit 0 149 | -------------------------------------------------------------------------------- /src/strings_rmw.c: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of rmw 3 | 4 | Copyright (C) 2012-2021 Andy Alt (arch_stanton5995@proton.me) 5 | Other authors: https://github.com/theimpossibleastronaut/rmw/blob/master/AUTHORS.md 6 | 7 | This program is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | This program is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with this program. If not, see . 19 | */ 20 | 21 | #ifndef INC_GLOBALS_H 22 | #define INC_GLOBALS_H 23 | #include "globals.h" 24 | #endif 25 | 26 | #include 27 | #include "messages.h" 28 | #include "utils.h" 29 | 30 | 31 | bool r_buffer_overrun = false; 32 | /*! 33 | * Verify that len doesn't exceed boundary, otherwise exit with an error code. 34 | * Usually used before concatenating 2 or more strings. Program will exit 35 | * with an error code if len exceeds boundary. len should already have space 36 | * for the null terminator when this function is called. 37 | */ 38 | void 39 | bufchk_len(const size_t len, const size_t dest_boundary, const char *func, 40 | const int line) 41 | { 42 | if (len <= dest_boundary) 43 | return; 44 | 45 | msg_err_buffer_overrun(func, line); 46 | #ifndef TEST_LIB 47 | exit(EBUF); 48 | #endif 49 | r_buffer_overrun = true; 50 | return; 51 | } 52 | 53 | void 54 | real_sn_check(const ssize_t len, const ssize_t dest_boundary, 55 | const char *func, const int line) 56 | { 57 | if (len < dest_boundary && len != -1) 58 | return; 59 | 60 | msg_err_buffer_overrun(func, line); 61 | 62 | #ifndef TEST_LIB 63 | exit(EBUF); 64 | #endif 65 | r_buffer_overrun = true; 66 | return; 67 | } 68 | 69 | 70 | /*! 71 | * Removes trailing white space from a string (including newlines, formfeeds, 72 | * tabs, etc 73 | * @param[out] str The string to be altered 74 | * @return void 75 | */ 76 | void 77 | trim_whitespace(char *str) 78 | { 79 | if (str == NULL) 80 | { 81 | #ifndef TEST_LIB 82 | print_msg_error(); 83 | fprintf(stderr, "%s received a NULL", __func__); 84 | exit(EXIT_FAILURE); 85 | #else 86 | errno = 1; 87 | return; 88 | #endif 89 | } 90 | 91 | char *pos_0 = str; 92 | /* Advance pointer until NULL terminator is found */ 93 | while (*str != '\0') 94 | str++; 95 | 96 | /* set pointer to segment preceding NULL terminator */ 97 | if (str != pos_0) 98 | str--; 99 | else 100 | return; 101 | 102 | while (isspace(*str)) 103 | { 104 | *str = '\0'; 105 | if (str != pos_0) 106 | str--; 107 | else 108 | break; 109 | } 110 | 111 | return; 112 | } 113 | 114 | 115 | /** 116 | * Add a null terminator to chop off part of a string 117 | * at a given position 118 | * @param[in] pos The position at which to add the \0 character 119 | * @param[out] str The string to change 120 | * @return void 121 | */ 122 | void 123 | truncate_str(char *str, const size_t pos) 124 | { 125 | str[strlen(str) - pos] = '\0'; 126 | } 127 | 128 | /////////////////////////////////////////////////////////////////////// 129 | #ifdef TEST_LIB 130 | 131 | #include "test.h" 132 | 133 | #define BUF_SIZE 80 134 | 135 | 136 | static void 137 | test_bufchk_len(void) 138 | { 139 | r_buffer_overrun = false; 140 | bufchk_len(12, 12, __func__, __LINE__); 141 | assert(!r_buffer_overrun); 142 | 143 | bufchk_len(12, 11, __func__, __LINE__); 144 | assert(r_buffer_overrun); 145 | r_buffer_overrun = false; 146 | return; 147 | } 148 | 149 | static void 150 | test_sn_check(void) 151 | { 152 | r_buffer_overrun = false; 153 | sn_check(24, 24); 154 | assert(r_buffer_overrun); 155 | r_buffer_overrun = false; 156 | 157 | sn_check(33, 34); 158 | assert(!r_buffer_overrun); 159 | 160 | char helpme[] = "what a very long and boring string"; 161 | char no[PATH_MAX]; 162 | 163 | sn_check(snprintf(no, sizeof no, "%s", helpme), sizeof no); 164 | assert(!r_buffer_overrun); 165 | return; 166 | } 167 | 168 | static void 169 | test_trim_whitespace() 170 | { 171 | // handle strings that are NULL 172 | errno = 0; 173 | char *test = NULL; 174 | trim_whitespace(test); 175 | assert(errno == 1); 176 | errno = 0; 177 | 178 | if (!(test = calloc(1, BUF_SIZE + 1))) 179 | fatal_malloc(); 180 | 181 | // handle strings that are empty 182 | test[0] = '\0'; 183 | trim_whitespace(test); 184 | assert(strcmp(test, "") == 0); 185 | 186 | strcpy(test, " \n\t\v\f\r "); 187 | trim_whitespace(test); 188 | assert(!strcmp(test, "")); 189 | 190 | /* fails if \b is present */ 191 | strcpy(test, "Hello World\n\t\v\f\r "); 192 | trim_whitespace(test); 193 | printf("'%s'\n", test); 194 | assert(!strcmp(test, "Hello World")); 195 | 196 | strcpy(test, "Hello World\n\t\v stop\f\r "); 197 | trim_whitespace(test); 198 | assert(!strcmp(test, "Hello World\n\t\v stop")); 199 | 200 | free(test); 201 | } 202 | 203 | 204 | int 205 | main() 206 | { 207 | test_trim_whitespace(); 208 | test_bufchk_len(); 209 | test_sn_check(); 210 | 211 | return 0; 212 | } 213 | 214 | #endif 215 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to rmw 2 | 3 | ## Bug Reports 4 | 5 | Anyone may [open an 6 | issue](https://github.com/theimpossibleastronaut/rmw/issues). 7 | 8 | ## Coding Standards 9 | 10 | The goal is to use a style similar to [GNU Coding 11 | Standards](https://www.gnu.org/prep/standards/html_node/Formatting.html#Formatting), 12 | but braces not indented: 13 | 14 | ``` 15 | if (cli_user_options.list) 16 | { 17 | list_waste_folders(st_config_data.st_waste_folder_props_head); 18 | return 0; 19 | } 20 | ``` 21 | 22 | You can format code automatically by using 23 | [indent](https://www.gnu.org/software/indent/) with the following 24 | arguments: 25 | 26 | indent -ci2 -bl -bli0 -nut -npcs 27 | 28 | ## Website 29 | 30 | See [Website Design](https://theimpossibleastronaut.github.io/rmw-website/website-design.html) 31 | for information specific to the rmw website. 32 | 33 | ## Translating 34 | 35 | See the [translating 36 | page](https://theimpossibleastronaut.com/rmw-website/translating.html) on the 37 | website. 38 | 39 | ## Patches and Pull Requests 40 | 41 | To prevent work-overlap, please post on a ticket if you'll be working 42 | on a specific issue (or create a ticket if there's not one open yet. 43 | **Note**: If more than one person submits a patch for the same thing, 44 | your patch may get rejected. 45 | 46 | **Note**: If you agreed to work to work on a ticket but later find that 47 | you're unable to work on it, or if you changed your mind, please post 48 | again on the ticket to let everyone know it's up for grabs. 49 | 50 | You can use [The GitHub 51 | flow](https://guides.github.com/introduction/flow/), which mostly just 52 | involves creating a separate branch for each patch you're working on. 53 | Using that method helps prevent merge conflicts later. *Note* that you 54 | should never need to work on the master branch or merge your patches 55 | into the master branch (don't forget to periodically [sync your 56 | repo](https://docs.github.com/en/github/collaborating-with-pull-requests/working-with-forks/syncing-a-fork).) 57 | 58 | Source code patches should only contain changes related to a single 59 | issue. This helps speed up the review and discussion process. However, 60 | if you're helping fix typos and grammar errors in documentation, 61 | multiple changes in one PR is fine. General rule of thumb for 62 | documentation patches on this project is 5 unrelated changes or fewer 63 | to a PR. But if they are only one-word or letter changes, I can be 64 | flexible and more than 5 will still be gratefully accepted for review. 65 | 66 | If you submit a pull request, please add yourself (along with your 67 | personal link) to 68 | [AUTHORS.md](https://github.com/theimpossibleastronaut.com/rmw/blob/master/AUTHORS.md) 69 | 70 | ## Code of Conduct 71 | 72 | In the interest of fostering an open and welcoming environment, we as 73 | contributors and maintainers would like to making participation in our 74 | project and our community a harassment-free experience for everyone. 75 | 76 | ### Our Standards 77 | 78 | Examples of behavior that contributes to creating a positive 79 | environment include: 80 | 81 | * Not being disrespectful of differing viewpoints and experiences 82 | (i.e., keep opinions to yourself if you can't discuss in an open and 83 | friendly way). 84 | 85 | * Accept or ignore constructive criticism. The choice is yours. But 86 | don't attack someone who has given you a reasonable opinion of your 87 | code or doc changes. 88 | 89 | Examples of unacceptable behavior by participants include: 90 | 91 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 92 | * Trolling, insulting/derogatory comments and personal, political, or religious attacks 93 | * Public or private harassment 94 | * Other conduct which could reasonably be considered inappropriate in a professional setting 95 | 96 | ## Our Responsibilities 97 | 98 | Project maintainers are responsible for clarifying the standards of 99 | acceptable behavior and are expected to take appropriate and fair 100 | corrective action in response to any instances of unacceptable 101 | behavior. 102 | 103 | Project maintainers have the right and responsibility to remove, edit, 104 | or reject comments, commits, code, wiki edits, issues, and other 105 | contributions that are not aligned to this Code of Conduct, or to ban 106 | temporarily or permanently any contributor for other behaviors that 107 | they deem inappropriate, threatening, offensive, or harmful. 108 | 109 | ## Enforcement 110 | 111 | Instances of abusive, harassing, or otherwise unacceptable behavior may 112 | be reported by contacting the project maintainer, [Andy 113 | Alt](https://github.com/andy5995) at arch_stanton5995@proton.me. He will 114 | review and investigate all complaints, and will respond in a way that 115 | he deems appropriate to the circumstances. He is obligated to maintain 116 | confidentiality with regard to the reporter of an incident. Further 117 | details of specific enforcement policies may be posted separately. 118 | 119 | Ultimately, Mr. Alt can not be held responsible for the actions of 120 | other members, but he will do his best to deal with any reported 121 | incidents. 122 | 123 | Furthermore, if you feel that the project maintainer has violated the 124 | Code of Conduct and don't feel comfortable contacting him about it, 125 | please [report him to 126 | GitHub](https://help.github.com/en/articles/reporting-abuse-or-spam). 127 | 128 | ## Attribution 129 | 130 | This Code of Conduct is a heavily modified version of the [Contributor 131 | Covenant][homepage], version 1.4, available at 132 | [http://contributor-covenant.org/version/1/4][version] 133 | 134 | [homepage]: http://contributor-covenant.org 135 | [version]: http://contributor-covenant.org/version/1/4/ 136 | -------------------------------------------------------------------------------- /test/test_purging.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # purging.sh: tests rmw's purging facilities 3 | # 4 | # This script can be used to demonstrate the purging feature of rmw to 5 | # new users, and for testing for bugs 6 | # 7 | set -ve 8 | 9 | if [ -e COMMON ]; then 10 | . ./COMMON 11 | else 12 | . "${MESON_SOURCE_ROOT}/test/COMMON" 13 | fi 14 | 15 | echo "== On first run, directories should get created" 16 | # show commands that are run 17 | 18 | 19 | $RMW_TEST_CMD_STRING 20 | 21 | # Make some temporary files using a fake-year 22 | mkdir "${RMW_FAKE_HOME}"/tmp-files 23 | cd "${RMW_FAKE_HOME}"/tmp-files 24 | 25 | 26 | create_temp_files() { 27 | cd "${RMW_FAKE_HOME}"/tmp-files 28 | 29 | echo "\n\n == creating temporary files to be rmw'ed" 30 | 31 | for file in abc 123 xyz; do 32 | i=0 33 | while [ "$i" -lt 8 ] 34 | do 35 | echo "0000" >> $file 36 | i=`expr $i + 1` 37 | done 38 | done 39 | } 40 | 41 | echo 42 | echo 43 | echo " Creating some files for testing..." 44 | cd "${RMW_FAKE_HOME}" 45 | create_some_files 46 | 47 | echo 48 | echo 49 | echo " == rmw --empty command should completely purge the waste folders," 50 | echo " == no matter when they were deleted" 51 | 52 | $RMW_TEST_CMD_STRING --verbose "${RMW_FAKE_HOME}"/somefiles/* 53 | 54 | echo "$SEPARATOR" 55 | echo ' == purging disabled should output a message that purging is disabled' 56 | output=`$RMW_PURGE_DISABLED_CMD --empty -f` 57 | expected=`echo "purging is disabled ('expire_age' is set to '0')" | cut -b1-20` 58 | output=`echo $output | cut -b1-20` 59 | test "${output}" = "${expected}" 60 | 61 | # Should not work if '-f' isn't used" 62 | substring= 63 | cmp_substr "$(echo y | $RMW_ALT_TEST_CMD_STRING --purge --empty)" \ 64 | "purge has been skipped" 65 | 66 | echo " == Should not work if 'Y' or 'y' is not supplied." 67 | echo "yfw" | $RMW_TEST_CMD_STRING --purge --empty 68 | 69 | echo "Yfw" | $RMW_TEST_CMD_STRING --purge --empty 70 | 71 | echo "n" | $RMW_TEST_CMD_STRING --purge --empty 72 | 73 | echo "Nqer" | $RMW_TEST_CMD_STRING --purge --empty 74 | 75 | echo " == (files should still be present)" 76 | test -e "$PRIMARY_WASTE_DIR"/files/read_only_file 77 | test -e "$PRIMARY_WASTE_DIR"/files/topdir 78 | 79 | echo "$SEPARATOR" 80 | echo " == Make sure the correct string (filename) is displayed when using -vvg" 81 | output="$($RMW_TEST_CMD_STRING -vvg)" 82 | echo $output 83 | cmp_substr "$output" "'read_only_file' will be purged in 90$(locale decimal_point)" 84 | cmp_substr "$output" "'topdir' will be purged in 90$(locale decimal_point)" 85 | 86 | echo "$SEPARATOR" 87 | echo " == Empty works with force empty (-fe)" 88 | echo "y" | $RMW_TEST_CMD_STRING -v --empty -ff 89 | test ! -e "$PRIMARY_WASTE_DIR"/files/read_only_file 90 | test ! -e "$PRIMARY_WASTE_DIR"/files/topdir 91 | test ! -e "$PRIMARY_WASTE_DIR"/info/read_only_file.trashinfo 92 | test ! -e "$PRIMARY_WASTE_DIR"/info/topdir.trashinfo 93 | 94 | echo " == Should work with 'Y' or 'y'" 95 | create_temp_files 96 | $RMW_TEST_CMD_STRING --verbose "${RMW_FAKE_HOME}"/tmp-files/* && echo "y" | $RMW_TEST_CMD_STRING --empty 97 | 98 | create_temp_files 99 | $RMW_TEST_CMD_STRING --verbose "${RMW_FAKE_HOME}"/tmp-files/* 100 | test -e "$PRIMARY_WASTE_DIR"/files/abc 101 | test -e "$PRIMARY_WASTE_DIR"/files/123 102 | test -e "$PRIMARY_WASTE_DIR"/files/xyz 103 | echo "Y" | $RMW_TEST_CMD_STRING --empty 104 | 105 | echo 106 | echo 107 | echo " == Test that the files are really gone" 108 | 109 | test ! -e "$PRIMARY_WASTE_DIR"/files/abc 110 | test ! -e "$PRIMARY_WASTE_DIR"/info/abc.trashinfo 111 | test ! -e "$PRIMARY_WASTE_DIR"/files/123 112 | test ! -e "$PRIMARY_WASTE_DIR"/info/123.trashinfo 113 | test ! -e "$PRIMARY_WASTE_DIR"/files/xyz 114 | test ! -e "$PRIMARY_WASTE_DIR"/info/xyz.trashinfo 115 | 116 | create_temp_files 117 | 118 | cd "${RMW_FAKE_HOME}"/.. 119 | echo "\n\n == use a built-in environmental variable to write a" 120 | echo " == fake year to the .trashinfo files when running rmw" 121 | echo "-----------------------------------------------------\n" 122 | 123 | RMW_FAKE_YEAR=true $RMW_TEST_CMD_STRING --verbose "${RMW_FAKE_HOME}"/tmp-files/* 124 | cmp_substr "$(cat "$PRIMARY_WASTE_DIR"/info/abc.trashinfo)" \ 125 | "DeletionDate=1999" 126 | 127 | echo 128 | echo 129 | echo " == The trashinfo records that these files were rmw'ed in 1999" 130 | echo " == So they will be purged now." 131 | $RMW_TEST_CMD_STRING --verbose --purge 132 | test ! -e "$PRIMARY_WASTE_DIR"/files/abc 133 | test ! -e "$PRIMARY_WASTE_DIR"/info/abc.trashinfo 134 | test ! -e "$PRIMARY_WASTE_DIR"/files/123 135 | test ! -e "$PRIMARY_WASTE_DIR"/info/123.trashinfo 136 | test ! -e "$PRIMARY_WASTE_DIR"/files/xyz 137 | test ! -e "$PRIMARY_WASTE_DIR"/info/xyz.trashinfo 138 | 139 | echo 140 | echo 141 | echo " Creating some files for testing..." 142 | cd "${RMW_FAKE_HOME}" 143 | create_some_files 144 | 145 | echo "$SEPARATOR" 146 | echo " == rmw should be able to purge directories and subdirectories" 147 | echo " == even if some of the dirs are read-only" 148 | 149 | echo "$SEPARATOR" 150 | 151 | RMW_FAKE_YEAR=True $RMW_TEST_CMD_STRING --verbose "${RMW_FAKE_HOME}"/somefiles 152 | 153 | echo "$SEPARATOR" 154 | echo " == 1 f, this should pass" 155 | $RMW_TEST_CMD_STRING -f --purge 156 | 157 | echo 158 | echo 159 | echo " == Show that the files are really gone" 160 | 161 | test ! -e "$PRIMARY_WASTE_DIR"/files/somefiles 162 | test ! -e "$PRIMARY_WASTE_DIR"/info/somefiles.trashinfo 163 | 164 | $RMW_TEST_CMD_STRING -o 165 | 166 | echo " == Test 'show_purge_stats' == " 167 | create_temp_files 168 | RMW_FAKE_YEAR=True $RMW_TEST_CMD_STRING "${RMW_FAKE_HOME}"/tmp-files/* 169 | output=$($RMW_TEST_CMD_STRING -g) 170 | echo $output 171 | cmp_substr "$output" "Purging files based" 172 | cmp_substr "$output" "3 items purged" 173 | 174 | # filenames with parens 175 | # THIS TEST DOESN'T WORK. 176 | 177 | #cd $RMW_FAKE_HOME 178 | #foo_file="foo (1)" 179 | #touch "$foo_file" 180 | #RMW_FAKE_YEAR=True $RMW_TEST_CMD_STRING -v "$foo_file" 181 | # 182 | # When rmw is invoked from here, the file gets 183 | # purged, when invoked from the command line, rmw fails with error 184 | # sh: -c: line 1: syntax error near unexpected token `(' 185 | #$RMW_TEST_CMD_STRING -v -g 186 | #test ! -e "$PRIMARY_WASTE_DIR/files/$foo_file" 187 | # 188 | # But I've patched rmw so the error won't happen anymore. 189 | 190 | exit 0 191 | -------------------------------------------------------------------------------- /src/messages.c: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of rmw 3 | 4 | Copyright (C) 2012-2021 Andy Alt (arch_stanton5995@proton.me) 5 | Other authors: https://github.com/theimpossibleastronaut/rmw/blob/master/AUTHORS.md 6 | 7 | This program is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | This program is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with this program. If not, see . 19 | */ 20 | 21 | #include "messages.h" 22 | 23 | void 24 | print_msg_error(void) 25 | { 26 | /* TRANSLATORS: this precedes a string which informs the user of a more 27 | * serious error, sometimes a fatal one */ 28 | fputs(_(" :error: "), stderr); 29 | } 30 | 31 | void 32 | print_msg_warn(void) 33 | { 34 | /* TRANSLATORS: this precedes a string which warns the user of some minor 35 | * but not fatal problem */ 36 | fputs(_(" :warning: "), stdout); 37 | } 38 | 39 | /** 40 | * Called if fopen() returns NULL. prints an error message and some 41 | * extra info about the cause. 42 | * @param[in] filename the name of the file 43 | * @param[in] function_name the name of the function where the error originated 44 | * @return void 45 | */ 46 | void 47 | open_err(const char *filename, const char *function_name) 48 | { 49 | print_msg_error(); 50 | /* TRANSLATORS: "opening" refers to a file */ 51 | fprintf(stderr, _("while opening %s\n\ 52 | function: <%s>\n\ 53 | %s\n"), filename, function_name, strerror(errno)); 54 | 55 | return; 56 | } 57 | 58 | /** 59 | * Closes a file, checks for an error. If one is returned, print a message 60 | * with some extra info about the error. 61 | * 62 | * @param[in] file_ptr a file pointer that already exists 63 | * @param[in] filename 64 | * @param[in] function_name the name of the calling function 65 | * @return an error number, 0 if no error 66 | */ 67 | int 68 | close_file(FILE **fp, const char *filename, const char *function_name) 69 | { 70 | if (*fp == NULL) 71 | return -1; 72 | 73 | if (fclose(*fp) != EOF) 74 | return 0; 75 | else 76 | { 77 | int dup_errno = errno; 78 | /* TRANSLATORS: "closing" refers to a file */ 79 | print_msg_error(); 80 | fprintf(stderr, _("while closing %s\n\ 81 | function: <%s>\n\ 82 | %s\n"), filename, function_name, strerror(dup_errno)); 83 | return dup_errno; 84 | } 85 | } 86 | 87 | 88 | void 89 | display_dot_trashinfo_error(const char *dti) 90 | { 91 | print_msg_error(); 92 | /* TRANSLATORS: ".trashinfo" should remain untranslated 93 | * 94 | * "format" refers to the layout of the file 95 | * contents 96 | */ 97 | printf(_("format of .trashinfo file '%s' is incorrect\n"), dti); 98 | return; 99 | } 100 | 101 | 102 | void 103 | real_fatal_malloc(const char *func, const int line) 104 | { 105 | int save_errno = errno; 106 | fprintf(stderr, "%s -- %s:L%d\n", strerror(errno), func, line); 107 | exit(save_errno); 108 | } 109 | 110 | 111 | void 112 | msg_err_close_dir(const char *dir, const char *func, const int line) 113 | { 114 | print_msg_error(); 115 | fprintf(stderr, "while closing %s -- %s:L%d\n", dir, func, line); 116 | perror("closedir()"); 117 | exit(errno); 118 | } 119 | 120 | void 121 | msg_err_open_dir(const char *dir, const char *func, const int line) 122 | { 123 | print_msg_error(); 124 | fprintf(stderr, _("while opening %s -- %s:L%d\n%s\n"), dir, func, line, 125 | strerror(errno)); 126 | return; 127 | } 128 | 129 | void 130 | msg_err_rename(const char *src_file, const char *dest_file) 131 | { 132 | print_msg_error(); 133 | fprintf(stderr, "%s -> %s\n\ 134 | rename: %s\n", src_file, dest_file, strerror(errno)); 135 | exit(EXIT_FAILURE); 136 | } 137 | 138 | /*! 139 | * Used for error-checking calls to fprintf. 140 | * @param[in] func The function in which the error occurred 141 | * @return exit failure 142 | */ 143 | void 144 | msg_err_fatal_fprintf(const char *func) 145 | { 146 | print_msg_error(); 147 | fprintf(stderr, "fprintf returned an error in %s.\n", func); 148 | exit(EXIT_FAILURE); 149 | } 150 | 151 | /*! 152 | * Used by functions that prevent buffer overflows 153 | * @param[in] func the function from which it was originally called 154 | * @param[in] line the line number of the original calling function 155 | * @return void 156 | * @see bufchk 157 | * @see bufchk_len 158 | */ 159 | void 160 | msg_err_buffer_overrun(const char *func, const int line) 161 | { 162 | print_msg_error(); 163 | fprintf(stderr, "func = %s:L%d\n", func, line); 164 | /* TRANSLATORS: "buffer" in the following instances refers to the amount 165 | * of memory allocated for a string */ 166 | fputs("Buffer length exceeded.\n", stderr); 167 | fputs 168 | ("If you think this may be a bug, please report it to the rmw developers.\n", 169 | stderr); 170 | } 171 | 172 | /*! 173 | * Called if lstat() returns an error. 174 | * @param[in] func the name of the calling function 175 | * @param[in] line the line number from where the function was called 176 | * @return void 177 | */ 178 | void 179 | msg_err_lstat(const char *file, const char *func, const int line) 180 | { 181 | print_msg_error(); 182 | perror("lstat()"); 183 | fprintf(stderr, "%s in %s:L%d\n", file, func, line); 184 | exit(errno); 185 | } 186 | 187 | void 188 | msg_err_remove(const char *file, const char *func) 189 | { 190 | int dup_errno = errno; 191 | print_msg_error(); 192 | fprintf(stderr, _("while removing %s:\n%s\n(func:%s)\n"), file, 193 | strerror(dup_errno), func); 194 | } 195 | 196 | 197 | void 198 | msg_err_mkdir(const char *dir, const char *func) 199 | { 200 | perror("rmw_mkdir()"); 201 | print_msg_error(); 202 | fprintf(stderr, _("while creating %s (%s)\n"), dir, func); 203 | } 204 | 205 | void 206 | msg_success_mkdir(const char *dir) 207 | { 208 | printf(_("Created directory %s\n"), dir); 209 | return; 210 | } 211 | 212 | 213 | void 214 | msg_warn_file_not_found(const char *file) 215 | { 216 | printf("'%s': %s\n", file, strerror(ENOENT)); 217 | } 218 | -------------------------------------------------------------------------------- /.github/workflows/c-cpp.yml: -------------------------------------------------------------------------------- 1 | name: C/C++ CI 2 | concurrency: 3 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} 4 | cancel-in-progress: true 5 | 6 | on: 7 | push: 8 | branches: master 9 | paths: 10 | - '**' 11 | - '!**.yml' 12 | - '!**/.github' 13 | - '!**.md' 14 | - '!docker/**' 15 | - '!packaging/**' 16 | - '!**.txt' 17 | - '**/c-cpp.yml' 18 | pull_request: 19 | branches: master 20 | paths: 21 | - '**' 22 | - '!**.yml' 23 | - '!**/.github' 24 | - '!**.md' 25 | - '!docker/**' 26 | - '!packaging/**' 27 | - '!**.txt' 28 | - '**/c-cpp.yml' 29 | 30 | env: 31 | TERM: xterm 32 | 33 | jobs: 34 | build: 35 | name: ${{ matrix.name }}-${{ matrix.os }}-${{ matrix.cc }} 36 | strategy: 37 | fail-fast: false 38 | matrix: 39 | name: [default] 40 | os: [ubuntu-22.04, ubuntu-24.04] 41 | cc: [gcc, clang] 42 | include: 43 | - name: Slackware 44 | os: ubuntu-latest 45 | container: 'andy5995/slackware-build-essential:15.0' 46 | - name: Alpine 47 | os: ubuntu-latest 48 | container: andy5995/rmw:build-env-alpine 49 | setup_options: -Db_sanitize=none 50 | - name: OpenSuse-Tumbleweed 51 | os: ubuntu-latest 52 | container: andy5995/rmw:build-env-tumbleweed 53 | 54 | runs-on: ${{ matrix.os }} 55 | needs: 56 | - various 57 | container: ${{ matrix.container }} 58 | steps: 59 | - uses: actions/checkout@v5 60 | with: 61 | submodules: false 62 | 63 | - name: Set CC 64 | run: | 65 | CC=${{ matrix.cc }} 66 | if [ "$CC" = "gcc" ] || [ -z "$CC" ]; then 67 | CC=gcc 68 | CXX=g++ 69 | else 70 | CC=clang 71 | CXX=clang++ 72 | fi 73 | echo "CC=$CC" >> $GITHUB_ENV 74 | echo "CXX=$CXX" >> $GITHUB_ENV 75 | 76 | - name: Show info 77 | run: | 78 | export -p 79 | echo '${{ toJSON(matrix) }}' 80 | 81 | - if: ${{ matrix.container == null }} 82 | run: | 83 | sudo apt update 84 | sudo apt remove -y firefox 85 | sudo apt upgrade -y 86 | sudo apt-get install -y \ 87 | gettext \ 88 | python3-pip \ 89 | python3-setuptools 90 | sudo -H python3 -m pip install meson ninja 91 | 92 | - if: ${{ contains(matrix.container, 'alpine') }} 93 | run: | 94 | apk update 95 | apk upgrade 96 | 97 | - if: ${{ contains(matrix.container, 'tumbleweed') }} 98 | run: | 99 | zypper --non-interactive refresh 100 | zypper --non-interactive update 101 | 102 | - name: Configure 103 | run: meson setup builddir $SETUP_OPTIONS ${{ matrix.setup_options }} 104 | 105 | - name: Build 106 | run: | 107 | cd builddir 108 | ninja -v 109 | - name: Test 110 | run: cd builddir && meson test -v 111 | 112 | openbsd: 113 | runs-on: ubuntu-latest 114 | needs: 115 | - build 116 | name: OpenBSD 117 | steps: 118 | - uses: actions/checkout@v5 119 | - name: Test in OpenBSD 120 | id: test 121 | uses: vmactions/openbsd-vm@v1 122 | with: 123 | usesh: true 124 | prepare: | 125 | pkg_add gettext git meson ninja 126 | 127 | run: | 128 | meson setup builddir -Db_sanitize=none $SETUP_OPTIONS || cat builddir/meson-logs/meson-log.txt 129 | cd builddir 130 | meson compile -v 131 | meson test -v --suite rmw 132 | 133 | macos: 134 | name: macos-${{ matrix.name }} 135 | strategy: 136 | fail-fast: false 137 | matrix: 138 | include: 139 | - name: brew-canfigger 140 | install_packages: canfigger 141 | setup_options: --wrap-mode nofallback 142 | - name: bundled-canfigger 143 | 144 | runs-on: macos-latest 145 | needs: 146 | - build 147 | steps: 148 | - uses: actions/checkout@v5 149 | - name: Show info 150 | run: | 151 | export -p 152 | echo '${{ toJSON(matrix) }}' 153 | 154 | - run: | 155 | brew update 156 | brew install \ 157 | gettext \ 158 | meson \ 159 | ninja \ 160 | ncurses \ 161 | pkg-config \ 162 | ${{ matrix.build.install_packages }} 163 | 164 | - name: Configure 165 | run: meson setup builddir ${{ matrix.build.setup_options }} 166 | 167 | - name: Build 168 | run: | 169 | cd builddir 170 | ninja -v 171 | - name: Test 172 | run: cd builddir && meson test -v 173 | 174 | various: 175 | name: Test Various Build Configurations 176 | runs-on: ubuntu-latest 177 | steps: 178 | - uses: actions/checkout@v5 179 | 180 | - name: Cache btrfs Image 181 | uses: actions/cache@v4 182 | id: btrfs-img-cache 183 | with: 184 | path: test/rmw-btrfs-test.img 185 | key: ${{ hashFiles('test/rmw-btrfs-test.img.sha256sum') }} 186 | 187 | - if: ${{ steps.btrfs-img-cache.outputs.cache-hit != 'true' }} 188 | run: curl -L -o test/rmw-btrfs-test.img 'https://www.dropbox.com/scl/fi/57g3ixd3w3tuz4qoc2zp1/rmw-btrfs-test.img?rlkey=yc7krtntswsa1bwz0sbugy4gi&st=hkgrht05&dl=0' 189 | 190 | - name: Test btrfs image existence 191 | run: test -f test/rmw-btrfs-test.img 192 | 193 | - name: Install Dependencies 194 | run: | 195 | sudo apt update 196 | sudo apt remove -y firefox 197 | sudo apt upgrade -y 198 | sudo apt-get install -y \ 199 | faketime \ 200 | gettext \ 201 | meson \ 202 | ninja-build \ 203 | xvfb 204 | 205 | - name: Configure with nls 206 | run: meson setup builddir 207 | 208 | - name: Build 209 | run: meson compile -C builddir 210 | 211 | - name: meson dist 212 | run: cd builddir && meson dist --include-subprojects 213 | 214 | - name: meson dist without gettext installed 215 | run: | 216 | sudo apt remove -y gettext 217 | cd builddir 218 | meson setup --wipe -Dnls=false 219 | meson dist --include-subprojects 220 | 221 | - name: Without curses or nls 222 | run: | 223 | cd builddir 224 | meson configure -Dwithout-curses=true -Dnls=false 225 | meson test -v 226 | 227 | - name: Epochalypse test 228 | run: | 229 | rm -rf builddir 230 | meson setup builddir -Db_sanitize=none 231 | cd builddir 232 | meson test -v --setup=epochalypse --suite=rmw 233 | 234 | - name: with lto and FORTIFY 235 | run: | 236 | cd builddir 237 | meson setup --wipe \ 238 | -Db_sanitize=none \ 239 | -Dc_args=-D_FORTIFY_SOURCE=3 \ 240 | -Dbuildtype=debugoptimized \ 241 | -Db_lto=true 242 | meson test -v 243 | -------------------------------------------------------------------------------- /src/trashinfo.c: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of rmw 3 | 4 | Copyright (C) 2012-2021 Andy Alt (arch_stanton5995@proton.me) 5 | Other authors: https://github.com/theimpossibleastronaut/rmw/blob/master/AUTHORS.md 6 | 7 | This program is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | This program is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with this program. If not, see . 19 | */ 20 | 21 | #include 22 | 23 | #include "trashinfo.h" 24 | #include "utils.h" 25 | #include "messages.h" 26 | 27 | const size_t LEN_MAX_TRASHINFO_PATH_LINE = 28 | sizeof "Path=" + LEN_MAX_ESCAPED_PATH - 1; 29 | 30 | const struct trashinfo_template trashinfo_template = 31 | { "[Trash Info]", "Path=", "DeletionDate=" }; 32 | 33 | 34 | int 35 | create_trashinfo(rmw_target *st_f_props, st_waste *waste_curr, 36 | st_time *st_time_var) 37 | { 38 | char *tmp_final_info_dest = 39 | join_paths(waste_curr->info, st_f_props->base_name); 40 | 41 | size_t req_len = 42 | strlen(tmp_final_info_dest) + len_trashinfo_ext + 43 | (st_f_props->is_duplicate ? (LEN_MAX_TIME_STR_SUFFIX - 1) : 0) + 1; 44 | bufchk_len(req_len, PATH_MAX, __func__, __LINE__); 45 | if (!(tmp_final_info_dest = realloc(tmp_final_info_dest, req_len))) 46 | fatal_malloc(); 47 | if (!tmp_final_info_dest) 48 | exit(ENOMEM); 49 | char final_info_dest[req_len]; 50 | 51 | if (st_f_props->is_duplicate) 52 | strcat(tmp_final_info_dest, st_time_var->suffix_added_dup_exists); 53 | 54 | strcat(tmp_final_info_dest, trashinfo_ext); 55 | strcpy(final_info_dest, tmp_final_info_dest); 56 | free(tmp_final_info_dest); 57 | 58 | FILE *fp = fopen(final_info_dest, "w"); 59 | if (fp != NULL) 60 | { 61 | /* Worst case scenario: whole path is escaped, so 3 chars per 62 | * actual character 63 | **/ 64 | char *escaped_path = escape_url(st_f_props->real_path); 65 | if (escaped_path == NULL) 66 | return close_file(&fp, final_info_dest, __func__); 67 | 68 | char *escaped_path_ptr = escaped_path; 69 | if (waste_curr->media_root != NULL 70 | && (waste_curr->dev_num == st_f_props->dev_num)) 71 | { 72 | escaped_path_ptr = &escaped_path[strlen(waste_curr->media_root)]; 73 | if (*escaped_path_ptr == '/') 74 | escaped_path_ptr++; 75 | else 76 | { 77 | close_file(&fp, final_info_dest, __func__); 78 | if (unlink(final_info_dest) != 0) 79 | perror("unlink"); 80 | print_msg_error(); 81 | fprintf(stderr, "Expected a leading '/' in the pathname '%s'\n", 82 | escaped_path_ptr); 83 | free(escaped_path); 84 | exit(EXIT_FAILURE); 85 | } 86 | } 87 | 88 | ssize_t want_size = strlen(trashinfo_template.header) + 1 + 89 | strlen(trashinfo_template.path_key) + 90 | strlen(escaped_path_ptr) + 1 + 91 | strlen(trashinfo_template.deletion_date_key) + 92 | strlen(st_time_var->deletion_date) + 1; 93 | 94 | int n = fprintf(fp, "%s\n%s%s\n%s%s\n", trashinfo_template.header, 95 | trashinfo_template.path_key, escaped_path_ptr, 96 | trashinfo_template.deletion_date_key, 97 | st_time_var->deletion_date); 98 | 99 | free(escaped_path); 100 | 101 | if (n < 0) 102 | { 103 | print_msg_error(); 104 | fprintf(stderr, "fprintf() failed due to an error writing to %s\n", 105 | final_info_dest); 106 | } 107 | else if (n != want_size) 108 | { 109 | print_msg_error(); 110 | fprintf(stderr, 111 | "Expected to write %zu bytes, but wrote %d bytes to %s\n", 112 | want_size, n, final_info_dest); 113 | } 114 | return close_file(&fp, final_info_dest, __func__); 115 | } 116 | else 117 | { 118 | open_err(final_info_dest, __func__); 119 | return errno; 120 | } 121 | } 122 | 123 | 124 | char * 125 | validate_and_get_value(const char *file, ti_key key) 126 | { 127 | const uint8_t LEN_DELETION_DATE_KEY_WITH_VALUE = 32; 128 | struct 129 | { 130 | bool header_ok; 131 | bool path_ok; 132 | bool date_ok; 133 | } ti_status = { false, false, false }; 134 | 135 | FILE *fp = fopen(file, "r"); 136 | if (fp != NULL) 137 | { 138 | char *key_value = NULL; 139 | uint8_t line_n = 0; 140 | 141 | char fp_line[LEN_MAX_TRASHINFO_PATH_LINE]; 142 | while (fgets(fp_line, LEN_MAX_TRASHINFO_PATH_LINE, fp) != NULL 143 | && line_n < TI_LINE_MAX) 144 | { 145 | trim_whitespace(fp_line); 146 | char *val_ptr; 147 | switch (line_n) 148 | { 149 | case TI_HEADER: 150 | ti_status.header_ok = 151 | (strcmp(fp_line, trashinfo_template.header) == 0); 152 | break; 153 | case PATH_KEY: 154 | ti_status.path_ok = 155 | (strncmp 156 | (fp_line, trashinfo_template.path_key, 157 | strlen(trashinfo_template.path_key)) == 0); 158 | if (ti_status.path_ok && key == PATH_KEY) 159 | { 160 | val_ptr = strchr(fp_line, '='); 161 | if (val_ptr) 162 | { 163 | val_ptr++; /* move past the '=' sign */ 164 | char *unescaped_path = unescape_url(val_ptr); 165 | if (!unescaped_path) 166 | fatal_malloc(); 167 | key_value = unescaped_path; 168 | } 169 | } 170 | break; 171 | case DELETIONDATE_KEY: 172 | ti_status.date_ok = 173 | (strncmp(fp_line, trashinfo_template.deletion_date_key, 174 | strlen(trashinfo_template.deletion_date_key)) == 0) 175 | && strlen(fp_line) == LEN_DELETION_DATE_KEY_WITH_VALUE; 176 | if (ti_status.date_ok && key == DELETIONDATE_KEY) 177 | { 178 | val_ptr = strchr(fp_line, '='); 179 | if (val_ptr) 180 | { 181 | val_ptr++; 182 | key_value = strdup(val_ptr); 183 | if (!key_value) 184 | fatal_malloc(); 185 | } 186 | } 187 | break; 188 | } 189 | line_n++; 190 | } 191 | close_file(&fp, file, __func__); 192 | 193 | if (ti_status.header_ok && ti_status.path_ok && ti_status.date_ok 194 | && key_value) 195 | return key_value; 196 | 197 | if (key_value != NULL) 198 | free(key_value); 199 | display_dot_trashinfo_error(file); 200 | return NULL; 201 | } 202 | open_err(file, __func__); 203 | return NULL; 204 | } 205 | 206 | //const char *ti_key_to_string(ti_key key) 207 | //{ 208 | //switch (key) 209 | //{ 210 | //case TI_HEADER: return "TI_HEADER"; 211 | //case PATH_KEY: return "PATH_KEY"; 212 | //case DELETIONDATE_KEY: return "DELETIONDATE_KEY"; 213 | //case TI_LINE_MAX: return "TI_LINE_MAX"; 214 | //default: return "UNKNOWN_KEY"; 215 | //} 216 | //} 217 | -------------------------------------------------------------------------------- /src/bsdutils/compat.h: -------------------------------------------------------------------------------- 1 | /* 2 | * compat.h 3 | * Local prototype definitions for functions put together in this library. 4 | * We don't have the full OpenBSD system headers, so use this header file 5 | * to be a placeholder. 6 | */ 7 | 8 | 9 | /* Windows compatibility */ 10 | /* This is experimental 11 | * Windows compatibility headers 12 | */ 13 | #if defined __MINGW32__ || defined _MSC_VER 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #endif 22 | 23 | 24 | /* 25 | * Reference from Apple's archived OS X (now macOS documentation) 26 | * we need to import this else we are going to get a "declaration expected at 27 | * line 29" 28 | * 29 | * including types.h allows us to fix errors in the mget declaration 30 | * 31 | */ 32 | 33 | #if defined __APPLE__ 34 | #include 35 | #include 36 | #include 37 | #endif 38 | 39 | /* General imports for non-Apple platforms */ 40 | #if defined(__linux__) || defined(__FreeBSD__) 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | #endif 49 | 50 | //! Fix a warning that complains about extern declarations 51 | #ifdef HAVE___PROGNAME 52 | extern const char *__progname; 53 | #else 54 | extern const char *__progname; 55 | #endif 56 | 57 | // Use whereami library 58 | #ifdef USE_LIBWHEREAMI 59 | #include "whereami.h" 60 | #define IS_USING_WHEREAMI_LIBRARY 61 | void setprogname(const char *progname); 62 | const char *getprogname(void); 63 | #endif 64 | 65 | /* sys/types.h */ 66 | #if defined __MINGW32__ || defined _MSC_VER 67 | typedef int gid_t; 68 | typedef int uid_t; 69 | typedef unsigned char u_char; 70 | 71 | typedef unsigned int u_int; 72 | #pragma push_macro("u_long") 73 | #undef u_long 74 | typedef unsigned long u_long; 75 | #pragma pop_macro("u_long") 76 | #endif 77 | 78 | /* Primarily musl... */ 79 | #ifndef u_long 80 | typedef unsigned long u_long; 81 | #endif 82 | 83 | #ifndef u_int 84 | typedef unsigned int u_int; 85 | #endif 86 | 87 | /* sys/stat.h */ 88 | #if defined __MINGW32__|| defined _MSC_VER 89 | #define S_ISUID 04000 90 | #define S_ISGID 02000 91 | #define S_ISVTX 01000 92 | 93 | #define S_IFSOCK 0140000 94 | #define S_IFLNK 0120000 /* Symbolic link */ 95 | #endif 96 | 97 | #if defined __MINGW32__ || defined _MSC_VER 98 | struct passwd { 99 | char *pw_name; 100 | char *pw_passwd; 101 | char *pw_gecos; 102 | char *pw_dir; 103 | char *pw_shell; 104 | uid_t pw_uid; 105 | gid_t pw_gid; 106 | }; 107 | 108 | int getpwuid_r (uid_t, struct passwd *, char *, 109 | size_t, struct passwd **); 110 | int getpwnam_r (const char *, struct passwd *, 111 | char *, size_t , struct passwd **); 112 | #endif 113 | 114 | #ifdef __MINGW32__ 115 | long sysconf(int name); 116 | #endif 117 | 118 | #if defined __MINGW32__ || defined _MSC_VER 119 | #define NAME_MAX _MAX_FNAME 120 | #endif 121 | 122 | #ifdef __MINGW32__ 123 | struct group { 124 | char *gr_name; 125 | char *gr_passwd; 126 | gid_t gr_gid; 127 | char **gr_mem; 128 | }; 129 | #endif 130 | 131 | #if defined __MINGW32__ || defined _MSC_VER 132 | #define AT_SYMLINK_NOFOLLOW 0x100 133 | #endif 134 | 135 | #ifdef _MSC_VER 136 | #define mode_t int 137 | #endif 138 | 139 | 140 | /* setmode.c */ 141 | #ifndef __MINGW32__ 142 | mode_t getmode (const void *, mode_t); 143 | void *setmode (const char *); 144 | #endif 145 | 146 | #ifdef __MINGW32__ 147 | unsigned long stroul(const char*, char **, int); 148 | #endif 149 | 150 | /* strtonum.c */ 151 | long long strtonum (const char *, long long, long long, const char **); 152 | 153 | /* strmode.c */ 154 | void strmode (int, char *); 155 | 156 | /* pwcache.c */ 157 | /* Darwin (OSX/macOS) requires the nouser and nogroup 158 | to be added */ 159 | 160 | #ifndef __APPLE__ 161 | const char *user_from_uid (uid_t, int); 162 | const char *group_from_gid (gid_t, int); 163 | #endif 164 | 165 | int uid_from_user (const char *, uid_t *); 166 | int gid_from_group (const char *, gid_t *); 167 | 168 | /* fmt_scaled.c */ 169 | int scan_scaled (char *, long long *); 170 | int fmt_scaled (long long, char *); 171 | 172 | /* getbsize.c */ 173 | char *getbsize (int *, long *); 174 | 175 | /* devname.c */ 176 | #ifndef __MINGW32__ 177 | char *devname (dev_t, mode_t); 178 | #endif 179 | 180 | #ifdef __MINGW32__ 181 | char *devname_mingw(dev_t, mode_t); 182 | #define devname devname_mingw; 183 | #endif 184 | 185 | /* merge.c */ 186 | int mergesort (void *, size_t, size_t, int (*) (const void *, const void *)); 187 | 188 | /* heapsort.c */ 189 | int heapsort (void *, size_t, size_t, int (*) (const void *, const void *)); 190 | 191 | /* recallocarray.c */ 192 | void *recallocarray (void *, size_t, size_t, size_t); 193 | 194 | /* reallocarray.c */ 195 | #if defined __APPLE__ 196 | void *reallocarray (void *ptr, size_t nmemb, size_t size); 197 | #endif 198 | 199 | /* strlcat.c */ 200 | #if defined (__linux__) 201 | size_t strlcat (char *, const char *, size_t); 202 | #endif 203 | 204 | /* strlcpy.c */ 205 | #if defined __linux__|| defined __MINGW32__ 206 | size_t strlcpy (char *, const char *, size_t); 207 | #endif 208 | 209 | /* 210 | * MAXBSIZE does not exist on Linux however, since Darwin is an OS 211 | * that derives from FreesBD this does exist on Darwin, so we dont 212 | * need to get oursevels, an extra warning for redefining a macro, 213 | * however this is the explainaition for Linux because filesystem block size 214 | * limits are per filesystem and not consistently enforced across 215 | * the different filesystems. If you look at e2fsprogs and its 216 | * header files, you'll see the max block size is defined as 65536 217 | * via (1 << EXT2_MAX_BLOCK_LOG_SIZE) where EXT2_MAX_BLOCK_LOG_SIZE 218 | * is 16. On OpenBSD, MAXBSIZE is simply (64 * 1024), which is 219 | * 65536. So we'll just define that here so as to avoid having 220 | * bsdutils depend on e2fsprogs to compile. 221 | */ 222 | #if defined (__linux__) || defined (__MINGW32__) 223 | #define MAXBSIZE (64 * 1024) 224 | #endif 225 | 226 | /* 227 | * fmt_scaled(3) specific flags. 228 | * This comes from lib/libutil/util.h in the OpenBSD source. 229 | */ 230 | #define FMT_SCALED_STRSIZE 7 /* minus sign, 4 digits, suffix, null byte */ 231 | 232 | /* Buffer sizes */ 233 | #if defined (__linux__) || defined (__APPLE__) || defined (__FreeBSD__) 234 | #define _PW_BUF_LEN sysconf (_SC_GETPW_R_SIZE_MAX) 235 | #define _GR_BUF_LEN sysconf (_SC_GETGR_R_SIZE_MAX) 236 | #endif 237 | 238 | #if defined __MINGW32__ 239 | #define _SC_GETPW_R_SIZE_MAX 0x0048 240 | #define _PW_BUF_LEN _SC_GETPW_R_SIZE_MAX 241 | #define _GR_BUF_LEN _SC_GETPW_R_SIZE_MAX 242 | #endif 243 | 244 | /* Linux spelling differences 245 | * And Windows too :p 246 | */ 247 | #if defined (__linux__) || defined (__MINGW32__) 248 | #define S_ISTXT S_ISVTX 249 | #endif 250 | 251 | #if defined __linux__ 252 | #define D_TAPE 1 253 | #endif 254 | 255 | /* This are extracted from libbsd, define missing symbols 256 | * and operations necessary on timespec structs */ 257 | #ifndef timespeccmp 258 | #define timespeccmp(tsp, usp, cmp) \ 259 | (((tsp)->tv_sec == (usp)->tv_sec) ? \ 260 | ((tsp)->tv_nsec cmp (usp)->tv_nsec) : \ 261 | ((tsp)->tv_sec cmp (usp)->tv_sec)) 262 | #endif 263 | 264 | #ifndef timespecclear 265 | #define timespecclear(tsp) (tsp)->tv_sec = (tsp)->tv_nsec = 0 266 | #endif 267 | 268 | void explicit_bzero(void *, size_t ); 269 | 270 | /* we use SIGUSR1 in place of SIGINFO */ 271 | #define SIGINFO SIGUSR1 272 | 273 | int signame_to_signum(const char *sig); 274 | const char *signum_to_signame(int signum); 275 | int get_signame_by_idx(size_t idx, const char **signame, int *signum); 276 | 277 | -------------------------------------------------------------------------------- /man/rmw.1: -------------------------------------------------------------------------------- 1 | .TH RMW "1" "Nov 2025" "rmw 0.9.4" "User Commands" 2 | .SH NAME 3 | rmw \- safe-remove utility for the command line 4 | .SH SYNOPSIS 5 | .B rmw 6 | [\fI\,OPTION\/\fR]... \fI\,FILE\/\fR... 7 | 8 | Move FILE(s) to a WASTE directory listed in configuration file 9 | 10 | .B rmw 11 | \fB\-s\fR 12 | .br 13 | .B rmw 14 | \fB\-u\fR 15 | .br 16 | .B rmw 17 | \fB\-z\fR FILE... 18 | 19 | Restore FILE(s) from a WASTE directory 20 | .SH DESCRIPTION 21 | rmw (ReMove to Waste) is a trashcan/recycle bin utility for the command line. 22 | It can move and restore files to and from directories specified in a 23 | configuration file, and can also be integrated with your regular desktop trash 24 | folder (if your desktop environment uses the FreeDesktop.org Trash 25 | specification). One of the unique features of rmw is the ability to purge 26 | items from your waste (or trash) directories after x number of days. 27 | .SH OPTIONS 28 | .TP 29 | \fB\-h\fR, \fB\-\-help\fR 30 | show help for command line options 31 | .TP 32 | \fB\-c\fR, \fB\-\-config\fR FILE 33 | use an alternate configuration 34 | .TP 35 | \fB\-l\fR, \fB\-\-list\fR 36 | list waste directories 37 | .TP 38 | \fB\-g[N_DAYS]\fR, \fB\-\-purge\fR[=\fI\,N_DAYS\/\fR] 39 | purge expired files; 40 | optional argument 'N_DAYS' overrides 'expire_age' 41 | value from the configuration file 42 | (Examples: \fB\-g90\fR, \fB\-\-purge\fR=\fI\,90\/\fR) 43 | .IP 44 | By default, purging is disabled ('expire_age' is set to '0' in the 45 | configuration file). To enable, set the 'expire_age' value in your 46 | config file to a value greater than '0' 47 | 48 | You can use '-vvg' to see when the remaining files in the waste 49 | directories will expire. 50 | .TP 51 | \fB\-o\fR, \fB\-\-orphaned\fR 52 | check for orphaned files (maintenance) 53 | .IP 54 | An orphan is an item in a waste directory that has no corresponding 55 | \fB.trashinfo\fR file, or vice versa. This option is intended primarily 56 | for developers. Orphans may happen while testing code changes or if rmw 57 | is unintentionally released with a bug. 58 | .br 59 | (see also: ) 60 | .TP 61 | \fB\-f\fR, \fB\-\-force\fR 62 | allow purging of expired files 63 | .IP 64 | 65 | By default, force is not required to enable the purge feature. If you would 66 | like to require it, add 'force_required' to your config file. 67 | .TP 68 | \fB\-\-empty\fR 69 | completely empty (purge) all waste directories 70 | .TP 71 | \fB\-r\fR, \fB\-R\fR, \fB\-\-recursive\fR 72 | option used for compatibility with rm 73 | (recursive operation is enabled by default) 74 | .TP 75 | \fB\-\-top-level-bypass\fR 76 | bypass protection of top-level files 77 | (added in v0.9.0) 78 | .TP 79 | \fB\-v\fR, \fB\-\-verbose\fR 80 | increase output messages 81 | .TP 82 | \fB\-w\fR, \fB\-\-warranty\fR 83 | display warranty 84 | .TP 85 | \fB\-V\fR, \fB\-\-version\fR 86 | display version and license information 87 | .IP 88 | .SS RESTORING 89 | .HP 90 | \fB\-z\fR, \fB\-\-restore\fR FILE(s) 91 | .IP 92 | To restore items, specify the path to them in the /files 93 | directory (wildcards ok). 94 | 95 | When restoring an item, if a file or directory with the same name 96 | already exists at the destination, the item being restored will have a 97 | time/date string (formatted as "_%H%M%S-%y%m%d") appended to it (e.g. 'foo_164353-210508'). 98 | .TP 99 | \fB\-s\fR, \fB\-\-select\fR 100 | select files from list to restore 101 | .IP 102 | Displays a list of items in your waste directories. You can use the 103 | left/right cursor keys to switch between waste directories. Use the 104 | space bar to select the items you wish to restore, then press enter to 105 | restore all selected items. 106 | .TP 107 | \fB\-u\fR, \fB\-\-undo\-last\fR 108 | undo last move 109 | .IP 110 | Restores files that were last rmw'ed 111 | .TP 112 | \fB\-m\fR, \fB\-\-most\-recent\-list\fR 113 | list most recently rmw'ed files 114 | .SH ENVIRONMENT 115 | These variables are intended only to be used for testing. See the 116 | code-testing page on the rmw website for more details. 117 | .TP 118 | .B RMW_FAKE_HOME 119 | .TP 120 | .B RMW_FAKE_YEAR 121 | .SH FILES 122 | On some systems, $HOME/.config and $HOME/.local/share may be replaced 123 | with $XDG_CONFIG_HOME and $XDG_DATA_HOME 124 | .TP 125 | .I $HOME/.config/rmwrc 126 | configuration file 127 | .TP 128 | .I $HOME/.local/share/rmw/purge-time 129 | text file that stores the time of the last purge 130 | .TP 131 | .I $HOME/.local/share/rmw/mrl 132 | text file containing a list of items that were last rmw'ed 133 | .SH NOTES 134 | rmw will not move items from one file system to another. If you try to rmw a 135 | file but don't have a waste directory defined in your configuration file that 136 | matches the file system on which it resides, rmw will refuse to do anything 137 | with it. 138 | .SS DESKTOP INTEGRATION 139 | Items will be moved to a waste basket in the same manner as when using 140 | the "move to trash" option from your desktop GUI. They will be 141 | separated from your desktop trash by default; or if you wish for them 142 | to share the same "trash" directory, uncomment the line (in your config 143 | file): 144 | 145 | (Note that this does not apply to MacOS; while rmw is yet unable to 146 | integrate with the desktop trash directory, you'll still be able to use 147 | the default Waste directory.) 148 | 149 | .RS 150 | WASTE = $HOME/.local/share/Trash 151 | .RE 152 | 153 | then comment out the line 154 | 155 | .RS 156 | WASTE = $HOME/.local/share/Waste 157 | .RE 158 | 159 | You can reverse which directories are enabled at any time if you ever 160 | change your mind. If both directories are on the same filesystem, rmw will 161 | use the directory listed first in your config file. 162 | 163 | It can be beneficial to have them both uncommented. If your desktop 164 | trash directory (~/.local/share/Trash) is listed after the rmw default 165 | (~/.local/share/Waste) and uncommented, rmw will place newly rmw'ed 166 | items into the default, and it will purge expired files from both. 167 | 168 | When rmw'ing an item, if a file or directory with the same name already 169 | exists in the waste (or trash) directory, it will not be overwritten; 170 | instead, the current file being rmw'ed will have a time/date string 171 | (formatted as "_%H%M%S-%y%m%d") appended to it (e.g. 'foo_164353-210508'). 172 | .SS REMOVABLE MEDIA 173 | The first time rmw is run, it will create a configuration file. 174 | Waste directories will be created automatically (Except for when the ',removable' 175 | option is used; see below) e.g., if '$HOME/.local/share/Waste' is uncommented in 176 | the config file, these two directories will be created: 177 | 178 | .RS 179 | $HOME/.local/share/Waste/files 180 | .br 181 | $HOME/.local/share/Waste/info 182 | .RE 183 | 184 | If a WASTE directory is on removable media, you may append ',removable'. 185 | In that case, rmw will not try to create it; it must be 186 | initially created manually. When rmw runs, it will check to see if the 187 | directory exists (which means the removable media containing the 188 | directory is currently mounted). If rmw can't find the directory, it is 189 | assumed the media containing the directory isn't mounted and that 190 | directory will not be used for the current run of rmw. 191 | 192 | With the media mounted, once you manually create the waste directory 193 | for that device (e.g. "/mnt/flash/.Trash-$UID") and run rmw, it will 194 | automatically create the two required child directories "files" and "info". 195 | .SH EXAMPLES 196 | .SS RESTORING 197 | rmw -z ~/.local/share/Waste/files/foo 198 | .br 199 | rmw -z ~/.local/share/Waste/files/bars* 200 | .SS CONFIGURATION 201 | .TP 202 | WASTE=/mnt/flash/.Trash-$UID, removable 203 | When using the removable attribute, you must also manually create the directory 204 | .TP 205 | expire_age = 45 206 | rmw will permanently delete files that have been in the waste (or 207 | trash) for more than 45 days. 208 | .SH AUTHORS 209 | Project Manager: Andy Alt 210 | .br 211 | The RMW team: see AUTHORS.md 212 | .SH REPORTING BUGS 213 | Report bugs to . 214 | .SH "COPYRIGHT" 215 | Copyright \(co 2012-2025 Andy Alt 216 | 217 | License GPLv3+: GNU GPL version 3 or later . 218 | .br 219 | This is free software: you are free to change and redistribute it. 220 | There is NO WARRANTY, to the extent permitted by law. 221 | .SH "SEE ALSO" 222 | mv(1), rm(1), rmdir(1) 223 | .PP 224 | .br 225 | Full documentation at: 226 | -------------------------------------------------------------------------------- /src/bsdutils/rm.c: -------------------------------------------------------------------------------- 1 | // Modified from the original by the rmw project 2 | // The original source is at 3 | // https://github.com/dcantrell/bsdutils 4 | /*- 5 | * SPDX-License-Identifier: BSD-3-Clause 6 | * 7 | * Copyright (c) 1990, 1993, 1994 8 | * The Regents of the University of California. All rights reserved. 9 | * 10 | * Redistribution and use in source and binary forms, with or without 11 | * modification, are permitted provided that the following conditions 12 | * are met: 13 | * 1. Redistributions of source code must retain the above copyright 14 | * notice, this list of conditions and the following disclaimer. 15 | * 2. Redistributions in binary form must reproduce the above copyright 16 | * notice, this list of conditions and the following disclaimer in the 17 | * documentation and/or other materials provided with the distribution. 18 | * 3. Neither the name of the University nor the names of its contributors 19 | * may be used to endorse or promote products derived from this software 20 | * without specific prior written permission. 21 | * 22 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 23 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 24 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 25 | * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 26 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 27 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 28 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 29 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 30 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 31 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 32 | * SUCH DAMAGE. 33 | */ 34 | 35 | #if 0 36 | #ifndef lint 37 | static const char copyright[] = 38 | "@(#) Copyright (c) 1990, 1993, 1994\n\ 39 | The Regents of the University of California. All rights reserved.\n"; 40 | #endif /* not lint */ 41 | 42 | #ifndef lint 43 | static char sccsid[] = "@(#)rm.c 8.5 (Berkeley) 4/18/94"; 44 | #endif /* not lint */ 45 | #endif 46 | 47 | #include 48 | #include 49 | #include 50 | 51 | #include 52 | #include 53 | #include 54 | #include 55 | #include 56 | #include 57 | #include 58 | #include 59 | #include 60 | #include 61 | #include 62 | #include 63 | #include 64 | #include 65 | 66 | #ifndef HAVE_STRMODE 67 | #include "compat.h" 68 | #endif 69 | 70 | int eval; 71 | static int dflag, fflag, iflag, vflag, stdin_ok; 72 | static int xflag; 73 | static uid_t uid; 74 | static volatile sig_atomic_t info; 75 | 76 | static int check(const char *, const char *, struct stat *); 77 | static void rm_tree(char * const *); 78 | static void siginfo(int __attribute__((unused))); 79 | 80 | /* 81 | * rm -- 82 | * This rm is different from historic rm's, but is expected to match 83 | * POSIX 1003.2 behavior. The most visible difference is that -f 84 | * has two specific effects now, ignore non-existent files and force 85 | * file removal. 86 | */ 87 | int 88 | bsdutils_rm(const char *const argv, bool want_verbose) 89 | { 90 | (void)setlocale(LC_ALL, ""); 91 | 92 | eval = 0; 93 | iflag = dflag = fflag = 0; 94 | 95 | vflag = want_verbose ? true : false; 96 | 97 | uid = geteuid(); 98 | 99 | (void)signal(SIGINFO, siginfo); 100 | 101 | char *const fake_fts_array[] = { 102 | (char *const)argv, 103 | NULL 104 | }; 105 | 106 | rm_tree(fake_fts_array); 107 | 108 | return (eval); 109 | } 110 | 111 | static void 112 | rm_tree(char * const * argv) 113 | { 114 | FTS *fts; 115 | FTSENT *p; 116 | int needstat; 117 | int flags; 118 | int rval; 119 | 120 | /* 121 | * Remove a file hierarchy. If forcing removal (-f), or interactive 122 | * (-i) or can't ask anyway (stdin_ok), don't stat the file. 123 | */ 124 | needstat = !uid || (!fflag && !iflag && stdin_ok); 125 | 126 | /* 127 | * If the -i option is specified, the user can skip on the pre-order 128 | * visit. The fts_number field flags skipped directories. 129 | */ 130 | #define SKIPPED 1 131 | 132 | flags = FTS_PHYSICAL; 133 | if (!needstat) 134 | flags |= FTS_NOSTAT; 135 | if (xflag) 136 | flags |= FTS_XDEV; 137 | if (!(fts = fts_open(argv, flags, NULL))) { 138 | if (fflag && errno == ENOENT) 139 | return; 140 | err(1, "fts_open"); 141 | } 142 | while (errno = 0, (p = fts_read(fts)) != NULL) { 143 | switch (p->fts_info) { 144 | case FTS_DNR: 145 | if (!fflag || p->fts_errno != ENOENT) { 146 | warnx("%s: %s", 147 | p->fts_path, strerror(p->fts_errno)); 148 | eval = 1; 149 | } 150 | continue; 151 | case FTS_ERR: 152 | errx(1, "%s: %s", p->fts_path, strerror(p->fts_errno)); 153 | case FTS_NS: 154 | /* 155 | * Assume that since fts_read() couldn't stat the 156 | * file, it can't be unlinked. 157 | */ 158 | if (!needstat) 159 | break; 160 | if (!fflag || p->fts_errno != ENOENT) { 161 | warnx("%s: %s", 162 | p->fts_path, strerror(p->fts_errno)); 163 | eval = 1; 164 | } 165 | continue; 166 | case FTS_D: 167 | /* Pre-order: give user chance to skip. */ 168 | if (!fflag && !check(p->fts_path, p->fts_accpath, 169 | p->fts_statp)) { 170 | (void)fts_set(fts, p, FTS_SKIP); 171 | p->fts_number = SKIPPED; 172 | } 173 | continue; 174 | case FTS_DP: 175 | /* Post-order: see if user skipped. */ 176 | if (p->fts_number == SKIPPED) 177 | continue; 178 | break; 179 | default: 180 | if (!fflag && 181 | !check(p->fts_path, p->fts_accpath, p->fts_statp)) 182 | continue; 183 | } 184 | 185 | /* 186 | * If we can't read or search the directory, may still be 187 | * able to remove it. Don't print out the un{read,search}able 188 | * message unless the remove fails. 189 | */ 190 | switch (p->fts_info) { 191 | case FTS_DP: 192 | case FTS_DNR: 193 | rval = rmdir(p->fts_accpath); 194 | if (rval == 0 || (fflag && errno == ENOENT)) { 195 | if (rval == 0 && vflag) 196 | (void)printf("%s\n", 197 | p->fts_path); 198 | if (rval == 0 && info) { 199 | info = 0; 200 | (void)printf("%s\n", 201 | p->fts_path); 202 | } 203 | continue; 204 | } 205 | break; 206 | case FTS_NS: 207 | /* 208 | * Assume that since fts_read() couldn't stat 209 | * the file, it can't be unlinked. 210 | */ 211 | if (fflag) 212 | continue; 213 | /* FALLTHROUGH */ 214 | case FTS_F: 215 | case FTS_NSOK: 216 | default: 217 | rval = unlink(p->fts_accpath); 218 | if (rval == 0 || (fflag && errno == ENOENT)) { 219 | if (rval == 0 && vflag) 220 | (void)printf("%s\n", 221 | p->fts_path); 222 | if (rval == 0 && info) { 223 | info = 0; 224 | (void)printf("%s\n", 225 | p->fts_path); 226 | } 227 | continue; 228 | } 229 | } 230 | warn("%s", p->fts_path); 231 | eval = 1; 232 | } 233 | if (!fflag && errno) 234 | err(1, "fts_read"); 235 | fts_close(fts); 236 | } 237 | 238 | 239 | static int 240 | check(const char *path, const char *name, struct stat *sp) 241 | { 242 | int ch, first; 243 | char modep[15]; 244 | struct passwd *pw = NULL; 245 | struct group *gr = NULL; 246 | 247 | /* Check -i first. */ 248 | if (iflag) 249 | (void)fprintf(stderr, "remove %s? ", path); 250 | else { 251 | /* 252 | * If it's not a symbolic link and it's unwritable and we're 253 | * talking to a terminal, ask. Symbolic links are excluded 254 | * because their permissions are meaningless. Check stdin_ok 255 | * first because we may not have stat'ed the file. 256 | */ 257 | if (!stdin_ok || S_ISLNK(sp->st_mode) || !access(name, W_OK)) 258 | return (1); 259 | strmode(sp->st_mode, modep); 260 | pw = getpwuid(sp->st_uid); 261 | if (pw == NULL) 262 | err(EXIT_FAILURE, "getpwuid"); 263 | gr = getgrgid(sp->st_gid); 264 | if (gr == NULL) 265 | err(EXIT_FAILURE, "getgrgid"); 266 | (void)fprintf(stderr, "override %s%s%s/%s for %s? ", 267 | modep + 1, modep[10] == ' ' ? "" : " ", 268 | pw->pw_name, 269 | gr->gr_name, 270 | path); 271 | } 272 | (void)fflush(stderr); 273 | 274 | first = ch = getchar(); 275 | while (ch != '\n' && ch != EOF) 276 | ch = getchar(); 277 | return (first == 'y' || first == 'Y'); 278 | } 279 | 280 | 281 | static void 282 | siginfo(int sig __attribute__((unused))) 283 | { 284 | 285 | info = 1; 286 | } 287 | 288 | /////////////////////////////////////////////////////////////////////// 289 | #ifdef TEST_LIB 290 | 291 | #include "test.h" 292 | 293 | static void 294 | test_bsdutils_rm(void) 295 | { 296 | const char *dir = "flippity-dip"; 297 | assert(bsdutils_rm(dir, 1) == 1); 298 | int i; 299 | for (i = 0; i < 3; i++) 300 | { 301 | assert(mkdir(dir, 0700) == 0); 302 | assert(chdir(dir) == 0); 303 | } 304 | for (i = 0; i < 3; i++) 305 | assert(chdir("..") == 0); 306 | 307 | assert(bsdutils_rm(dir, 1) == 0); 308 | 309 | return; 310 | } 311 | 312 | int main(void) 313 | { 314 | test_bsdutils_rm(); 315 | return 0; 316 | } 317 | 318 | #endif 319 | -------------------------------------------------------------------------------- /src/gettext.h: -------------------------------------------------------------------------------- 1 | /* Convenience header for conditional use of GNU . 2 | Copyright (C) 1995-1998, 2000-2002, 2004-2006, 2009-2016 Free Software 3 | Foundation, Inc. 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation; either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . */ 17 | 18 | #ifndef _LIBGETTEXT_H 19 | #define _LIBGETTEXT_H 1 20 | 21 | /* NLS can be disabled through the configure --disable-nls option. */ 22 | #if ENABLE_NLS 23 | 24 | /* Get declarations of GNU message catalog functions. */ 25 | #include 26 | 27 | /* You can set the DEFAULT_TEXT_DOMAIN macro to specify the domain used by 28 | the gettext() and ngettext() macros. This is an alternative to calling 29 | textdomain(), and is useful for libraries. */ 30 | #ifdef DEFAULT_TEXT_DOMAIN 31 | #undef gettext 32 | #define gettext(Msgid) \ 33 | dgettext (DEFAULT_TEXT_DOMAIN, Msgid) 34 | #undef ngettext 35 | #define ngettext(Msgid1, Msgid2, N) \ 36 | dngettext (DEFAULT_TEXT_DOMAIN, Msgid1, Msgid2, N) 37 | #endif 38 | 39 | #else 40 | 41 | /* Solaris /usr/include/locale.h includes /usr/include/libintl.h, which 42 | chokes if dcgettext is defined as a macro. So include it now, to make 43 | later inclusions of a NOP. We don't include 44 | as well because people using "gettext.h" will not include , 45 | and also including would fail on SunOS 4, whereas 46 | is OK. */ 47 | #if defined(__sun) 48 | #include 49 | #endif 50 | 51 | /* Many header files from the libstdc++ coming with g++ 3.3 or newer include 52 | , which chokes if dcgettext is defined as a macro. So include 53 | it now, to make later inclusions of a NOP. */ 54 | #if defined(__cplusplus) && defined(__GNUG__) && (__GNUC__ >= 3) 55 | #include 56 | #if (__GLIBC__ >= 2 && !defined __UCLIBC__) || _GLIBCXX_HAVE_LIBINTL_H 57 | #include 58 | #endif 59 | #endif 60 | 61 | /* Disabled NLS. 62 | The casts to 'const char *' serve the purpose of producing warnings 63 | for invalid uses of the value returned from these functions. 64 | On pre-ANSI systems without 'const', the config.h file is supposed to 65 | contain "#define const". */ 66 | #undef gettext 67 | #define gettext(Msgid) ((const char *) (Msgid)) 68 | #undef dgettext 69 | #define dgettext(Domainname, Msgid) ((void) (Domainname), gettext (Msgid)) 70 | #undef dcgettext 71 | #define dcgettext(Domainname, Msgid, Category) \ 72 | ((void) (Category), dgettext (Domainname, Msgid)) 73 | #undef ngettext 74 | #define ngettext(Msgid1, Msgid2, N) \ 75 | ((N) == 1 \ 76 | ? ((void) (Msgid2), (const char *) (Msgid1)) \ 77 | : ((void) (Msgid1), (const char *) (Msgid2))) 78 | #undef dngettext 79 | #define dngettext(Domainname, Msgid1, Msgid2, N) \ 80 | ((void) (Domainname), ngettext (Msgid1, Msgid2, N)) 81 | #undef dcngettext 82 | #define dcngettext(Domainname, Msgid1, Msgid2, N, Category) \ 83 | ((void) (Category), dngettext (Domainname, Msgid1, Msgid2, N)) 84 | #undef textdomain 85 | #define textdomain(Domainname) ((const char *) (Domainname)) 86 | #undef bindtextdomain 87 | #define bindtextdomain(Domainname, Dirname) \ 88 | ((void) (Domainname), (const char *) (Dirname)) 89 | #undef bind_textdomain_codeset 90 | #define bind_textdomain_codeset(Domainname, Codeset) \ 91 | ((void) (Domainname), (const char *) (Codeset)) 92 | 93 | #endif 94 | 95 | /* Prefer gnulib's setlocale override over libintl's setlocale override. */ 96 | #ifdef GNULIB_defined_setlocale 97 | #undef setlocale 98 | #define setlocale rpl_setlocale 99 | #endif 100 | 101 | /* A pseudo function call that serves as a marker for the automated 102 | extraction of messages, but does not call gettext(). The run-time 103 | translation is done at a different place in the code. 104 | The argument, String, should be a literal string. Concatenated strings 105 | and other string expressions won't work. 106 | The macro's expansion is not parenthesized, so that it is suitable as 107 | initializer for static 'char[]' or 'const char[]' variables. */ 108 | #define gettext_noop(String) String 109 | 110 | /* The separator between msgctxt and msgid in a .mo file. */ 111 | #define GETTEXT_CONTEXT_GLUE "\004" 112 | 113 | /* Pseudo function calls, taking a MSGCTXT and a MSGID instead of just a 114 | MSGID. MSGCTXT and MSGID must be string literals. MSGCTXT should be 115 | short and rarely need to change. 116 | The letter 'p' stands for 'particular' or 'special'. */ 117 | #ifdef DEFAULT_TEXT_DOMAIN 118 | #define pgettext(Msgctxt, Msgid) \ 119 | pgettext_aux (DEFAULT_TEXT_DOMAIN, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid, LC_MESSAGES) 120 | #else 121 | #define pgettext(Msgctxt, Msgid) \ 122 | pgettext_aux (NULL, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid, LC_MESSAGES) 123 | #endif 124 | #define dpgettext(Domainname, Msgctxt, Msgid) \ 125 | pgettext_aux (Domainname, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid, LC_MESSAGES) 126 | #define dcpgettext(Domainname, Msgctxt, Msgid, Category) \ 127 | pgettext_aux (Domainname, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid, Category) 128 | #ifdef DEFAULT_TEXT_DOMAIN 129 | #define npgettext(Msgctxt, Msgid, MsgidPlural, N) \ 130 | npgettext_aux (DEFAULT_TEXT_DOMAIN, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid, MsgidPlural, N, LC_MESSAGES) 131 | #else 132 | #define npgettext(Msgctxt, Msgid, MsgidPlural, N) \ 133 | npgettext_aux (NULL, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid, MsgidPlural, N, LC_MESSAGES) 134 | #endif 135 | #define dnpgettext(Domainname, Msgctxt, Msgid, MsgidPlural, N) \ 136 | npgettext_aux (Domainname, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid, MsgidPlural, N, LC_MESSAGES) 137 | #define dcnpgettext(Domainname, Msgctxt, Msgid, MsgidPlural, N, Category) \ 138 | npgettext_aux (Domainname, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid, MsgidPlural, N, Category) 139 | 140 | #ifdef __GNUC__ 141 | __inline 142 | #else 143 | #ifdef __cplusplus 144 | inline 145 | #endif 146 | #endif 147 | static const char * 148 | pgettext_aux(const char *domain, 149 | const char *msg_ctxt_id, const char *msgid, int category) 150 | { 151 | const char *translation = dcgettext(domain, msg_ctxt_id, category); 152 | if (translation == msg_ctxt_id) 153 | return msgid; 154 | else 155 | return translation; 156 | } 157 | 158 | #ifdef __GNUC__ 159 | __inline 160 | #else 161 | #ifdef __cplusplus 162 | inline 163 | #endif 164 | #endif 165 | static const char * 166 | npgettext_aux(const char *domain, 167 | const char *msg_ctxt_id, const char *msgid, 168 | const char *msgid_plural, unsigned long int n, int category) 169 | { 170 | const char *translation = 171 | dcngettext(domain, msg_ctxt_id, msgid_plural, n, category); 172 | if (translation == msg_ctxt_id || translation == msgid_plural) 173 | return (n == 1 ? msgid : msgid_plural); 174 | else 175 | return translation; 176 | } 177 | 178 | /* The same thing extended for non-constant arguments. Here MSGCTXT and MSGID 179 | can be arbitrary expressions. But for string literals these macros are 180 | less efficient than those above. */ 181 | 182 | #include 183 | 184 | #if (((__GNUC__ >= 3 || __GNUG__ >= 2) && !defined __STRICT_ANSI__) \ 185 | /* || __STDC_VERSION__ >= 199901L */ ) 186 | #define _LIBGETTEXT_HAVE_VARIABLE_SIZE_ARRAYS 1 187 | #else 188 | #define _LIBGETTEXT_HAVE_VARIABLE_SIZE_ARRAYS 0 189 | #endif 190 | 191 | #if !_LIBGETTEXT_HAVE_VARIABLE_SIZE_ARRAYS 192 | #include 193 | #endif 194 | 195 | #define pgettext_expr(Msgctxt, Msgid) \ 196 | dcpgettext_expr (NULL, Msgctxt, Msgid, LC_MESSAGES) 197 | #define dpgettext_expr(Domainname, Msgctxt, Msgid) \ 198 | dcpgettext_expr (Domainname, Msgctxt, Msgid, LC_MESSAGES) 199 | 200 | #ifdef __GNUC__ 201 | __inline 202 | #else 203 | #ifdef __cplusplus 204 | inline 205 | #endif 206 | #endif 207 | static const char * 208 | dcpgettext_expr(const char *domain, 209 | const char *msgctxt, const char *msgid, int category) 210 | { 211 | size_t msgctxt_len = strlen(msgctxt) + 1; 212 | size_t msgid_len = strlen(msgid) + 1; 213 | const char *translation; 214 | #if _LIBGETTEXT_HAVE_VARIABLE_SIZE_ARRAYS 215 | char msg_ctxt_id[msgctxt_len + msgid_len]; 216 | #else 217 | char buf[1024]; 218 | char *msg_ctxt_id = 219 | (msgctxt_len + msgid_len <= sizeof(buf) 220 | ? buf : (char *) malloc(msgctxt_len + msgid_len)); 221 | if (msg_ctxt_id != NULL) 222 | #endif 223 | { 224 | int found_translation; 225 | memcpy(msg_ctxt_id, msgctxt, msgctxt_len - 1); 226 | msg_ctxt_id[msgctxt_len - 1] = '\004'; 227 | memcpy(msg_ctxt_id + msgctxt_len, msgid, msgid_len); 228 | translation = dcgettext(domain, msg_ctxt_id, category); 229 | found_translation = (translation != msg_ctxt_id); 230 | #if !_LIBGETTEXT_HAVE_VARIABLE_SIZE_ARRAYS 231 | if (msg_ctxt_id != buf) 232 | free(msg_ctxt_id); 233 | #endif 234 | if (found_translation) 235 | return translation; 236 | } 237 | return msgid; 238 | } 239 | 240 | #define npgettext_expr(Msgctxt, Msgid, MsgidPlural, N) \ 241 | dcnpgettext_expr (NULL, Msgctxt, Msgid, MsgidPlural, N, LC_MESSAGES) 242 | #define dnpgettext_expr(Domainname, Msgctxt, Msgid, MsgidPlural, N) \ 243 | dcnpgettext_expr (Domainname, Msgctxt, Msgid, MsgidPlural, N, LC_MESSAGES) 244 | 245 | #ifdef __GNUC__ 246 | __inline 247 | #else 248 | #ifdef __cplusplus 249 | inline 250 | #endif 251 | #endif 252 | static const char * 253 | dcnpgettext_expr(const char *domain, 254 | const char *msgctxt, const char *msgid, 255 | const char *msgid_plural, unsigned long int n, int category) 256 | { 257 | size_t msgctxt_len = strlen(msgctxt) + 1; 258 | size_t msgid_len = strlen(msgid) + 1; 259 | const char *translation; 260 | #if _LIBGETTEXT_HAVE_VARIABLE_SIZE_ARRAYS 261 | char msg_ctxt_id[msgctxt_len + msgid_len]; 262 | #else 263 | char buf[1024]; 264 | char *msg_ctxt_id = 265 | (msgctxt_len + msgid_len <= sizeof(buf) 266 | ? buf : (char *) malloc(msgctxt_len + msgid_len)); 267 | if (msg_ctxt_id != NULL) 268 | #endif 269 | { 270 | int found_translation; 271 | memcpy(msg_ctxt_id, msgctxt, msgctxt_len - 1); 272 | msg_ctxt_id[msgctxt_len - 1] = '\004'; 273 | memcpy(msg_ctxt_id + msgctxt_len, msgid, msgid_len); 274 | translation = dcngettext(domain, msg_ctxt_id, msgid_plural, n, category); 275 | found_translation = !(translation == msg_ctxt_id 276 | || translation == msgid_plural); 277 | #if !_LIBGETTEXT_HAVE_VARIABLE_SIZE_ARRAYS 278 | if (msg_ctxt_id != buf) 279 | free(msg_ctxt_id); 280 | #endif 281 | if (found_translation) 282 | return translation; 283 | } 284 | return (n == 1 ? msgid : msgid_plural); 285 | } 286 | 287 | #endif /* _LIBGETTEXT_H */ 288 | -------------------------------------------------------------------------------- /src/parse_cli_options.c: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of rmw 3 | 4 | Copyright (C) 2012-2023 Andy Alt (arch_stanton5995@proton.me) 5 | Other authors: https://github.com/theimpossibleastronaut/rmw/blob/master/AUTHORS.md 6 | 7 | This program is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | This program is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with this program. If not, see . 19 | */ 20 | 21 | #ifndef INC_GLOBALS_H 22 | #define INC_GLOBALS_H 23 | #include "globals.h" 24 | #endif 25 | 26 | #include 27 | #include 28 | 29 | #include "parse_cli_options.h" 30 | #include "config_rmw.h" 31 | 32 | #define RMW_VERSION_STRING VERSION 33 | 34 | 35 | typedef enum 36 | { 37 | HELP, 38 | VERBOSE, 39 | CONFIG, 40 | DRY_RUN, 41 | LIST, 42 | PURGE, 43 | ORPHANED, 44 | RESTORE, 45 | SELECT, 46 | UNDO_LAST, 47 | MOST_RECENT_LIST, 48 | WARRANTY, 49 | _VERSION, 50 | RECURSIVE, 51 | FORCE, 52 | EMPTY, 53 | TOP_LEVEL_BYPASS 54 | } cli_opt_id; 55 | 56 | 57 | static struct cli_opt 58 | { 59 | cli_opt_id id; 60 | char *str; 61 | } cli_opt[] = { 62 | {HELP, "help"}, 63 | {VERBOSE, "verbose"}, 64 | {CONFIG, "config"}, 65 | {DRY_RUN, "dry-run"}, 66 | {LIST, "list"}, 67 | {PURGE, "purge"}, 68 | {ORPHANED, "orphaned"}, 69 | {RESTORE, "restore"}, 70 | {SELECT, "select"}, 71 | {UNDO_LAST, "undo-last"}, 72 | {MOST_RECENT_LIST, "most-recent-list"}, 73 | {WARRANTY, "warranty"}, 74 | {_VERSION, "version"}, 75 | {RECURSIVE, "recursive"}, 76 | {FORCE, "force"}, 77 | {EMPTY, "empty"}, 78 | {TOP_LEVEL_BYPASS, "top-level-bypass"} 79 | }; 80 | 81 | 82 | /* For long options that have no equivalent short option, use a 83 | non-character as a pseudo short option, starting with CHAR_MAX + 1. */ 84 | enum 85 | { 86 | L_EMPTY = CHAR_MAX + 1, 87 | L_TOP_LEVEL_BYPASS 88 | }; 89 | 90 | 91 | static void 92 | print_usage(const char *prog_name) 93 | { 94 | printf(_("Usage: %s [OPTION]... FILE...\n\ 95 | Move FILE(s) to a WASTE directory listed in configuration file\n\ 96 | \n\ 97 | or: %s -s\n\ 98 | or: %s -u\n\ 99 | or: %s -z FILE...\n\ 100 | "), prog_name, prog_name, prog_name, prog_name); 101 | puts(_("\ 102 | Restore FILE(s) from a WASTE directory")); 103 | putchar('\n'); 104 | printf(_("\ 105 | -h, --%s show help for command line options\n\ 106 | "), cli_opt[HELP].str); 107 | printf(_("\ 108 | -c, --%s FILE use an alternate configuration\n\ 109 | "), cli_opt[CONFIG].str); 110 | printf(_("\ 111 | -l, --%s list waste directories\n\ 112 | "), cli_opt[LIST].str); 113 | printf(_("\ 114 | -g[N_DAYS], --%s[=N_DAYS]\n\ 115 | purge expired files;\n\ 116 | optional argument 'N_DAYS' overrides '%s'\n\ 117 | value from the configuration file\n\ 118 | (Examples: -g90, --purge=90)\n\ 119 | "), cli_opt[PURGE].str, expire_age_str); 120 | printf(_("\ 121 | -o, --%s check for orphaned files (maintenance)\n\ 122 | "), cli_opt[ORPHANED].str); 123 | printf(_("\ 124 | -f, --%s allow purging of expired files\n\ 125 | "), cli_opt[FORCE].str); 126 | printf(_("\ 127 | --%s completely empty (purge) all waste directories\n\ 128 | "), cli_opt[EMPTY].str); 129 | printf(_("\ 130 | -r, -R, --%s option used for compatibility with rm\n\ 131 | (recursive operation is enabled by default)\n\ 132 | "), cli_opt[RECURSIVE].str); 133 | printf(_("\ 134 | --%s bypass protection of top-level files\n\ 135 | "), cli_opt[TOP_LEVEL_BYPASS].str); 136 | printf(_("\ 137 | -v, --%s increase output messages\n\ 138 | "), cli_opt[VERBOSE].str); 139 | printf(_("\ 140 | -w, --%s display warranty\n\ 141 | "), cli_opt[WARRANTY].str); 142 | printf(_("\ 143 | -V, --%s display version and license information\n\ 144 | "), cli_opt[_VERSION].str); 145 | puts(_("\ 146 | \n\ 147 | \n\ 148 | \t===] Restoring [===\n")); 149 | printf(_("\ 150 | -z, --%s FILE(s) restore FILE(s) (see man page example)\n\ 151 | "), cli_opt[RESTORE].str); 152 | printf(_("\ 153 | -s, --%s select files from list to restore\n\ 154 | "), cli_opt[SELECT].str); 155 | printf(_("\ 156 | -u, --%s undo last move\n\ 157 | "), cli_opt[UNDO_LAST].str); 158 | printf(_("\ 159 | -m, --%s list most recently rmw'ed files\n\ 160 | "), cli_opt[MOST_RECENT_LIST].str); 161 | puts(_("\ 162 | \n\n\ 163 | Visit the rmw home page for more help, and information about\n\ 164 | how to obtain support - <" PACKAGE_URL ">\n")); 165 | printf(_("Report bugs to <%s>.\n"), PACKAGE_BUGREPORT); 166 | } 167 | 168 | static void 169 | warranty(void) 170 | { 171 | printf(_("\ 172 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\n\ 173 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\n\ 174 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\n\ 175 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\n\ 176 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n\ 177 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\n\ 178 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\n\ 179 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n")); 180 | 181 | exit(0); 182 | } 183 | 184 | static void 185 | version(void) 186 | { 187 | printf(_("\ 188 | rmw %s\n\ 189 | Author: Andy Alt (%s)\n\ 190 | The RMW team: see AUTHORS file\n\ 191 | This program comes with ABSOLUTELY NO WARRANTY; for details type 'rmw -w.'\n\ 192 | This is free software, and you are welcome to redistribute it\n\ 193 | under certain conditions; see <%s>\n\ 194 | for details.\n"), RMW_VERSION_STRING, "arch_stanton5995@proton.me", "http://www.gnu.org/licenses/gpl.html"); 195 | exit(0); 196 | } 197 | 198 | /* Advise the user about invalid usages like "rm -foo" if the file 199 | * "-foo" exists, assuming ARGC and ARGV are as with 'main'. 200 | * 201 | * This function (lightly modified) is on loan from rm.c in the 202 | * GNU coreutils package */ 203 | static void 204 | diagnose_leading_hyphen(const int argc, char *const argv[]) 205 | { 206 | /* OPTIND is unreliable, so iterate through the arguments looking 207 | for a file name that looks like an option. */ 208 | int i; 209 | for (i = 1; i < argc; i++) 210 | { 211 | char const *arg = argv[i]; 212 | struct stat st; 213 | 214 | if (arg[0] == '-' && arg[1] && lstat(arg, &st) == 0) 215 | { 216 | fprintf(stderr, 217 | _("Try '%s ./%s' to remove the file '%s'.\n"), 218 | argv[0], arg, arg); 219 | break; 220 | } 221 | } 222 | } 223 | 224 | void 225 | init_rmw_options(rmw_options *x) 226 | { 227 | verbose = 0; 228 | x->want_restore = false; 229 | x->want_dry_run = false; 230 | x->want_purge = 0; 231 | x->want_empty_trash = false; 232 | x->want_top_level_bypass = false; 233 | x->want_orphan_chk = false; 234 | x->want_selection_menu = false; 235 | x->want_undo = false; 236 | x->most_recent_list = false; 237 | x->force = 0; 238 | x->list = false; 239 | x->alt_config_file = NULL; 240 | } 241 | 242 | 243 | void 244 | parse_cli_options(const int argc, char *const argv[], rmw_options *options) 245 | { 246 | const struct option long_options[] = { 247 | {cli_opt[HELP].str, 0, NULL, 'h'}, 248 | {cli_opt[VERBOSE].str, 0, NULL, 'v'}, 249 | {cli_opt[CONFIG].str, 1, NULL, 'c'}, 250 | {cli_opt[DRY_RUN].str, 0, NULL, 'n'}, 251 | {cli_opt[LIST].str, 0, NULL, 'l'}, 252 | {cli_opt[PURGE].str, 2, NULL, 'g'}, 253 | {cli_opt[ORPHANED].str, 0, NULL, 'o'}, 254 | {cli_opt[RESTORE].str, 1, NULL, 'z'}, 255 | {cli_opt[SELECT].str, 0, NULL, 's'}, 256 | {cli_opt[UNDO_LAST].str, 0, NULL, 'u'}, 257 | {cli_opt[MOST_RECENT_LIST].str, 0, NULL, 'm'}, 258 | {cli_opt[WARRANTY].str, 0, NULL, 'w'}, 259 | {cli_opt[_VERSION].str, 0, NULL, 'V'}, 260 | {cli_opt[RECURSIVE].str, 0, NULL, 'r'}, 261 | {cli_opt[FORCE].str, 0, NULL, 'f'}, 262 | {cli_opt[EMPTY].str, 0, NULL, L_EMPTY}, 263 | {cli_opt[TOP_LEVEL_BYPASS].str, 0, NULL, L_TOP_LEVEL_BYPASS}, 264 | {NULL, 0, NULL, 0} 265 | }; 266 | 267 | int c; 268 | while ((c = 269 | getopt_long(argc, argv, "hvc:g::oz:lnsumwVfrR", long_options, 270 | NULL)) != -1) 271 | { 272 | switch (c) 273 | { 274 | case 'h': 275 | print_usage(argv[0]); 276 | exit(0); 277 | case 'v': 278 | verbose++; 279 | break; 280 | case 'c': 281 | options->alt_config_file = optarg; 282 | break; 283 | case 'n': 284 | options->want_dry_run = true; 285 | puts(_("dry-run mode enabled.")); 286 | /* assume verbosity as well */ 287 | if (verbose == 0) 288 | verbose = 1; 289 | break; 290 | case 'l': 291 | options->list = true; 292 | break; 293 | case 'g': 294 | /* Ignore if used twice, but parse it if --purge was given no argument the first time */ 295 | if (options->want_purge > 0) 296 | break; 297 | 298 | if (optarg == NULL) 299 | { 300 | options->want_purge = -1; 301 | break; 302 | } 303 | char *p = optarg; 304 | while (*p != '\0') 305 | { 306 | if (!isdigit(*p)) 307 | { 308 | puts(_("Arguments given to --purge must be numeric only")); 309 | exit(EXIT_FAILURE); 310 | } 311 | p++; 312 | } 313 | options->want_purge = atoi(optarg); 314 | break; 315 | case 'o': 316 | options->want_orphan_chk = true; 317 | break; 318 | case 'z': 319 | options->want_restore = true; 320 | break; 321 | case 's': 322 | options->want_selection_menu = true; 323 | break; 324 | case 'u': 325 | options->want_undo = true; 326 | break; 327 | case 'm': 328 | options->most_recent_list = true; 329 | break; 330 | case 'w': 331 | warranty(); 332 | break; 333 | case 'V': 334 | version(); 335 | break; 336 | case 'r': 337 | case 'R': 338 | printf(_ 339 | ("-r, -R, --recursive: option not required (enabled by default)\n")); 340 | break; 341 | case 'f': 342 | if (options->force < 2) /* This doesn't need to go higher than 2 */ 343 | options->force++; 344 | break; 345 | case L_EMPTY: 346 | options->want_empty_trash = true; 347 | options->want_purge = -1; 348 | break; 349 | case L_TOP_LEVEL_BYPASS: 350 | options->want_top_level_bypass = true; 351 | break; 352 | default: 353 | diagnose_leading_hyphen(argc, argv); 354 | fprintf(stderr, _("Try '%s --help' for more information.\n"), argv[0]); 355 | exit(EXIT_FAILURE); 356 | } 357 | } 358 | 359 | return; 360 | } 361 | -------------------------------------------------------------------------------- /src/purging.c: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of rmw 3 | 4 | Copyright (C) 2012-2023 Andy Alt (arch_stanton5995@proton.me) 5 | Other authors: https://github.com/theimpossibleastronaut/rmw/blob/master/AUTHORS.md 6 | 7 | This program is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | This program is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with this program. If not, see . 19 | */ 20 | 21 | #include "globals.h" 22 | #include "parse_cli_options.h" 23 | #include "purging.h" 24 | #include "messages.h" 25 | #include "utils.h" 26 | #include "trashinfo.h" 27 | 28 | 29 | enum 30 | { 31 | CONTINUE 32 | }; 33 | 34 | /*! 35 | * Called in main() to determine whether or not purge() was run today, reads 36 | * and writes to the 'lastpurge` file. If it hasn't been run today, the 37 | * current day will be written. If 'lastpurge' doesn't exist, it gets 38 | * created. 39 | */ 40 | bool 41 | is_time_to_purge(st_time *st_time_var, const char *file) 42 | { 43 | const int BUF_TIME = 80; 44 | 45 | FILE *fp = fopen(file, "r"); 46 | bool init = (fp); 47 | if (fp) 48 | { 49 | char time_prev[BUF_TIME]; 50 | 51 | if (fgets(time_prev, sizeof time_prev, fp) == NULL) 52 | { 53 | print_msg_error(); 54 | printf("while getting line from %s\n", file); 55 | perror(__func__); 56 | close_file(&fp, file, __func__); 57 | exit(EXIT_FAILURE); 58 | } 59 | 60 | trim_whitespace(time_prev); 61 | close_file(&fp, file, __func__); 62 | 63 | if ((st_time_var->now - atoll(time_prev)) < SECONDS_IN_A_DAY) 64 | return false; 65 | } 66 | 67 | fp = fopen(file, "w"); 68 | if (fp) 69 | { 70 | fprintf(fp, "%ld\n", st_time_var->now); 71 | close_file(&fp, file, __func__); 72 | 73 | /* 74 | * if this is the first time the file got created, it's very likely 75 | * indeed that purge does not need to run. Only return FALSE if the 76 | * file didn't previously exist. 77 | */ 78 | return init; 79 | } 80 | 81 | /* 82 | * if we can't even write this file to the config directory, something 83 | * is not right. Make it fatal. 84 | */ 85 | open_err(file, __func__); 86 | exit(errno); 87 | } 88 | 89 | 90 | /*! 91 | * Get the time a file was rmw'ed by reading the corresponding trashinfo 92 | * file. Called from purge() 93 | */ 94 | static time_t 95 | get_then_time(const char *tinfo_file) 96 | { 97 | char *raw_deletion_date = 98 | validate_and_get_value(tinfo_file, DELETIONDATE_KEY); 99 | if (raw_deletion_date != NULL) 100 | { 101 | struct tm tm_then; 102 | memset(&tm_then, 0, sizeof(struct tm)); 103 | strptime(raw_deletion_date, "%Y-%m-%dT%H:%M:%S", &tm_then); 104 | free(raw_deletion_date); 105 | return mktime(&tm_then); 106 | } 107 | print_msg_error(); 108 | fprintf(stderr, "while getting deletion date from %s.\n", tinfo_file); 109 | return 0; 110 | } 111 | 112 | 113 | static void 114 | print_header(char *files_dir) 115 | { 116 | putchar('\n'); 117 | printf(" [%s]\n", files_dir); 118 | printf(" "); 119 | char *p = files_dir; 120 | while (*(p++) != '\0') 121 | printf("-"); 122 | puts("--"); 123 | } 124 | 125 | static int 126 | do_file_purge(char *purge_target, const rmw_options *cli_user_options, 127 | const char *trashinfo_entry_realpath, int *orphan_ctr, 128 | const char *pt_basename, int *ctr) 129 | { 130 | int p_state = check_pathname_state(purge_target); 131 | if (p_state == ENOENT) 132 | { 133 | if (cli_user_options->want_orphan_chk && cli_user_options->force >= 2) 134 | { 135 | int res = 0; 136 | if (cli_user_options->want_dry_run == false) 137 | { 138 | res = remove(trashinfo_entry_realpath); 139 | if (res != 0) 140 | msg_err_remove(trashinfo_entry_realpath, __func__); 141 | } 142 | if (res == 0) 143 | printf("removed '%s'\n", trashinfo_entry_realpath); 144 | (*orphan_ctr)++; 145 | return CONTINUE; 146 | } 147 | else 148 | { 149 | printf("While processing %s:\n", trashinfo_entry_realpath); 150 | puts("You can remove the trashinfo file with '-offg'"); 151 | msg_warn_file_not_found(purge_target); 152 | } 153 | } 154 | else if (p_state == -1) 155 | exit(p_state); 156 | 157 | bool is_dir = is_dir_f(purge_target); 158 | 159 | int status = 0; 160 | if (verbose) 161 | printf("removing '%s\n", purge_target); 162 | 163 | if (!is_dir) 164 | { 165 | if (cli_user_options->want_dry_run == false) 166 | status = remove(purge_target); 167 | 168 | if (status != 0) 169 | { 170 | msg_err_remove(purge_target, __func__); 171 | errno = 0; 172 | } 173 | } 174 | else 175 | { 176 | if (cli_user_options->want_dry_run == false) 177 | status = bsdutils_rm(purge_target, verbose); 178 | } 179 | 180 | if (status == 0) 181 | { 182 | if (cli_user_options->want_dry_run == false) 183 | status = remove(trashinfo_entry_realpath); 184 | 185 | if (!status) 186 | { 187 | if (*ctr == 0) 188 | putchar('\n'); 189 | (*ctr)++; 190 | if (!verbose) 191 | { 192 | printf("\r%d", *ctr); 193 | fflush(stdout); 194 | // sleep(1); 195 | } 196 | else 197 | printf("-%s\n", pt_basename); 198 | } 199 | else 200 | msg_err_remove(trashinfo_entry_realpath, __func__); 201 | } 202 | 203 | return 0; 204 | } 205 | 206 | 207 | static char * 208 | get_pt_basename(const char *purge_target) 209 | { 210 | static char *pt_basename; 211 | static char pt_tmp[PATH_MAX]; 212 | sn_check(snprintf(pt_tmp, sizeof pt_tmp, "%s", purge_target), 213 | sizeof pt_tmp); 214 | pt_basename = basename(pt_tmp); 215 | return pt_basename; 216 | } 217 | 218 | 219 | static void 220 | get_purge_target(char *purge_target, const char *tinfo_d_name, 221 | const char *files_dir) 222 | { 223 | char temp[PATH_MAX]; 224 | sn_check(snprintf(temp, sizeof temp, "%s", tinfo_d_name), sizeof temp); 225 | truncate_str(temp, len_trashinfo_ext); /* acquire the (basename - trashinfo extension) */ 226 | char *path = join_paths(files_dir, temp); 227 | strcpy(purge_target, path); 228 | free(path); 229 | return; 230 | } 231 | 232 | 233 | /*! 234 | * Purges files older than x number of days, unless expire_age is set to 235 | * 0 in the config file. 236 | */ 237 | int 238 | purge(st_config *st_config_data, 239 | const rmw_options *cli_user_options, 240 | st_time *st_time_var, int *orphan_ctr) 241 | { 242 | if (!st_config_data->expire_age) 243 | { 244 | /* TRANSLATORS: "purging" refers to permanently deleting a file or a 245 | * directory */ 246 | printf(_("purging is disabled ('%s' is set to '0')\n\n"), expire_age_str); 247 | return 0; 248 | } 249 | 250 | if (cli_user_options->want_empty_trash) 251 | { 252 | puts(_("The contents of all waste folders will be deleted -")); 253 | if (!user_verify()) 254 | { 255 | puts(_("Action cancelled.")); 256 | return 0; 257 | } 258 | } 259 | 260 | putchar('\n'); 261 | if (cli_user_options->want_empty_trash) 262 | printf(_("Purging all files in waste folders ...\n")); 263 | else 264 | printf(_ 265 | ("Purging files based on number of days in the waste folders (%u) ...\n"), 266 | st_config_data->expire_age); 267 | 268 | int ctr = 0; 269 | 270 | st_waste *waste_curr = st_config_data->st_waste_folder_props_head; 271 | while (waste_curr != NULL) 272 | { 273 | struct dirent *st_trashinfo_dir_entry; 274 | DIR *trashinfo_dir = opendir(waste_curr->info); 275 | if (trashinfo_dir == NULL) 276 | { 277 | msg_err_open_dir(waste_curr->info, __func__, __LINE__); 278 | exit(errno); 279 | } 280 | 281 | if (verbose) 282 | print_header(waste_curr->files); 283 | 284 | /* 285 | * Read each file in /info 286 | */ 287 | while ((st_trashinfo_dir_entry = readdir(trashinfo_dir)) != NULL) 288 | { 289 | if (isdotdir(st_trashinfo_dir_entry->d_name)) 290 | continue; 291 | 292 | char *tmp_str = 293 | join_paths(waste_curr->info, st_trashinfo_dir_entry->d_name); 294 | char trashinfo_entry_realpath[strlen(tmp_str) + 1]; 295 | strcpy(trashinfo_entry_realpath, tmp_str); 296 | free(tmp_str); 297 | 298 | time_t then = get_then_time(trashinfo_entry_realpath); 299 | if (!cli_user_options->want_empty_trash && !then) 300 | continue; 301 | 302 | double days_remaining = 303 | ((double) then + (SECONDS_IN_A_DAY * st_config_data->expire_age) - 304 | st_time_var->now) / SECONDS_IN_A_DAY; 305 | 306 | bool want_purge = days_remaining <= 0 307 | || cli_user_options->want_empty_trash; 308 | if (want_purge || verbose >= 2) 309 | { 310 | char purge_target[PATH_MAX] = { 0 }; 311 | get_purge_target(purge_target, st_trashinfo_dir_entry->d_name, 312 | waste_curr->files); 313 | char *pt_basename = get_pt_basename(purge_target); 314 | 315 | if (want_purge) 316 | { 317 | if (do_file_purge(purge_target, cli_user_options, 318 | trashinfo_entry_realpath, orphan_ctr, pt_basename, 319 | &ctr) == CONTINUE) 320 | continue; 321 | } 322 | else if (verbose >= 2) 323 | { 324 | printf(_("'%s' will be purged in %.2f days\n"), 325 | pt_basename, days_remaining); 326 | } 327 | } 328 | } 329 | 330 | if (closedir(trashinfo_dir)) 331 | msg_err_close_dir(waste_curr->info, __func__, __LINE__); 332 | 333 | waste_curr = waste_curr->next_node; 334 | } 335 | 336 | putchar('\n'); 337 | printf(ngettext("%d item purged", "%d items purged", ctr), ctr); 338 | putchar('\n'); 339 | 340 | return 0; 341 | } 342 | 343 | short 344 | orphan_maint(st_waste *waste_head, st_time *st_time_var, int *orphan_ctr) 345 | { 346 | rmw_target st_file_properties; 347 | 348 | /* searching for files that don't have a trashinfo: There will 349 | * never be a duplicate, but it initializes the struct member, and 350 | * create_trashinfo() will check for it, which is called later. 351 | */ 352 | st_file_properties.is_duplicate = 0; 353 | 354 | char path_to_trashinfo[PATH_MAX]; 355 | st_waste *waste_curr = waste_head; 356 | while (waste_curr != NULL) 357 | { 358 | struct dirent *entry; 359 | DIR *files; 360 | files = opendir(waste_curr->files); 361 | if (files == NULL) 362 | { 363 | msg_err_open_dir(waste_curr->files, __func__, __LINE__); 364 | exit(errno); 365 | } 366 | 367 | while ((entry = readdir(files)) != NULL) 368 | { 369 | if (isdotdir(entry->d_name)) 370 | continue; 371 | 372 | st_file_properties.base_name = basename(entry->d_name); 373 | 374 | char *tmp_str = 375 | join_paths(waste_curr->info, st_file_properties.base_name); 376 | int r = snprintf(path_to_trashinfo, PATH_MAX, "%s%s", tmp_str, 377 | trashinfo_ext); 378 | sn_check(r, PATH_MAX); 379 | 380 | free(tmp_str); 381 | 382 | if (check_pathname_state(path_to_trashinfo) == EEXIST) 383 | continue; 384 | 385 | /* destination if restored */ 386 | st_file_properties.real_path = 387 | join_paths(waste_curr->parent, "orphans", 388 | st_file_properties.base_name, NULL); 389 | 390 | if (!create_trashinfo(&st_file_properties, waste_curr, st_time_var)) 391 | { 392 | /* TRANSLATORS: "created" refers to a file */ 393 | printf(_("Created %s\n"), path_to_trashinfo); 394 | (*orphan_ctr)++; 395 | } 396 | else 397 | { 398 | print_msg_error(); 399 | printf(_("while creating %s\n"), path_to_trashinfo); 400 | } 401 | free(st_file_properties.real_path); 402 | 403 | } 404 | if (closedir(files)) 405 | msg_err_close_dir(waste_curr->files, __func__, __LINE__); 406 | 407 | waste_curr = waste_curr->next_node; 408 | } 409 | 410 | printf("%d %s found\n", *orphan_ctr, 411 | *orphan_ctr == 1 ? "orphan" : "orphans"); 412 | 413 | return 0; 414 | } 415 | --------------------------------------------------------------------------------