pathrs 0.2.4

C-friendly API to make path resolution safer on Linux.
Documentation
#!/usr/bin/bats -t
# SPDX-License-Identifier: MPL-2.0
#
# libpathrs: safe path resolution on Linux
# Copyright (C) 2019-2025 SUSE LLC
# Copyright (C) 2026 Aleksa Sarai <cyphar@cyphar.com>
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at https://mozilla.org/MPL/2.0/.

load helpers

function setup() {
	setup_tmpdirs
}

function teardown() {
	teardown_tmpdirs
}

@test "root unlink [non-existent]" {
	ROOT="$(setup_tmpdir)"

	pathrs-cmd root --root "$ROOT" unlink non-exist
	check-errno ENOENT
}

@test "root rmdir [non-existent]" {
	ROOT="$(setup_tmpdir)"

	pathrs-cmd root --root "$ROOT" rmdir non-exist
	check-errno ENOENT
}

@test "root rmdir-all [non-existent]" {
	ROOT="$(setup_tmpdir)"

	pathrs-cmd root --root "$ROOT" rmdir-all non-exist
	[ "$status" -eq 0 ]
}

@test "root unlink [file]" {
	ROOT="$(setup_tmpdir)"

	echo "file" >"$ROOT/file"
	[ -e "$ROOT/file" ]

	pathrs-cmd root --root "$ROOT" unlink /file
	[ "$status" -eq 0 ]
	! [ -e "$ROOT/file" ]
}

@test "root rmdir [file]" {
	ROOT="$(setup_tmpdir)"

	echo "file" >"$ROOT/file"
	[ -e "$ROOT/file" ]

	pathrs-cmd root --root "$ROOT" rmdir /file
	check-errno ENOTDIR
	[ -e "$ROOT/file" ]
}

@test "root rmdir-all [file]" {
	ROOT="$(setup_tmpdir)"

	echo "file" >"$ROOT/file"
	[ -e "$ROOT/file" ]

	pathrs-cmd root --root "$ROOT" rmdir-all /file
	[ "$status" -eq 0 ]
	! [ -e "$ROOT/file" ]
}

@test "root unlink [fifo]" {
	ROOT="$(setup_tmpdir)"

	mkfifo "$ROOT/fifo"
	[ -e "$ROOT/fifo" ]

	pathrs-cmd root --root "$ROOT" unlink /fifo
	[ "$status" -eq 0 ]
	! [ -e "$ROOT/fifo" ]
}

@test "root rmdir [fifo]" {
	ROOT="$(setup_tmpdir)"

	mkfifo "$ROOT/fifo"
	[ -e "$ROOT/fifo" ]

	pathrs-cmd root --root "$ROOT" rmdir /fifo
	check-errno ENOTDIR
	[ -e "$ROOT/fifo" ]
}

@test "root rmdir-all [fifo]" {
	ROOT="$(setup_tmpdir)"

	mkfifo "$ROOT/fifo"
	[ -e "$ROOT/fifo" ]

	pathrs-cmd root --root "$ROOT" rmdir-all /fifo
	[ "$status" -eq 0 ]
	! [ -e "$ROOT/fifo" ]
}

@test "root unlink [symlink]" {
	ROOT="$(setup_tmpdir)"

	touch "$ROOT/file"
	ln -s file "$ROOT/alt"

	pathrs-cmd root --root "$ROOT" unlink alt
	[ "$status" -eq 0 ]
	! [ -e "$ROOT/alt" ]
	[ -e "$ROOT/file" ]
}

@test "root rmdir [symlink]" {
	ROOT="$(setup_tmpdir)"

	mkdir -p "$ROOT/some/directory"
	ln -s some "$ROOT/alt"

	pathrs-cmd root --root "$ROOT" rmdir-all alt
	[ "$status" -eq 0 ]
	! [ -e "$ROOT/alt" ]
	[ -e "$ROOT/some" ]
	[ -e "$ROOT/some/directory" ]
}

@test "root rmdir-all [symlink]" {
	ROOT="$(setup_tmpdir)"

	mkdir -p "$ROOT/some/directory"
	ln -s some "$ROOT/alt"

	pathrs-cmd root --root "$ROOT" rmdir-all alt
	[ "$status" -eq 0 ]
	! [ -e "$ROOT/alt" ]
	[ -e "$ROOT/some" ]
	[ -e "$ROOT/some/directory" ]
}

@test "root unlink [directory]" {
	ROOT="$(setup_tmpdir)"

	mkdir -p "$ROOT/some/directory"

	pathrs-cmd root --root "$ROOT" unlink ../some/directory
	check-errno EISDIR
	[ -e "$ROOT/some/directory" ]

	pathrs-cmd root --root "$ROOT" unlink ../some
	check-errno EISDIR
	[ -e "$ROOT/some/directory" ]
}

@test "root rmdir [directory]" {
	ROOT="$(setup_tmpdir)"

	mkdir -p "$ROOT/some/directory"

	pathrs-cmd root --root "$ROOT" rmdir ../some
	check-errno ENOTEMPTY
	[ -e "$ROOT/some" ]

	pathrs-cmd root --root "$ROOT" rmdir ../some/directory
	[ "$status" -eq 0 ]
	! [ -e "$ROOT/some/directory" ]

	pathrs-cmd root --root "$ROOT" rmdir ../some
	[ "$status" -eq 0 ]
	! [ -e "$ROOT/some" ]
}

@test "root rmdir-all [directory]" {
	ROOT="$(setup_tmpdir)"

	mkdir -p "$ROOT/some/directory"
	touch "$ROOT/some/file"

	pathrs-cmd root --root "$ROOT" rmdir-all ../some
	[ "$status" -eq 0 ]
	! [ -e "$ROOT/some" ]
	! [ -e "$ROOT/some/file" ]
	! [ -e "$ROOT/some/directory" ]
}

@test "root rmdir-all [big tree]" {
	ROOT="$(setup_tmpdir)"

	# Make a fairly deep tree.
	for dir1 in $(seq 16); do
		for dir2 in $(seq 32); do
			subdir="tree/dir-$dir1/dirdir-$dir2"
			mkdir -p "$ROOT/$subdir"
			for file in $(seq 16); do
				echo "file $file in $subdir" >"$ROOT/$subdir/file-$file-$RANDOM"
			done
		done
	done

	pathrs-cmd root --root "$ROOT" rmdir-all tree
	[ "$status" -eq 0 ]
	! [ -e "$ROOT/tree" ]
}

@test "root rmdir-all ." {
	ROOT="$(setup_tmpdir)"

	# Make a fairly deep tree.
	for dir1 in $(seq 16); do
		for dir2 in $(seq 16); do
			subdir="dir-$dir1/dirdir-$dir2"
			mkdir -p "$ROOT/$subdir"
			for file in $(seq 16); do
				echo "file $file in $subdir" >"$ROOT/$subdir/file-$file-$RANDOM"
			done
		done
	done

	pathrs-cmd root --root "$ROOT" rmdir-all .
	# TODO: Implement this.
	check-errno EINVAL
	#[ "$status" -eq 0 ]
	#! [ -e "$ROOT/tree" ]
	# The top-level root should not be removed.
	[ -d "$ROOT" ]
}

@test "root rmdir-all /" {
	ROOT="$(setup_tmpdir)"

	# Make a fairly deep tree.
	for dir1 in $(seq 16); do
		for dir2 in $(seq 16); do
			subdir="dir-$dir1/dirdir-$dir2"
			mkdir -p "$ROOT/$subdir"
			for file in $(seq 16); do
				echo "file $file in $subdir" >"$ROOT/$subdir/file-$file-$RANDOM"
			done
		done
	done

	pathrs-cmd root --root "$ROOT" rmdir-all /
	# TODO: Implement this.
	check-errno EINVAL
	#[ "$status" -eq 0 ]
	#! [ -e "$ROOT/tree" ]
	# The top-level root should not be removed.
	[ -d "$ROOT" ]
}

# TODO: Add a test for removing rootless-unfriendly chmod 000 directories.
# See <https://github.com/cyphar/libpathrs/issues/279>.